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.
- package/README.md +279 -0
- package/package.json +9 -2
- package/src/index.js +57 -26
- package/src/{template → templates/base}/commitlint.config.js +20 -1
- package/src/templates/base/src/entities/index.ts +15 -0
- package/src/templates/base/src/features/index.ts +15 -0
- package/src/templates/base/src/pages/index.ts +10 -0
- package/src/templates/base/src/shared/api/index.ts +29 -0
- package/src/{template/src/app → templates/base/src/shared}/styles/global.scss +3 -1
- package/src/templates/base/src/shared/ui/index.ts +15 -0
- package/src/templates/base/src/widgets/index.ts +12 -0
- package/src/templates/nextjs/_package.json +64 -0
- package/src/templates/nextjs/eslint.config.js +132 -0
- package/src/templates/nextjs/next.config.ts +20 -0
- package/src/templates/nextjs/src/app/layout.tsx +21 -0
- package/src/templates/nextjs/src/app/page.tsx +7 -0
- package/src/templates/nextjs/src/app/providers/index.tsx +20 -0
- package/src/templates/nextjs/tsconfig.json +43 -0
- package/src/{template → templates/react}/_package.json +1 -3
- package/src/templates/react/index.html +12 -0
- package/src/templates/react/src/app/index.tsx +19 -0
- package/src/templates/react/src/app/providers/index.tsx +18 -0
- package/src/templates/react/src/main.tsx +19 -0
- package/src/{template → templates/react}/vite.config.ts +2 -2
- /package/src/{template → templates/base}/.husky/commit-msg +0 -0
- /package/src/{template → templates/base}/.husky/pre-commit +0 -0
- /package/src/{template → templates/base}/.prettierrc +0 -0
- /package/src/{template → templates/base}/.stylelintrc.json +0 -0
- /package/src/{template → templates/base}/gitignore +0 -0
- /package/src/{template → templates/base}/lint-staged.config.js +0 -0
- /package/src/{template → templates/base}/src/shared/config/breakpoints.ts +0 -0
- /package/src/{template → templates/base}/src/shared/config/tokens.ts +0 -0
- /package/src/{template → templates/base}/src/shared/lib/hooks/index.ts +0 -0
- /package/src/{template → templates/base}/src/shared/lib/hooks/useMediaQuery.ts +0 -0
- /package/src/{template/src/app → templates/base/src/shared}/styles/_mixins.scss +0 -0
- /package/src/{template/src/app → templates/base/src/shared}/styles/_typography.scss +0 -0
- /package/src/{template/src/app → templates/base/src/shared}/styles/_variables.scss +0 -0
- /package/src/{template → templates/react}/eslint.config.js +0 -0
- /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.
|
|
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": [
|
|
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
|
|
12
|
-
const
|
|
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
|
-
|
|
25
|
-
const args
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
const
|
|
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
|
-
//
|
|
36
|
+
// ─── Вопросы ──────────────────────────────────────────────────────────────
|
|
37
37
|
let answers;
|
|
38
|
+
|
|
38
39
|
if (flagYes) {
|
|
39
|
-
answers = {
|
|
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
|
|
80
|
-
const { pkgManager, install } = answers;
|
|
81
|
-
const targetDir
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
// ───
|
|
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
|
-
// ───
|
|
137
|
+
// ─── 4. Подставляем имя проекта ──────────────────────────────────────────
|
|
107
138
|
const pkgPath = path.join(targetDir, 'package.json');
|
|
108
|
-
const pkg
|
|
109
|
-
pkg.name
|
|
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
|
-
// ───
|
|
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
|
-
// ───
|
|
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
|
-
|
|
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
|
-
// Подключается один раз в
|
|
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,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
|
+
}
|
|
@@ -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/
|
|
16
|
-
@use "src/
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|