feat: initial dev-configs monorepo

Shared configs for TypeScript projects: ESLint, Prettier, TypeScript,
Vite, Jest, Playwright, Knip. Published as @vigdorov/* npm packages
to Gitea registry.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-16 23:40:22 +03:00
commit cf64bf6d7d
38 changed files with 11287 additions and 0 deletions

View File

@ -0,0 +1,36 @@
{
"name": "@vigdorov/eslint-config",
"version": "1.0.0",
"description": "Shared ESLint configuration with flat config",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": ["dist"],
"exports": {
".": "./dist/index.js",
"./base": "./dist/base.js",
"./react": "./dist/react.js",
"./node": "./dist/node.js"
},
"scripts": {
"build": "tsc"
},
"peerDependencies": {
"eslint": ">=9.0.0",
"typescript": ">=5.0.0"
},
"dependencies": {
"typescript-eslint": "^8.32.1",
"@stylistic/eslint-plugin": "^4.2.0",
"eslint-plugin-unused-imports": "^4.1.4"
},
"devDependencies": {
"eslint": "^9.27.0",
"typescript": "^5.8.3",
"@types/eslint": "^9.6.1"
},
"optionalDependencies": {
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0"
}
}

View File

@ -0,0 +1,30 @@
import tseslint from 'typescript-eslint';
import stylistic from '@stylistic/eslint-plugin';
import unusedImports from 'eslint-plugin-unused-imports';
import {qualityRules, typescriptRules, unusedImportsRules, stylisticRules} from './rules.js';
type ConfigArray = ReturnType<typeof tseslint.config>;
type ConfigOverride = (configs: ConfigArray) => ConfigArray;
export function base(override: ConfigOverride = (c) => c): ConfigArray {
const configs = tseslint.config(
...tseslint.configs.recommended,
{
plugins: {
'@stylistic': stylistic,
'unused-imports': unusedImports,
},
rules: {
...qualityRules,
...typescriptRules,
...unusedImportsRules,
...stylisticRules,
},
},
{
ignores: ['dist/', 'node_modules/', 'coverage/'],
},
);
return override(configs);
}

View File

@ -0,0 +1,3 @@
export {base} from './base.js';
export {react} from './react.js';
export {node} from './node.js';

View File

@ -0,0 +1,9 @@
import tseslint from 'typescript-eslint';
import {base} from './base.js';
type ConfigArray = ReturnType<typeof tseslint.config>;
type ConfigOverride = (configs: ConfigArray) => ConfigArray;
export function node(override: ConfigOverride = (c) => c): ConfigArray {
return base(override);
}

View File

@ -0,0 +1,33 @@
import tseslint from 'typescript-eslint';
import reactPlugin from 'eslint-plugin-react';
import reactHooksPlugin from 'eslint-plugin-react-hooks';
import {base} from './base.js';
import {reactRules, reactHooksRules} from './rules.js';
type ConfigArray = ReturnType<typeof tseslint.config>;
type ConfigOverride = (configs: ConfigArray) => ConfigArray;
export function react(override: ConfigOverride = (c) => c): ConfigArray {
const baseConfigs = base();
const configs = tseslint.config(
...baseConfigs,
{
plugins: {
'react': reactPlugin,
'react-hooks': reactHooksPlugin,
},
settings: {
react: {
version: 'detect',
},
},
rules: {
...reactRules,
...reactHooksRules,
},
},
);
return override(configs);
}

View File

@ -0,0 +1,91 @@
import type {Linter} from 'eslint';
type Rules = Partial<Linter.RulesRecord>;
export const qualityRules: Rules = {
'eqeqeq': 'error',
'no-console': ['warn', {allow: ['warn', 'error']}],
'no-alert': 'warn',
'no-param-reassign': ['error', {props: true}],
'no-useless-concat': 'warn',
'no-else-return': 'warn',
'no-lonely-if': 'warn',
'no-constructor-return': 'warn',
'no-sequences': 'warn',
'prefer-promise-reject-errors': 'warn',
'require-await': 'warn',
'no-new': 'warn',
'no-multi-str': 'warn',
'no-multi-assign': 'warn',
'no-nested-ternary': 'warn',
'no-useless-computed-key': 'warn',
'no-useless-constructor': 'warn',
'no-var': 'warn',
'no-duplicate-imports': 'warn',
'no-plusplus': 'warn',
'no-bitwise': 'warn',
'prefer-const': 'warn',
'prefer-rest-params': 'warn',
'prefer-template': 'warn',
'array-callback-return': ['warn', {allowImplicit: true, checkForEach: true}],
'default-param-last': 'warn',
'yoda': 'warn',
};
export const typescriptRules: Rules = {
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-empty-object-type': 'error',
'@typescript-eslint/no-unsafe-function-type': 'error',
'@typescript-eslint/no-wrapper-object-types': 'error',
'@typescript-eslint/no-use-before-define': 'warn',
'@typescript-eslint/no-shadow': 'warn',
'no-shadow': 'off',
};
export const unusedImportsRules: Rules = {
'unused-imports/no-unused-imports': 'warn',
'unused-imports/no-unused-vars': [
'warn',
{
vars: 'all',
varsIgnorePattern: '^_',
args: 'after-used',
argsIgnorePattern: '^_',
},
],
};
export const stylisticRules: Rules = {
'@stylistic/no-multiple-empty-lines': ['warn', {max: 1}],
'@stylistic/lines-between-class-members': ['warn', 'always'],
'@stylistic/line-comment-position': ['warn', {position: 'above'}],
'@stylistic/multiline-comment-style': ['warn', 'starred-block'],
'@stylistic/capitalized-comments': 'warn',
'@stylistic/max-len': [
'warn',
{
code: 120,
ignoreComments: true,
ignoreUrls: true,
ignoreStrings: true,
ignoreTemplateLiterals: true,
ignoreRegExpLiterals: true,
},
],
};
export const reactRules: Rules = {
'react/prop-types': 'off',
'react/react-in-jsx-scope': 'off',
'react/jsx-filename-extension': ['warn', {extensions: ['.tsx']}],
'react/jsx-props-no-spreading': 'warn',
'react/jsx-key': 'warn',
'react/no-array-index-key': 'warn',
'react/destructuring-assignment': 'warn',
'react/prefer-stateless-function': 'warn',
'react/jsx-fragments': ['off', 'element'],
};
export const reactHooksRules: Rules = {
'react-hooks/exhaustive-deps': 'warn',
};

View File

@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}