Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/docs-contributing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@vp-tw/eslint-config": patch
---

Add contributing guide and CLAUDE.md
95 changes: 95 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# ESLint Config Development Guide

## Architecture

```
packages/eslint-config/src/
├── vdustr.ts # Main entry point, orchestrates all configs
├── configs/ # Optional feature configs (emotion, mdx, prettier, etc.)
├── extends/ # Extensions for antfu configs (react, yaml, etc.)
├── lib/ # Dynamic import wrappers for optional dependencies
├── utils/ # Shared utilities (mergeConfig, extendsConfig, etc.)
└── types.ts # Type definitions
```

## Key Patterns

### Dynamic Imports for Optional Dependencies

Optional ESLint plugins must use dynamic imports to avoid loading when disabled:

```typescript
// src/lib/eslint-react.ts
import type EslintReact from "@eslint-react/eslint-plugin";
import { interopDefault } from "@antfu/eslint-config";

// IMPORTANT: Explicit return type required to avoid TS2742 build errors
const eslintReact = (): Promise<typeof EslintReact> =>
interopDefault(import("@eslint-react/eslint-plugin"));

export { eslintReact };
```

**Why explicit types?** Without them, TypeScript cannot infer types referencing internal modules (e.g., `@eslint-react/shared`), causing TS2742 errors during `unbuild`.

### extendsConfig Pattern

Use `extendsConfig` to extend antfu's configs conditionally:

```typescript
extendsConfig(composer, "antfu/react/rules", async (config) => {
// Only executes if "antfu/react/rules" exists
const plugin = await importPlugin();
return [config, modifiedConfig];
});
```

### Config Structure

- `configs/` - Standalone configs that use `config.append()`, return arrays
- `extends/` - Modify existing antfu configs via `extendsConfig()`
- `lib/` - Dynamic import wrappers, always require explicit return types

## Commands

```bash
pnpm build # Build @vp-tw/eslint-config (in packages/eslint-config)
pnpm test # Run tests for @vp-tw/eslint-config
pnpm run checkTypes # Type check entire workspace (root)
pnpm lint # Lint entire workspace (root)
```

## Release

Managed by changesets. CI auto-publishes on main branch merge.

```bash
pnpm changeset # Create changeset (interactive)
# Or manually create .changeset/<name>.md
```

## Common Issues

### TS2742: Inferred type cannot be named

**Cause:** Dynamic import returns type referencing internal module.
**Fix:** Add explicit return type annotation.

```typescript
// Bad - causes TS2742
const fn = () => interopDefault(import("pkg"));

// Good
const fn = (): Promise<(typeof import("pkg"))["default"]> => interopDefault(import("pkg"));
```

### Feature not loading when enabled

Check `vdustr.ts` for conditional logic. Features disabled by default need explicit enable check:

```typescript
const reactEnabled = Boolean(antfuOptions?.react ?? false);
if (reactEnabled) {
react(config, vpOptions?.extends?.react);
}
```
37 changes: 32 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,43 @@ export default vdustr({

Automatically sorts keys in `pnpm-workspace.yaml` alphabetically using `yaml/sort-keys` rule.

## Release
## Contributing

### Development

```bash
pnpm install # Install dependencies
pnpm build # Build @vp-tw/eslint-config (in packages/eslint-config)
pnpm test # Run tests for @vp-tw/eslint-config
pnpm run checkTypes # Type check entire workspace
pnpm lint # Lint entire workspace
```

### Release

Managed by [changesets](https://github.com/changesets/changesets). CI auto-publishes when merged to main.

```bash
pnpm -w v:patch # or
pnpm -w v:minor # or
pnpm -w v:major
pnpm changeset # Create changeset interactively
```

### Adding Dynamic Import Wrappers

When adding support for optional ESLint plugins, create a wrapper in `src/lib/`:

pnpm -r publish
```typescript
// src/lib/my-plugin.ts
import type MyPlugin from "eslint-plugin-my";
import { interopDefault } from "@antfu/eslint-config";

// Explicit return type required to avoid TS2742 build errors
const myPlugin = (): Promise<typeof MyPlugin> => interopDefault(import("eslint-plugin-my"));

export { myPlugin };
```

See [CLAUDE.md](./CLAUDE.md) for detailed architecture and patterns.

## License

MIT © ViPro <vdustr@gmail.com> (<http://vdustr.dev>)