create-nadvan-app 1.0.0 → 1.0.1

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 +191 -0
  2. package/package.json +9 -2
  3. package/src/index.js +57 -26
  4. package/src/{template → templates/base}/commitlint.config.js +1 -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,191 @@
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
+ ## Автоматические проверки
125
+
126
+ При каждом `git commit` автоматически запускаются:
127
+
128
+ 1. **ESLint** — проверяет и исправляет `.ts`, `.tsx`
129
+ 2. **Stylelint** — проверяет и исправляет `.scss`
130
+ 3. **Prettier** — форматирует изменённые файлы
131
+ 4. **TypeScript** — `tsc --noEmit`, коммит блокируется при ошибках типов
132
+ 5. **commitlint** — проверяет формат сообщения коммита
133
+
134
+ ### Формат коммита
135
+
136
+ ```
137
+ <тип>(<область>): <описание>
138
+ ```
139
+
140
+ Примеры:
141
+ ```
142
+ feat(features): добавить авторизацию через OAuth
143
+ fix(shared): исправить отступы в компоненте Button
144
+ style(widgets): обновить вёрстку Header под мобайл
145
+ chore(deps): обновить @nadvan/ui до 2.1.0
146
+ ```
147
+
148
+ ---
149
+
150
+ ## Скрипты
151
+
152
+ ```bash
153
+ npm run dev # Dev-сервер
154
+ npm run build # Сборка
155
+ npm run typecheck # Проверка типов
156
+ npm run lint # ESLint
157
+ npm run lint:fix # ESLint с автоисправлением
158
+ npm run lint:styles # Stylelint
159
+ npm run lint:styles:fix # Stylelint с автоисправлением
160
+ npm run format # Prettier
161
+ npm run test # Vitest
162
+ npm run test:coverage # Тесты с покрытием
163
+ npm run storybook # Storybook
164
+ ```
165
+
166
+ ---
167
+
168
+ ## Разработка CLI
169
+
170
+ ```bash
171
+ # Локальное тестирование
172
+ node src/index.js test-project --yes --no-install
173
+
174
+ # Линк для глобального использования
175
+ npm link
176
+ create-nadvan-app my-project
177
+ ```
178
+
179
+ ---
180
+
181
+ ## Публикация
182
+
183
+ ```bash
184
+ npm login
185
+ npm publish --access public
186
+ ```
187
+
188
+ После публикации:
189
+ ```bash
190
+ npm create nadvan-app@latest my-project
191
+ ```
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.1",
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`));
@@ -57,6 +57,6 @@ export default {
57
57
  fix(shared): исправить padding в компоненте Button
58
58
  style(widgets): обновить вёрстку Header под мобайл
59
59
  refactor(entities): переименовать поля в модели User
60
- chore(deps): обновить @nadvan/ui до 2.1.0
60
+ chore(deps): обновить nadvan-ui-kit до 1.1.0
61
61
  docs: добавить описание компонентов в Storybook
62
62
  */
@@ -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