Skip to content

refactor: Add oxlint base configuration and integrate with ESLint via eslint-plugin-oxlint#832

Merged
kingston merged 23 commits into
mainfrom
kingston/improve-lint-and-build-performance
Mar 15, 2026
Merged

refactor: Add oxlint base configuration and integrate with ESLint via eslint-plugin-oxlint#832
kingston merged 23 commits into
mainfrom
kingston/improve-lint-and-build-performance

Conversation

@kingston

@kingston kingston commented Mar 15, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

Release Notes

New Features

  • Added oxlint and oxfmt tooling for enhanced code linting and formatting.
  • Introduced pnpm check:full command for comprehensive validation including metadata sync and dependency checks.

Improvements

  • pnpm check now optimizes performance by validating only affected packages during development.
  • Enhanced pre-commit hooks for automatic linting before commits.
  • Improved error handling with contextual information across the codebase.

Dependencies

  • Updated eslint-plugin-better-tailwindcss and eslint-plugin-import-x to latest versions.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Mar 15, 2026

Copy link
Copy Markdown

Deploying baseplate-storybook with  Cloudflare Pages  Cloudflare Pages

Latest commit: 47fe3c0
Status: ✅  Deploy successful!
Preview URL: https://5e5ba39b.baseplate-storybook.pages.dev
Branch Preview URL: https://kingston-improve-lint-and-bu.baseplate-storybook.pages.dev

View logs

@changeset-bot

changeset-bot Bot commented Mar 15, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 637aea8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 23 packages
Name Type
@baseplate-dev/tools Patch
@baseplate-dev/code-morph Patch
@baseplate-dev/core-generators Patch
@baseplate-dev/create-project Patch
@baseplate-dev/fastify-generators Patch
@baseplate-dev/project-builder-cli Patch
@baseplate-dev/project-builder-common Patch
@baseplate-dev/project-builder-dev Patch
@baseplate-dev/project-builder-lib Patch
@baseplate-dev/project-builder-server Patch
@baseplate-dev/project-builder-test Patch
@baseplate-dev/project-builder-web Patch
@baseplate-dev/react-generators Patch
@baseplate-dev/sync Patch
@baseplate-dev/ui-components Patch
@baseplate-dev/utils Patch
@baseplate-dev/plugin-auth Patch
@baseplate-dev/plugin-email Patch
@baseplate-dev/plugin-observability Patch
@baseplate-dev/plugin-payments Patch
@baseplate-dev/plugin-queue Patch
@baseplate-dev/plugin-rate-limit Patch
@baseplate-dev/plugin-storage Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Mar 15, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@kingston has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 4 minutes and 16 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 75eb0f14-a707-4f04-a589-b8669695d954

📥 Commits

Reviewing files that changed from the base of the PR and between 116519f and 637aea8.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (3)
  • .github/workflows/lint.yml
  • package.json
  • turbo.json
📝 Walkthrough

Walkthrough

This pull request introduces oxlint linting and oxfmt formatting tools across the monorepo. It adds oxlint configuration files to all packages, updates package.json scripts to run oxlint before eslint, replaces the pre-commit hook, and includes supporting changes for error handling, test assertions, type imports, and documentation updates.

Changes

