create-nadvan-app 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "create-nadvan-app",
3
+ "version": "1.0.0",
4
+ "description": "Официальный scaffolder для frontend-проектов Nadvan",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-nadvan-app": "./src/index.js"
8
+ },
9
+ "files": [
10
+ "src"
11
+ ],
12
+ "engines": {
13
+ "node": ">=20.0.0"
14
+ },
15
+ "keywords": ["nadvan", "create", "frontend", "fsd", "vite", "react"],
16
+ "dependencies": {
17
+ "chalk": "^5.3.0",
18
+ "fs-extra": "^11.2.0",
19
+ "prompts": "^2.4.2"
20
+ }
21
+ }
package/src/index.js ADDED
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execSync } from 'child_process';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ import chalk from 'chalk';
8
+ import fs from 'fs-extra';
9
+ import prompts from 'prompts';
10
+
11
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
+ const TEMPLATE_DIR = path.join(__dirname, 'template');
13
+
14
+ function step(msg) { console.log(chalk.blue(`\n → ${msg}`)); }
15
+ function success(msg) { console.log(chalk.green(` ✓ ${msg}`)); }
16
+ function warn(msg) { console.log(chalk.yellow(` ⚠ ${msg}`)); }
17
+
18
+ async function main() {
19
+ console.log(chalk.bold('\n┌─────────────────────────────────────────┐'));
20
+ console.log(chalk.bold('│ Nadvan Frontend Scaffolder │'));
21
+ console.log(chalk.bold('└─────────────────────────────────────────┘\n'));
22
+
23
+ // Парсим аргументы
24
+ // Использование: create-nadvan-app my-project [--yes] [--pm=pnpm] [--no-install]
25
+ const args = process.argv.slice(2);
26
+ const argName = args.find((a) => !a.startsWith('-'));
27
+ const flagYes = args.includes('--yes') || args.includes('-y');
28
+ const flagNoInstall = args.includes('--no-install');
29
+ const flagPm = (args.find((a) => a.startsWith('--pm=')) ?? '').replace('--pm=', '') || null;
30
+
31
+ if (flagYes && !argName) {
32
+ console.log(chalk.red(' При --yes укажи имя: create-nadvan-app my-project --yes'));
33
+ process.exit(1);
34
+ }
35
+
36
+ // В неинтерактивном режиме (--yes) пропускаем prompts
37
+ let answers;
38
+ if (flagYes) {
39
+ answers = { pkgManager: flagPm ?? 'npm', install: !flagNoInstall };
40
+ } else {
41
+ answers = await prompts(
42
+ [
43
+ {
44
+ type: argName ? null : 'text',
45
+ name: 'projectName',
46
+ message: 'Название проекта:',
47
+ initial: 'my-project',
48
+ validate: (v) =>
49
+ /^[a-z0-9][a-z0-9-]*$/.test(v) ||
50
+ 'Только строчные буквы, цифры и дефис',
51
+ },
52
+ {
53
+ type: 'select',
54
+ name: 'pkgManager',
55
+ message: 'Менеджер пакетов:',
56
+ choices: [
57
+ { title: 'npm', value: 'npm' },
58
+ { title: 'pnpm', value: 'pnpm' },
59
+ { title: 'yarn', value: 'yarn' },
60
+ ],
61
+ initial: 0,
62
+ },
63
+ {
64
+ type: 'confirm',
65
+ name: 'install',
66
+ message: 'Установить зависимости сейчас?',
67
+ initial: true,
68
+ },
69
+ ],
70
+ {
71
+ onCancel: () => {
72
+ console.log(chalk.red('\n Отменено.'));
73
+ process.exit(1);
74
+ },
75
+ },
76
+ );
77
+ }
78
+
79
+ const projectName = argName ?? answers.projectName;
80
+ const { pkgManager, install } = answers;
81
+ const targetDir = path.resolve(process.cwd(), projectName);
82
+
83
+ if (fs.existsSync(targetDir)) {
84
+ console.log(chalk.red(`\n Ошибка: папка "${projectName}" уже существует.`));
85
+ process.exit(1);
86
+ }
87
+
88
+ // ─── 1. Копируем шаблон ──────────────────────────────────────────────────
89
+ step('Копируем шаблон...');
90
+ await fs.copy(TEMPLATE_DIR, targetDir);
91
+ success('Шаблон скопирован');
92
+
93
+ // ─── 2. Переименовываем служебные файлы ──────────────────────────────────
94
+ step('Настраиваем файлы проекта...');
95
+
96
+ await fs.move(
97
+ path.join(targetDir, '_package.json'),
98
+ path.join(targetDir, 'package.json'),
99
+ );
100
+
101
+ await fs.move(
102
+ path.join(targetDir, 'gitignore'),
103
+ path.join(targetDir, '.gitignore'),
104
+ );
105
+
106
+ // ─── 3. Подставляем имя проекта ──────────────────────────────────────────
107
+ const pkgPath = path.join(targetDir, 'package.json');
108
+ const pkg = await fs.readJson(pkgPath);
109
+ pkg.name = projectName;
110
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
111
+ success(`package.json → name: "${projectName}"`);
112
+
113
+ // ─── 4. Права на husky-хуки ──────────────────────────────────────────────
114
+ try {
115
+ fs.chmodSync(path.join(targetDir, '.husky', 'pre-commit'), 0o755);
116
+ fs.chmodSync(path.join(targetDir, '.husky', 'commit-msg'), 0o755);
117
+ } catch { /* не критично */ }
118
+
119
+ // ─── 5. npm install ──────────────────────────────────────────────────────
120
+ if (install) {
121
+ step(`Устанавливаем зависимости (${pkgManager} install)...`);
122
+ try {
123
+ execSync(`${pkgManager} install`, { cwd: targetDir, stdio: 'inherit' });
124
+ execSync(`${pkgManager} run prepare`, { cwd: targetDir, stdio: 'inherit' });
125
+ success('Зависимости установлены');
126
+ } catch {
127
+ warn(`${pkgManager} install завершился с ошибкой. Запусти вручную.`);
128
+ }
129
+ }
130
+
131
+ // ─── Итог ────────────────────────────────────────────────────────────────
132
+ console.log(chalk.bold.green(`\n ✅ Проект "${projectName}" создан!\n`));
133
+ console.log(' Следующие шаги:\n');
134
+ console.log(chalk.cyan(` cd ${projectName}`));
135
+ if (!install) console.log(chalk.cyan(` ${pkgManager} install`));
136
+ console.log(chalk.cyan(` ${pkgManager} run dev\n`));
137
+ console.log(' Полезные команды:\n');
138
+ console.log(` ${chalk.cyan(`${pkgManager} run lint`)} — ESLint`);
139
+ console.log(` ${chalk.cyan(`${pkgManager} run lint:styles`)} — Stylelint`);
140
+ console.log(` ${chalk.cyan(`${pkgManager} run typecheck`)} — TypeScript`);
141
+ console.log(` ${chalk.cyan(`${pkgManager} run test`)} — Тесты`);
142
+ console.log(` ${chalk.cyan(`${pkgManager} run storybook`)} — Storybook\n`);
143
+ }
144
+
145
+ main().catch((e) => {
146
+ console.error(chalk.red('\nОшибка:'), e.message);
147
+ process.exit(1);
148
+ });
@@ -0,0 +1,2 @@
1
+ #!/bin/sh
2
+ npx --no-install commitlint --edit "$1"
@@ -0,0 +1,2 @@
1
+ #!/bin/sh
2
+ npx lint-staged
@@ -0,0 +1,20 @@
1
+ {
2
+ "semi": true,
3
+ "singleQuote": true,
4
+ "trailingComma": "all",
5
+ "printWidth": 100,
6
+ "tabWidth": 2,
7
+ "useTabs": false,
8
+ "bracketSpacing": true,
9
+ "bracketSameLine": false,
10
+ "arrowParens": "always",
11
+ "endOfLine": "lf",
12
+ "overrides": [
13
+ {
14
+ "files": "*.scss",
15
+ "options": {
16
+ "singleQuote": false
17
+ }
18
+ }
19
+ ]
20
+ }
@@ -0,0 +1,93 @@
1
+ {
2
+ "extends": ["stylelint-config-standard-scss"],
3
+ "plugins": ["stylelint-order"],
4
+ "rules": {
5
+ // ─── Порядок свойств (Box Model → Visual → Typography → Animation) ────────
6
+ "order/properties-order": [
7
+ [
8
+ "content",
9
+
10
+ "position", "top", "right", "bottom", "left", "z-index",
11
+
12
+ "display",
13
+ "flex-direction", "flex-wrap", "flex-flow",
14
+ "justify-content", "justify-items", "justify-self",
15
+ "align-content", "align-items", "align-self",
16
+ "gap", "row-gap", "column-gap",
17
+ "grid-template-columns", "grid-template-rows", "grid-template-areas",
18
+ "grid-column", "grid-row", "grid-area",
19
+
20
+ "width", "min-width", "max-width",
21
+ "height", "min-height", "max-height",
22
+
23
+ "padding", "padding-top", "padding-right", "padding-bottom", "padding-left",
24
+ "padding-block", "padding-inline",
25
+ "margin", "margin-top", "margin-right", "margin-bottom", "margin-left",
26
+ "margin-block", "margin-inline",
27
+
28
+ "overflow", "overflow-x", "overflow-y",
29
+
30
+ "background", "background-color", "background-image",
31
+ "background-size", "background-position", "background-repeat",
32
+ "border", "border-width", "border-style", "border-color",
33
+ "border-top", "border-right", "border-bottom", "border-left",
34
+ "border-radius",
35
+ "box-shadow",
36
+ "outline", "outline-offset",
37
+ "opacity", "visibility",
38
+ "cursor", "pointer-events",
39
+
40
+ "font-family", "font-size", "font-weight", "font-style",
41
+ "line-height", "letter-spacing",
42
+ "text-align", "text-decoration", "text-transform",
43
+ "white-space", "word-break", "overflow-wrap",
44
+ "color",
45
+ "list-style",
46
+
47
+ "transition", "animation", "transform", "will-change"
48
+ ]
49
+ ],
50
+
51
+ // ─── Запрет магических значений — цвета, размеры должны быть из переменных ─
52
+ "color-named": "never",
53
+ "color-no-invalid-hex": true,
54
+ "scss/dollar-variable-pattern": "^[a-z][a-z0-9-]*$",
55
+
56
+ // ─── BEM-нейминг классов ──────────────────────────────────────────────────
57
+ "selector-class-pattern": [
58
+ "^[a-z][a-z0-9]*(-[a-z0-9]+)*(__[a-z][a-z0-9]*(-[a-z0-9]+)*)?(--[a-z][a-z0-9]*(-[a-z0-9]+)*)?$",
59
+ { "message": "Используй BEM: block__element--modifier" }
60
+ ],
61
+
62
+ // ─── Единицы и значения ───────────────────────────────────────────────────
63
+ "length-zero-no-unit": true,
64
+ "number-max-precision": 4,
65
+ "alpha-value-notation": "number",
66
+
67
+ // ─── Вложенность ─────────────────────────────────────────────────────────
68
+ "max-nesting-depth": [3, {
69
+ "ignore": ["blockless-at-rules", "pseudo-classes"]
70
+ }],
71
+
72
+ // ─── SCSS ─────────────────────────────────────────────────────────────────
73
+ "scss/no-global-function-names": true,
74
+ "scss/at-import-no-partial-leading-underscore": true,
75
+ "scss/at-rule-no-unknown": true,
76
+ "scss/selector-no-redundant-nesting-selector": true,
77
+ "scss/no-duplicate-dollar-variables": true,
78
+
79
+ // ─── Запрет !important ────────────────────────────────────────────────────
80
+ "declaration-no-important": [true, {
81
+ "message": "Не используй !important. Реши проблему специфичности иначе."
82
+ }],
83
+
84
+ // ─── Пустые строки и форматирование ──────────────────────────────────────
85
+ "rule-empty-line-before": ["always", {
86
+ "except": ["first-nested"],
87
+ "ignore": ["after-comment"]
88
+ }],
89
+ "comment-empty-line-before": ["always", {
90
+ "except": ["first-nested"]
91
+ }]
92
+ }
93
+ }
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "project-name",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "engines": {
7
+ "node": ">=20.0.0",
8
+ "npm": ">=10.0.0"
9
+ },
10
+ "scripts": {
11
+ "dev": "vite",
12
+ "build": "tsc && vite build",
13
+ "preview": "vite preview",
14
+ "lint": "eslint src --max-warnings 0",
15
+ "lint:fix": "eslint src --fix",
16
+ "lint:styles": "stylelint 'src/**/*.scss'",
17
+ "lint:styles:fix": "stylelint 'src/**/*.scss' --fix",
18
+ "format": "prettier --write 'src/**/*.{ts,tsx,scss,json}'",
19
+ "format:check": "prettier --check 'src/**/*.{ts,tsx,scss,json}'",
20
+ "typecheck": "tsc --noEmit",
21
+ "test": "vitest",
22
+ "test:ui": "vitest --ui",
23
+ "test:coverage": "vitest run --coverage",
24
+ "storybook": "storybook dev -p 6006",
25
+ "build-storybook": "storybook build",
26
+ "prepare": "husky"
27
+ },
28
+ "dependencies": {
29
+ "@nadvan/ui": "latest",
30
+ "@nadvan/fonts": "latest",
31
+ "@nadvan/grid": "latest",
32
+ "react": "^18.3.1",
33
+ "react-dom": "^18.3.1",
34
+ "react-router-dom": "^6.27.0"
35
+ },
36
+ "devDependencies": {
37
+ "@commitlint/cli": "^19.5.0",
38
+ "@commitlint/config-conventional": "^19.5.0",
39
+ "@storybook/addon-a11y": "^8.3.6",
40
+ "@storybook/addon-essentials": "^8.3.6",
41
+ "@storybook/react-vite": "^8.3.6",
42
+ "@testing-library/jest-dom": "^6.6.3",
43
+ "@testing-library/react": "^16.0.1",
44
+ "@testing-library/user-event": "^14.5.2",
45
+ "@types/react": "^18.3.12",
46
+ "@types/react-dom": "^18.3.1",
47
+ "@typescript-eslint/eslint-plugin": "^8.12.2",
48
+ "@typescript-eslint/parser": "^8.12.2",
49
+ "@vitejs/plugin-react": "^4.3.3",
50
+ "@vitest/coverage-v8": "^2.1.4",
51
+ "@vitest/ui": "^2.1.4",
52
+ "eslint": "^9.13.0",
53
+ "eslint-config-prettier": "^9.1.0",
54
+ "eslint-plugin-boundaries": "^4.3.1",
55
+ "eslint-plugin-import": "^2.31.0",
56
+ "eslint-plugin-jsx-a11y": "^6.10.1",
57
+ "eslint-plugin-react": "^7.37.2",
58
+ "eslint-plugin-react-hooks": "^5.0.0",
59
+ "husky": "^9.1.6",
60
+ "lint-staged": "^15.2.10",
61
+ "prettier": "^3.3.3",
62
+ "sass": "^1.80.6",
63
+ "stylelint": "^16.10.0",
64
+ "stylelint-config-standard-scss": "^13.1.0",
65
+ "stylelint-order": "^6.0.4",
66
+ "typescript": "^5.6.3",
67
+ "vite": "^5.4.11",
68
+ "vite-tsconfig-paths": "^5.1.2",
69
+ "vitest": "^2.1.4"
70
+ }
71
+ }
@@ -0,0 +1,62 @@
1
+ export default {
2
+ extends: ['@commitlint/config-conventional'],
3
+
4
+ rules: {
5
+ // Тип коммита обязателен
6
+ 'type-enum': [
7
+ 2,
8
+ 'always',
9
+ [
10
+ 'feat', // Новая функциональность
11
+ 'fix', // Исправление бага
12
+ 'style', // Только стили/вёрстка (не логика)
13
+ 'refactor', // Рефакторинг без изменения поведения
14
+ 'perf', // Улучшение производительности
15
+ 'test', // Тесты
16
+ 'docs', // Документация
17
+ 'chore', // Конфигурация, зависимости, инфраструктура
18
+ 'ci', // CI/CD
19
+ 'revert', // Откат коммита
20
+ 'wip', // Work in progress (не в main)
21
+ ],
22
+ ],
23
+
24
+ // Область (scope) — название FSD-слоя или модуля
25
+ 'scope-enum': [
26
+ 1, // warn, не error — scope свободный, но рекомендованные значения:
27
+ 'always',
28
+ [
29
+ 'app',
30
+ 'pages',
31
+ 'widgets',
32
+ 'features',
33
+ 'entities',
34
+ 'shared',
35
+ 'ui',
36
+ 'api',
37
+ 'config',
38
+ 'styles',
39
+ 'deps',
40
+ 'ci',
41
+ ],
42
+ ],
43
+
44
+ // Длина заголовка
45
+ 'header-max-length': [2, 'always', 100],
46
+ 'subject-min-length': [2, 'always', 5],
47
+
48
+ // Не требуем lowercase — русский язык это нарушает
49
+ 'subject-case': [0],
50
+ 'type-case': [2, 'always', 'lower-case'],
51
+ },
52
+ };
53
+
54
+ /*
55
+ Примеры правильных коммитов:
56
+ feat(features): добавить авторизацию через OAuth
57
+ fix(shared): исправить padding в компоненте Button
58
+ style(widgets): обновить вёрстку Header под мобайл
59
+ refactor(entities): переименовать поля в модели User
60
+ chore(deps): обновить @nadvan/ui до 2.1.0
61
+ docs: добавить описание компонентов в Storybook
62
+ */
@@ -0,0 +1,154 @@
1
+ import tsParser from '@typescript-eslint/parser';
2
+ import tsPlugin from '@typescript-eslint/eslint-plugin';
3
+ import reactPlugin from 'eslint-plugin-react';
4
+ import reactHooksPlugin from 'eslint-plugin-react-hooks';
5
+ import jsxA11yPlugin from 'eslint-plugin-jsx-a11y';
6
+ import importPlugin from 'eslint-plugin-import';
7
+ import boundariesPlugin from 'eslint-plugin-boundaries';
8
+ import prettierConfig from 'eslint-config-prettier';
9
+
10
+ export default [
11
+ // Игнорируемые файлы
12
+ {
13
+ ignores: ['dist/**', 'node_modules/**', 'storybook-static/**', '*.config.js'],
14
+ },
15
+
16
+ // Основные правила для src
17
+ {
18
+ files: ['src/**/*.{ts,tsx}'],
19
+
20
+ languageOptions: {
21
+ parser: tsParser,
22
+ parserOptions: {
23
+ project: true,
24
+ tsconfigRootDir: import.meta.dirname,
25
+ },
26
+ },
27
+
28
+ plugins: {
29
+ '@typescript-eslint': tsPlugin,
30
+ react: reactPlugin,
31
+ 'react-hooks': reactHooksPlugin,
32
+ 'jsx-a11y': jsxA11yPlugin,
33
+ import: importPlugin,
34
+ boundaries: boundariesPlugin,
35
+ },
36
+
37
+ settings: {
38
+ react: { version: 'detect' },
39
+ 'import/resolver': { typescript: true },
40
+
41
+ // FSD слои для проверки границ
42
+ 'boundaries/elements': [
43
+ { type: 'app', pattern: 'src/app/*' },
44
+ { type: 'pages', pattern: 'src/pages/*' },
45
+ { type: 'widgets', pattern: 'src/widgets/*' },
46
+ { type: 'features', pattern: 'src/features/*' },
47
+ { type: 'entities', pattern: 'src/entities/*' },
48
+ { type: 'shared', pattern: 'src/shared/*' },
49
+ ],
50
+ 'boundaries/ignore': ['**/*.test.*', '**/*.spec.*', '**/*.stories.*'],
51
+ },
52
+
53
+ rules: {
54
+ // ─── TypeScript ───────────────────────────────────────────────────────────
55
+ ...tsPlugin.configs['recommended'].rules,
56
+ ...tsPlugin.configs['recommended-requiring-type-checking'].rules,
57
+
58
+ '@typescript-eslint/no-explicit-any': 'error',
59
+ '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
60
+ '@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports' }],
61
+ '@typescript-eslint/no-floating-promises': 'error',
62
+ '@typescript-eslint/no-misused-promises': 'error',
63
+ '@typescript-eslint/strict-boolean-expressions': 'error',
64
+ '@typescript-eslint/no-non-null-assertion': 'error',
65
+ '@typescript-eslint/prefer-nullish-coalescing': 'error',
66
+ '@typescript-eslint/prefer-optional-chain': 'error',
67
+
68
+ // ─── React ────────────────────────────────────────────────────────────────
69
+ ...reactPlugin.configs.recommended.rules,
70
+ ...reactPlugin.configs['jsx-runtime'].rules,
71
+
72
+ 'react/prop-types': 'off', // TypeScript заменяет это
73
+ 'react/self-closing-comp': 'error',
74
+ 'react/jsx-boolean-value': ['error', 'never'],
75
+ 'react/jsx-curly-brace-presence': ['error', { props: 'never', children: 'never' }],
76
+ 'react/jsx-no-useless-fragment': 'error',
77
+ 'react/no-array-index-key': 'warn',
78
+
79
+ // ─── React Hooks ─────────────────────────────────────────────────────────
80
+ ...reactHooksPlugin.configs.recommended.rules,
81
+
82
+ // ─── Доступность (a11y) ───────────────────────────────────────────────────
83
+ ...jsxA11yPlugin.configs.recommended.rules,
84
+
85
+ // ─── Импорты ──────────────────────────────────────────────────────────────
86
+ 'import/order': [
87
+ 'error',
88
+ {
89
+ groups: ['builtin', 'external', 'internal', ['parent', 'sibling', 'index']],
90
+ pathGroups: [
91
+ { pattern: '@app/**', group: 'internal', position: 'before' },
92
+ { pattern: '@pages/**', group: 'internal', position: 'before' },
93
+ { pattern: '@widgets/**', group: 'internal', position: 'before' },
94
+ { pattern: '@features/**', group: 'internal', position: 'before' },
95
+ { pattern: '@entities/**', group: 'internal', position: 'before' },
96
+ { pattern: '@shared/**', group: 'internal', position: 'before' },
97
+ ],
98
+ pathGroupsExcludedImportTypes: ['builtin'],
99
+ 'newlines-between': 'always',
100
+ alphabetize: { order: 'asc', caseInsensitive: true },
101
+ },
102
+ ],
103
+ 'import/no-duplicates': 'error',
104
+ // Запрет default export — используем named; исключения ниже
105
+ 'import/no-default-export': 'error',
106
+
107
+ // ─── FSD — границы слоёв ─────────────────────────────────────────────────
108
+ // Нижний слой не может импортировать из верхнего
109
+ 'boundaries/element-types': [
110
+ 'error',
111
+ {
112
+ default: 'disallow',
113
+ rules: [
114
+ { from: 'app', allow: ['pages', 'widgets', 'features', 'entities', 'shared'] },
115
+ { from: 'pages', allow: ['widgets', 'features', 'entities', 'shared'] },
116
+ { from: 'widgets', allow: ['features', 'entities', 'shared'] },
117
+ { from: 'features', allow: ['entities', 'shared'] },
118
+ { from: 'entities', allow: ['shared'] },
119
+ { from: 'shared', allow: [] },
120
+ ],
121
+ },
122
+ ],
123
+
124
+ // ─── Общие ────────────────────────────────────────────────────────────────
125
+ 'no-console': ['warn', { allow: ['warn', 'error'] }],
126
+ 'no-magic-numbers': ['warn', {
127
+ ignore: [-1, 0, 1, 2, 100],
128
+ ignoreArrayIndexes: true,
129
+ ignoreDefaultValues: true,
130
+ ignoreClassFieldInitialValues: true,
131
+ }],
132
+ 'prefer-const': 'error',
133
+ 'no-var': 'error',
134
+ 'eqeqeq': ['error', 'always'],
135
+ 'no-nested-ternary': 'error',
136
+ },
137
+ },
138
+
139
+ // Исключения: default export разрешён для страниц, stories, конфигов
140
+ {
141
+ files: [
142
+ 'src/pages/**/*.tsx',
143
+ '**/*.stories.tsx',
144
+ '**/*.stories.ts',
145
+ 'vite.config.ts',
146
+ ],
147
+ rules: {
148
+ 'import/no-default-export': 'off',
149
+ },
150
+ },
151
+
152
+ // Prettier должен быть последним — отключает конфликтующие правила
153
+ prettierConfig,
154
+ ];
@@ -0,0 +1,10 @@
1
+ node_modules
2
+ dist
3
+ dist-ssr
4
+ *.local
5
+ .env
6
+ .env.*
7
+ !.env.example
8
+ storybook-static
9
+ coverage
10
+ .DS_Store
@@ -0,0 +1,21 @@
1
+ export default {
2
+ // TypeScript и TSX — линт + форматирование
3
+ 'src/**/*.{ts,tsx}': [
4
+ 'eslint --max-warnings 0 --fix',
5
+ 'prettier --write',
6
+ ],
7
+
8
+ // SCSS — стайллинт + форматирование
9
+ 'src/**/*.scss': [
10
+ 'stylelint --fix',
11
+ 'prettier --write',
12
+ ],
13
+
14
+ // JSON, MD — только форматирование
15
+ '*.{json,md}': [
16
+ 'prettier --write',
17
+ ],
18
+
19
+ // Проверка типов при изменении любого TS-файла
20
+ 'src/**/*.{ts,tsx}': () => 'tsc --noEmit',
21
+ };