create-nadvan-app 1.0.0 → 1.0.2

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.
Files changed (39) hide show
  1. package/README.md +279 -0
  2. package/package.json +9 -2
  3. package/src/index.js +57 -26
  4. package/src/{template → templates/base}/commitlint.config.js +20 -1
  5. package/src/templates/base/src/entities/index.ts +15 -0
  6. package/src/templates/base/src/features/index.ts +15 -0
  7. package/src/templates/base/src/pages/index.ts +10 -0
  8. package/src/templates/base/src/shared/api/index.ts +29 -0
  9. package/src/{template/src/app → templates/base/src/shared}/styles/global.scss +3 -1
  10. package/src/templates/base/src/shared/ui/index.ts +15 -0
  11. package/src/templates/base/src/widgets/index.ts +12 -0
  12. package/src/templates/nextjs/_package.json +64 -0
  13. package/src/templates/nextjs/eslint.config.js +132 -0
  14. package/src/templates/nextjs/next.config.ts +20 -0
  15. package/src/templates/nextjs/src/app/layout.tsx +21 -0
  16. package/src/templates/nextjs/src/app/page.tsx +7 -0
  17. package/src/templates/nextjs/src/app/providers/index.tsx +20 -0
  18. package/src/templates/nextjs/tsconfig.json +43 -0
  19. package/src/{template → templates/react}/_package.json +1 -3
  20. package/src/templates/react/index.html +12 -0
  21. package/src/templates/react/src/app/index.tsx +19 -0
  22. package/src/templates/react/src/app/providers/index.tsx +18 -0
  23. package/src/templates/react/src/main.tsx +19 -0
  24. package/src/{template → templates/react}/vite.config.ts +2 -2
  25. /package/src/{template → templates/base}/.husky/commit-msg +0 -0
  26. /package/src/{template → templates/base}/.husky/pre-commit +0 -0
  27. /package/src/{template → templates/base}/.prettierrc +0 -0
  28. /package/src/{template → templates/base}/.stylelintrc.json +0 -0
  29. /package/src/{template → templates/base}/gitignore +0 -0
  30. /package/src/{template → templates/base}/lint-staged.config.js +0 -0
  31. /package/src/{template → templates/base}/src/shared/config/breakpoints.ts +0 -0
  32. /package/src/{template → templates/base}/src/shared/config/tokens.ts +0 -0
  33. /package/src/{template → templates/base}/src/shared/lib/hooks/index.ts +0 -0
  34. /package/src/{template → templates/base}/src/shared/lib/hooks/useMediaQuery.ts +0 -0
  35. /package/src/{template/src/app → templates/base/src/shared}/styles/_mixins.scss +0 -0
  36. /package/src/{template/src/app → templates/base/src/shared}/styles/_typography.scss +0 -0
  37. /package/src/{template/src/app → templates/base/src/shared}/styles/_variables.scss +0 -0
  38. /package/src/{template → templates/react}/eslint.config.js +0 -0
  39. /package/src/{template → templates/react}/tsconfig.json +0 -0