Cohort / File(s) Summary
Oxlint base configuration & exports
packages/tools/oxlint.config.base.ts, packages/tools/oxlint.config.ts, packages/tools/package.json
Introduces oxlint base configuration with rules, ignore patterns, and environment settings. Exports oxlint-config-base for reuse across packages. Updates packages/tools package.json to include oxlint as a dependency and public export.
Oxlint configuration files
oxlint.config.ts (root), packages/*/oxlint.config.ts, plugins/*/oxlint.config.ts
Adds oxlint.config.ts files to ~20+ packages and plugins, each extending the base oxlint configuration and applying package-specific ignore patterns. Standardizes linting setup across monorepo.
Oxfmt formatter configuration
.oxfmtrc.json, packages/tools/eslint-configs/oxlint.js
Introduces oxfmt configuration with formatting preferences, import sorting rules, and ignore patterns. Adds oxlint ESLint plugin integration via eslint-plugin-oxlint.
Package.json script and dependency updates
package.json (root), packages/*/package.json, plugins/*/package.json, examples/*/package.json
Updates lint scripts to run oxlint before eslint across all packages. Adds oxlint to devDependencies. Updates eslint-plugin-better-tailwindcss and eslint-plugin-import-x versions. Adjusts keywords for consistency.
ESLint ignore pattern narrowing
packages/*/eslint.config.js, plugins/*/eslint.config.js
Narrows ESLint template ignore patterns from **/templates/**/* to **/generators/**/templates/**/* across multiple packages and plugins.
Check workflow script updates
.agents/dev-commands.md, AGENTS.md, package.json
Replaces single pnpm check with dual commands: pnpm check (affected packages only) and pnpm check:full (complete pass with knip and metadata sync). Updates documentation and adds new scripts.
Pre-commit hook updates
.husky/pre-commit, .husky/post-checkout
Adds new pre-commit hook running pnpm lint:staged. Removes post-checkout hook that previously copied environment files for worktrees.
Error handling improvements
packages/project-builder-lib/src/plugins/migrations/run-migrations.ts, packages/project-builder-server/src/templates/utils/resolve-generator.ts, packages/sync/src/output/string-merge-algorithms/git-merge-driver.ts, scripts/check-changesets.ts
Replaces direct Error throws with enhanceErrorWithContext calls to wrap errors with contextual messages. Improves error cause handling in error constructors.
Test assertion tightening
packages/core-generators/src/test-helpers/matchers.unit.test.ts, packages/project-builder-server/src/actions/definition/draft-lifecycle.int.test.ts, packages/sync/src/templates/extractor/utils/index-template-configs.unit.test.ts, packages/sync/src/templates/metadata/*.unit.test.ts, packages/sync/src/utils/create-generator.unit.test.ts
Updates test assertions to check for specific error messages (e.g., "ENOENT", "Invalid JSON") instead of generic error throws. Tightens validation of error handling behavior.
Type import conversions
packages/code-morph/src/types.ts, packages/core-generators/src/renderers/typescript/templates/*.ts
Converts value imports to type-only imports for TypeScript types (e.g., import type { z } instead of import type z). Ensures strict type-only semantics.
Minor code refactoring
packages/core-generators/src/renderers/extractor/plugins/template-renderers/renderers-file.ts, packages/core-generators/src/renderers/typescript/imports/ts-morph-operations.unit.test.ts, packages/project-builder-server/src/compiler/web/web-compiler.ts, packages/ui-components/src/components/ui/date-time-picker-field/date-time-picker-field.stories.tsx, packages/ui-components/src/hooks/use-event-callback.ts, packages/utils/src/fs/handle-not-found-error.unit.test.ts
Removes unnecessary spread operators, simplifies conditional expressions, inlines handler functions, removes redundant test cases, and simplifies test assertions.
Configuration and metadata updates
.devcontainer/conf/starship.toml, .claude/settings.json, knip.config.js, packages/tools/eslint-configs/typescript.js, packages/tools/tsconfig.json, scripts/format-and-lint.ts, tsconfig.scripts.json, turbo.json, pnpm-workspace.yaml
Adds oxlint to tool allowlist and configurations. Updates TypeScript configs to include *.ts files. Refactors format-and-lint script to handle oxlint/oxfmt. Adds oxlint version to pnpm workspace catalog. Updates Turbo configuration for type checking.

Sequence Diagram(s)

sequenceDiagram
    participant User as Developer
    participant Pre as Pre-commit Hook
    participant Lint as pnpm lint:staged
    participant Oxlint as Oxlint
    participant ESLint as ESLint
    participant Git as Git

    User->>Git: Stage changes
    User->>Git: Commit
    Git->>Pre: Trigger pre-commit
    Pre->>Lint: Execute pnpm lint:staged
    Lint->>Oxlint: Run oxlint --fix
    Oxlint-->>Lint: Fixed code issues
    Lint->>ESLint: Run eslint
    ESLint-->>Lint: Linting results
    Lint-->>Pre: Formatting/linting complete
    Pre->>Git: Allow/block commit
    Git-->>User: Commit result
Loading
sequenceDiagram
    participant User as Developer
    participant Check as pnpm check
    participant Affected as Affected Packages
    participant CheckFull as pnpm check:full
    participant All as All Packages
    participant Knip as Knip
    participant Meta as Metadata Sync

    User->>Check: Run during development
    Check->>Affected: oxfmt:fix → oxlint:fix → lint → typecheck → test
    Affected-->>Check: Results on affected code
    Check-->>User: Feedback (fast)
    
    User->>CheckFull: Run before task completion
    CheckFull->>All: oxfmt:fix → oxlint:fix → lint → typecheck → test
    All-->>CheckFull: Results on all packages
    CheckFull->>Knip: Check unused code
    Knip-->>CheckFull: Results
    CheckFull->>Meta: Sync metadata
    Meta-->>CheckFull: Complete
    CheckFull-->>User: Final validation
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding oxlint base configuration and integrating it with ESLint, which is clearly demonstrated across the changeset with new oxlint.config.ts files, configuration setup, and ESLint integration.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch kingston/improve-lint-and-build-performance
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@kingston kingston changed the title Add oxlint base configuration and integrate with ESLint via eslint-plugin-oxlint refactor: Add oxlint base configuration and integrate with ESLint via eslint-plugin-oxlint Mar 15, 2026
@socket-security

socket-security Bot commented Mar 15, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedoxfmt@​0.40.0901008996100
Addedoxlint@​1.55.0991009196100
Updatedeslint-plugin-import-x@​4.16.1 ⏵ 4.16.29710010093100
Updatedeslint-plugin-better-tailwindcss@​4.2.0 ⏵ 4.3.299100100 +196100
Addedeslint-plugin-oxlint@​1.55.010010010097100

View full report

@socket-security

socket-security Bot commented Mar 15, 2026

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn Medium
Low adoption: npm @package-json/types

Location: Package overview

From: examples/todo-with-better-auth/pnpm-lock.yamlnpm/eslint-plugin-import-x@4.16.2npm/@package-json/types@0.0.12

ℹ Read more on: This package | This alert | What are unpopular packages?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Unpopular packages may have less maintenance and contain other problems.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@package-json/types@0.0.12. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/ui-components/src/hooks/use-event-callback.ts (1)

22-29: ⚠️ Potential issue | 🔴 Critical

Critical bug: Callback captures stale function reference.

The current variable is destructured outside the inner arrow function (line 23), so it captures the value of ref.current at memoization time. Since useMemo has an empty dependency array, the returned callback will always invoke the initial function, ignoring subsequent updates from the useEffect.

The fix is to read ref.current inside the returned callback:

🐛 Proposed fix
   return useMemo(() => {
-    const { current } = ref;
-    if (!current) {
-      return;
-    }
-    // eslint-disable-next-line `@typescript-eslint/no-unsafe-return` -- safe to return the function
-    return (...args) => current(...args);
+    // eslint-disable-next-line `@typescript-eslint/no-unsafe-return` -- safe to return the function
+    return (...args) => ref.current?.(...args);
   }, []);

If you need to preserve the behavior of returning undefined when fn is initially undefined (rather than a no-op function), an alternative approach:

Alternative fix preserving undefined return
   return useMemo(() => {
-    const { current } = ref;
-    if (!current) {
+    if (!ref.current) {
       return;
     }
     // eslint-disable-next-line `@typescript-eslint/no-unsafe-return` -- safe to return the function
-    return (...args) => current(...args);
+    return (...args) => ref.current!(...args);
   }, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui-components/src/hooks/use-event-callback.ts` around lines 22 - 29,
The returned callback captures a stale reference because ref.current is
destructured into current outside the inner function; update
use-event-callback's useMemo so the returned function reads ref.current inside
its body (e.g., return (...args) => { const fn = ref.current; if (!fn) return;
return fn(...args); }) so subsequent updates to ref.current are honored; keep
the memo dependencies the same to preserve return-undefined-on-initial-undefined
behavior if desired.
🧹 Nitpick comments (6)
packages/sync/src/utils/create-generator.unit.test.ts (1)

64-66: Use regex matching instead of exact string for Zod error message assertions.

Lines 64–66 assert a full literal message string. Zod error messages can vary across versions (e.g., v3 uses "Expected string, received number" with uppercase; v4 prefixes "Invalid input: "). Use a case-insensitive regex to match the stable part.

Proposed change
-    expect(() => generator({ value: 123 as unknown as string })).toThrow(
-      'expected string, received number',
-    );
+    expect(() => generator({ value: 123 as unknown as string })).toThrow(
+      /expected string, received number/i,
+    );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sync/src/utils/create-generator.unit.test.ts` around lines 64 - 66,
Update the failing assertion in the create-generator.unit.test to use a
case-insensitive regex instead of an exact string: change the expect for
generator({ value: 123 as unknown as string }) to toThrow matching a regex
(e.g., /expected string, received number/i or allow optional "invalid input: "
prefix via /(?:invalid input:\s*)?expected string, received number/i) so it
tolerates Zod v3/v4 message differences; locate the assertion around the
generator(...) call and replace the literal string with the regex.
packages/tools/eslint-configs/typescript.js (1)

48-49: Consider removing redundant oxlint.config.ts handling.

oxlint.config.ts is added to allowDefaultProject and also ignored globally. Keeping only one approach would reduce config ambiguity.

♻️ Optional cleanup
   const defaultProjectFiles = [
     'vitest.config.ts',
-    'oxlint.config.ts',
     ...(options.extraDefaultProjectFiles ?? []),
   ];

Also applies to: 320-320

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/tools/eslint-configs/typescript.js` around lines 48 - 49, Remove the
redundant handling of 'oxlint.config.ts' by choosing a single strategy: either
stop injecting 'oxlint.config.ts' into the default projects array (the literal
entry in the array alongside ...(options.extraDefaultProjectFiles ?? [])) or
remove it from the global ignore configuration where it's currently excluded;
update the code around the array that currently contains 'oxlint.config.ts' and
...(options.extraDefaultProjectFiles ?? []) accordingly and ensure the project's
allowDefaultProject/ignore settings remain consistent (modify the entry near the
array or the global ignore list, and remove the duplicate reference).
.oxfmtrc.json (1)

11-20: The instrumentation exception is only documented in comments, not configured.

The comment on line 10 claims special handling for instrumentation imports, but the groups configuration has no separate instrumentation bucket—side_effect remains a single bucket. This misleading comment also appears in packages/tools/eslint-configs/typescript.js and packages/core-generators/src/generators/node/eslint/templates/eslint.config.js.

Currently, instrumentation imports are implicitly protected only because examples/ is in ignorePatterns. For clarity and robustness, either remove the misleading comment or implement actual instrumentation handling by adding a separate group before side_effect.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.oxfmtrc.json around lines 11 - 20, The comment claiming special handling
for instrumentation imports is misleading because the "groups" array lacks a
separate instrumentation bucket; add an explicit instrumentation group
immediately before the "side_effect" entry in the "groups" config (e.g., insert
"instrumentation" or a pair like
["value-instrumentation","type-instrumentation"] depending on how other groups
are defined) and mirror this change in the corresponding ESLint configs (the
templates that reference this behavior) so instrumentation imports are actually
segregated, or alternatively remove the misleading comment if you prefer not to
implement the bucket.
scripts/format-and-lint.ts (3)

198-202: Same ordering concern: ESLint runs before Prettier.

For example files, ESLint's --fix runs before Prettier. This is the reverse of the typical workflow (format first, then lint). ESLint autofix may introduce code that Prettier would reformat, making the hook non-idempotent.

♻️ Proposed fix
-    if (deps.eslint) await runEslint(files, pkgDir);
-
     if (deps.prettier) {
       await Promise.all(files.map((f) => runPrettier(f)));
     }
+
+    if (deps.eslint) await runEslint(files, pkgDir);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/format-and-lint.ts` around lines 198 - 202, The current ordering runs
ESLint (runEslint) before Prettier (runPrettier), which makes fixes
non-idempotent; change the control flow so that when both deps.prettier and
deps.eslint are true you run Prettier first (e.g., await Promise.all(files.map(f
=> runPrettier(f)))) and only after that call await runEslint(files, pkgDir);
update the block containing deps.prettier/deps.eslint to reflect this swapped
order using the existing runPrettier and runEslint symbols.

157-164: Consider reversing the order: format before lint.

The current order runs oxlint --fix before oxfmt. Typically, formatters should run first (to fix spacing/formatting), followed by linters (to apply semantic fixes). Otherwise, oxlint --fix may introduce formatting that oxfmt then changes, potentially creating a non-idempotent workflow where re-running produces different output.

♻️ Proposed fix
 // Process monorepo files: run oxlint then oxfmt from root.
 if (monorepoFiles.length > 0) {
   console.info(
-    `\nProcessing ${monorepoFiles.length} monorepo file(s) with oxlint + oxfmt...`,
+    `\nProcessing ${monorepoFiles.length} monorepo file(s) with oxfmt + oxlint...`,
   );
-  await runOxlint(monorepoFiles);
   await runOxfmt(monorepoFiles);
+  await runOxlint(monorepoFiles);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/format-and-lint.ts` around lines 157 - 164, The current sequence runs
runOxlint(monorepoFiles) before runOxfmt(monorepoFiles), which can make fixes
non-idempotent; swap the calls so formatting runs first: call
runOxfmt(monorepoFiles) before runOxlint(monorepoFiles) in the monorepoFiles
handling block to ensure oxfmt normalizes formatting prior to oxlint (including
oxlint --fix) applying semantic fixes.

109-131: Error handling silently continues on failure.

Both runOxfmt and runOxlint catch errors and log them but don't propagate failures. The script will exit with code 0 even if these tools fail, potentially allowing badly formatted or linted code to be committed.

If this is intentional (to avoid blocking commits), consider documenting this behavior. Otherwise, track failures and exit with a non-zero code at the end.

♻️ Proposed approach to track failures
+let hasFailures = false;
+
 async function runOxfmt(filePaths: string[]): Promise<void> {
   console.info(`Running oxfmt on ${filePaths.length} file(s)...`);
   try {
     await execFilePromise('npx', ['oxfmt', ...filePaths], { cwd: ROOT_DIR });
     console.info(`✅ oxfmt completed for ${filePaths.length} file(s).`);
   } catch (error) {
     console.error('❌ oxfmt failed.');
     console.error(error instanceof Error ? error.message : String(error));
+    hasFailures = true;
   }
 }

 async function runOxlint(filePaths: string[]): Promise<void> {
   console.info(`Running oxlint on ${filePaths.length} file(s)...`);
   try {
     await execFilePromise('npx', ['oxlint', '--fix', ...filePaths], {
       cwd: ROOT_DIR,
     });
     console.info(`✅ oxlint completed for ${filePaths.length} file(s).`);
   } catch (error) {
     console.error('❌ oxlint failed.');
     console.error(error instanceof Error ? error.message : String(error));
+    hasFailures = true;
   }
 }

Then at the end of the script:

console.info(`\nFormatting hook finished.`);
if (hasFailures) process.exit(1);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/format-and-lint.ts` around lines 109 - 131, The runOxfmt and
runOxlint functions currently swallow errors by logging them and continuing;
change them to record failures and cause a non‑zero exit: add a top‑level
boolean (e.g., hasFailures) initialized false, set hasFailures = true inside
each catch in runOxfmt and runOxlint (referencing those function names), and at
the end of the script check if (hasFailures) process.exit(1); alternatively you
can rethrow the error from the catch to let the caller fail, but ensure one of
these approaches is implemented so formatting/lint failures are reflected in the
process exit code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.husky/pre-commit:
- Line 1: Update the husky pre-commit hook to run both linting and typechecking
by chaining the existing "pnpm lint:staged" command with the project's typecheck
script; specifically modify the command in the .husky/pre-commit file so it
invokes "pnpm lint:staged" followed by "pnpm typecheck" (e.g., using &&) to
ensure both linting and TypeScript type validation run before commits.

In @.oxfmtrc.json:
- Line 10: The .oxfmtrc.json contains an inline JavaScript-style comment ("// We
use the default groups but ensure we place the side-effect imports last except
for instrumentation") which invalidates the JSON; remove that comment text
(delete the "// ..." line) so the file is valid JSON, or move the explanatory
note into documentation/README if you need to preserve it; ensure the resulting
.oxfmtrc.json parses as strict JSON (no // or /* */ comments).

