refactor: удалить английскую локаль и упростить структуру
- Удалена английская версия документации (docs/en/) и артефакты en - Контент перенесён docs/ru/ → docs/docs/, URL /ru/ заменён на /docs/ - Из .vitepress/config.ts убраны locales и enSidebar, оставлен один sidebar - Из лендинга удалён переключатель языка ru/en и en-словарь - generate-llms.ts переписан без параметра lang; llms.txt, llms-full.txt и nextjs-style-guide.zip генерируются в корень docs/public/ - README_RU.md занял место корневого README.md - Обновлены CONTRIBUTING.md, custom.css, комментарий в Dockerfile
This commit is contained in:
@@ -1,101 +1,51 @@
|
||||
import { defineConfig } from 'vitepress';
|
||||
|
||||
const ruSidebar = [
|
||||
const sidebar = [
|
||||
{
|
||||
text: 'Главная',
|
||||
link: '/ru/',
|
||||
link: '/docs/',
|
||||
},
|
||||
{
|
||||
text: 'Workflow',
|
||||
link: '/ru/workflow',
|
||||
link: '/docs/workflow',
|
||||
},
|
||||
{
|
||||
text: 'Базовые правила',
|
||||
items: [
|
||||
{ text: 'Технологии и библиотеки', link: '/ru/basics/tech-stack' },
|
||||
{ text: 'Именование', link: '/ru/basics/naming' },
|
||||
{ text: 'Технологии и библиотеки', link: '/docs/basics/tech-stack' },
|
||||
{ text: 'Именование', link: '/docs/basics/naming' },
|
||||
{
|
||||
text: 'Архитектура',
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: 'Обзор', link: '/ru/basics/architecture/' },
|
||||
{ text: 'Слои', link: '/ru/basics/architecture/reference/layers' },
|
||||
{ text: 'Модули', link: '/ru/basics/architecture/reference/modules' },
|
||||
{ text: 'Сегменты', link: '/ru/basics/architecture/reference/segments' },
|
||||
{ text: 'Обзор', link: '/docs/basics/architecture/' },
|
||||
{ text: 'Слои', link: '/docs/basics/architecture/reference/layers' },
|
||||
{ text: 'Модули', link: '/docs/basics/architecture/reference/modules' },
|
||||
{ text: 'Сегменты', link: '/docs/basics/architecture/reference/segments' },
|
||||
],
|
||||
},
|
||||
{ text: 'Стиль кода', link: '/ru/basics/code-style' },
|
||||
{ text: 'Документирование', link: '/ru/basics/documentation' },
|
||||
{ text: 'Типизация', link: '/ru/basics/typing' },
|
||||
{ text: 'Стиль кода', link: '/docs/basics/code-style' },
|
||||
{ text: 'Документирование', link: '/docs/basics/documentation' },
|
||||
{ text: 'Типизация', link: '/docs/basics/typing' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Прикладные разделы',
|
||||
items: [
|
||||
{ text: 'Структура проекта', link: '/ru/applied/project-structure' },
|
||||
{ text: 'Компоненты', link: '/ru/applied/components' },
|
||||
{ text: 'Страницы (App Router)', link: '/ru/applied/page-level' },
|
||||
{ text: 'Шаблоны и генерация кода', link: '/ru/applied/templates-generation' },
|
||||
{ text: 'Стили', link: '/ru/applied/styles' },
|
||||
{ text: 'Изображения', link: '/ru/applied/images-sprites' },
|
||||
{ text: 'SVG-спрайты', link: '/ru/applied/svg-sprites' },
|
||||
{ text: 'Видео', link: '/ru/applied/video' },
|
||||
{ text: 'API', link: '/ru/applied/api' },
|
||||
{ text: 'Stores', link: '/ru/applied/stores' },
|
||||
{ text: 'Хуки', link: '/ru/applied/hooks' },
|
||||
{ text: 'Шрифты', link: '/ru/applied/fonts' },
|
||||
{ text: 'Локализация', link: '/ru/applied/localization' },
|
||||
{ text: 'Настройка VS Code', link: '/ru/applied/vscode' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const enSidebar = [
|
||||
{
|
||||
text: 'Home',
|
||||
link: '/en/',
|
||||
},
|
||||
{
|
||||
text: 'Processes',
|
||||
items: [
|
||||
{ text: 'Getting Started', link: '/en/workflow/getting-started' },
|
||||
{ text: 'Creating an App', link: '/en/workflow/creating-app' },
|
||||
{ text: 'Creating Pages', link: '/en/workflow/creating-pages' },
|
||||
{ text: 'Creating Components', link: '/en/workflow/creating-components' },
|
||||
{ text: 'Styling', link: '/en/workflow/styling' },
|
||||
{ text: 'Data Fetching', link: '/en/workflow/data-fetching' },
|
||||
{ text: 'State Management', link: '/en/workflow/state-management' },
|
||||
{ text: 'Localization', link: '/en/workflow/localization' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Basic Rules',
|
||||
items: [
|
||||
{ text: 'Tech Stack', link: '/en/basics/tech-stack' },
|
||||
{ text: 'Architecture', link: '/en/basics/architecture' },
|
||||
{ text: 'Code Style', link: '/en/basics/code-style' },
|
||||
{ text: 'Naming', link: '/en/basics/naming' },
|
||||
{ text: 'Documentation', link: '/en/basics/documentation' },
|
||||
{ text: 'Typing', link: '/en/basics/typing' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Applied Sections',
|
||||
items: [
|
||||
{ text: 'VS Code Setup', link: '/en/applied/vscode' },
|
||||
{ text: 'Project Structure', link: '/en/applied/project-structure' },
|
||||
{ text: 'Components', link: '/en/applied/components' },
|
||||
{ text: 'Page-level Components', link: '/en/applied/page-level' },
|
||||
{ text: 'Templates & Code Generation', link: '/en/applied/templates-generation' },
|
||||
{ text: 'Styles', link: '/en/applied/styles' },
|
||||
{ text: 'Images', link: '/en/applied/images-sprites' },
|
||||
{ text: 'SVG Sprites', link: '/en/applied/svg-sprites' },
|
||||
{ text: 'Video', link: '/en/applied/video' },
|
||||
{ text: 'API', link: '/en/applied/api' },
|
||||
{ text: 'Stores', link: '/en/applied/stores' },
|
||||
{ text: 'Hooks', link: '/en/applied/hooks' },
|
||||
{ text: 'Fonts', link: '/en/applied/fonts' },
|
||||
{ text: 'Localization', link: '/en/applied/localization' },
|
||||
{ text: 'Структура проекта', link: '/docs/applied/project-structure' },
|
||||
{ text: 'Компоненты', link: '/docs/applied/components' },
|
||||
{ text: 'Страницы (App Router)', link: '/docs/applied/page-level' },
|
||||
{ text: 'Шаблоны и генерация кода', link: '/docs/applied/templates-generation' },
|
||||
{ text: 'Стили', link: '/docs/applied/styles' },
|
||||
{ text: 'Изображения', link: '/docs/applied/images-sprites' },
|
||||
{ text: 'SVG-спрайты', link: '/docs/applied/svg-sprites' },
|
||||
{ text: 'Видео', link: '/docs/applied/video' },
|
||||
{ text: 'API', link: '/docs/applied/api' },
|
||||
{ text: 'Stores', link: '/docs/applied/stores' },
|
||||
{ text: 'Хуки', link: '/docs/applied/hooks' },
|
||||
{ text: 'Шрифты', link: '/docs/applied/fonts' },
|
||||
{ text: 'Локализация', link: '/docs/applied/localization' },
|
||||
{ text: 'Настройка VS Code', link: '/docs/applied/vscode' },
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -123,8 +73,9 @@ export default defineConfig({
|
||||
// (попадают в корень `dist/` как статика). Исключаем из сканирования
|
||||
// страниц, иначе VitePress рендерит их как HTML-страницы.
|
||||
srcExclude: ['public/**'],
|
||||
lang: 'ru-RU',
|
||||
title: 'NextJS Style Guide',
|
||||
description: 'Правила и стандарты разработки на NextJS и TypeScript',
|
||||
description: 'Стандарты разработки на Next.js + TypeScript с архитектурой SLM',
|
||||
|
||||
vite: {
|
||||
plugins: [utf8TextPlugin],
|
||||
@@ -133,44 +84,18 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
|
||||
locales: {
|
||||
root: {
|
||||
label: 'Languages',
|
||||
lang: 'en',
|
||||
},
|
||||
ru: {
|
||||
label: 'Русский',
|
||||
lang: 'ru-RU',
|
||||
link: '/ru/',
|
||||
description: 'Стандарты разработки на Next.js + TypeScript с архитектурой SLM',
|
||||
themeConfig: {
|
||||
sidebar: ruSidebar,
|
||||
sidebar,
|
||||
socialLinks: [
|
||||
{ icon: 'github', link: 'https://gromlab.ru/docs/nextjs-style-guide' },
|
||||
],
|
||||
},
|
||||
|
||||
// Расширенный блок описания для llms.txt — даёт LLM полный
|
||||
// технический контекст: стек, методология, охват тем.
|
||||
// Используется в generate-llms.ts.
|
||||
llmsBlockquote:
|
||||
'Стандарты разработки frontend-приложений на Next.js (App Router) + TypeScript + React с архитектурой SLM (Scoped Layered Module Design — модульная архитектура со слоями ответственности, где каждый модуль содержит всё необходимое: компоненты, хуки, сторы, типы, стили).',
|
||||
llmsContext:
|
||||
'Стек: React, TypeScript, Next.js App Router, Mantine UI, SWR, Zustand, i18next, PostCSS Modules, Vitest, clsx.\n\nДокументация покрывает архитектуру SLM (слои, модули, сегменты, направление зависимостей, публичный API), правила оформления кода (именование, форматирование, импорты, типизация, JSDoc), реализацию компонентов и хуков, работу с App Router, кодогенерацию из шаблонов, стилизацию (Mobile First, токены), работу с API и сокетами, управление состоянием через Zustand, локализацию, ассеты (шрифты, изображения, SVG-спрайты) и настройку VS Code.',
|
||||
},
|
||||
en: {
|
||||
label: 'English',
|
||||
lang: 'en-US',
|
||||
link: '/en/',
|
||||
description: 'Next.js + TypeScript development standards with SLM architecture',
|
||||
themeConfig: {
|
||||
sidebar: enSidebar,
|
||||
socialLinks: [
|
||||
{ icon: 'github', link: 'https://gromlab.ru/docs/nextjs-style-guide' },
|
||||
],
|
||||
},
|
||||
llmsBlockquote:
|
||||
'Frontend development standards for Next.js (App Router) + TypeScript + React projects with SLM architecture (Scoped Layered Module Design — a modular architecture with responsibility layers, where each module contains everything it needs: components, hooks, stores, types, styles).',
|
||||
llmsContext:
|
||||
'Stack: React, TypeScript, Next.js App Router, Mantine UI, SWR, Zustand, i18next, PostCSS Modules, Vitest, clsx.\n\nThe documentation covers SLM architecture (layers, modules, segments, dependency direction, public API), code conventions (naming, formatting, imports, typing, JSDoc), component and hook implementation, App Router usage, code generation from templates, styling (Mobile First, design tokens), API and socket integration, state management via Zustand, localization, assets (fonts, images, SVG sprites), and VS Code setup.',
|
||||
},
|
||||
},
|
||||
});
|
||||
} as any);
|
||||
|
||||
@@ -16,13 +16,4 @@
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
/*
|
||||
* Скрыть root-локаль (лендинг с layout: false) из переключателя языка.
|
||||
* VitePress не даёт исключить root через config — она всегда попадает
|
||||
* в lang-switcher. Идентифицируем по href="/" — другие локали используют
|
||||
* href="/ru/" и href="/en/".
|
||||
*/
|
||||
.VPNavBarTranslations a[href="/"],
|
||||
.VPNavScreenTranslations a[href="/"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,8 @@
|
||||
Документационный сайт с правилами и стандартами фронтенд-разработки на Next.js + TypeScript.
|
||||
|
||||
- Движок: VitePress
|
||||
- Языки: русский (основной), английский
|
||||
- Русская версия: `docs/ru/`
|
||||
- Английская версия: `docs/en/`
|
||||
- Язык: русский
|
||||
- Контент: `docs/docs/`
|
||||
|
||||
## Команды
|
||||
|
||||
@@ -17,51 +16,53 @@
|
||||
|---------|-----------|
|
||||
| `npm run dev` | Локальный сервер разработки |
|
||||
| `npm run build` | Сборка статического сайта |
|
||||
| `npm run llms` | Генерация `generated/{lang}/llms.txt` (карта документации для LLM) и README |
|
||||
| `npm run llms` | Генерация `llms.txt` (карта документации для LLM) и README |
|
||||
|
||||
## Структура файлов
|
||||
|
||||
```
|
||||
docs/
|
||||
├── ru/ # Русская версия (основная)
|
||||
│ ├── index.md # Главная страница
|
||||
│ ├── basics/ # Базовые правила
|
||||
│ │ ├── tech-stack.md
|
||||
│ │ ├── architecture.md
|
||||
│ │ ├── code-style.md
|
||||
│ │ ├── naming.md
|
||||
│ │ ├── documentation.md
|
||||
│ │ └── typing.md
|
||||
│ └── applied/ # Прикладные разделы
|
||||
│ ├── vscode.md
|
||||
│ ├── project-structure.md
|
||||
│ ├── components.md
|
||||
│ ├── page-level.md
|
||||
│ ├── templates-generation.md
|
||||
│ ├── styles.md
|
||||
│ ├── images-sprites.md
|
||||
│ ├── svg-sprites.md
|
||||
│ ├── video.md
|
||||
│ ├── api.md
|
||||
│ ├── stores.md
|
||||
│ ├── hooks.md
|
||||
│ ├── fonts.md
|
||||
│ └── localization.md
|
||||
├── en/ # Английская версия (зеркало ru/)
|
||||
├── index.md # Лендинг (URL `/`)
|
||||
└── docs/ # Контент документации (URL `/docs/...`)
|
||||
├── index.md # Главная страница
|
||||
├── workflow.md
|
||||
├── workflow/ # Процессы разработки
|
||||
├── basics/ # Базовые правила
|
||||
│ ├── tech-stack.md
|
||||
│ ├── architecture/
|
||||
│ ├── code-style.md
|
||||
│ ├── naming.md
|
||||
│ ├── documentation.md
|
||||
│ └── typing.md
|
||||
└── applied/ # Прикладные разделы
|
||||
├── vscode.md
|
||||
├── project-structure.md
|
||||
├── components.md
|
||||
├── page-level.md
|
||||
├── templates-generation.md
|
||||
├── styles.md
|
||||
├── images-sprites.md
|
||||
├── svg-sprites.md
|
||||
├── video.md
|
||||
├── api.md
|
||||
├── stores.md
|
||||
├── hooks.md
|
||||
├── fonts.md
|
||||
└── localization.md
|
||||
.vitepress/
|
||||
├── config.ts # Конфигурация VitePress, сайдбары, локали
|
||||
generated/
|
||||
├── ru/llms.txt # Карта документации для LLM (ru, llmstxt.org)
|
||||
└── en/llms.txt # Карта документации для LLM (en, llmstxt.org)
|
||||
└── config.ts # Конфигурация VitePress, сайдбар
|
||||
generate-llms.ts # Скрипт генерации llms.txt и README
|
||||
```
|
||||
|
||||
Сгенерированные артефакты (`docs/public/`): `llms.txt`, `llms-full.txt`,
|
||||
`nextjs-style-guide.zip`, `manifest.json`, копии `.md` в `docs/public/docs/`.
|
||||
|
||||
### Добавление нового раздела
|
||||
|
||||
1. Создать `.md`-файл в нужной папке (`basics/` или `applied/`).
|
||||
2. Добавить пункт в сайдбар — `.vitepress/config.ts` (оба языка, если есть перевод).
|
||||
1. Создать `.md`-файл в нужной папке (`docs/docs/basics/` или `docs/docs/applied/`).
|
||||
2. Добавить пункт в сайдбар — `.vitepress/config.ts`.
|
||||
Сайдбар — единственный источник порядка и группировки для `llms.txt`.
|
||||
3. Запустить `npm run llms` для обновления `generated/{lang}/llms.txt`.
|
||||
3. Запустить `npm run llms` для обновления `llms.txt` и README.
|
||||
|
||||
## Два типа документации
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
FROM node:24-alpine AS build
|
||||
WORKDIR /app
|
||||
# zip нужен для упаковки nextjs-style-guide-{lang}.zip
|
||||
# zip нужен для упаковки nextjs-style-guide.zip
|
||||
RUN apk add --no-cache zip
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
|
||||
97
README.md
97
README.md
@@ -1,66 +1,69 @@
|
||||
# NextJS Style Guide
|
||||
|
||||
Conventions for Next.js project development: application architecture and layers, code structure, module organization, styling, typing, and infrastructure.
|
||||
Соглашения по разработке Next.js проектов: архитектура и слои приложения, структура кода, организация модулей, стилизация, типизация и инфраструктура.
|
||||
|
||||
## Usage
|
||||
## Использование
|
||||
|
||||
**For AI agents:**
|
||||
**Для AI-агентов:**
|
||||
|
||||
- [Sections map](https://nextjs-style-guide.gromlab.ru/en/llms.txt) — `llms.txt`, table of contents with links to sections.
|
||||
- [Full text](https://nextjs-style-guide.gromlab.ru/en/llms-full.txt) — `llms-full.txt`, the entire documentation in a single file.
|
||||
- [Карта разделов](https://nextjs-style-guide.gromlab.ru/llms.txt) — `llms.txt`, оглавление со ссылками на разделы.
|
||||
- [Полный текст](https://nextjs-style-guide.gromlab.ru/llms-full.txt) — `llms-full.txt`, вся документация одним файлом.
|
||||
|
||||
**For projects:**
|
||||
**Для проекта:**
|
||||
|
||||
- [Rules archive](https://nextjs-style-guide.gromlab.ru/nextjs-style-guide-en.zip) — `nextjs-style-guide-en.zip`, a set of Markdown files to unpack into `./ai/nextjs-style-guide/` or another project folder.
|
||||
- [Архив с правилами](https://nextjs-style-guide.gromlab.ru/nextjs-style-guide.zip) — `nextjs-style-guide.zip`, набор Markdown-файлов для распаковки в `./ai/nextjs-style-guide/` или другую папку проекта.
|
||||
|
||||
## Documentation Structure
|
||||
## Структура документации
|
||||
|
||||
### Processes
|
||||
### Workflow
|
||||
|
||||
**What to do** in a specific situation — step-by-step instructions.
|
||||
**Что делать и в каком порядке** — пошаговые инструкции.
|
||||
|
||||
| Section | Answers the question |
|
||||
|---------|---------------------|
|
||||
| Getting Started | What tools to install before starting development? |
|
||||
| Creating an App | How to create a new project, where to get a template? |
|
||||
| Creating Pages | How to add a page: routing and screen? |
|
||||
| Creating Components | How to generate components using templates? |
|
||||
| Styling | What to use: Mantine, tokens, or PostCSS? |
|
||||
| Data Fetching | How to fetch data: SWR, codegen, sockets? |
|
||||
| State Management | When and how to create a store (Zustand)? |
|
||||
| Localization | How to add translations and work with i18next? |
|
||||
| Раздел | Отвечает на вопрос |
|
||||
|--------|-------------------|
|
||||
| Начало работы | Что нужно знать перед началом разработки? |
|
||||
| Создание проекта | Как начать новый проект? |
|
||||
| Генерация кода | Какие модули должны генерироваться из шаблонов? |
|
||||
| Добавление страницы | Как добавить новую страницу в проект? |
|
||||
| Добавление UI-модуля | Как создать компонент, бизнес-модуль, виджет или layout? |
|
||||
| Стилизация | Как стилизовать компоненты в проекте? |
|
||||
| Получение данных | Как получать данные с сервера? |
|
||||
| Управление состоянием | Как работать с состоянием? |
|
||||
| Локализация | Как добавлять переводы и подключать локализацию? |
|
||||
|
||||
### Basic Rules
|
||||
### Базовые правила
|
||||
|
||||
**What the code should look like** — standards not tied to a specific technology.
|
||||
**Каким должен быть код** — стандарты, не привязанные к конкретной технологии.
|
||||
|
||||
| Section | Answers the question |
|
||||
|---------|---------------------|
|
||||
| Tech Stack | What stack do we use? |
|
||||
| Architecture | How are SLM layers, dependencies, and public API structured? |
|
||||
| Code Style | How to format code: indentation, quotes, imports, early return? |
|
||||
| Naming | How to name files, variables, components, hooks? |
|
||||
| Documentation | How to write JSDoc: what to document and what not? |
|
||||
| Typing | How to type: type vs interface, any/unknown? |
|
||||
| Раздел | Отвечает на вопрос |
|
||||
|--------|-------------------|
|
||||
| Технологии и библиотеки | Какой стек используем? |
|
||||
| Архитектура | Как устроены слои SLM, зависимости, публичный API? |
|
||||
| Стиль кода | Как оформлять код: отступы, кавычки, импорты, early return? |
|
||||
| Именование | Как называть файлы, переменные, компоненты, хуки? |
|
||||
| Документирование | Как писать JSDoc: что документировать, а что нет? |
|
||||
| Типизация | Как типизировать: type vs interface, any/unknown? |
|
||||
|
||||
### Applied Sections
|
||||
### Прикладные разделы
|
||||
|
||||
**How a specific area works** — rules, structure, and code examples for specific technologies and tools.
|
||||
**Как это настроить и использовать** — конфигурация, структура и примеры кода для конкретных областей.
|
||||
|
||||
| Раздел | Отвечает на вопрос |
|
||||
|--------|-------------------|
|
||||
| Настройка VS Code | Как настроить редактор для проекта? |
|
||||
| Структура проекта | Как организованы папки и файлы по SLM? |
|
||||
| Компоненты | Как устроен компонент: файлы, пропсы, clsx? |
|
||||
| Page-level компоненты | Как описывать layout, page, loading, error, not-found? |
|
||||
| Шаблоны и генерация кода | Как работают шаблоны, синтаксис и инструменты генерации? |
|
||||
| Стили | Как писать CSS: PostCSS Modules, вложенность, медиа, токены? |
|
||||
| Изображения | _(не заполнен)_ |
|
||||
| SVG-спрайты | _(не заполнен)_ |
|
||||
| Видео | _(не заполнен)_ |
|
||||
| API | _(не заполнен)_ |
|
||||
| Stores | _(не заполнен)_ |
|
||||
| Хуки | _(не заполнен)_ |
|
||||
| Шрифты | _(не заполнен)_ |
|
||||
| Локализация | _(не заполнен)_ |
|
||||
|
||||
| Section | Answers the question |
|
||||
|---------|---------------------|
|
||||
| Project Structure | How are folders and files organized by FSD? |
|
||||
| Components | How is a component structured: files, props, clsx? |
|
||||
| Page-level Components | How to define layout, page, loading, error, not-found? |
|
||||
| Templates & Code Generation | How do templates work: syntax, variables, modifiers? |
|
||||
| Styles | How to write CSS: PostCSS Modules, nesting, media, tokens? |
|
||||
| Images | _(not filled)_ |
|
||||
| SVG Sprites | _(not filled)_ |
|
||||
| Video | _(not filled)_ |
|
||||
| API | _(not filled)_ |
|
||||
| Stores | _(not filled)_ |
|
||||
| Hooks | _(not filled)_ |
|
||||
| Fonts | _(not filled)_ |
|
||||
| Localization | _(not filled)_ |
|
||||
|
||||
|
||||
|
||||
69
README_RU.md
69
README_RU.md
@@ -1,69 +0,0 @@
|
||||
# NextJS Style Guide
|
||||
|
||||
Соглашения по разработке Next.js проектов: архитектура и слои приложения, структура кода, организация модулей, стилизация, типизация и инфраструктура.
|
||||
|
||||
## Использование
|
||||
|
||||
**Для AI-агентов:**
|
||||
|
||||
- [Карта разделов](https://nextjs-style-guide.gromlab.ru/ru/llms.txt) — `llms.txt`, оглавление со ссылками на разделы.
|
||||
- [Полный текст](https://nextjs-style-guide.gromlab.ru/ru/llms-full.txt) — `llms-full.txt`, вся документация одним файлом.
|
||||
|
||||
**Для проекта:**
|
||||
|
||||
- [Архив с правилами](https://nextjs-style-guide.gromlab.ru/nextjs-style-guide-ru.zip) — `nextjs-style-guide-ru.zip`, набор Markdown-файлов для распаковки в `./ai/nextjs-style-guide/` или другую папку проекта.
|
||||
|
||||
## Структура документации
|
||||
|
||||
### Workflow
|
||||
|
||||
**Что делать и в каком порядке** — пошаговые инструкции.
|
||||
|
||||
| Раздел | Отвечает на вопрос |
|
||||
|--------|-------------------|
|
||||
| Начало работы | Что нужно знать перед началом разработки? |
|
||||
| Создание проекта | Как начать новый проект? |
|
||||
| Генерация кода | Какие модули должны генерироваться из шаблонов? |
|
||||
| Добавление страницы | Как добавить новую страницу в проект? |
|
||||
| Добавление UI-модуля | Как создать компонент, бизнес-модуль, виджет или layout? |
|
||||
| Стилизация | Как стилизовать компоненты в проекте? |
|
||||
| Получение данных | Как получать данные с сервера? |
|
||||
| Управление состоянием | Как работать с состоянием? |
|
||||
| Локализация | Как добавлять переводы и подключать локализацию? |
|
||||
|
||||
### Базовые правила
|
||||
|
||||
**Каким должен быть код** — стандарты, не привязанные к конкретной технологии.
|
||||
|
||||
| Раздел | Отвечает на вопрос |
|
||||
|--------|-------------------|
|
||||
| Технологии и библиотеки | Какой стек используем? |
|
||||
| Архитектура | Как устроены слои SLM, зависимости, публичный API? |
|
||||
| Стиль кода | Как оформлять код: отступы, кавычки, импорты, early return? |
|
||||
| Именование | Как называть файлы, переменные, компоненты, хуки? |
|
||||
| Документирование | Как писать JSDoc: что документировать, а что нет? |
|
||||
| Типизация | Как типизировать: type vs interface, any/unknown? |
|
||||
|
||||
### Прикладные разделы
|
||||
|
||||
**Как это настроить и использовать** — конфигурация, структура и примеры кода для конкретных областей.
|
||||
|
||||
| Раздел | Отвечает на вопрос |
|
||||
|--------|-------------------|
|
||||
| Настройка VS Code | Как настроить редактор для проекта? |
|
||||
| Структура проекта | Как организованы папки и файлы по SLM? |
|
||||
| Компоненты | Как устроен компонент: файлы, пропсы, clsx? |
|
||||
| Page-level компоненты | Как описывать layout, page, loading, error, not-found? |
|
||||
| Шаблоны и генерация кода | Как работают шаблоны, синтаксис и инструменты генерации? |
|
||||
| Стили | Как писать CSS: PostCSS Modules, вложенность, медиа, токены? |
|
||||
| Изображения | _(не заполнен)_ |
|
||||
| SVG-спрайты | _(не заполнен)_ |
|
||||
| Видео | _(не заполнен)_ |
|
||||
| API | _(не заполнен)_ |
|
||||
| Stores | _(не заполнен)_ |
|
||||
| Хуки | _(не заполнен)_ |
|
||||
| Шрифты | _(не заполнен)_ |
|
||||
| Локализация | _(не заполнен)_ |
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ title: Компоненты
|
||||
|
||||
Правила написания React-компонентов: файловая структура модуля, типизация пропсов, документирование и реализация. Раздел охватывает компоненты всех слоёв — от `shared/ui` до `screens`.
|
||||
|
||||
Архитектурные слои и их назначение описаны в разделе [Архитектура](/ru/basics/architecture/).
|
||||
Архитектурные слои и их назначение описаны в разделе [Архитектура](/docs/basics/architecture/).
|
||||
|
||||
|
||||
## Правила организации
|
||||
@@ -43,7 +43,7 @@ container/
|
||||
- **`type` вместо `interface`** — гибче для пропсов: поддерживает union, intersection, mapped types. Declaration merging пропсам не нужно.
|
||||
- **Без `FC`** — неявно добавляет `children`, усложняет дженерики, не даёт преимуществ перед аннотацией параметра.
|
||||
- **Типы в `types/`, а не в `.tsx`** — предотвращает циклические зависимости (компонент импортирует хук, хук импортирует тип из компонента) и разделяет ответственность: `.tsx` для рендера, `.type.ts` для данных.
|
||||
- **Без возвращаемого типа** — TypeScript выводит из JSX. Осознанное исключение из [базового правила](/ru/basics/typing).
|
||||
- **Без возвращаемого типа** — TypeScript выводит из JSX. Осознанное исключение из [базового правила](/docs/basics/typing).
|
||||
|
||||
## Реализация
|
||||
|
||||
@@ -52,7 +52,7 @@ auth/
|
||||
└── index.ts # публичный API
|
||||
```
|
||||
|
||||
Подробное описание каждого сегмента — в разделе [Сегменты](/ru/basics/architecture/reference/segments).
|
||||
Подробное описание каждого сегмента — в разделе [Сегменты](/docs/basics/architecture/reference/segments).
|
||||
|
||||
## Публичный API
|
||||
|
||||
@@ -13,7 +13,7 @@ title: Технологии и библиотеки
|
||||
- `Next.js` — для продуктовых сайтов.
|
||||
|
||||
### Архитектура
|
||||
- `SLM Design` — собственная модульная архитектура проекта. Подробнее в разделе [Архитектура](/ru/basics/architecture/).
|
||||
- `SLM Design` — собственная модульная архитектура проекта. Подробнее в разделе [Архитектура](/docs/basics/architecture/).
|
||||
|
||||
### UI компоненты
|
||||
- `Mantine UI` — базовые UI-компоненты.
|
||||
@@ -6,12 +6,12 @@
|
||||
|
||||
**Для AI-агентов:**
|
||||
|
||||
- [Карта разделов](https://nextjs-style-guide.gromlab.ru/ru/llms.txt) — `llms.txt`, оглавление со ссылками на разделы.
|
||||
- [Полный текст](https://nextjs-style-guide.gromlab.ru/ru/llms-full.txt) — `llms-full.txt`, вся документация одним файлом.
|
||||
- [llms.txt](/llms.txt) — Карта разделов, оглавление со ссылками на разделы.
|
||||
- [llms-full.txt](/llms-full.txt) — Вся документация одним файлом.
|
||||
|
||||
**Для проекта:**
|
||||
|
||||
- [Архив с правилами](https://nextjs-style-guide.gromlab.ru/nextjs-style-guide-ru.zip) — `nextjs-style-guide-ru.zip`, набор Markdown-файлов для распаковки в `./ai/nextjs-style-guide/` или другую папку проекта.
|
||||
- [nextjs-style-guide.zip](/nextjs-style-guide.zip) — Набор Markdown-файлов для распаковки в `./ai/nextjs-style-guide/` или другую папку проекта.
|
||||
|
||||
## Структура документации
|
||||
|
||||
@@ -28,4 +28,4 @@ title: Генерация кода
|
||||
- Повторяющаяся структура появляется больше одного раза.
|
||||
- Существующий шаблон не покрывает нужный тип модуля.
|
||||
|
||||
Инструменты и синтаксис шаблонов — [Шаблоны и генерация кода](/ru/applied/templates-generation).
|
||||
Инструменты и синтаксис шаблонов — [Шаблоны и генерация кода](/docs/applied/templates-generation).
|
||||
@@ -12,11 +12,11 @@ title: Добавление UI-модуля
|
||||
|
||||
## Порядок действий
|
||||
|
||||
1. [Сгенерировать](/ru/applied/templates-generation) модуль из соответствующего шаблона в целевой слой.
|
||||
1. [Сгенерировать](/docs/applied/templates-generation) модуль из соответствующего шаблона в целевой слой.
|
||||
2. Заполнить модуль логикой и стилями.
|
||||
|
||||
## Дочерние компоненты
|
||||
|
||||
Если модулю нужны внутренние подкомпоненты — [генерировать](/ru/applied/templates-generation) их из шаблона `component` в папку `ui/` внутри родительского модуля. Дочерние компоненты не экспортируются через `index.ts` родителя.
|
||||
Если модулю нужны внутренние подкомпоненты — [генерировать](/docs/applied/templates-generation) их из шаблона `component` в папку `ui/` внутри родительского модуля. Дочерние компоненты не экспортируются через `index.ts` родителя.
|
||||
|
||||
Правила написания компонентов — [Компоненты](/ru/applied/components).
|
||||
Правила написания компонентов — [Компоненты](/docs/applied/components).
|
||||
@@ -12,7 +12,7 @@ title: Добавление страницы
|
||||
|
||||
## Порядок действий
|
||||
|
||||
1. [Сгенерировать](/ru/applied/templates-generation) экран из шаблона `screen` в папку `src/screens/`.
|
||||
1. [Сгенерировать](/docs/applied/templates-generation) экран из шаблона `screen` в папку `src/screens/`.
|
||||
|
||||
2. Заполнить экран логикой и стилями.
|
||||
|
||||
@@ -20,8 +20,8 @@ title: Добавление страницы
|
||||
|
||||
## Правила
|
||||
|
||||
- Ручное создание файловой структуры экрана запрещено — только [генерация](/ru/applied/templates-generation) из шаблона.
|
||||
- Ручное создание файловой структуры экрана запрещено — только [генерация](/docs/applied/templates-generation) из шаблона.
|
||||
- Логика, стили и зависимости размещаются в экране, не в `page.tsx`.
|
||||
- Каждая страница содержит `metadata` с `title` и `description`.
|
||||
|
||||
Примеры `page.tsx` и `metadata` — [Page-level компоненты](/ru/applied/page-level).
|
||||
Примеры `page.tsx` и `metadata` — [Page-level компоненты](/docs/applied/page-level).
|
||||
@@ -10,7 +10,7 @@ title: Начало работы
|
||||
|
||||
**Next.js** (App Router), **Mantine**, **Zustand**, **SLM Design**.
|
||||
|
||||
Подробнее — [Технологии и библиотеки](/ru/basics/tech-stack).
|
||||
Подробнее — [Технологии и библиотеки](/docs/basics/tech-stack).
|
||||
|
||||
## Ключевые особенности
|
||||
|
||||
@@ -19,4 +19,4 @@ title: Начало работы
|
||||
|
||||
## Настройка окружения
|
||||
|
||||
Открыть проект в VS Code и установить рекомендуемые расширения — редактор предложит это автоматически. Подробнее — [Настройка VS Code](/ru/applied/vscode).
|
||||
Открыть проект в VS Code и установить рекомендуемые расширения — редактор предложит это автоматически. Подробнее — [Настройка VS Code](/docs/applied/vscode).
|
||||
@@ -20,4 +20,4 @@ title: Стилизация
|
||||
- **Магические значения** — произвольные цвета, отступы и скругления запрещены, использовать токены.
|
||||
- **Глобальные стили** вне `app/styles/` запрещены.
|
||||
|
||||
Правила написания CSS, вложенность, медиа-запросы и токены — [Стили](/ru/applied/styles).
|
||||
Правила написания CSS, вложенность, медиа-запросы и токены — [Стили](/docs/applied/styles).
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
title: API
|
||||
---
|
||||
|
||||
# API
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Components
|
||||
---
|
||||
|
||||
# Components
|
||||
|
||||
Rules for creating UI components across all FSD layers.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
title: Fonts
|
||||
---
|
||||
|
||||
# Fonts
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
title: Hooks
|
||||
---
|
||||
|
||||
# Hooks
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
title: Images
|
||||
---
|
||||
|
||||
# Images
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
title: Localization
|
||||
---
|
||||
|
||||
# Localization
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Page-level Components
|
||||
---
|
||||
|
||||
# Page-level Components
|
||||
|
||||
Next.js App Router special files used by the framework by convention: `layout.tsx`, `page.tsx`, `loading.tsx`, `error.tsx`, `not-found.tsx`, `template.tsx`.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Project Structure
|
||||
---
|
||||
|
||||
# Project Structure
|
||||
|
||||
Base project structure and principles of module organization at folder and file level.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
title: Stores
|
||||
---
|
||||
|
||||
# Stores
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Styles
|
||||
---
|
||||
|
||||
# Styles
|
||||
|
||||
CSS writing rules: PostCSS Modules, nesting, media queries, variables, formatting.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
title: SVG Sprites
|
||||
---
|
||||
|
||||
# SVG Sprites
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Templates & Code Generation
|
||||
---
|
||||
|
||||
# Templates & Code Generation
|
||||
|
||||
Template tools, syntax, and examples for code generation.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
title: Video
|
||||
---
|
||||
|
||||
# Video
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Architecture
|
||||
---
|
||||
|
||||
# Architecture
|
||||
|
||||
Architecture based on FSD (Feature-Sliced Design) and strict module boundaries.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Code Style
|
||||
---
|
||||
|
||||
# Code Style
|
||||
|
||||
Unified code formatting rules: indentation, line breaks, quotes, import order, and readability.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Documentation
|
||||
---
|
||||
|
||||
# Documentation
|
||||
|
||||
Documentation should help understand the purpose of an entity, not duplicate its types or obvious details.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Naming
|
||||
---
|
||||
|
||||
# Naming
|
||||
|
||||
Naming should be predictable, concise, and reflect the meaning of the entity.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Tech Stack
|
||||
---
|
||||
|
||||
# Tech Stack
|
||||
|
||||
Base technology stack and libraries used in projects.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Typing
|
||||
---
|
||||
|
||||
# Typing
|
||||
|
||||
Typing is required for all public interfaces, functions, and components.
|
||||
@@ -1,66 +0,0 @@
|
||||
# NextJS Style Guide
|
||||
|
||||
Conventions for Next.js project development: application architecture and layers, code structure, module organization, styling, typing, and infrastructure.
|
||||
|
||||
## Usage
|
||||
|
||||
**For AI agents:**
|
||||
|
||||
- [Sections map](https://nextjs-style-guide.gromlab.ru/en/llms.txt) — `llms.txt`, table of contents with links to sections.
|
||||
- [Full text](https://nextjs-style-guide.gromlab.ru/en/llms-full.txt) — `llms-full.txt`, the entire documentation in a single file.
|
||||
|
||||
**For projects:**
|
||||
|
||||
- [Rules archive](https://nextjs-style-guide.gromlab.ru/nextjs-style-guide-en.zip) — `nextjs-style-guide-en.zip`, a set of Markdown files to unpack into `./ai/nextjs-style-guide/` or another project folder.
|
||||
|
||||
## Documentation Structure
|
||||
|
||||
### Processes
|
||||
|
||||
**What to do** in a specific situation — step-by-step instructions.
|
||||
|
||||
| Section | Answers the question |
|
||||
|---------|---------------------|
|
||||
| Getting Started | What tools to install before starting development? |
|
||||
| Creating an App | How to create a new project, where to get a template? |
|
||||
| Creating Pages | How to add a page: routing and screen? |
|
||||
| Creating Components | How to generate components using templates? |
|
||||
| Styling | What to use: Mantine, tokens, or PostCSS? |
|
||||
| Data Fetching | How to fetch data: SWR, codegen, sockets? |
|
||||
| State Management | When and how to create a store (Zustand)? |
|
||||
| Localization | How to add translations and work with i18next? |
|
||||
|
||||
### Basic Rules
|
||||
|
||||
**What the code should look like** — standards not tied to a specific technology.
|
||||
|
||||
| Section | Answers the question |
|
||||
|---------|---------------------|
|
||||
| Tech Stack | What stack do we use? |
|
||||
| Architecture | How are SLM layers, dependencies, and public API structured? |
|
||||
| Code Style | How to format code: indentation, quotes, imports, early return? |
|
||||
| Naming | How to name files, variables, components, hooks? |
|
||||
| Documentation | How to write JSDoc: what to document and what not? |
|
||||
| Typing | How to type: type vs interface, any/unknown? |
|
||||
|
||||
### Applied Sections
|
||||
|
||||
**How a specific area works** — rules, structure, and code examples for specific technologies and tools.
|
||||
|
||||
| Section | Answers the question |
|
||||
|---------|---------------------|
|
||||
| Project Structure | How are folders and files organized by FSD? |
|
||||
| Components | How is a component structured: files, props, clsx? |
|
||||
| Page-level Components | How to define layout, page, loading, error, not-found? |
|
||||
| Templates & Code Generation | How do templates work: syntax, variables, modifiers? |
|
||||
| Styles | How to write CSS: PostCSS Modules, nesting, media, tokens? |
|
||||
| Images | _(not filled)_ |
|
||||
| SVG Sprites | _(not filled)_ |
|
||||
| Video | _(not filled)_ |
|
||||
| API | _(not filled)_ |
|
||||
| Stores | _(not filled)_ |
|
||||
| Hooks | _(not filled)_ |
|
||||
| Fonts | _(not filled)_ |
|
||||
| Localization | _(not filled)_ |
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Creating an App
|
||||
---
|
||||
|
||||
# Creating an App
|
||||
|
||||
How to create a new application: choosing a project template and initialization.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Creating Components
|
||||
---
|
||||
|
||||
# Creating Components
|
||||
|
||||
Generating components using templates, working with child components.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Creating Pages
|
||||
---
|
||||
|
||||
# Creating Pages
|
||||
|
||||
Page creation pattern: routing (page.tsx) and screen.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Data Fetching
|
||||
---
|
||||
|
||||
# Data Fetching
|
||||
|
||||
How to fetch data: SWR, API client codegen, sockets.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Getting Started
|
||||
---
|
||||
|
||||
# Getting Started
|
||||
|
||||
Setting up the environment and installing tools before starting development.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Localization
|
||||
---
|
||||
|
||||
# Localization
|
||||
|
||||
How to add translations and work with i18next.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: State Management
|
||||
---
|
||||
|
||||
# State Management
|
||||
|
||||
When and how to create a store (Zustand), what to store locally vs globally.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Styling
|
||||
---
|
||||
|
||||
# Styling
|
||||
|
||||
Styling tools priority and rules for their application.
|
||||
185
docs/index.md
185
docs/index.md
@@ -3,102 +3,21 @@ layout: false
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
const STORAGE_KEY = 'nsg-landing-lang'
|
||||
const THEME_KEY = 'vitepress-theme-appearance'
|
||||
|
||||
// __BUILD_VERSION__ подставляется Vite-define из ENV `BUILD_VERSION`
|
||||
// (см. .vitepress/config.ts). В dev и build всегда определена.
|
||||
const buildVersion = __BUILD_VERSION__
|
||||
|
||||
const dict = {
|
||||
ru: {
|
||||
tagline: 'Соглашения по разработке Next.js проектов: архитектура и слои приложения, структура кода, организация модулей, стилизация, типизация и инфраструктура.',
|
||||
langLabel: 'Язык',
|
||||
themeLabel: 'Тема',
|
||||
repoLabel: 'Репозиторий',
|
||||
themes: { auto: 'Авто', light: 'Светлая', dark: 'Тёмная' },
|
||||
cards: {
|
||||
docs: {
|
||||
title: 'Документация',
|
||||
desc: 'Все разделы: процессы разработки, базовые правила, прикладные руководства.',
|
||||
href: './ru/',
|
||||
cta: 'Открыть',
|
||||
},
|
||||
ai: {
|
||||
title: 'Ассистенту',
|
||||
desc: 'Карта документации в формате llms.txt для AI-агентов.',
|
||||
buttons: [
|
||||
{ label: 'llms.txt', href: './ru/llms.txt' },
|
||||
{ label: 'llms-full.txt', href: './ru/llms-full.txt' },
|
||||
],
|
||||
},
|
||||
zip: {
|
||||
title: 'Скачать правила',
|
||||
desc: 'Архив всех Markdown-файлов одним ZIP.',
|
||||
href: './nextjs-style-guide-ru.zip',
|
||||
cta: 'Скачать',
|
||||
},
|
||||
},
|
||||
},
|
||||
en: {
|
||||
tagline: 'Conventions for Next.js project development: application architecture and layers, code structure, module organization, styling, typing, and infrastructure.',
|
||||
langLabel: 'Language',
|
||||
themeLabel: 'Theme',
|
||||
repoLabel: 'Repository',
|
||||
themes: { auto: 'Auto', light: 'Light', dark: 'Dark' },
|
||||
cards: {
|
||||
docs: {
|
||||
title: 'Documentation',
|
||||
desc: 'All sections: development processes, basic rules, applied guides.',
|
||||
href: '#',
|
||||
cta: 'Open',
|
||||
badge: 'in development',
|
||||
},
|
||||
ai: {
|
||||
title: 'For Assistant',
|
||||
desc: 'Documentation map in llms.txt format for AI agents.',
|
||||
badge: 'in development',
|
||||
buttons: [
|
||||
{ label: 'llms.txt', href: '#' },
|
||||
{ label: 'llms-full.txt', href: '#' },
|
||||
],
|
||||
},
|
||||
zip: {
|
||||
title: 'Download rules',
|
||||
desc: 'Archive of all Markdown files and llms.txt in a single ZIP.',
|
||||
href: '#',
|
||||
cta: 'Download',
|
||||
badge: 'soon',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const lang = ref('ru')
|
||||
const theme = ref('auto')
|
||||
|
||||
onMounted(() => {
|
||||
const savedLang = localStorage.getItem(STORAGE_KEY)
|
||||
if (savedLang === 'ru' || savedLang === 'en') {
|
||||
lang.value = savedLang
|
||||
} else {
|
||||
const nav = (navigator.language || 'ru').toLowerCase()
|
||||
lang.value = nav.startsWith('ru') ? 'ru' : 'en'
|
||||
}
|
||||
|
||||
const savedTheme = localStorage.getItem(THEME_KEY)
|
||||
theme.value = savedTheme === 'dark' || savedTheme === 'light' ? savedTheme : 'auto'
|
||||
})
|
||||
|
||||
const t = computed(() => dict[lang.value])
|
||||
|
||||
function setLang(value) {
|
||||
lang.value = value
|
||||
localStorage.setItem(STORAGE_KEY, value)
|
||||
}
|
||||
|
||||
function setTheme(value) {
|
||||
theme.value = value
|
||||
if (value === 'auto') {
|
||||
@@ -124,7 +43,7 @@ function toggleTheme(value) {
|
||||
<section class="landing__hero">
|
||||
<h1 class="landing__title">NextJS Style Guide</h1>
|
||||
<ClientOnly>
|
||||
<p class="landing__tagline">{{ t.tagline }}</p>
|
||||
<p class="landing__tagline">Соглашения по разработке Next.js проектов: архитектура и слои приложения, структура кода, организация модулей, стилизация, типизация и инфраструктура.</p>
|
||||
<div class="landing__controls">
|
||||
<a
|
||||
class="landing__repo"
|
||||
@@ -135,31 +54,15 @@ function toggleTheme(value) {
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path d="M12 .3a12 12 0 0 0-3.8 23.38c.6.12.83-.26.83-.57L9 21.07c-3.34.72-4.04-1.61-4.04-1.61-.55-1.39-1.34-1.76-1.34-1.76-1.08-.74.09-.73.09-.73 1.2.09 1.83 1.24 1.83 1.24 1.08 1.83 2.81 1.3 3.5 1 .1-.78.42-1.31.76-1.61-2.67-.3-5.47-1.33-5.47-5.93 0-1.31.47-2.38 1.24-3.22-.14-.3-.54-1.52.1-3.18 0 0 1-.32 3.3 1.23a11.5 11.5 0 0 1 6 0c2.28-1.55 3.29-1.23 3.29-1.23.64 1.66.24 2.88.12 3.18a4.65 4.65 0 0 1 1.23 3.22c0 4.61-2.8 5.63-5.48 5.92.42.36.81 1.1.81 2.22l-.01 3.29c0 .31.2.69.82.57A12 12 0 0 0 12 .3Z"/>
|
||||
</svg>
|
||||
<span>{{ t.repoLabel }}</span>
|
||||
<span>Репозиторий</span>
|
||||
</a>
|
||||
<div class="seg" role="group" :aria-label="t.langLabel">
|
||||
<button
|
||||
type="button"
|
||||
class="seg__btn"
|
||||
:class="{ 'seg__btn--active': lang === 'ru' }"
|
||||
:aria-pressed="lang === 'ru'"
|
||||
@click="setLang('ru')"
|
||||
>Русский</button>
|
||||
<button
|
||||
type="button"
|
||||
class="seg__btn"
|
||||
:class="{ 'seg__btn--active': lang === 'en' }"
|
||||
:aria-pressed="lang === 'en'"
|
||||
@click="setLang('en')"
|
||||
>English</button>
|
||||
</div>
|
||||
<div class="seg seg--icons" role="group" :aria-label="t.themeLabel">
|
||||
<div class="seg seg--icons" role="group" aria-label="Тема">
|
||||
<button
|
||||
type="button"
|
||||
class="seg__btn"
|
||||
:class="{ 'seg__btn--active': theme === 'light' }"
|
||||
:aria-pressed="theme === 'light'"
|
||||
:title="t.themes.light"
|
||||
title="Светлая"
|
||||
@click="toggleTheme('light')"
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/></svg>
|
||||
@@ -169,7 +72,7 @@ function toggleTheme(value) {
|
||||
class="seg__btn"
|
||||
:class="{ 'seg__btn--active': theme === 'dark' }"
|
||||
:aria-pressed="theme === 'dark'"
|
||||
:title="t.themes.dark"
|
||||
title="Тёмная"
|
||||
@click="toggleTheme('dark')"
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
|
||||
@@ -179,48 +82,26 @@ function toggleTheme(value) {
|
||||
</ClientOnly>
|
||||
</section>
|
||||
|
||||
<ClientOnly>
|
||||
<section class="landing__cards">
|
||||
<template v-for="key in ['docs', 'ai', 'zip']" :key="key">
|
||||
<div
|
||||
v-if="t.cards[key].buttons"
|
||||
class="landing__card landing__card--multi"
|
||||
:class="{ 'landing__card--soon': t.cards[key].badge }"
|
||||
:aria-disabled="t.cards[key].badge ? 'true' : null"
|
||||
>
|
||||
<h3>
|
||||
{{ t.cards[key].title }}
|
||||
<span v-if="t.cards[key].badge" class="landing__badge">{{ t.cards[key].badge }}</span>
|
||||
</h3>
|
||||
<p>{{ t.cards[key].desc }}</p>
|
||||
<div class="landing__buttons">
|
||||
<a
|
||||
v-for="btn in t.cards[key].buttons"
|
||||
:key="btn.label"
|
||||
class="landing__button"
|
||||
:href="btn.href"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>{{ btn.label }}</a>
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
v-else
|
||||
class="landing__card"
|
||||
:class="{ 'landing__card--soon': t.cards[key].badge }"
|
||||
:href="t.cards[key].href"
|
||||
:aria-disabled="t.cards[key].badge ? 'true' : null"
|
||||
>
|
||||
<h3>
|
||||
{{ t.cards[key].title }}
|
||||
<span v-if="t.cards[key].badge" class="landing__badge">{{ t.cards[key].badge }}</span>
|
||||
</h3>
|
||||
<p>{{ t.cards[key].desc }}</p>
|
||||
<span class="landing__cta">{{ t.cards[key].cta }} →</span>
|
||||
<a class="landing__card" href="./docs/">
|
||||
<h3>Документация</h3>
|
||||
<p>Все разделы: процессы разработки, базовые правила, прикладные руководства.</p>
|
||||
<span class="landing__cta">Открыть →</span>
|
||||
</a>
|
||||
<div class="landing__card landing__card--multi">
|
||||
<h3>Ассистенту</h3>
|
||||
<p>Карта документации в формате llms.txt для AI-агентов.</p>
|
||||
<div class="landing__buttons">
|
||||
<a class="landing__button" href="./llms.txt" target="_blank" rel="noopener">llms.txt</a>
|
||||
<a class="landing__button" href="./llms-full.txt" target="_blank" rel="noopener">llms-full.txt</a>
|
||||
</div>
|
||||
</div>
|
||||
<a class="landing__card" href="./nextjs-style-guide.zip">
|
||||
<h3>Скачать правила</h3>
|
||||
<p>Архив всех Markdown-файлов одним ZIP.</p>
|
||||
<span class="landing__cta">Скачать →</span>
|
||||
</a>
|
||||
</template>
|
||||
</section>
|
||||
</ClientOnly>
|
||||
|
||||
<p class="landing__version">v{{ buildVersion }}</p>
|
||||
</div>
|
||||
@@ -374,12 +255,6 @@ function toggleTheme(value) {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.landing__card--soon {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.landing__card h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
@@ -436,20 +311,6 @@ function toggleTheme(value) {
|
||||
font-family: var(--vp-font-family-mono, monospace);
|
||||
}
|
||||
|
||||
.landing__badge {
|
||||
display: inline-block;
|
||||
margin-left: 8px;
|
||||
padding: 2px 8px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
border-radius: 999px;
|
||||
background: var(--vp-c-bg-mute);
|
||||
color: var(--vp-c-text-3);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.landing {
|
||||
padding: 48px 20px 56px;
|
||||
|
||||
281
generate-llms.ts
281
generate-llms.ts
@@ -11,7 +11,8 @@ const BUILD_DATE = new Date().toISOString();
|
||||
/** Корневая папка для генерируемой статики (попадает в build dist). */
|
||||
const PUBLIC_DIR = 'docs/public';
|
||||
|
||||
type Lang = 'ru' | 'en';
|
||||
/** Префикс URL документации. Соответствует структуре `docs/docs/...`. */
|
||||
const DOC_PREFIX = '/docs/';
|
||||
|
||||
interface SidebarItem {
|
||||
text: string;
|
||||
@@ -76,13 +77,13 @@ const firstParagraphAfterH1 = (body: string): string | null => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Преобразовать sidebar `link` в относительный путь файла внутри
|
||||
* `docs/{lang}/`. Sidebar links содержат полный префикс локали
|
||||
* (`/ru/...`, `/en/...`) — отрезаем его.
|
||||
* Преобразовать sidebar `link` (например `/docs/foo`) в относительный
|
||||
* путь файла внутри `docs/docs/`. Префикс `/docs/` отрезается.
|
||||
*/
|
||||
const linkToRel = (link: string, lang: Lang): string => {
|
||||
const prefix = `/${lang}/`;
|
||||
let rel = link.startsWith(prefix) ? link.slice(prefix.length) : link.replace(/^\//, '');
|
||||
const linkToRel = (link: string): string => {
|
||||
let rel = link.startsWith(DOC_PREFIX)
|
||||
? link.slice(DOC_PREFIX.length)
|
||||
: link.replace(/^\//, '');
|
||||
if (rel === '' || rel.endsWith('/')) {
|
||||
rel += 'index.md';
|
||||
} else {
|
||||
@@ -91,15 +92,12 @@ const linkToRel = (link: string, lang: Lang): string => {
|
||||
return rel;
|
||||
};
|
||||
|
||||
const linkToFilePath = (link: string, lang: Lang): string =>
|
||||
path.join('docs', lang, linkToRel(link, lang));
|
||||
const linkToFilePath = (link: string): string =>
|
||||
path.join('docs/docs', linkToRel(link));
|
||||
|
||||
/**
|
||||
* Абсолютный путь от корня сайта к `.md`-копии страницы.
|
||||
* После build файлы лежат в `dist/{lang}/...md` (через `docs/public/`).
|
||||
*/
|
||||
const linkToSiteUrl = (link: string, lang: Lang): string =>
|
||||
`/${lang}/${linkToRel(link, lang)}`;
|
||||
/** Абсолютный URL `.md`-копии страницы на сайте. */
|
||||
const linkToSiteUrl = (link: string): string =>
|
||||
`${DOC_PREFIX}${linkToRel(link)}`;
|
||||
|
||||
/**
|
||||
* Развернуть sidebar в плоский список с сохранением группы и
|
||||
@@ -153,34 +151,20 @@ const groupBySection = (entries: Entry[]): Map<string, Entry[]> => {
|
||||
return map;
|
||||
};
|
||||
|
||||
const buildLlms = (lang: Lang): void => {
|
||||
const localeKey = lang;
|
||||
// VitePress-конфиг типизирован как `UserConfig`, но обращаемся к
|
||||
// фактически переданным значениям — сужаем тип через any.
|
||||
const cfg = config as unknown as {
|
||||
interface SiteConfig {
|
||||
title: string;
|
||||
description: string;
|
||||
locales: Record<
|
||||
string,
|
||||
{
|
||||
description?: string;
|
||||
themeConfig: { sidebar: SidebarItem[] };
|
||||
llmsBlockquote?: string;
|
||||
llmsContext?: string;
|
||||
themeConfig?: { sidebar?: SidebarItem[] };
|
||||
}
|
||||
>;
|
||||
};
|
||||
}
|
||||
|
||||
const locale = cfg.locales[localeKey];
|
||||
const sidebar = locale?.themeConfig?.sidebar;
|
||||
if (!sidebar) {
|
||||
console.warn(`[${lang}] sidebar не найден в config`);
|
||||
return;
|
||||
}
|
||||
// Для blockquote предпочитаем расширенный llms-текст; короткий
|
||||
// description — fallback и используется для HTML meta-тега VitePress.
|
||||
const blockquote = locale.llmsBlockquote ?? locale.description ?? cfg.description;
|
||||
const context = locale.llmsContext;
|
||||
const cfg = config as unknown as SiteConfig;
|
||||
|
||||
const buildLlms = (): void => {
|
||||
const sidebar = cfg.themeConfig.sidebar;
|
||||
const blockquote = cfg.llmsBlockquote ?? cfg.description;
|
||||
const context = cfg.llmsContext;
|
||||
|
||||
const entries = flattenSidebar(sidebar);
|
||||
const grouped = groupBySection(entries);
|
||||
@@ -200,19 +184,16 @@ const buildLlms = (lang: Lang): void => {
|
||||
lines.push('');
|
||||
|
||||
for (const entry of items) {
|
||||
const filePath = linkToFilePath(entry.link, lang);
|
||||
const url = linkToSiteUrl(entry.link, lang);
|
||||
const filePath = linkToFilePath(entry.link);
|
||||
const url = linkToSiteUrl(entry.link);
|
||||
|
||||
// Текст ссылки берём из sidebar — он специально написан для навигации
|
||||
// и точнее отражает иерархию (например "Обзор" внутри группы "Архитектура").
|
||||
let description: string | null = null;
|
||||
|
||||
if (fs.existsSync(filePath)) {
|
||||
const raw = fs.readFileSync(filePath, 'utf8');
|
||||
const { data, body } = parseFrontmatter(raw);
|
||||
description = data.description || firstParagraphAfterH1(body);
|
||||
} else {
|
||||
console.warn(`[${lang}] файл не найден: ${filePath}`);
|
||||
console.warn(`файл не найден: ${filePath}`);
|
||||
}
|
||||
|
||||
const display = entry.prefix
|
||||
@@ -225,40 +206,6 @@ const buildLlms = (lang: Lang): void => {
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
const outDir = path.join(PUBLIC_DIR, lang);
|
||||
fs.mkdirSync(outDir, { recursive: true });
|
||||
const outFile = path.join(outDir, 'llms.txt');
|
||||
fs.writeFileSync(outFile, lines.join('\n'), 'utf8');
|
||||
console.log(`${outFile} создан`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Корневой `/llms.txt` — роутер. По стандарту llmstxt.org это
|
||||
* единственный файл в корне сайта; для двуязычного проекта он
|
||||
* указывает LLM на локализованные карты документации.
|
||||
*/
|
||||
const buildRootIndex = (): void => {
|
||||
const cfg = config as unknown as {
|
||||
title: string;
|
||||
description: string;
|
||||
locales: Record<string, { description?: string }>;
|
||||
};
|
||||
|
||||
const ruDesc = cfg.locales.ru?.description ?? cfg.description;
|
||||
const enDesc = cfg.locales.en?.description ?? cfg.description;
|
||||
|
||||
const lines: string[] = [
|
||||
`# ${cfg.title}`,
|
||||
'',
|
||||
`> ${enDesc}.`,
|
||||
'',
|
||||
'## Documentation',
|
||||
'',
|
||||
`- [Русская версия (Russian)](/ru/llms.txt): ${ruDesc}.`,
|
||||
'- English version: in development',
|
||||
'',
|
||||
];
|
||||
|
||||
fs.mkdirSync(PUBLIC_DIR, { recursive: true });
|
||||
const outFile = path.join(PUBLIC_DIR, 'llms.txt');
|
||||
fs.writeFileSync(outFile, lines.join('\n'), 'utf8');
|
||||
@@ -288,26 +235,26 @@ const copyDirSync = (
|
||||
};
|
||||
|
||||
/**
|
||||
* Скопировать все `.md`-файлы локали в `docs/public/{lang}/`,
|
||||
* чтобы они попали в build `dist/` и были доступны по URL `/lang/path.md`.
|
||||
* Скопировать все `.md`-файлы документации в `docs/public/docs/`,
|
||||
* чтобы они попали в build `dist/` и были доступны по URL `/docs/path.md`.
|
||||
*/
|
||||
const copyMdFiles = (lang: Lang): void => {
|
||||
const srcDir = path.join('docs', lang);
|
||||
const destDir = path.join(PUBLIC_DIR, lang);
|
||||
const copyMdFiles = (): void => {
|
||||
const srcDir = 'docs/docs';
|
||||
const destDir = path.join(PUBLIC_DIR, 'docs');
|
||||
if (!fs.existsSync(srcDir)) return;
|
||||
|
||||
const copied = copyDirSync(srcDir, destDir, (name) => name.endsWith('.md'));
|
||||
console.log(`[${lang}] скопировано ${copied} .md-файлов в ${destDir}`);
|
||||
console.log(`скопировано ${copied} .md-файлов в ${destDir}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Преобразовать sidebar `link` в относительный путь файла внутри архива
|
||||
* (от корня папки `nextjs-style-guide/`). Это путь, по которому файл лежит
|
||||
* в распакованной папке, без расширения добавляется `.md`.
|
||||
* (от корня папки `nextjs-style-guide/`).
|
||||
*/
|
||||
const linkToArchiveRel = (link: string, lang: Lang): string => {
|
||||
const prefix = `/${lang}/`;
|
||||
let rel = link.startsWith(prefix) ? link.slice(prefix.length) : link.replace(/^\//, '');
|
||||
const linkToArchiveRel = (link: string): string => {
|
||||
let rel = link.startsWith(DOC_PREFIX)
|
||||
? link.slice(DOC_PREFIX.length)
|
||||
: link.replace(/^\//, '');
|
||||
if (rel === '' || rel.endsWith('/')) {
|
||||
rel += 'index.md';
|
||||
} else {
|
||||
@@ -317,12 +264,12 @@ const linkToArchiveRel = (link: string, lang: Lang): string => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Заменить во всех `.md` архива ссылки `[text](/ru/foo)` на относительные
|
||||
* Заменить во всех `.md` архива ссылки `[text](/docs/foo)` на относительные
|
||||
* пути от расположения файла. Без этого внутренние ссылки в распакованной
|
||||
* папке не работают.
|
||||
*/
|
||||
const transformLinksInDir = (rootDir: string, lang: Lang): void => {
|
||||
const linkRe = /\]\(\/([a-z]{2})\/([^)\s#]*)(#[^)]*)?\)/g;
|
||||
const transformLinksInDir = (rootDir: string): void => {
|
||||
const linkRe = /\]\(\/docs\/([^)\s#]*)(#[^)]*)?\)/g;
|
||||
|
||||
const walk = (dir: string): void => {
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
@@ -336,12 +283,8 @@ const transformLinksInDir = (rootDir: string, lang: Lang): void => {
|
||||
const content = fs.readFileSync(full, 'utf8');
|
||||
const fileDir = path.dirname(full);
|
||||
|
||||
const updated = content.replace(linkRe, (match, urlLang, route, hash = '') => {
|
||||
// Ссылки на другую локаль не трогаем — её в архиве нет.
|
||||
if (urlLang !== lang) return match;
|
||||
|
||||
const fakeLink = `/${urlLang}/${route}${route.endsWith('/') ? '' : ''}`;
|
||||
const targetRel = linkToArchiveRel(fakeLink, lang);
|
||||
const updated = content.replace(linkRe, (_match, route, hash = '') => {
|
||||
const targetRel = linkToArchiveRel(`${DOC_PREFIX}${route}`);
|
||||
const targetAbs = path.join(rootDir, targetRel);
|
||||
let rel = path.relative(fileDir, targetAbs);
|
||||
if (!rel.startsWith('.')) rel = './' + rel;
|
||||
@@ -362,29 +305,14 @@ const transformLinksInDir = (rootDir: string, lang: Lang): void => {
|
||||
* с относительными ссылками, описаниями из frontmatter/первого абзаца
|
||||
* и метаинфо сборки.
|
||||
*/
|
||||
const buildArchiveReadme = (lang: Lang, rootDir: string): void => {
|
||||
const cfg = config as unknown as {
|
||||
title: string;
|
||||
locales: Record<
|
||||
string,
|
||||
{
|
||||
description?: string;
|
||||
llmsBlockquote?: string;
|
||||
llmsContext?: string;
|
||||
themeConfig?: { sidebar?: SidebarItem[] };
|
||||
}
|
||||
>;
|
||||
};
|
||||
const buildArchiveReadme = (rootDir: string): void => {
|
||||
const sidebar = cfg.themeConfig.sidebar;
|
||||
const blockquote = cfg.llmsBlockquote ?? cfg.description ?? '';
|
||||
const context = cfg.llmsContext;
|
||||
|
||||
const locale = cfg.locales[lang];
|
||||
const sidebar = locale?.themeConfig?.sidebar;
|
||||
if (!sidebar) return;
|
||||
|
||||
const blockquote = locale.llmsBlockquote ?? locale.description ?? '';
|
||||
const context = locale.llmsContext;
|
||||
const entries = flattenSidebar(sidebar).filter(
|
||||
// «Главная» из sidebar — это страница локали для веба, в архиве не нужна.
|
||||
(e) => !(e.section === 'Главная' || e.section === 'Home'),
|
||||
// «Главная» из sidebar — это страница раздела для веба, в архиве не нужна.
|
||||
(e) => e.section !== 'Главная',
|
||||
);
|
||||
const grouped = groupBySection(entries);
|
||||
|
||||
@@ -400,16 +328,15 @@ const buildArchiveReadme = (lang: Lang, rootDir: string): void => {
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
const heading = lang === 'ru' ? 'Содержание' : 'Contents';
|
||||
lines.push(`## ${heading}`);
|
||||
lines.push('## Содержание');
|
||||
lines.push('');
|
||||
|
||||
for (const [section, items] of grouped) {
|
||||
lines.push(`### ${section}`);
|
||||
lines.push('');
|
||||
for (const entry of items) {
|
||||
const targetRel = './' + linkToArchiveRel(entry.link, lang);
|
||||
const filePath = path.join(rootDir, linkToArchiveRel(entry.link, lang));
|
||||
const targetRel = './' + linkToArchiveRel(entry.link);
|
||||
const filePath = path.join(rootDir, linkToArchiveRel(entry.link));
|
||||
|
||||
let description: string | null = null;
|
||||
if (fs.existsSync(filePath)) {
|
||||
@@ -436,33 +363,30 @@ const buildArchiveReadme = (lang: Lang, rootDir: string): void => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Собрать `nextjs-style-guide-{lang}.zip`. Внутри архива — единая папка
|
||||
* `nextjs-style-guide/` с `.md`-файлами локали, README-точкой входа,
|
||||
* `llms-full.txt` и `VERSION`. Внутренние ссылки в `.md` преобразуются
|
||||
* в относительные.
|
||||
*
|
||||
* Веб-`index.md` локали из архива удаляется — его роль выполняет README.md.
|
||||
* Собрать `nextjs-style-guide.zip`. Внутри — папка `nextjs-style-guide/`
|
||||
* с `.md`-файлами, README, `llms-full.txt` и `VERSION`. Внутренние ссылки
|
||||
* преобразуются в относительные.
|
||||
*/
|
||||
const buildZip = (lang: Lang): void => {
|
||||
const buildZip = (): void => {
|
||||
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'nsg-'));
|
||||
const stage = path.join(tmpRoot, 'nextjs-style-guide');
|
||||
fs.mkdirSync(stage, { recursive: true });
|
||||
|
||||
// 1. Копируем все .md локали в staging.
|
||||
copyDirSync(path.join('docs', lang), stage, (name) => name.endsWith('.md'));
|
||||
// 1. Копируем все .md в staging.
|
||||
copyDirSync('docs/docs', stage, (name) => name.endsWith('.md'));
|
||||
|
||||
// 2. Удаляем веб-index.md локали — в архиве он избыточен.
|
||||
// 2. Удаляем веб-index.md — в архиве его роль выполняет README.md.
|
||||
const indexPath = path.join(stage, 'index.md');
|
||||
if (fs.existsSync(indexPath)) fs.unlinkSync(indexPath);
|
||||
|
||||
// 3. Преобразуем абсолютные ссылки `/ru/...` в относительные.
|
||||
transformLinksInDir(stage, lang);
|
||||
// 3. Преобразуем абсолютные ссылки `/docs/...` в относительные.
|
||||
transformLinksInDir(stage);
|
||||
|
||||
// 4. Генерируем точку входа README.md.
|
||||
buildArchiveReadme(lang, stage);
|
||||
buildArchiveReadme(stage);
|
||||
|
||||
// 5. Кладём llms-full.txt — удобно для одноразового чтения LLM.
|
||||
const llmsFullSrc = path.join(PUBLIC_DIR, lang, 'llms-full.txt');
|
||||
const llmsFullSrc = path.join(PUBLIC_DIR, 'llms-full.txt');
|
||||
if (fs.existsSync(llmsFullSrc)) {
|
||||
fs.copyFileSync(llmsFullSrc, path.join(stage, 'llms-full.txt'));
|
||||
}
|
||||
@@ -473,10 +397,7 @@ const buildZip = (lang: Lang): void => {
|
||||
`${VERSION}\n${BUILD_DATE}\n`,
|
||||
);
|
||||
|
||||
const outFile = path.resolve(
|
||||
PUBLIC_DIR,
|
||||
`nextjs-style-guide-${lang}.zip`,
|
||||
);
|
||||
const outFile = path.resolve(PUBLIC_DIR, 'nextjs-style-guide.zip');
|
||||
fs.rmSync(outFile, { force: true });
|
||||
|
||||
execFileSync('zip', ['-rq', outFile, 'nextjs-style-guide'], {
|
||||
@@ -509,42 +430,26 @@ const shiftHeadings = (content: string): string => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Собрать `llms-full.txt` — все страницы локали в одном файле.
|
||||
* Собрать `llms-full.txt` — все страницы в одном файле.
|
||||
* Порядок страниц повторяет порядок в sidebar.
|
||||
*/
|
||||
const buildLlmsFull = (lang: Lang): void => {
|
||||
const cfg = config as unknown as {
|
||||
title: string;
|
||||
locales: Record<
|
||||
string,
|
||||
{
|
||||
description?: string;
|
||||
llmsBlockquote?: string;
|
||||
llmsContext?: string;
|
||||
themeConfig?: { sidebar?: SidebarItem[] };
|
||||
}
|
||||
>;
|
||||
};
|
||||
|
||||
const locale = cfg.locales[lang];
|
||||
const sidebar = locale?.themeConfig?.sidebar;
|
||||
if (!sidebar) return;
|
||||
|
||||
const buildLlmsFull = (): void => {
|
||||
const sidebar = cfg.themeConfig.sidebar;
|
||||
const entries = flattenSidebar(sidebar);
|
||||
const blockquote = locale.llmsBlockquote ?? locale.description ?? '';
|
||||
const blockquote = cfg.llmsBlockquote ?? cfg.description ?? '';
|
||||
|
||||
const parts: string[] = [];
|
||||
parts.push(`# ${cfg.title}`);
|
||||
parts.push('');
|
||||
if (blockquote) parts.push(`> ${blockquote}`);
|
||||
if (locale.llmsContext) {
|
||||
if (cfg.llmsContext) {
|
||||
parts.push('');
|
||||
parts.push(locale.llmsContext);
|
||||
parts.push(cfg.llmsContext);
|
||||
}
|
||||
parts.push('');
|
||||
|
||||
for (const entry of entries) {
|
||||
const filePath = linkToFilePath(entry.link, lang);
|
||||
const filePath = linkToFilePath(entry.link);
|
||||
if (!fs.existsSync(filePath)) continue;
|
||||
|
||||
const raw = fs.readFileSync(filePath, 'utf8');
|
||||
@@ -558,9 +463,8 @@ const buildLlmsFull = (lang: Lang): void => {
|
||||
parts.push('');
|
||||
}
|
||||
|
||||
const outDir = path.join(PUBLIC_DIR, lang);
|
||||
fs.mkdirSync(outDir, { recursive: true });
|
||||
const outFile = path.join(outDir, 'llms-full.txt');
|
||||
fs.mkdirSync(PUBLIC_DIR, { recursive: true });
|
||||
const outFile = path.join(PUBLIC_DIR, 'llms-full.txt');
|
||||
fs.writeFileSync(outFile, parts.join('\n'), 'utf8');
|
||||
console.log(`${outFile} создан`);
|
||||
};
|
||||
@@ -570,18 +474,9 @@ const writeManifest = (): void => {
|
||||
const manifest = {
|
||||
version: VERSION,
|
||||
buildDate: BUILD_DATE,
|
||||
languages: {
|
||||
ru: {
|
||||
llms: '/ru/llms.txt',
|
||||
llmsFull: '/ru/llms-full.txt',
|
||||
zip: '/nextjs-style-guide-ru.zip',
|
||||
},
|
||||
en: {
|
||||
llms: '/en/llms.txt',
|
||||
llmsFull: '/en/llms-full.txt',
|
||||
zip: '/nextjs-style-guide-en.zip',
|
||||
},
|
||||
},
|
||||
llms: '/llms.txt',
|
||||
llmsFull: '/llms-full.txt',
|
||||
zip: '/nextjs-style-guide.zip',
|
||||
};
|
||||
fs.mkdirSync(PUBLIC_DIR, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
@@ -592,28 +487,22 @@ const writeManifest = (): void => {
|
||||
console.log(`${PUBLIC_DIR}/manifest.json создан`);
|
||||
};
|
||||
|
||||
/** Скопировать `index.md` локали в корневой README без frontmatter */
|
||||
const buildReadme = (lang: Lang, outFile: string): void => {
|
||||
const indexPath = path.join('docs', lang, 'index.md');
|
||||
/** Скопировать `index.md` документации в корневой README без frontmatter. */
|
||||
const buildReadme = (): void => {
|
||||
const indexPath = 'docs/docs/index.md';
|
||||
if (!fs.existsSync(indexPath)) {
|
||||
console.warn(`Пропуск ${outFile}: ${indexPath} не найден`);
|
||||
console.warn(`Пропуск README.md: ${indexPath} не найден`);
|
||||
return;
|
||||
}
|
||||
const raw = fs.readFileSync(indexPath, 'utf8');
|
||||
const { body } = parseFrontmatter(raw);
|
||||
fs.writeFileSync(outFile, body.trimStart(), 'utf8');
|
||||
console.log(`${outFile} обновлён из ${indexPath}`);
|
||||
fs.writeFileSync('README.md', body.trimStart(), 'utf8');
|
||||
console.log(`README.md обновлён из ${indexPath}`);
|
||||
};
|
||||
|
||||
buildLlms('ru');
|
||||
buildLlms('en');
|
||||
buildLlmsFull('ru');
|
||||
buildLlmsFull('en');
|
||||
buildRootIndex();
|
||||
copyMdFiles('ru');
|
||||
copyMdFiles('en');
|
||||
buildZip('ru');
|
||||
buildZip('en');
|
||||
buildLlms();
|
||||
buildLlmsFull();
|
||||
copyMdFiles();
|
||||
buildZip();
|
||||
writeManifest();
|
||||
buildReadme('en', 'README.md');
|
||||
buildReadme('ru', 'README_RU.md');
|
||||
buildReadme();
|
||||
|
||||
Reference in New Issue
Block a user