package/README.md ADDED
@@ -0,0 +1,279 @@
1
+ # create-nadvan-app
2
+
3
+ Официальный CLI-инструмент для создания frontend-проектов Nadvan.
4
+
5
+ Разворачивает проект с готовой настройкой: TypeScript, FSD-архитектура, ESLint, Stylelint, Prettier, Husky, Commitlint и SCSS design-токены.
6
+
7
+ ---
8
+
9
+ ## Использование
10
+
11
+ ```bash
12
+ npm create nadvan-app@latest my-project
13
+ # или
14
+ npx create-nadvan-app my-project
15
+ ```
16
+
17
+ CLI задаст четыре вопроса:
18
+
19
+ 1. **Название проекта** — используется как `name` в `package.json`
20
+ 2. **Фреймворк** — React + Vite или Next.js
21
+ 3. **Менеджер пакетов** — npm, pnpm или yarn
22
+ 4. **Установить зависимости сейчас?**
23
+
24
+ ---
25
+
26
+ ## Фреймворки
27
+
28
+ | Вариант | Сборщик | Роутинг | Рендеринг |
29
+ |---|---|---|---|
30
+ | **React + Vite** | Vite | react-router-dom | CSR (SPA) |
31
+ | **Next.js** | Next.js | App Router | SSR / SSG / CSR |
32
+
33
+ Оба варианта включают TypeScript в строгом режиме и FSD-архитектуру.
34
+
35
+ ---
36
+
37
+ ## Флаги (неинтерактивный режим)
38
+
39
+ Удобно для CI/CD или скриптов.
40
+
41
+ ```bash
42
+ # React + Vite, npm, без установки зависимостей
43
+ npx create-nadvan-app my-project --yes --no-install
44
+
45
+ # Next.js + pnpm, с установкой
46
+ npx create-nadvan-app my-app --yes --framework=nextjs --pm=pnpm
47
+ ```
48
+
49
+ | Флаг | Описание | По умолчанию |
50
+ |---|---|---|
51
+ | `--yes` / `-y` | Пропустить все вопросы | — |
52
+ | `--framework=react` | Выбрать React + Vite | `react` |
53
+ | `--framework=nextjs` | Выбрать Next.js | — |
54
+ | `--pm=npm` | Менеджер пакетов | `npm` |
55
+ | `--pm=pnpm` | — | — |
56
+ | `--pm=yarn` | — | — |
57
+ | `--no-install` | Не запускать установку зависимостей | — |
58
+
59
+ ---
60
+
61
+ ## Что входит в проект
62
+
63
+ ### Общее для всех фреймворков
64
+
65
+ | Файл / Папка | Назначение |
66
+ |---|---|
67
+ | `.prettierrc` | Форматирование кода |
68
+ | `.stylelintrc.json` | Линтер стилей: порядок свойств, BEM, запрет `!important` |
69
+ | `commitlint.config.js` | Проверка формата коммитов (Conventional Commits) |
70
+ | `lint-staged.config.js` | Авто-линтинг изменённых файлов перед коммитом |
71
+ | `.husky/` | Git-хуки: `pre-commit` и `commit-msg` |
72
+ | `src/app/styles/` | Design tokens, миксины брейкпоинтов, типографика, CSS reset |
73
+ | `src/shared/config/` | `breakpoints.ts`, `tokens.ts` — токены для JS/TS |
74
+ | `src/shared/lib/hooks/` | `useMediaQuery` — реактивный хук брейкпоинтов |
75
+
76
+ ### React + Vite
77
+
78
+ | Файл | Назначение |
79
+ |---|---|
80
+ | `vite.config.ts` | Сборщик, FSD-алиасы, автоинъекция SCSS-переменных |
81
+ | `tsconfig.json` | TypeScript strict + FSD-алиасы |
82
+ | `eslint.config.js` | ESLint: TypeScript, React, a11y, FSD-границы |
83
+
84
+ ### Next.js
85
+
86
+ | Файл | Назначение |
87
+ |---|---|
88
+ | `next.config.ts` | Next.js конфиг, автоинъекция SCSS-переменных |
89
+ | `tsconfig.json` | TypeScript strict + Next.js plugin + FSD-алиасы |
90
+ | `eslint.config.js` | ESLint: TypeScript, React, Next.js core-web-vitals, FSD-границы |
91
+ | `src/app/layout.tsx` | Корневой layout (App Router) |
92
+ | `src/app/page.tsx` | Стартовая страница |
93
+
94
+ ---
95
+
96
+ ## Структура сгенерированного проекта (FSD)
97
+
98
+ ```
99
+ src/
100
+ ├── app/ # Инициализация, провайдеры, глобальные стили
101
+ │ └── styles/ # Design tokens, миксины, типографика
102
+
103
+ ├── pages/ # Страницы — компонуют виджеты и фичи
104
+ │ # В Next.js: импортируются из src/app/(routes)/
105
+
106
+ ├── widgets/ # Независимые UI-блоки (Header, Footer, Sidebar)
107
+
108
+ ├── features/ # Пользовательские сценарии (auth, cart, search)
109
+
110
+ ├── entities/ # Бизнес-сущности (User, Product, Order)
111
+
112
+ └── shared/ # Переиспользуемый код без бизнес-логики
113
+ ├── ui/ # Базовые компоненты (@nadvan/ui)
114
+ ├── api/ # HTTP-клиент
115
+ ├── config/ # Константы, токены, env
116
+ └── lib/ # Утилиты и хуки
117
+ ```
118
+
119
+ > Правило: каждый слой импортирует только из слоёв **ниже** себя.
120
+ > `app → pages → widgets → features → entities → shared`
121
+
122
+ ---
123
+
124
+ ## Работа с FSD структурой (fsd-generator)
125
+
126
+ После создания проекта используй **`fsd-generator`** для быстрого scaffolding слоёв и слайсов.
127
+
128
+ ```bash
129
+ # Установка (один раз)
130
+ npm install -g fsd-generator
131
+
132
+ # Инициализировать FSD (если не запускали)
133
+ fsd init
134
+ ```
135
+
136
+ ### Создание компонентов
137
+
138
+ ```bash
139
+ # Простой виджет
140
+ fsd -t widget -n header
141
+
142
+ # Несколько виджетов сразу
143
+ fsd -t widget -n header,footer,sidebar
144
+
145
+ # Фича со структурой ui + model
146
+ fsd slice -t feature -n user-auth
147
+
148
+ # Сущность с ui + model + api + lib
149
+ fsd slice -t entity -n product --full
150
+
151
+ # Несколько компонентов в одной папке
152
+ fsd -t widget -n form -c Button,Input,Select
153
+
154
+ # Плоская структура (без папки ui)
155
+ fsd -t shared -n icons --flat -c CloseIcon,ArrowIcon
156
+ ```
157
+
158
+ ### Что создаётся
159
+
160
+ | Команда | Структура |
161
+ |---|---|
162
+ | `fsd -t widget -n header` | `widgets/header/ui/Header.tsx` + `index.ts` |
163
+ | `fsd slice -t feature -n auth` | `features/auth/ui/` + `model/types.ts` + `index.ts` |
164
+ | `fsd slice -t entity -n user --full` | `entities/user/ui/` + `model/` + `api/` + `lib/` |
165
+
166
+ ### Управление структурой
167
+
168
+ ```bash
169
+ fsd list # Показать весь проект
170
+ fsd list widget # Показать компоненты слоя widget
171
+ fsd check # Проверить соответствие FSD
172
+ fsd info # Информация о проекте
173
+ fsd remove -t widget -n header # Удалить компонент
174
+ fsd rename -t widget -n header header-v2 # Переименовать
175
+ ```
176
+
177
+ > Файлы не перезаписываются, экспорты добавляются в `index.ts` автоматически.
178
+
179
+ ---
180
+
181
+ ## Автоматические проверки
182
+
183
+ При каждом `git commit` автоматически запускаются:
184
+
185
+ 1. **ESLint** — проверяет и исправляет `.ts`, `.tsx`
186
+ 2. **Stylelint** — проверяет и исправляет `.scss`
187
+ 3. **Prettier** — форматирует изменённые файлы
188
+ 4. **TypeScript** — `tsc --noEmit`, коммит блокируется при ошибках типов
189
+ 5. **commitlint** — проверяет формат сообщения коммита
190
+
191
+ ### Формат коммита
192
+
193
+ ```
194
+ <тип>(<область>): <описание>
195
+ ```
196
+
197
+ **Типы:**
198
+
199
+ | Тип | Когда использовать |
200
+ |---|---|
201
+ | `feat` | Новая функциональность |
202
+ | `fix` | Исправление бага |
203
+ | `style` | Только стили/вёрстка, без логики |
204
+ | `refactor` | Рефакторинг без изменения поведения |
205
+ | `perf` | Оптимизация производительности |
206
+ | `test` | Тесты |
207
+ | `docs` | Документация, README, Storybook |
208
+ | `chore` | Зависимости, конфиги, инфраструктура |
209
+ | `ci` | CI/CD пайплайны |
210
+ | `revert` | Откат коммита |
211
+ | `wip` | Work in progress (только в feature-ветках) |
212
+
213
+ **Область (scope)** — FSD-слой или техническая зона: `app`, `pages`, `widgets`, `features`, `entities`, `shared`, `ui`, `api`, `styles`, `deps`, `config`, `ci`.
214
+
215
+ Можно уточнять до конкретного модуля: `features/auth`, `widgets/header`.
216
+
217
+ **Правила описания:**
218
+ - Инфинитив: «добавить», «исправить», «обновить» — не «добавил»
219
+ - Без точки в конце
220
+ - Конкретно: «исправить отступ в Button» лучше «исправить баг»
221
+
222
+ **Примеры:**
223
+ ```
224
+ feat(features): добавить авторизацию через OAuth
225
+ fix(shared): исправить отступы в компоненте Button
226
+ style(widgets): обновить вёрстку Header под мобайл
227
+ refactor(entities): вынести логику фильтрации в хук
228
+ perf(widgets): добавить React.memo для ProductCard
229
+ chore(deps): обновить nadvan-ui-kit до 1.0.3
230
+ docs: добавить раздел про fsd-generator в README
231
+ wip(features): начать реализацию корзины
232
+ ```
233
+
234
+ Подробная документация — в [frontend-standard.md, раздел 5.4](../frontend-standard.md).
235
+
236
+ ---
237
+
238
+ ## Скрипты
239
+
240
+ ```bash
241
+ npm run dev # Dev-сервер
242
+ npm run build # Сборка
243
+ npm run typecheck # Проверка типов
244
+ npm run lint # ESLint
245
+ npm run lint:fix # ESLint с автоисправлением
246
+ npm run lint:styles # Stylelint
247
+ npm run lint:styles:fix # Stylelint с автоисправлением
248
+ npm run format # Prettier
249
+ npm run test # Vitest
250
+ npm run test:coverage # Тесты с покрытием
251
+ npm run storybook # Storybook
252
+ ```
253
+
254
+ ---
255
+
256
+ ## Разработка CLI
257
+
258
+ ```bash
259
+ # Локальное тестирование
260
+ node src/index.js test-project --yes --no-install
261
+
262
+ # Линк для глобального использования
263
+ npm link
264
+ create-nadvan-app my-project
265
+ ```
266
+
267
+ ---
268
+
269
+ ## Публикация
270
+
271
+ ```bash
272
+ npm login
273
+ npm publish --access public
274
+ ```
275
+
276
+ После публикации:
277
+ ```bash
278
+ npm create nadvan-app@latest my-project
279
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-nadvan-app",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Официальный scaffolder для frontend-проектов Nadvan",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,7 +12,14 @@
12
12
  "engines": {
13
13
  "node": ">=20.0.0"
14
14
  },
15
- "keywords": ["nadvan", "create", "frontend", "fsd", "vite", "react"],
15
+ "keywords": [
16
+ "nadvan",
17
+ "create",
18
+ "frontend",
19
+ "fsd",
20
+ "vite",
21
+ "react"
22
+ ],
16
23
  "dependencies": {
17
24
  "chalk": "^5.3.0",
18
25
  "fs-extra": "^11.2.0",
package/src/index.js CHANGED
@@ -8,8 +8,8 @@ import chalk from 'chalk';
8
8
  import fs from 'fs-extra';
9
9
  import prompts from 'prompts';
10
10
 
11
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
- const TEMPLATE_DIR = path.join(__dirname, 'template');
11
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
+ const TEMPLATES = path.join(__dirname, 'templates');
13
13
 
14
14
  function step(msg) { console.log(chalk.blue(`\n → ${msg}`)); }
15
15
  function success(msg) { console.log(chalk.green(` ✓ ${msg}`)); }
@@ -20,23 +20,28 @@ async function main() {
20
20
  console.log(chalk.bold('│ Nadvan Frontend Scaffolder │'));
21
21
  console.log(chalk.bold('└─────────────────────────────────────────┘\n'));
22
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;
23
+ // Флаги: create-nadvan-app my-project --yes --framework=nextjs --pm=pnpm --no-install
24
+ const args = process.argv.slice(2);
25
+ const argName = args.find((a) => !a.startsWith('-'));
26
+ const flagYes = args.includes('--yes') || args.includes('-y');
27
+ const flagNoInstall = args.includes('--no-install');
28
+ const flagPm = (args.find((a) => a.startsWith('--pm=')) ?? '').replace('--pm=', '') || null;
29
+ const flagFramework = (args.find((a) => a.startsWith('--framework=')) ?? '').replace('--framework=', '') || null;
30
30
 
31
31
  if (flagYes && !argName) {
32
32
  console.log(chalk.red(' При --yes укажи имя: create-nadvan-app my-project --yes'));
33
33
  process.exit(1);
34
34
  }
35
35
 
36
- // В неинтерактивном режиме (--yes) пропускаем prompts
36
+ // ─── Вопросы ──────────────────────────────────────────────────────────────
37
37
  let answers;
38
+
38
39
  if (flagYes) {
39
- answers = { pkgManager: flagPm ?? 'npm', install: !flagNoInstall };
40
+ answers = {
41
+ framework: flagFramework ?? 'react',
42
+ pkgManager: flagPm ?? 'npm',
43
+ install: !flagNoInstall,
44
+ };
40
45
  } else {
41
46
  answers = await prompts(
42
47
  [
@@ -49,6 +54,24 @@ async function main() {
49
54
  /^[a-z0-9][a-z0-9-]*$/.test(v) ||
50
55
  'Только строчные буквы, цифры и дефис',
51
56
  },
57
+ {
58
+ type: 'select',
59
+ name: 'framework',
60
+ message: 'Фреймворк:',
61
+ choices: [
62
+ {
63
+ title: 'React + Vite',
64
+ value: 'react',
65
+ description: 'SPA, Vite, react-router-dom',
66
+ },
67
+ {
68
+ title: 'Next.js',
69
+ value: 'nextjs',
70
+ description: 'SSR / SSG / App Router',
71
+ },
72
+ ],
73
+ initial: 0,
74
+ },
52
75
  {
53
76
  type: 'select',
54
77
  name: 'pkgManager',
@@ -76,22 +99,30 @@ async function main() {
76
99
  );
77
100
  }
78
101
 
79
- const projectName = argName ?? answers.projectName;
80
- const { pkgManager, install } = answers;
81
- const targetDir = path.resolve(process.cwd(), projectName);
102
+ const projectName = argName ?? answers.projectName;
103
+ const { framework, pkgManager, install } = answers;
104
+ const targetDir = path.resolve(process.cwd(), projectName);
82
105
 
83
106
  if (fs.existsSync(targetDir)) {
84
107
  console.log(chalk.red(`\n Ошибка: папка "${projectName}" уже существует.`));
85
108
  process.exit(1);
86
109
  }
87
110
 
88
- // ─── 1. Копируем шаблон ──────────────────────────────────────────────────
89
- step('Копируем шаблон...');
90
- await fs.copy(TEMPLATE_DIR, targetDir);
91
- success('Шаблон скопирован');
111
+ const frameworkLabel = framework === 'nextjs' ? 'Next.js' : 'React + Vite';
112
+ console.log(chalk.dim(`\n Фреймворк: ${frameworkLabel} · TypeScript · FSD\n`));
113
+
114
+ // ─── 1. Копируем base ───────────────────────────────────────────────────
115
+ step('Копируем базовый шаблон...');
116
+ await fs.copy(path.join(TEMPLATES, 'base'), targetDir);
117
+ success('Базовый шаблон скопирован');
118
+
119
+ // ─── 2. Поверх — фреймворк-специфичные файлы ────────────────────────────
120
+ step(`Применяем шаблон ${frameworkLabel}...`);
121
+ await fs.copy(path.join(TEMPLATES, framework), targetDir, { overwrite: true });
122
+ success(`Шаблон ${frameworkLabel} применён`);
92
123
 
93
- // ─── 2. Переименовываем служебные файлы ──────────────────────────────────
94
- step('Настраиваем файлы проекта...');
124
+ // ─── 3. Переименовываем служебные файлы ──────────────────────────────────
125
+ step('Настраиваем файлы...');
95
126
 
96
127
  await fs.move(
97
128
  path.join(targetDir, '_package.json'),
@@ -103,20 +134,20 @@ async function main() {
103
134
  path.join(targetDir, '.gitignore'),
104
135
  );
105
136
 
106
- // ─── 3. Подставляем имя проекта ──────────────────────────────────────────
137
+ // ─── 4. Подставляем имя проекта ──────────────────────────────────────────
107
138
  const pkgPath = path.join(targetDir, 'package.json');
108
- const pkg = await fs.readJson(pkgPath);
109
- pkg.name = projectName;
139
+ const pkg = await fs.readJson(pkgPath);
140
+ pkg.name = projectName;
110
141
  await fs.writeJson(pkgPath, pkg, { spaces: 2 });
111
142
  success(`package.json → name: "${projectName}"`);
112
143
 
113
- // ─── 4. Права на husky-хуки ──────────────────────────────────────────────
144
+ // ─── 5. Права husky ──────────────────────────────────────────────────────
114
145
  try {
115
146
  fs.chmodSync(path.join(targetDir, '.husky', 'pre-commit'), 0o755);
116
147
  fs.chmodSync(path.join(targetDir, '.husky', 'commit-msg'), 0o755);
117
148
  } catch { /* не критично */ }
118
149
 
119
- // ─── 5. npm install ──────────────────────────────────────────────────────
150
+ // ─── 6. Установка зависимостей ───────────────────────────────────────────
120
151
  if (install) {
121
152
  step(`Устанавливаем зависимости (${pkgManager} install)...`);
122
153
  try {
@@ -129,7 +160,7 @@ async function main() {
129
160
  }
130
161
 
131
162
  // ─── Итог ────────────────────────────────────────────────────────────────
132
- console.log(chalk.bold.green(`\n ✅ Проект "${projectName}" создан!\n`));
163
+ console.log(chalk.bold.green(`\n ✅ Проект "${projectName}" (${frameworkLabel}) создан!\n`));
133
164
  console.log(' Следующие шаги:\n');
134
165
  console.log(chalk.cyan(` cd ${projectName}`));
135
166
  if (!install) console.log(chalk.cyan(` ${pkgManager} install`));
@@ -52,11 +52,30 @@ export default {
52
52
  };
53
53
 
54
54
  /*
55
+ Формат: <тип>(<область>): <описание>
56
+
57
+ Правила описания:
58
+ - Инфинитив: «добавить», «исправить» — не «добавил», «исправил»
59
+ - Без точки в конце
60
+ - Конкретно и понятно
61
+
55
62
  Примеры правильных коммитов:
56
63
  feat(features): добавить авторизацию через OAuth
64
+ feat(widgets): добавить выпадающее меню в Header
57
65
  fix(shared): исправить padding в компоненте Button
66
+ fix(features): устранить зависание формы при быстром вводе
58
67
  style(widgets): обновить вёрстку Header под мобайл
59
68
  refactor(entities): переименовать поля в модели User
60
- chore(deps): обновить @nadvan/ui до 2.1.0
69
+ refactor(features): вынести валидацию формы в отдельный хук
70
+ perf(widgets): добавить React.memo для ProductCard
71
+ test(features): покрыть тестами хук useAuth
61
72
  docs: добавить описание компонентов в Storybook
73
+ chore(deps): обновить nadvan-ui-kit до 1.0.3
74
+ chore(config): добавить алиас @icons в tsconfig
75
+ ci: добавить GitHub Actions workflow для деплоя
76
+ wip(features): начать реализацию корзины ← только в feature-ветках!
77
+
78
+ Область (scope) — FSD-слой или техническая зона.
79
+ Можно уточнять: features/auth, widgets/header.
80
+ Подробнее: https://github.com/your-org/nadvan-specifications
62
81
  */
@@ -0,0 +1,15 @@
1
+ // Слой entities — бизнес-сущности (User, Product, Order, Article…).
2
+ // Содержит модели данных, базовые UI-компоненты сущности и API-запросы.
3
+ //
4
+ // Пример структуры:
5
+ // src/entities/user/
6
+ // ├── index.ts ← публичное API сущности
7
+ // ├── ui/
8
+ // │ └── UserAvatar.tsx
9
+ // ├── model/
10
+ // │ ├── user.store.ts
11
+ // │ └── user.types.ts
12
+ // └── api/
13
+ // └── user.api.ts
14
+ //
15
+ // Правило импортов: entities → shared
@@ -0,0 +1,15 @@
1
+ // Слой features — пользовательские сценарии и бизнес-действия.
2
+ // Например: auth, cart, search, like-post, change-language.
3
+ //
4
+ // Пример структуры:
5
+ // src/features/auth/
6
+ // ├── index.ts ← публичное API фичи
7
+ // ├── ui/
8
+ // │ └── LoginForm.tsx
9
+ // ├── model/
10
+ // │ ├── auth.store.ts
11
+ // │ └── auth.types.ts
12
+ // └── api/
13
+ // └── auth.api.ts
14
+ //
15
+ // Правило импортов: features → entities, shared
@@ -0,0 +1,10 @@
1
+ // Слой pages — страницы приложения.
2
+ // Каждая страница находится в отдельной папке и компонует виджеты и фичи.
3
+ //
4
+ // Пример структуры:
5
+ // src/pages/HomePage/
6
+ // ├── index.ts ← публичное API страницы
7
+ // └── ui/
8
+ // └── HomePage.tsx
9
+ //
10
+ // Правило импортов: pages → widgets, features, entities, shared
@@ -0,0 +1,29 @@
1
+ // HTTP-клиент — базовая настройка запросов к API.
2
+ // Конфигурируется под конкретный проект (baseURL, токены, интерсепторы).
3
+
4
+ const BASE_URL = (
5
+ typeof process !== 'undefined'
6
+ ? (process.env['NEXT_PUBLIC_API_URL'] ?? process.env['VITE_API_URL'])
7
+ : (import.meta as { env?: Record<string, string> }).env?.['VITE_API_URL']
8
+ ) ?? '/api';
9
+
10
+ async function request<T>(path: string, init?: RequestInit): Promise<T> {
11
+ const res = await fetch(`${BASE_URL}${path}`, {
12
+ headers: { 'Content-Type': 'application/json', ...init?.headers },
13
+ ...init,
14
+ });
15
+
16
+ if (!res.ok) {
17
+ throw new Error(`[API] ${res.status} ${res.statusText}: ${path}`);
18
+ }
19
+
20
+ return res.json() as Promise<T>;
21
+ }
22
+
23
+ export const api = {
24
+ get: <T>(path: string) => request<T>(path),
25
+ post: <T>(path: string, body: unknown) => request<T>(path, { method: 'POST', body: JSON.stringify(body) }),
26
+ put: <T>(path: string, body: unknown) => request<T>(path, { method: 'PUT', body: JSON.stringify(body) }),
27
+ patch: <T>(path: string, body: unknown) => request<T>(path, { method: 'PATCH', body: JSON.stringify(body) }),
28
+ delete: <T>(path: string) => request<T>(path, { method: 'DELETE' }),
29
+ };
@@ -1,6 +1,8 @@
1
1
  // =============================================================================
2
2
  // GLOBAL STYLES — базовый сброс + глобальные переменные
3
- // Подключается один раз в src/app/index.tsx
3
+ // Подключается один раз в точке входа:
4
+ // React: src/main.tsx → import '@shared/styles/global.scss'
5
+ // Next.js: src/app/layout.tsx → import '@shared/styles/global.scss'
4
6
  // =============================================================================
5
7
 
6
8
  @use 'variables' as *;
@@ -0,0 +1,15 @@
1
+ // Базовые UI-компоненты из nadvan-ui-kit.
2
+ // Здесь НЕТ бизнес-логики: только переиспользуемые примитивы.
3
+ //
4
+ // Доступные компоненты:
5
+ // Button, Checkbox, Dropzone, Elevation, Field, Link, Modal,
6
+ // Notification, NotificationProvider, useNotification,
7
+ // GlobalNotificationContainer, notificationService,
8
+ // showInfo, showError, showSuccess,
9
+ // Pagination, PaginationDot, PriceRange, ProgressBar,
10
+ // Radiobutton, Toggle, Tooltip
11
+ //
12
+ // CSS подключается один раз в точке входа:
13
+ // import 'nadvan-ui-kit/styles';
14
+
15
+ export * from 'nadvan-ui-kit';
@@ -0,0 +1,12 @@
1
+ // Слой widgets — самостоятельные UI-блоки (Header, Footer, Sidebar, Banner…).
2
+ // Виджет не зависит от бизнес-контекста страницы.
3
+ //
4
+ // Пример структуры:
5
+ // src/widgets/Header/
6
+ // ├── index.ts
7
+ // ├── ui/
8
+ // │ ├── Header.tsx
9
+ // │ └── Header.module.scss
10
+ // └── model/ ← если виджет имеет локальное состояние
11
+ //
12
+ // Правило импортов: widgets → features, entities, shared
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "project-name",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "engines": {
6
+ "node": ">=20.0.0",
7
+ "npm": ">=10.0.0"
8
+ },
9
+ "scripts": {
10
+ "dev": "next dev",
11
+ "build": "next build",
12
+ "start": "next start",
13
+ "lint": "eslint src --max-warnings 0",
14
+ "lint:fix": "eslint src --fix",
15
+ "lint:styles": "stylelint 'src/**/*.scss'",
16
+ "lint:styles:fix": "stylelint 'src/**/*.scss' --fix",
17
+ "format": "prettier --write 'src/**/*.{ts,tsx,scss,json}'",
18
+ "format:check": "prettier --check 'src/**/*.{ts,tsx,scss,json}'",
19
+ "typecheck": "tsc --noEmit",
20
+ "test": "vitest",
21
+ "test:ui": "vitest --ui",
22
+ "test:coverage": "vitest run --coverage",
23
+ "storybook": "storybook dev -p 6006",
24
+ "build-storybook": "storybook build",
25
+ "prepare": "husky"
26
+ },
27
+ "dependencies": {
28
+ "nadvan-ui-kit": "^1.0.2",
29
+ "next": "^15.1.0",
30
+ "react": "^18.3.1",
31
+ "react-dom": "^18.3.1"
32
+ },
33
+ "devDependencies": {
34
+ "@commitlint/cli": "^19.5.0",
35
+ "@commitlint/config-conventional": "^19.5.0",
36
+ "@storybook/addon-a11y": "^8.3.6",
37
+ "@storybook/addon-essentials": "^8.3.6",
38
+ "@storybook/nextjs": "^8.3.6",
39
+ "@testing-library/jest-dom": "^6.6.3",
40
+ "@testing-library/react": "^16.0.1",
41
+ "@testing-library/user-event": "^14.5.2",
42
+ "@types/node": "^22.0.0",
43
+ "@types/react": "^18.3.12",
44
+ "@types/react-dom": "^18.3.1",
45
+ "@typescript-eslint/eslint-plugin": "^8.12.2",
46
+ "@typescript-eslint/parser": "^8.12.2",
47
+ "@vitest/coverage-v8": "^2.1.4",
48
+ "@vitest/ui": "^2.1.4",
49
+ "eslint": "^9.13.0",
50
+ "eslint-config-next": "^15.1.0",
51
+ "eslint-config-prettier": "^9.1.0",
52
+ "eslint-plugin-boundaries": "^4.3.1",
53
+ "eslint-plugin-import": "^2.31.0",
54
+ "husky": "^9.1.6",
55
+ "lint-staged": "^15.2.10",
56
+ "prettier": "^3.3.3",
57
+ "sass": "^1.80.6",
58
+ "stylelint": "^16.10.0",
59
+ "stylelint-config-standard-scss": "^13.1.0",
60
+ "stylelint-order": "^6.0.4",
61
+ "typescript": "^5.6.3",
62
+ "vitest": "^2.1.4"
63
+ }
64
+ }
@@ -0,0 +1,132 @@
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 importPlugin from 'eslint-plugin-import';
6
+ import boundariesPlugin from 'eslint-plugin-boundaries';
7
+ import nextPlugin from '@next/eslint-plugin-next';
8
+ import prettierConfig from 'eslint-config-prettier';
9
+
10
+ export default [
11
+ {
12
+ ignores: ['dist/**', 'node_modules/**', '.next/**', 'storybook-static/**'],
13
+ },
14
+
15
+ // Next.js core rules
16
+ {
17
+ plugins: { '@next/next': nextPlugin },
18
+ rules: {
19
+ ...nextPlugin.configs.recommended.rules,
20
+ ...nextPlugin.configs['core-web-vitals'].rules,
21
+ },
22
+ },
23
+
24
+ {
25
+ files: ['src/**/*.{ts,tsx}'],
26
+
27
+ languageOptions: {
28
+ parser: tsParser,
29
+ parserOptions: {
30
+ project: true,
31
+ tsconfigRootDir: import.meta.dirname,
32
+ },
33
+ },
34
+
35
+ plugins: {
36
+ '@typescript-eslint': tsPlugin,
37
+ react: reactPlugin,
38
+ 'react-hooks': reactHooksPlugin,
39
+ import: importPlugin,
40
+ boundaries: boundariesPlugin,
41
+ },
42
+
43
+ settings: {
44
+ react: { version: 'detect' },
45
+ 'import/resolver': { typescript: true },
46
+ 'boundaries/elements': [
47
+ { type: 'app', pattern: 'src/app/*' },
48
+ { type: 'pages', pattern: 'src/pages/*' },
49
+ { type: 'widgets', pattern: 'src/widgets/*' },
50
+ { type: 'features', pattern: 'src/features/*' },
51
+ { type: 'entities', pattern: 'src/entities/*' },
52
+ { type: 'shared', pattern: 'src/shared/*' },
53
+ ],
54
+ 'boundaries/ignore': ['**/*.test.*', '**/*.spec.*', '**/*.stories.*'],
55
+ },
56
+
57
+ rules: {
58
+ // TypeScript
59
+ ...tsPlugin.configs['recommended'].rules,
60
+ ...tsPlugin.configs['recommended-requiring-type-checking'].rules,
61
+ '@typescript-eslint/no-explicit-any': 'error',
62
+ '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
63
+ '@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports' }],
64
+ '@typescript-eslint/no-floating-promises': 'error',
65
+ '@typescript-eslint/no-misused-promises': 'error',
66
+ '@typescript-eslint/strict-boolean-expressions': 'error',
67
+ '@typescript-eslint/prefer-nullish-coalescing': 'error',
68
+ '@typescript-eslint/prefer-optional-chain': 'error',
69
+
70
+ // React
71
+ ...reactPlugin.configs.recommended.rules,
72
+ ...reactPlugin.configs['jsx-runtime'].rules,
73
+ 'react/prop-types': 'off',
74
+ 'react/self-closing-comp': 'error',
75
+ 'react/jsx-boolean-value': ['error', 'never'],
76
+ 'react/jsx-curly-brace-presence': ['error', { props: 'never', children: 'never' }],
77
+ 'react/jsx-no-useless-fragment': 'error',
78
+ 'react/no-array-index-key': 'warn',
79
+
80
+ // React Hooks
81
+ ...reactHooksPlugin.configs.recommended.rules,
82
+
83
+ // Импорты
84
+ 'import/order': ['error', {
85
+ groups: ['builtin', 'external', 'internal', ['parent', 'sibling', 'index']],
86
+ pathGroups: [
87
+ { pattern: '@app/**', group: 'internal', position: 'before' },
88
+ { pattern: '@pages/**', group: 'internal', position: 'before' },
89
+ { pattern: '@widgets/**', group: 'internal', position: 'before' },
90
+ { pattern: '@features/**', group: 'internal', position: 'before' },
91
+ { pattern: '@entities/**', group: 'internal', position: 'before' },
92
+ { pattern: '@shared/**', group: 'internal', position: 'before' },
93
+ ],
94
+ 'newlines-between': 'always',
95
+ alphabetize: { order: 'asc' },
96
+ }],
97
+ 'import/no-duplicates': 'error',
98
+ 'import/no-default-export': 'error',
99
+
100
+ // FSD границы слоёв
101
+ 'boundaries/element-types': ['error', {
102
+ default: 'disallow',
103
+ rules: [
104
+ { from: 'app', allow: ['pages', 'widgets', 'features', 'entities', 'shared'] },
105
+ { from: 'pages', allow: ['widgets', 'features', 'entities', 'shared'] },
106
+ { from: 'widgets', allow: ['features', 'entities', 'shared'] },
107
+ { from: 'features', allow: ['entities', 'shared'] },
108
+ { from: 'entities', allow: ['shared'] },
109
+ { from: 'shared', allow: [] },
110
+ ],
111
+ }],
112
+
113
+ 'no-console': ['warn', { allow: ['warn', 'error'] }],
114
+ 'prefer-const': 'error',
115
+ 'no-var': 'error',
116
+ 'eqeqeq': ['error', 'always'],
117
+ },
118
+ },
119
+
120
+ // Default export разрешён для Next.js App Router файлов и stories
121
+ {
122
+ files: [
123
+ 'src/app/**/{page,layout,loading,error,not-found,template,default}.tsx',
124
+ 'src/app/**/route.ts',
125
+ '**/*.stories.{ts,tsx}',
126
+ 'next.config.ts',
127
+ ],
128
+ rules: { 'import/no-default-export': 'off' },
129
+ },
130
+
131
+ prettierConfig,
132
+ ];
@@ -0,0 +1,20 @@
1
+ import type { NextConfig } from 'next';
2
+
3
+ const nextConfig: NextConfig = {
4
+ reactStrictMode: true,
5
+
6
+ // SCSS: переменные и миксины автоматически доступны во всех .scss файлах
7
+ sassOptions: {
8
+ additionalData: `
9
+ @use "src/shared/styles/variables" as *;
10
+ @use "src/shared/styles/mixins" as *;
11
+ `,
12
+ },
13
+
14
+ // FSD aliases дублируем здесь для runtime-резолва
15
+ experimental: {
16
+ typedRoutes: true,
17
+ },
18
+ };
19
+
20
+ export default nextConfig;
@@ -0,0 +1,21 @@
1
+ import type { Metadata } from 'next';
2
+
3
+ import 'nadvan-ui-kit/styles';
4
+ import '@shared/styles/global.scss';
5
+
6
+ import { Providers } from './providers';
7
+
8
+ export const metadata: Metadata = {
9
+ title: 'Nadvan App',
10
+ description: 'Nadvan Frontend Project',
11
+ };
12
+
13
+ export default function RootLayout({ children }: { readonly children: React.ReactNode }) {
14
+ return (
15
+ <html lang="ru">
16
+ <body>
17
+ <Providers>{children}</Providers>
18
+ </body>
19
+ </html>
20
+ );
21
+ }
@@ -0,0 +1,7 @@
1
+ export default function HomePage() {
2
+ return (
3
+ <main>
4
+ <h1>Добро пожаловать</h1>
5
+ </main>
6
+ );
7
+ }
@@ -0,0 +1,20 @@
1
+ 'use client';
2
+
3
+ import type { ReactNode } from 'react';
4
+
5
+ interface ProvidersProps {
6
+ children: ReactNode;
7
+ }
8
+
9
+ // Client-side провайдеры: store, theme, i18n, toasts и т.д.
10
+ // Пример:
11
+ // import { Provider } from 'react-redux';
12
+ // import { store } from '@shared/config/store';
13
+ //
14
+ // export function Providers({ children }: ProvidersProps) {
15
+ // return <Provider store={store}>{children}</Provider>;
16
+ // }
17
+
18
+ export function Providers({ children }: ProvidersProps) {
19
+ return <>{children}</>;
20
+ }
@@ -0,0 +1,43 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+
17
+ // Strict
18
+ "noImplicitAny": true,
19
+ "strictNullChecks": true,
20
+ "noUncheckedIndexedAccess": true,
21
+ "noImplicitReturns": true,
22
+ "noFallthroughCasesInSwitch": true,
23
+ "noUnusedLocals": true,
24
+ "noUnusedParameters": true,
25
+ "exactOptionalPropertyTypes": true,
26
+
27
+ // Next.js plugin
28
+ "plugins": [{ "name": "next" }],
29
+
30
+ // FSD aliases
31
+ "baseUrl": ".",
32
+ "paths": {
33
+ "@app/*": ["src/app/*"],
34
+ "@pages/*": ["src/pages/*"],
35
+ "@widgets/*": ["src/widgets/*"],
36
+ "@features/*": ["src/features/*"],
37
+ "@entities/*": ["src/entities/*"],
38
+ "@shared/*": ["src/shared/*"]
39
+ }
40
+ },
41
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
42
+ "exclude": ["node_modules"]
43
+ }
@@ -26,9 +26,7 @@
26
26
  "prepare": "husky"
27
27
  },
28
28
  "dependencies": {
29
- "@nadvan/ui": "latest",
30
- "@nadvan/fonts": "latest",
31
- "@nadvan/grid": "latest",
29
+ "nadvan-ui-kit": "^1.0.2",
32
30
  "react": "^18.3.1",
33
31
  "react-dom": "^18.3.1",
34
32
  "react-router-dom": "^6.27.0"
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="ru">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Nadvan App</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,19 @@
1
+ import { Suspense } from 'react';
2
+ import { Route, Routes } from 'react-router-dom';
3
+
4
+ import { Providers } from './providers';
5
+
6
+ // Импортируй страницы из @pages и добавляй маршруты здесь
7
+ // import { HomePage } from '@pages/home';
8
+
9
+ export function App() {
10
+ return (
11
+ <Providers>
12
+ <Suspense fallback={null}>
13
+ <Routes>
14
+ <Route path="/" element={<div>Home</div>} />
15
+ </Routes>
16
+ </Suspense>
17
+ </Providers>
18
+ );
19
+ }
@@ -0,0 +1,18 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ interface ProvidersProps {
4
+ children: ReactNode;
5
+ }
6
+
7
+ // Оборачивай здесь глобальные провайдеры: store, theme, i18n, toasts и т.д.
8
+ // Пример:
9
+ // import { Provider } from 'react-redux';
10
+ // import { store } from '@shared/config/store';
11
+ //
12
+ // export function Providers({ children }: ProvidersProps) {
13
+ // return <Provider store={store}>{children}</Provider>;
14
+ // }
15
+
16
+ export function Providers({ children }: ProvidersProps) {
17
+ return <>{children}</>;
18
+ }
@@ -0,0 +1,19 @@
1
+ import { StrictMode } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import { BrowserRouter } from 'react-router-dom';
4
+
5
+ import 'nadvan-ui-kit/styles';
6
+ import '@shared/styles/global.scss';
7
+
8
+ import { App } from '@app/index';
9
+
10
+ const rootElement = document.getElementById('root');
11
+ if (!rootElement) throw new Error('Root element #root not found');
12
+
13
+ createRoot(rootElement).render(
14
+ <StrictMode>
15
+ <BrowserRouter>
16
+ <App />
17
+ </BrowserRouter>
18
+ </StrictMode>,
19
+ );
@@ -12,8 +12,8 @@ export default defineConfig({
12
12
  scss: {
13
13
  // Переменные и миксины автоматически доступны во всех .scss файлах
14
14
  additionalData: `
15
- @use "src/app/styles/variables" as *;
16
- @use "src/app/styles/mixins" as *;
15
+ @use "src/shared/styles/variables" as *;
16
+ @use "src/shared/styles/mixins" as *;
17
17
  `,
18
18
  },
19
19
  },
File without changes
File without changes
File without changes