In `@knip.config.js`:
- Around line 6-9: The knip config currently ignores only the root-level
oxlint.config.ts under the '.' workspace; update the configuration to also
ignore all per-package/plugin oxlint.config.ts files by adding
"oxlint.config.ts" to the global ignore array (the top-level ignore list) or by
adding "oxlint.config.ts" to each workspace's ignore field (e.g., in the
workspace objects that define entry: ['packages/*/...'] or 'plugins/*/...') so
Knip no longer reports the 22 per-package/plugin oxlint.config.ts files as
unused.

In `@packages/project-builder-server/src/dev-server/mcp/fastify.ts`:
- Line 49: Restore the removed ESLint suppression by adding the eslint-disable
comment for unicorn/prefer-add-event-listener back next to the existing oxlint
directive immediately above the property assignment that triggers the rule (the
comment that was on Line 49); specifically add "eslint-disable-next-line
unicorn/prefer-add-event-listener" alongside the existing oxlint comment so both
linters are satisfied.

In `@packages/tools/eslint-configs/oxlint.js`:
- Around line 4-7: Replace the relative import of oxlint.config.base.ts with the
published package export and update the TS suppression: import oxlintConfig from
'@baseplate-dev/tools/oxlint-config-base' instead of '../oxlint.config.base.ts',
then keep using oxlint.buildFromOxlintConfig(oxlintConfig) to construct
oxlintEslintConfigs and change the comment above it from // `@ts-ignore` to //
`@ts-expect-error` so the type suppression will fail loudly if the typing is fixed
in the future.

In `@packages/tools/eslint-configs/react.js`:
- Around line 75-83: The current manual react-hooks config replaces the plugin
preset and drops many React Compiler rules; instead restore the plugin preset by
using reactHooksPlugin.configs.flat['recommended-latest'] and override only the
exhaustive-deps rule to 'off' (keep the plugin reference reactHooksPlugin and
ensure 'react-hooks/rules-of-hooks' remains enabled by inheriting the preset,
e.g. spread the preset and set rules: { 'react-hooks/exhaustive-deps': 'off' }).

In `@packages/tools/eslint-configs/typescript.js`:
- Around line 294-295: The new lint rule 'vitest/require-to-throw-message' will
fail existing tests that call .toThrow(), .not.toThrow(), or .rejects.toThrow()
without an expected message; either update each failing test (e.g., in
packages/utils/src/events/typed-event-emitter.unit.test.ts,
packages/utils/src/arrays/assert-no-duplicates.unit.test.ts,
examples/todo-with-better-auth/.../mime.unit.test.ts,
packages/sync/src/templates,
packages/project-builder-lib/src/migrations/migrations.unit.test.ts,
packages/core-generators/.../extract-ts-template-variables-simple.unit.test.ts)
to provide an explicit message or regex (use .toThrow('message') or
.toThrow(/pattern/) or assert the Error constructor/type), or temporarily
disable the rule by changing 'vitest/require-to-throw-message' from 'error' to
'off' in the eslint config (typescript.js) until all tests are updated; pick one
approach and apply it consistently.

In `@packages/tools/oxlint.config.base.ts`:
- Around line 23-27: The rule key is inconsistent: replace the incorrect
'no-unassigned-import' rule entry with the canonical
'import/no-unassigned-import' so the allowlist object is applied to the same
rule referenced elsewhere; update the rule name where it's defined (the array
with the allow: ['**/*.css', '**/vitest']) to 'import/no-unassigned-import' and
ensure any overrides that reference 'import/no-unassigned-import' remain
consistent.

In `@packages/tools/package.json`:
- Line 32: The package export "./oxlint-config-base" references
"./oxlint.config.base.ts" but that file is not listed in the package "files"
array, so add "oxlint.config.base.ts" (matching exact filename and extension) to
the "files" array in packages/tools/package.json; also ensure any other related
entries (the other files array ranges mentioned around lines 44-51) include the
same file so the export is bundled when publishing.

---

Outside diff comments:
In `@packages/ui-components/src/hooks/use-event-callback.ts`:
- Around line 22-29: The returned callback captures a stale reference because
ref.current is destructured into current outside the inner function; update
use-event-callback's useMemo so the returned function reads ref.current inside
its body (e.g., return (...args) => { const fn = ref.current; if (!fn) return;
return fn(...args); }) so subsequent updates to ref.current are honored; keep
the memo dependencies the same to preserve return-undefined-on-initial-undefined
behavior if desired.

---

Nitpick comments:
In @.oxfmtrc.json:
- Around line 11-20: The comment claiming special handling for instrumentation
imports is misleading because the "groups" array lacks a separate
instrumentation bucket; add an explicit instrumentation group immediately before
the "side_effect" entry in the "groups" config (e.g., insert "instrumentation"
or a pair like ["value-instrumentation","type-instrumentation"] depending on how
other groups are defined) and mirror this change in the corresponding ESLint
configs (the templates that reference this behavior) so instrumentation imports
are actually segregated, or alternatively remove the misleading comment if you
prefer not to implement the bucket.

In `@packages/sync/src/utils/create-generator.unit.test.ts`:
- Around line 64-66: Update the failing assertion in the
create-generator.unit.test to use a case-insensitive regex instead of an exact
string: change the expect for generator({ value: 123 as unknown as string }) to
toThrow matching a regex (e.g., /expected string, received number/i or allow
optional "invalid input: " prefix via /(?:invalid input:\s*)?expected string,
received number/i) so it tolerates Zod v3/v4 message differences; locate the
assertion around the generator(...) call and replace the literal string with the
regex.

In `@packages/tools/eslint-configs/typescript.js`:
- Around line 48-49: Remove the redundant handling of 'oxlint.config.ts' by
choosing a single strategy: either stop injecting 'oxlint.config.ts' into the
default projects array (the literal entry in the array alongside
...(options.extraDefaultProjectFiles ?? [])) or remove it from the global ignore
configuration where it's currently excluded; update the code around the array
that currently contains 'oxlint.config.ts' and
...(options.extraDefaultProjectFiles ?? []) accordingly and ensure the project's
allowDefaultProject/ignore settings remain consistent (modify the entry near the
array or the global ignore list, and remove the duplicate reference).

In `@scripts/format-and-lint.ts`:
- Around line 198-202: The current ordering runs ESLint (runEslint) before
Prettier (runPrettier), which makes fixes non-idempotent; change the control
flow so that when both deps.prettier and deps.eslint are true you run Prettier
first (e.g., await Promise.all(files.map(f => runPrettier(f)))) and only after
that call await runEslint(files, pkgDir); update the block containing
deps.prettier/deps.eslint to reflect this swapped order using the existing
runPrettier and runEslint symbols.
- Around line 157-164: The current sequence runs runOxlint(monorepoFiles) before
runOxfmt(monorepoFiles), which can make fixes non-idempotent; swap the calls so
formatting runs first: call runOxfmt(monorepoFiles) before
runOxlint(monorepoFiles) in the monorepoFiles handling block to ensure oxfmt
normalizes formatting prior to oxlint (including oxlint --fix) applying semantic
fixes.
- Around line 109-131: The runOxfmt and runOxlint functions currently swallow
errors by logging them and continuing; change them to record failures and cause
a non‑zero exit: add a top‑level boolean (e.g., hasFailures) initialized false,
set hasFailures = true inside each catch in runOxfmt and runOxlint (referencing
those function names), and at the end of the script check if (hasFailures)
process.exit(1); alternatively you can rethrow the error from the catch to let
the caller fail, but ensure one of these approaches is implemented so
formatting/lint failures are reflected in the process exit code.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d4bd9967-df29-4555-83d3-3f03e637dc27

📥 Commits

Reviewing files that changed from the base of the PR and between 1115ba0 and 116519f.

⛔ Files ignored due to path filters (9)
  • examples/blog-with-auth/apps/admin/baseplate/generated/package.json is excluded by !**/generated/**, !**/generated/**
  • examples/blog-with-auth/apps/backend/baseplate/generated/package.json is excluded by !**/generated/**, !**/generated/**
  • examples/blog-with-auth/libs/transactional/baseplate/generated/package.json is excluded by !**/generated/**, !**/generated/**
  • examples/blog-with-auth/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • examples/todo-with-better-auth/apps/admin/baseplate/generated/package.json is excluded by !**/generated/**, !**/generated/**
  • examples/todo-with-better-auth/apps/backend/baseplate/generated/package.json is excluded by !**/generated/**, !**/generated/**
  • examples/todo-with-better-auth/apps/web/baseplate/generated/package.json is excluded by !**/generated/**, !**/generated/**
  • examples/todo-with-better-auth/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (108)
  • .agents/dev-commands.md
  • .changeset/improve-lint-build-performance.md
  • .claude/settings.json
  • .devcontainer/conf/starship.toml
  • .husky/post-checkout
  • .husky/pre-commit
  • .oxfmtrc.json
  • AGENTS.md
  • examples/blog-with-auth/apps/admin/package.json
  • examples/blog-with-auth/apps/backend/package.json
  • examples/blog-with-auth/libs/transactional/package.json
  • examples/todo-with-better-auth/apps/admin/package.json
  • examples/todo-with-better-auth/apps/backend/package.json
  • examples/todo-with-better-auth/apps/web/package.json
  • knip.config.js
  • oxlint.config.ts
  • package.json
  • packages/code-morph/oxlint.config.ts
  • packages/code-morph/package.json
  • packages/code-morph/src/types.ts
  • packages/core-generators/eslint.config.js
  • packages/core-generators/oxlint.config.ts
  • packages/core-generators/package.json
  • packages/core-generators/src/constants/core-packages.ts
  • packages/core-generators/src/renderers/extractor/plugins/template-renderers/renderers-file.ts
  • packages/core-generators/src/renderers/typescript/imports/ts-morph-operations.unit.test.ts
  • packages/core-generators/src/renderers/typescript/templates/creators.ts
  • packages/core-generators/src/renderers/typescript/templates/types.ts
  • packages/core-generators/src/test-helpers/matchers.unit.test.ts
  • packages/create-project/oxlint.config.ts
  • packages/create-project/package.json
  • packages/fastify-generators/eslint.config.js
  • packages/fastify-generators/oxlint.config.ts
  • packages/fastify-generators/package.json
  • packages/project-builder-cli/oxlint.config.ts
  • packages/project-builder-cli/package.json
  • packages/project-builder-common/package.json
  • packages/project-builder-dev/oxlint.config.ts
  • packages/project-builder-dev/package.json
  • packages/project-builder-lib/oxlint.config.ts
  • packages/project-builder-lib/package.json
  • packages/project-builder-lib/src/plugins/migrations/run-migrations.ts
  • packages/project-builder-lib/src/web/components/definition-diff-alert.tsx
  • packages/project-builder-server/oxlint.config.ts
  • packages/project-builder-server/package.json
  • packages/project-builder-server/src/actions/definition/draft-lifecycle.int.test.ts
  • packages/project-builder-server/src/compiler/web/web-compiler.ts
  • packages/project-builder-server/src/dev-server/mcp/fastify.ts
  • packages/project-builder-server/src/templates/utils/resolve-generator.ts
  • packages/project-builder-test/oxlint.config.ts
  • packages/project-builder-test/package.json
  • packages/project-builder-web/oxlint.config.ts
  • packages/project-builder-web/package.json
  • packages/react-generators/eslint.config.js
  • packages/react-generators/oxlint.config.ts
  • packages/react-generators/package.json
  • packages/sync/oxlint.config.ts
  • packages/sync/package.json
  • packages/sync/src/output/string-merge-algorithms/git-merge-driver.ts
  • packages/sync/src/output/string-merge-algorithms/simple-diff.unit.test.ts
  • packages/sync/src/templates/extractor/utils/index-template-configs.unit.test.ts
  • packages/sync/src/templates/metadata/delete-metadata.unit.test.ts
  • packages/sync/src/templates/metadata/read-template-info-files.unit.test.ts
  • packages/sync/src/utils/create-generator.unit.test.ts
  • packages/tools/eslint-configs/oxlint.js
  • packages/tools/eslint-configs/react.js
  • packages/tools/eslint-configs/typescript.js
  • packages/tools/eslint.config.node.js
  • packages/tools/eslint.config.react.js
  • packages/tools/oxlint.config.base.ts
  • packages/tools/oxlint.config.ts
  • packages/tools/package.json
  • packages/tools/tsconfig.json
  • packages/ui-components/oxlint.config.ts
  • packages/ui-components/package.json
  • packages/ui-components/src/components/ui/date-time-picker-field/date-time-picker-field.stories.tsx
  • packages/ui-components/src/hooks/use-event-callback.ts
  • packages/utils/oxlint.config.ts
  • packages/utils/package.json
  • packages/utils/src/events/typed-event-emitter.ts
  • packages/utils/src/fs/handle-not-found-error.unit.test.ts
  • packages/utils/src/toposort/toposort.unit.test.ts
  • plugins/plugin-auth/eslint.config.js
  • plugins/plugin-auth/oxlint.config.ts
  • plugins/plugin-auth/package.json
  • plugins/plugin-email/eslint.config.js
  • plugins/plugin-email/oxlint.config.ts
  • plugins/plugin-email/package.json
  • plugins/plugin-observability/eslint.config.js
  • plugins/plugin-observability/oxlint.config.ts
  • plugins/plugin-observability/package.json
  • plugins/plugin-payments/eslint.config.js
  • plugins/plugin-payments/oxlint.config.ts
  • plugins/plugin-payments/package.json
  • plugins/plugin-queue/eslint.config.js
  • plugins/plugin-queue/oxlint.config.ts
  • plugins/plugin-queue/package.json
  • plugins/plugin-rate-limit/eslint.config.js
  • plugins/plugin-rate-limit/oxlint.config.ts
  • plugins/plugin-rate-limit/package.json
  • plugins/plugin-storage/eslint.config.js
  • plugins/plugin-storage/oxlint.config.ts
  • plugins/plugin-storage/package.json
  • pnpm-workspace.yaml
  • scripts/check-changesets.ts
  • scripts/format-and-lint.ts
  • tsconfig.scripts.json
  • turbo.json
💤 Files with no reviewable changes (2)
  • packages/utils/src/fs/handle-not-found-error.unit.test.ts
  • .husky/post-checkout

Comment thread .husky/pre-commit
@@ -0,0 +1 @@
pnpm lint:staged

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify that lint:staged script is defined in root package.json
echo "Checking for lint:staged script in root package.json..."
jq -r '.scripts["lint:staged"] // "NOT FOUND"' package.json

Repository: halfdomelabs/baseplate

Length of output: 133


Add typecheck to pre-commit hook for complete pre-commit validation.

The lint:staged script exists and is correctly referenced. However, based on the project learnings that "pnpm lint --fix and pnpm typecheck before committing code", consider enhancing the hook to also run typecheck:

pnpm lint:staged && pnpm typecheck

This ensures both linting and type validation pass before commits are allowed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.husky/pre-commit at line 1, Update the husky pre-commit hook to run both
linting and typechecking by chaining the existing "pnpm lint:staged" command
with the project's typecheck script; specifically modify the command in the
.husky/pre-commit file so it invokes "pnpm lint:staged" followed by "pnpm
typecheck" (e.g., using &&) to ensure both linting and TypeScript type
validation run before commits.

Comment thread .oxfmtrc.json
"printWidth": 80,
"sortImports": {
"internalPattern": ["@src/", "#"],
// We use the default groups but ensure we place the side-effect imports last except for instrumentation

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
python - <<'PY'
import json
from pathlib import Path

with Path(".oxfmtrc.json").open() as fh:
    json.load(fh)

print("valid json")
PY

Repository: halfdomelabs/baseplate

Length of output: 830


Remove the inline comment from this JSON file.

.json format does not support // comments, causing a parse error at line 10. The configuration file is currently invalid and will fail to load.

Proposed fix
   "sortImports": {
     "internalPattern": ["@src/", "#"],
-    // We use the default groups but ensure we place the side-effect imports last except for instrumentation
     "groups": [
       "type-builtin",
       ["value-builtin", "value-external"],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// We use the default groups but ensure we place the side-effect imports last except for instrumentation
"sortImports": {
"internalPattern": ["@src/", "#"],
"groups": [
"type-builtin",
["value-builtin", "value-external"],
🧰 Tools
🪛 Biome (2.4.6)

[error] 10-10: Expected a property but instead found '// We use the default groups but ensure we place the side-effect imports last except for instrumentation'.

(parse)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.oxfmtrc.json at line 10, The .oxfmtrc.json contains an inline
JavaScript-style comment ("// We use the default groups but ensure we place the
side-effect imports last except for instrumentation") which invalidates the
JSON; remove that comment text (delete the "// ..." line) so the file is valid
JSON, or move the explanatory note into documentation/README if you need to
preserve it; ensure the resulting .oxfmtrc.json parses as strict JSON (no // or
/* */ comments).

Comment thread knip.config.js
Comment on lines 6 to 9
'.': {
entry: ['.workspace-meta/config.ts', 'scripts/**/*.{js,ts}'],
ignore: ['oxlint.config.ts'],
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all oxlint.config.ts files in the repo to verify coverage
echo "Finding all oxlint.config.ts files..."
fd -t f 'oxlint.config.ts'

echo ""
echo "Checking if any are outside root level..."
fd -t f 'oxlint.config.ts' | grep -v '^oxlint.config.ts$' || echo "All configs are at root level"

Repository: halfdomelabs/baseplate

Length of output: 1975


🏁 Script executed:

cat -n knip.config.js | head -200

Repository: halfdomelabs/baseplate

Length of output: 6179


Add oxlint.config.ts to global ignore array or per-workspace ignore patterns.

The root workspace's ignore pattern covers only the root-level oxlint.config.ts. However, there are 22 oxlint.config.ts files across the repository (15 in packages/*, 7 in plugins/*). Since the packages/* and plugins/* workspace definitions lack ignore patterns for oxlint.config.ts, Knip will report these configuration files as unused exports.

Add oxlint.config.ts to the global ignore array (lines 144–151), or add it to the ignore field in each workspace that has per-package configs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@knip.config.js` around lines 6 - 9, The knip config currently ignores only
the root-level oxlint.config.ts under the '.' workspace; update the
configuration to also ignore all per-package/plugin oxlint.config.ts files by
adding "oxlint.config.ts" to the global ignore array (the top-level ignore list)
or by adding "oxlint.config.ts" to each workspace's ignore field (e.g., in the
workspace objects that define entry: ['packages/*/...'] or 'plugins/*/...') so
Knip no longer reports the 22 per-package/plugin oxlint.config.ts files as
unused.


// Clean up transport when closed
// eslint-disable-next-line unicorn/prefer-add-event-listener -- MCP server requires this signature
// oxlint-disable-next-line unicorn/prefer-add-event-listener -- false positive from oxlint

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Check whether ESLint still configures unicorn rule(s)
rg -n -C3 "eslint-plugin-unicorn|unicorn/prefer-add-event-listener|prefer-add-event-listener" --type=js --type=ts --glob '!**/node_modules/**'

# 2) Inspect suppressions in target file
rg -n -C2 "oxlint-disable-next-line unicorn/prefer-add-event-listener|eslint-disable-next-line unicorn/prefer-add-event-listener|transport\\.onclose" packages/project-builder-server/src/dev-server/mcp/fastify.ts

Repository: halfdomelabs/baseplate

Length of output: 37857


🏁 Script executed:

# Check for ESLint config in project-builder-server
find packages/project-builder-server -maxdepth 3 -name "eslint.config.*" -o -name ".eslintrc*" | head -20

# Check package.json to see which config is extended
fd "package.json" packages/project-builder-server --maxdepth 2 -x grep -A 10 -B 5 "eslint"

# Verify the main TypeScript config that would apply
cat packages/tools/eslint-configs/typescript.js | head -260 | tail -30

Repository: halfdomelabs/baseplate

Length of output: 2279


🏁 Script executed:

# Check the content of project-builder-server's own eslint config
cat packages/project-builder-server/eslint.config.js

Repository: halfdomelabs/baseplate

Length of output: 217


🏁 Script executed:

# Find and examine the defineNodeEslintConfig function
fd "eslint-node" packages/tools -type f -name "*.ts" -o -name "*.js" | head -10

Repository: halfdomelabs/baseplate

Length of output: 236


🏁 Script executed:

# Find the defineNodeEslintConfig function
rg -l "defineNodeEslintConfig" packages/tools --type ts --type js

Repository: halfdomelabs/baseplate

Length of output: 134


🏁 Script executed:

# Examine the defineNodeEslintConfig implementation
cat packages/tools/eslint.config.node.js

Repository: halfdomelabs/baseplate

Length of output: 1125


🏁 Script executed:

# Confirm the full list of disabled unicorn rules in the typescript config
rg -A 50 "unicorn/" packages/tools/eslint-configs/typescript.js | grep "unicorn/" | head -30

Repository: halfdomelabs/baseplate

Length of output: 968


🏁 Script executed:

# Check if there are any ESLint rule overrides for the MCP directory
rg -B 5 -A 10 "dev-server|mcp" packages/tools/eslint-configs/typescript.js

Repository: halfdomelabs/baseplate

Length of output: 48


🏁 Script executed:

# Verify the actual code structure at the problematic line and understand the pattern
cat -n packages/project-builder-server/src/dev-server/mcp/fastify.ts | sed -n '45,55p'

Repository: halfdomelabs/baseplate

Length of output: 569


Keep ESLint suppression alongside OxLint

On Line 49, the ESLint disable directive for unicorn/prefer-add-event-listener was removed while keeping only the OxLint directive. Since unicorn/prefer-add-event-listener is not globally disabled in ESLint configuration and both linters run in CI, this will cause ESLint to fail on the property assignment pattern at Line 50.

Suggested patch
-      // oxlint-disable-next-line unicorn/prefer-add-event-listener -- false positive from oxlint
+      // eslint-disable-next-line unicorn/prefer-add-event-listener -- false positive for property-based callback API
+      // oxlint-disable-next-line unicorn/prefer-add-event-listener -- false positive from oxlint
       transport.onclose = () => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/project-builder-server/src/dev-server/mcp/fastify.ts` at line 49,
Restore the removed ESLint suppression by adding the eslint-disable comment for
unicorn/prefer-add-event-listener back next to the existing oxlint directive
immediately above the property assignment that triggers the rule (the comment
that was on Line 49); specifically add "eslint-disable-next-line
unicorn/prefer-add-event-listener" alongside the existing oxlint comment so both
linters are satisfied.

Comment on lines +4 to +7
import oxlintConfig from '../oxlint.config.base.ts';

// @ts-ignore -- oxlintConfig is not typed correctly
const oxlintEslintConfigs = oxlint.buildFromOxlintConfig(oxlintConfig);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect tools package module wiring and where the TS config is consumed from JS.
jq '{name,type,main,exports,files,scripts}' packages/tools/package.json
rg -n -C2 "oxlint.config.base" packages/tools
rg -n -C2 "eslint-configs/oxlint" packages/tools

Repository: halfdomelabs/baseplate

Length of output: 3481


🏁 Script executed:

cd packages/tools && find . -maxdepth 2 -type f \( -name "*.config.*" -o -name ".loader*" -o -name "package.json" \) | head -20

Repository: halfdomelabs/baseplate

Length of output: 314


🏁 Script executed:

# Check for TypeScript loaders in root config
cd packages/tools && cat package.json | grep -A5 -B5 "type\|loader\|tsx\|ts-node" || echo "No loader config in package.json"

Repository: halfdomelabs/baseplate

Length of output: 2096


🏁 Script executed:

# Check if .ts files are in the build output or if there's a build step
cat packages/tools/tsconfig*.json 2>/dev/null | head -50

Repository: halfdomelabs/baseplate

Length of output: 1313


🏁 Script executed:

# Look for loader or tsx configuration in repo root and package setup
git ls-files | grep -E "(loader|tsx|register)" | head -10

Repository: halfdomelabs/baseplate

Length of output: 983


🏁 Script executed:

# Check the actual eslint.config.node.js to see how it's meant to be executed
sed -n '1,20p' packages/tools/eslint.config.node.js

Repository: halfdomelabs/baseplate

Length of output: 814


🏁 Script executed:

# Check root-level eslint/tsconfig setup
cat .eslintrc.json 2>/dev/null || cat .eslintrc.js 2>/dev/null || cat .eslintrc.yaml 2>/dev/null || echo "No .eslintrc found at root"

Repository: halfdomelabs/baseplate

Length of output: 92


🏁 Script executed:

# Check if there's a build output directory or how eslint.config.js is meant to be used
git ls-files | grep -E "eslint.config|loader" | head -20

Repository: halfdomelabs/baseplate

Length of output: 1532


🏁 Script executed:

# Look at how the repo root oxlint.config.ts compares with the oxlint.js config
echo "=== oxlint.config.ts (root, .ts file) ===" && head -10 packages/tools/oxlint.config.ts && echo -e "\n=== oxlint.js (in eslint-configs, .js file) ===" && cat packages/tools/eslint-configs/oxlint.js

Repository: halfdomelabs/baseplate

Length of output: 675


🏁 Script executed:

# Check if oxlint.config.base.ts is actually being distributed/built
cat packages/tools/package.json | grep -A10 '"files"'

Repository: halfdomelabs/baseplate

Length of output: 337


Use the package export for oxlint.config.base.ts instead of a relative import.

Line 4 imports ../oxlint.config.base.ts with a relative path, but oxlint.config.base.ts is excluded from the "files" array in package.json and will not be distributed with the package. When ESLint config consumers run eslint.config.node.js or eslint.config.react.js, the import will fail at runtime.

Change to:

import oxlintConfig from '@baseplate-dev/tools/oxlint-config-base';

This matches the pattern already used in the root oxlint.config.ts and respects the package export mapping. Also replace // @ts-ignore with `// `@ts-expect-error on line 6 so the suppression self-expires if the typing issue is later resolved.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/tools/eslint-configs/oxlint.js` around lines 4 - 7, Replace the
relative import of oxlint.config.base.ts with the published package export and
update the TS suppression: import oxlintConfig from
'@baseplate-dev/tools/oxlint-config-base' instead of '../oxlint.config.base.ts',
then keep using oxlint.buildFromOxlintConfig(oxlintConfig) to construct
oxlintEslintConfigs and change the comment above it from // `@ts-ignore` to //
`@ts-expect-error` so the type suppression will fail loudly if the typing is fixed
in the future.

Comment on lines +75 to +83
// React Hooks -- only enable rules-of-hooks rule for performance reasons
// oxc handles exhaustive-deps rule
/** @type {ESLintConfig} */ ({
files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'],
plugins: { 'react-hooks': reactHooksPlugin },
rules: {
// Disable new strict rules from react-hooks v7 until we enable React Compiler
'react-hooks/refs': 'off',
'react-hooks/set-state-in-effect': 'off',
'react-hooks/preserve-manual-memoization': 'off',
'react-hooks/incompatible-library': 'off',
'react-hooks/rules-of-hooks': 'error',
},
},
}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

What rules are included in eslint-plugin-react-hooks's flat "recommended-latest" config, and does Oxlint/Oxc cover all of them or only react-hooks/exhaustive-deps?

💡 Result:

Rules in eslint-plugin-react-hooks flat recommended-latest

The (flat) recommended-latest config includes the same “recommended” rule set shown in the React docs (i.e., the two classic Hooks rules plus the React-Compiler-related rules): (react.dev)

  • react-hooks/exhaustive-deps
  • react-hooks/rules-of-hooks
  • react-hooks/component-hook-factories
  • react-hooks/config
  • react-hooks/error-boundaries
  • react-hooks/gating
  • react-hooks/globals
  • react-hooks/immutability
  • react-hooks/incompatible-library
  • react-hooks/preserve-manual-memoization
  • react-hooks/purity
  • react-hooks/refs
  • react-hooks/set-state-in-effect
  • react-hooks/set-state-in-render
  • react-hooks/static-components
  • react-hooks/unsupported-syntax
  • react-hooks/use-memo (react.dev)

Does Oxlint/Oxc cover all of them?

No—Oxlint/Oxc covers the two core Hooks rules, but not the additional compiler-focused rules listed above:

  • react/exhaustive-deps (oxc.rs)
  • react/rules-of-hooks (oxc.rs)

Everything else in eslint-plugin-react-hooks’s recommended-latest (e.g. component-hook-factories, immutability, refs, static-components, etc.) is not covered by Oxlint/Oxc under the React plugin. (react.dev)

Citations:


Keep the preset and disable only exhaustive-deps explicitly.

The recommended-latest config includes 17 rules—not just exhaustive-deps and rules-of-hooks. Replacing it with a one-rule manual config drops 15 additional React Compiler-related rules (e.g., component-hook-factories, immutability, refs, set-state-in-effect, static-components, etc.). Oxlint/Oxc covers only the two core Hooks rules and does not provide equivalents for these compiler-focused rules.

To keep coverage while delegating only exhaustive-deps to Oxlint, extend the preset and disable that single rule:

{ ...reactHooksPlugin.configs.flat['recommended-latest'], rules: { 'react-hooks/exhaustive-deps': 'off' } }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/tools/eslint-configs/react.js` around lines 75 - 83, The current
manual react-hooks config replaces the plugin preset and drops many React
Compiler rules; instead restore the plugin preset by using
reactHooksPlugin.configs.flat['recommended-latest'] and override only the
exhaustive-deps rule to 'off' (keep the plugin reference reactHooksPlugin and
ensure 'react-hooks/rules-of-hooks' remains enabled by inheriting the preset,
e.g. spread the preset and set rules: { 'react-hooks/exhaustive-deps': 'off' }).

Comment on lines +294 to +295
// Require toThrow with an error message
'vitest/require-to-throw-message': 'error',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read-only check: find zero-arg toThrow/toThrowError calls that will violate the new rule.
# Expected result: no matches.
rg -nP --type=ts --type=tsx --type=js --type=jsx '\.toThrow(?:Error)?\(\s*\)' \
  -g '!**/node_modules/**' \
  -g '!**/dist/**'

Repository: halfdomelabs/baseplate

Length of output: 93


🏁 Script executed:

#!/bin/bash
# Search for zero-arg toThrow/toThrowError calls in test files
# Adjusted to avoid file type issues
rg -nP '\.toThrow(?:Error)?\(\s*\)' \
  --glob='*.test.ts' \
  --glob='*.test.js' \
  --glob='*.spec.ts' \
  --glob='*.spec.js' \
  --glob='**/*.{unit,int}.test.ts' \
  -g '!**/node_modules/**' \
  -g '!**/dist/**' \
  -g '!**/build/**'

Repository: halfdomelabs/baseplate

Length of output: 2263


Address zero-argument .toThrow() calls before enforcing vitest/require-to-throw-message.

The vitest/require-to-throw-message rule at line 295 will cause immediate lint failures. At least 19 instances of zero-argument .toThrow(), .not.toThrow(), and .rejects.toThrow() assertions exist across the codebase:

  • packages/utils/src/events/typed-event-emitter.unit.test.ts
  • packages/utils/src/arrays/assert-no-duplicates.unit.test.ts
  • examples/todo-with-better-auth/apps/backend/src/modules/storage/utils/mime.unit.test.ts
  • packages/sync/src/templates (multiple files)
  • packages/project-builder-lib/src/migrations/migrations.unit.test.ts
  • packages/core-generators/src/renderers/typescript/extractor/extract-ts-template-variables-simple.unit.test.ts

Either fix all violations first or disable the rule until compliance is achieved.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/tools/eslint-configs/typescript.js` around lines 294 - 295, The new
lint rule 'vitest/require-to-throw-message' will fail existing tests that call
.toThrow(), .not.toThrow(), or .rejects.toThrow() without an expected message;
either update each failing test (e.g., in
packages/utils/src/events/typed-event-emitter.unit.test.ts,
packages/utils/src/arrays/assert-no-duplicates.unit.test.ts,
examples/todo-with-better-auth/.../mime.unit.test.ts,
packages/sync/src/templates,
packages/project-builder-lib/src/migrations/migrations.unit.test.ts,
packages/core-generators/.../extract-ts-template-variables-simple.unit.test.ts)
to provide an explicit message or regex (use .toThrow('message') or
.toThrow(/pattern/) or assert the Error constructor/type), or temporarily
disable the rule by changing 'vitest/require-to-throw-message' from 'error' to
'off' in the eslint config (typescript.js) until all tests are updated; pick one
approach and apply it consistently.

Comment on lines +23 to +27
'no-unassigned-import': [
'error',
{
allow: ['**/*.css', '**/vitest'],
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Oxlint config, what is the correct rule identifier for the unassigned import rule: no-unassigned-importorimport/no-unassigned-import?

💡 Result:

Use import/no-unassigned-import (and enable the import plugin), e.g.:

{
  "plugins": ["import"],
  "rules": { "import/no-unassigned-import": "error" }
}

Oxlint’s rule docs and CLI example both use import/no-unassigned-import. [1]

Sources

[1] Oxlint rule docs: import/no-unassigned-import https://oxc.rs/docs/guide/usage/linter/rules/import/no-unassigned-import


🏁 Script executed:

cat -n packages/tools/oxlint.config.base.ts | sed -n '20,90p'

Repository: halfdomelabs/baseplate

Length of output: 2522


Use the canonical rule key for unassigned imports.

Line 23 uses no-unassigned-import, while line 85 uses import/no-unassigned-import. According to Oxlint's official documentation, import/no-unassigned-import is the correct rule identifier. This inconsistency means the allowlist configuration on line 23–27 may not apply to the override on line 85.

🔧 Proposed fix
-    'no-unassigned-import': [
+    'import/no-unassigned-import': [
       'error',
       {
         allow: ['**/*.css', '**/vitest'],
       },
     ],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
'no-unassigned-import': [
'error',
{
allow: ['**/*.css', '**/vitest'],
},
'import/no-unassigned-import': [
'error',
{
allow: ['**/*.css', '**/vitest'],
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/tools/oxlint.config.base.ts` around lines 23 - 27, The rule key is
inconsistent: replace the incorrect 'no-unassigned-import' rule entry with the
canonical 'import/no-unassigned-import' so the allowlist object is applied to
the same rule referenced elsewhere; update the rule name where it's defined (the
array with the allow: ['**/*.css', '**/vitest']) to
'import/no-unassigned-import' and ensure any overrides that reference
'import/no-unassigned-import' remain consistent.

"./eslint-configs/storybook": "./eslint-configs/storybook.js",
"./eslint-node": "./eslint.config.node.js",
"./eslint-react": "./eslint.config.react.js",
"./oxlint-config-base": "./oxlint.config.base.ts",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing oxlint.config.base.ts in the files array.

The new export "./oxlint-config-base" points to ./oxlint.config.base.ts, but the files array doesn't include this file. When the package is published, this file won't be included, causing the export to fail for consumers.

🐛 Proposed fix to include the oxlint config file
   "files": [
     "eslint-configs/**/*.js",
     "prettier.config.*.js",
     "eslint.config.*.js",
     "tsconfig.*.json",
     "vitest.*.js",
-    "src-subpath-import-plugin.js"
+    "src-subpath-import-plugin.js",
+    "oxlint.config.base.ts"
   ],

Also applies to: 44-51

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/tools/package.json` at line 32, The package export
"./oxlint-config-base" references "./oxlint.config.base.ts" but that file is not
listed in the package "files" array, so add "oxlint.config.base.ts" (matching
exact filename and extension) to the "files" array in
packages/tools/package.json; also ensure any other related entries (the other
files array ranges mentioned around lines 44-51) include the same file so the
export is bundled when publishing.

@kingston kingston merged commit 6776496 into main Mar 15, 2026
12 checks passed
@kingston kingston deleted the kingston/improve-lint-and-build-performance branch March 15, 2026 16:47
@github-actions github-actions Bot mentioned this pull request Mar 15, 2026
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.

1 participant