Compare commits
3 Commits
v3
...
464c709859
| Author | SHA1 | Date | |
|---|---|---|---|
| 464c709859 | |||
| 64db18917b | |||
| ae103e962e |
@@ -1,6 +1,10 @@
|
|||||||
import { defineConfig } from 'vitepress';
|
import { defineConfig } from 'vitepress';
|
||||||
|
|
||||||
const ruSidebar = [
|
const ruSidebar = [
|
||||||
|
{
|
||||||
|
text: 'Главная',
|
||||||
|
link: '/ru/',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
text: 'Workflow',
|
text: 'Workflow',
|
||||||
link: '/ru/workflow',
|
link: '/ru/workflow',
|
||||||
@@ -47,6 +51,10 @@ const ruSidebar = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const enSidebar = [
|
const enSidebar = [
|
||||||
|
{
|
||||||
|
text: 'Home',
|
||||||
|
link: '/en/',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
text: 'Processes',
|
text: 'Processes',
|
||||||
items: [
|
items: [
|
||||||
@@ -133,7 +141,7 @@ export default defineConfig({
|
|||||||
ru: {
|
ru: {
|
||||||
label: 'Русский',
|
label: 'Русский',
|
||||||
lang: 'ru-RU',
|
lang: 'ru-RU',
|
||||||
link: '/ru/',
|
link: '/',
|
||||||
description: 'Стандарты разработки на Next.js + TypeScript с архитектурой SLM',
|
description: 'Стандарты разработки на Next.js + TypeScript с архитектурой SLM',
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
sidebar: ruSidebar,
|
sidebar: ruSidebar,
|
||||||
@@ -148,7 +156,7 @@ export default defineConfig({
|
|||||||
en: {
|
en: {
|
||||||
label: 'English',
|
label: 'English',
|
||||||
lang: 'en-US',
|
lang: 'en-US',
|
||||||
link: '/en/',
|
link: '/',
|
||||||
description: 'Next.js + TypeScript development standards with SLM architecture',
|
description: 'Next.js + TypeScript development standards with SLM architecture',
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
sidebar: enSidebar,
|
sidebar: enSidebar,
|
||||||
|
|||||||
@@ -185,6 +185,50 @@ title: Название раздела
|
|||||||
- Подсекции внутри `h2` — `h3`.
|
- Подсекции внутри `h2` — `h3`.
|
||||||
- `h4` не используется.
|
- `h4` не используется.
|
||||||
|
|
||||||
|
### Вводный абзац
|
||||||
|
|
||||||
|
Абзац сразу после `h1` отвечает на вопрос «о чём этот раздел?».
|
||||||
|
Он попадает в `llms.txt` и `README.md` архива как краткое описание,
|
||||||
|
поэтому должен быть плотным и без воды.
|
||||||
|
|
||||||
|
**Правила:**
|
||||||
|
|
||||||
|
- Не начинать с «Раздел описывает», «Этот раздел», «В этом разделе»,
|
||||||
|
«Здесь рассмотрено», «В этом документе».
|
||||||
|
- Начинать с подлежащего — самой темы (`Слои SLM:`, `Соглашения об именовании:`).
|
||||||
|
- Двоеточие или тире для перечисления **категорий и областей**, а не
|
||||||
|
конкретных значений из содержимого.
|
||||||
|
- Не дублировать содержимое: если внутри раздела 12 правил —
|
||||||
|
не перечислять их во вводном абзаце.
|
||||||
|
- Не аргументировать («единые правила делают код предсказуемым»).
|
||||||
|
- 1–2 предложения.
|
||||||
|
|
||||||
|
**Проверка:** если при добавлении нового правила/инструмента/раздела
|
||||||
|
вводный абзац придётся править — он слишком конкретный.
|
||||||
|
|
||||||
|
**Хорошо:**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Слои SLM: назначение, классификация, направление зависимостей, правила.
|
||||||
|
```
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Базовый стек проекта по областям: UI, архитектура, данные, состояние,
|
||||||
|
локализация, тестирование, стили, генерация кода.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Плохо:**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Раздел описывает слои SLM: что такое слой, какие бывают, как между
|
||||||
|
ними направлены зависимости и какие правила действуют на каждом.
|
||||||
|
```
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Этот раздел описывает базовый стек технологий и библиотек, принятый в
|
||||||
|
проекте. React, TypeScript, Next.js, SWR, Zustand, i18next.
|
||||||
|
```
|
||||||
|
|
||||||
### Примеры кода
|
### Примеры кода
|
||||||
|
|
||||||
- Блоки кода с указанием языка: ` ```tsx `, ` ```css `, ` ```bash `, ` ```text `.
|
- Блоки кода с указанием языка: ` ```tsx `, ` ```css `, ` ```bash `, ` ```text `.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# NextJS Style Guide
|
# NextJS Style Guide
|
||||||
|
|
||||||
Rules and standards for NextJS and TypeScript development: architecture, typing, styles, components, API, and infrastructure.
|
Conventions for Next.js project development: application architecture and layers, code structure, module organization, styling, typing, and infrastructure.
|
||||||
|
|
||||||
## Documentation Structure
|
## Documentation Structure
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ Rules and standards for NextJS and TypeScript development: architecture, typing,
|
|||||||
| Section | Answers the question |
|
| Section | Answers the question |
|
||||||
|---------|---------------------|
|
|---------|---------------------|
|
||||||
| Tech Stack | What stack do we use? |
|
| Tech Stack | What stack do we use? |
|
||||||
| Architecture | How are FSD layers, dependencies, and public API structured? |
|
| Architecture | How are SLM layers, dependencies, and public API structured? |
|
||||||
| Code Style | How to format code: indentation, quotes, imports, early return? |
|
| Code Style | How to format code: indentation, quotes, imports, early return? |
|
||||||
| Naming | How to name files, variables, components, hooks? |
|
| Naming | How to name files, variables, components, hooks? |
|
||||||
| Documentation | How to write JSDoc: what to document and what not? |
|
| Documentation | How to write JSDoc: what to document and what not? |
|
||||||
@@ -51,8 +51,3 @@ Rules and standards for NextJS and TypeScript development: architecture, typing,
|
|||||||
| Hooks | _(not filled)_ |
|
| Hooks | _(not filled)_ |
|
||||||
| Fonts | _(not filled)_ |
|
| Fonts | _(not filled)_ |
|
||||||
| Localization | _(not filled)_ |
|
| Localization | _(not filled)_ |
|
||||||
|
|
||||||
## For Assistants
|
|
||||||
|
|
||||||
Documentation map with links to all sections ([llmstxt.org](https://llmstxt.org) format):
|
|
||||||
https://gromlab.ru/docs/nextjs-style-guide/raw/branch/main/generated/en/llms.txt
|
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
# NextJS Style Guide
|
# NextJS Style Guide
|
||||||
|
|
||||||
Правила и стандарты разработки на NextJS и TypeScript: архитектура, типизация, стили, компоненты, API и инфраструктурные разделы.
|
Соглашения по разработке Next.js проектов: архитектура и слои приложения, структура кода, организация модулей, стилизация, типизация и инфраструктура.
|
||||||
|
|
||||||
## Для ассистентов
|
|
||||||
|
|
||||||
Карта документации со ссылками на все разделы (формат [llmstxt.org](https://llmstxt.org)):
|
|
||||||
https://gromlab.ru/docs/nextjs-style-guide/raw/branch/main/generated/ru/llms.txt
|
|
||||||
|
|
||||||
## Структура документации
|
## Структура документации
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# NextJS Style Guide
|
# NextJS Style Guide
|
||||||
|
|
||||||
Rules and standards for NextJS and TypeScript development: architecture, typing, styles, components, API, and infrastructure.
|
Conventions for Next.js project development: application architecture and layers, code structure, module organization, styling, typing, and infrastructure.
|
||||||
|
|
||||||
## Documentation Structure
|
## Documentation Structure
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ Rules and standards for NextJS and TypeScript development: architecture, typing,
|
|||||||
| Section | Answers the question |
|
| Section | Answers the question |
|
||||||
|---------|---------------------|
|
|---------|---------------------|
|
||||||
| Tech Stack | What stack do we use? |
|
| Tech Stack | What stack do we use? |
|
||||||
| Architecture | How are FSD layers, dependencies, and public API structured? |
|
| Architecture | How are SLM layers, dependencies, and public API structured? |
|
||||||
| Code Style | How to format code: indentation, quotes, imports, early return? |
|
| Code Style | How to format code: indentation, quotes, imports, early return? |
|
||||||
| Naming | How to name files, variables, components, hooks? |
|
| Naming | How to name files, variables, components, hooks? |
|
||||||
| Documentation | How to write JSDoc: what to document and what not? |
|
| Documentation | How to write JSDoc: what to document and what not? |
|
||||||
@@ -51,8 +51,3 @@ Rules and standards for NextJS and TypeScript development: architecture, typing,
|
|||||||
| Hooks | _(not filled)_ |
|
| Hooks | _(not filled)_ |
|
||||||
| Fonts | _(not filled)_ |
|
| Fonts | _(not filled)_ |
|
||||||
| Localization | _(not filled)_ |
|
| Localization | _(not filled)_ |
|
||||||
|
|
||||||
## For Assistants
|
|
||||||
|
|
||||||
Documentation map with links to all sections ([llmstxt.org](https://llmstxt.org) format):
|
|
||||||
https://gromlab.ru/docs/nextjs-style-guide/raw/branch/main/generated/en/llms.txt
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const buildVersion = __BUILD_VERSION__
|
|||||||
|
|
||||||
const dict = {
|
const dict = {
|
||||||
ru: {
|
ru: {
|
||||||
tagline: 'Готовые соглашения по архитектуре, коду, компонентам и инфраструктуре для Next.js + TypeScript-проектов — чтобы команда писала одинаково, а новые разработчики включались в проект быстрее.',
|
tagline: 'Соглашения по разработке Next.js проектов: архитектура и слои приложения, структура кода, организация модулей, стилизация, типизация и инфраструктура.',
|
||||||
langLabel: 'Язык',
|
langLabel: 'Язык',
|
||||||
themeLabel: 'Тема',
|
themeLabel: 'Тема',
|
||||||
themes: { auto: 'Авто', light: 'Светлая', dark: 'Тёмная' },
|
themes: { auto: 'Авто', light: 'Светлая', dark: 'Тёмная' },
|
||||||
@@ -28,8 +28,10 @@ const dict = {
|
|||||||
ai: {
|
ai: {
|
||||||
title: 'Ассистенту',
|
title: 'Ассистенту',
|
||||||
desc: 'Карта документации в формате llms.txt для AI-агентов.',
|
desc: 'Карта документации в формате llms.txt для AI-агентов.',
|
||||||
href: './ru/llms.txt',
|
buttons: [
|
||||||
cta: 'Открыть',
|
{ label: 'llms.txt', href: './ru/llms.txt' },
|
||||||
|
{ label: 'llms-full.txt', href: './ru/llms-full.txt' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
zip: {
|
zip: {
|
||||||
title: 'Скачать правила',
|
title: 'Скачать правила',
|
||||||
@@ -40,7 +42,7 @@ const dict = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
en: {
|
en: {
|
||||||
tagline: 'Ready-made standards for architecture, code, components, and infrastructure in Next.js + TypeScript projects — so your team writes consistently and new developers ramp up faster.',
|
tagline: 'Conventions for Next.js project development: application architecture and layers, code structure, module organization, styling, typing, and infrastructure.',
|
||||||
langLabel: 'Language',
|
langLabel: 'Language',
|
||||||
themeLabel: 'Theme',
|
themeLabel: 'Theme',
|
||||||
themes: { auto: 'Auto', light: 'Light', dark: 'Dark' },
|
themes: { auto: 'Auto', light: 'Light', dark: 'Dark' },
|
||||||
@@ -55,9 +57,11 @@ const dict = {
|
|||||||
ai: {
|
ai: {
|
||||||
title: 'For Assistant',
|
title: 'For Assistant',
|
||||||
desc: 'Documentation map in llms.txt format for AI agents.',
|
desc: 'Documentation map in llms.txt format for AI agents.',
|
||||||
href: '#',
|
|
||||||
cta: 'Open',
|
|
||||||
badge: 'in development',
|
badge: 'in development',
|
||||||
|
buttons: [
|
||||||
|
{ label: 'llms.txt', href: '#' },
|
||||||
|
{ label: 'llms-full.txt', href: '#' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
zip: {
|
zip: {
|
||||||
title: 'Download rules',
|
title: 'Download rules',
|
||||||
@@ -164,21 +168,44 @@ function toggleTheme(value) {
|
|||||||
|
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
<section class="landing__cards">
|
<section class="landing__cards">
|
||||||
<a
|
<template v-for="key in ['docs', 'ai', 'zip']" :key="key">
|
||||||
v-for="key in ['docs', 'ai', 'zip']"
|
<div
|
||||||
:key="key"
|
v-if="t.cards[key].buttons"
|
||||||
class="landing__card"
|
class="landing__card landing__card--multi"
|
||||||
:class="{ 'landing__card--soon': t.cards[key].badge }"
|
:class="{ 'landing__card--soon': t.cards[key].badge }"
|
||||||
:href="t.cards[key].href"
|
:aria-disabled="t.cards[key].badge ? 'true' : null"
|
||||||
:aria-disabled="t.cards[key].badge ? 'true' : null"
|
>
|
||||||
>
|
<h3>
|
||||||
<h3>
|
{{ t.cards[key].title }}
|
||||||
{{ t.cards[key].title }}
|
<span v-if="t.cards[key].badge" class="landing__badge">{{ t.cards[key].badge }}</span>
|
||||||
<span v-if="t.cards[key].badge" class="landing__badge">{{ t.cards[key].badge }}</span>
|
</h3>
|
||||||
</h3>
|
<p>{{ t.cards[key].desc }}</p>
|
||||||
<p>{{ t.cards[key].desc }}</p>
|
<div class="landing__buttons">
|
||||||
<span class="landing__cta">{{ t.cards[key].cta }} →</span>
|
<a
|
||||||
</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>
|
||||||
|
</template>
|
||||||
</section>
|
</section>
|
||||||
</ClientOnly>
|
</ClientOnly>
|
||||||
|
|
||||||
@@ -332,6 +359,33 @@ function toggleTheme(value) {
|
|||||||
color: var(--vp-c-brand-1);
|
color: var(--vp-c-brand-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.landing__buttons {
|
||||||
|
margin-top: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing__button {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
font-family: var(--vp-font-family-mono, monospace);
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
background: var(--vp-c-bg);
|
||||||
|
border: 1px solid var(--vp-c-divider);
|
||||||
|
border-radius: 8px;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: border-color 0.15s, color 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing__button:hover {
|
||||||
|
border-color: var(--vp-c-brand-1);
|
||||||
|
color: var(--vp-c-brand-1);
|
||||||
|
}
|
||||||
|
|
||||||
.landing__version {
|
.landing__version {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin: 24px 0 0;
|
margin: 24px 0 0;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: Структура проекта
|
|||||||
|
|
||||||
# Структура проекта
|
# Структура проекта
|
||||||
|
|
||||||
Раздел описывает расположение файлов и папок в проекте Next.js (App Router).
|
Файловая организация Next.js-проекта по архитектуре SLM.
|
||||||
|
|
||||||
## Корень репозитория
|
## Корень репозитория
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: Стили
|
|||||||
|
|
||||||
# Стили
|
# Стили
|
||||||
|
|
||||||
Раздел описывает правила написания CSS: PostCSS Modules, вложенность, медиа-запросы, переменные, форматирование.
|
Правила написания CSS: PostCSS Modules, форматирование, переменные.
|
||||||
|
|
||||||
## Общие правила
|
## Общие правила
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: Архитектура
|
title: Архитектура
|
||||||
description: "Раздел описывает архитектуру проекта: из каких слоёв состоит приложение, как организован код внутри слоёв и какие правила управляют зависимостями."
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# SLM Design
|
# SLM Design
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: Слои
|
|||||||
|
|
||||||
# Слои
|
# Слои
|
||||||
|
|
||||||
Раздел описывает слои SLM: что такое слой, какие бывают, как между ними направлены зависимости и какие правила действуют на каждом.
|
Слои SLM: назначение, классификация, направление зависимостей, правила.
|
||||||
|
|
||||||
## Определение
|
## Определение
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: Модули
|
|||||||
|
|
||||||
# Модули
|
# Модули
|
||||||
|
|
||||||
Раздел описывает модули SLM: что такое модуль, из чего он состоит и как взаимодействует с остальным кодом.
|
Модули SLM: состав, границы, взаимодействие с остальным кодом.
|
||||||
|
|
||||||
## Определение
|
## Определение
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: Сегменты
|
|||||||
|
|
||||||
# Сегменты
|
# Сегменты
|
||||||
|
|
||||||
Раздел описывает сегменты SLM: что такое сегмент, какие бывают и что в каждом из них лежит.
|
Сегменты SLM: типы, назначение, что лежит внутри каждого.
|
||||||
|
|
||||||
## Определение
|
## Определение
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: Стиль кода
|
|||||||
|
|
||||||
# Стиль кода
|
# Стиль кода
|
||||||
|
|
||||||
Раздел описывает единые правила оформления кода: отступы, переносы, кавычки, порядок импортов и базовую читаемость.
|
Единые правила оформления кода: форматирование, импорты, читаемость.
|
||||||
|
|
||||||
## Отступы
|
## Отступы
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ title: Документирование
|
|||||||
|
|
||||||
# Документирование
|
# Документирование
|
||||||
|
|
||||||
Этот раздел описывает правила документирования кода: когда и как писать
|
Правила документирования кода: что и когда документировать через JSDoc.
|
||||||
комментарии к компонентам, функциям, типам и интерфейсам.
|
|
||||||
|
|
||||||
## Общие правила
|
## Общие правила
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: Именование
|
|||||||
|
|
||||||
# Именование
|
# Именование
|
||||||
|
|
||||||
Этот раздел описывает соглашения об именовании в проекте. Единые правила делают код предсказуемым и упрощают навигацию по проекту.
|
Соглашения об именовании в коде: что и как называть.
|
||||||
|
|
||||||
## Базовые правила
|
## Базовые правила
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: Технологии и библиотеки
|
|||||||
|
|
||||||
# Технологии и библиотеки
|
# Технологии и библиотеки
|
||||||
|
|
||||||
Этот раздел описывает базовый стек технологий и библиотек, принятый в проекте.
|
Базовый стек проекта по областям: UI, архитектура, данные, состояние, локализация, тестирование, стили, генерация кода.
|
||||||
|
|
||||||
## Что используем
|
## Что используем
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ title: Типизация
|
|||||||
|
|
||||||
# Типизация
|
# Типизация
|
||||||
|
|
||||||
Этот раздел описывает правила типизации: как типизировать компоненты, функции и работу с `any`/`unknown`.
|
Правила типизации в TypeScript: общие принципы и работа с динамическими типами.
|
||||||
|
|
||||||
## Общие правила
|
## Общие правила
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
# NextJS Style Guide
|
# NextJS Style Guide
|
||||||
|
|
||||||
Правила и стандарты разработки на NextJS и TypeScript: архитектура, типизация, стили, компоненты, API и инфраструктурные разделы.
|
Соглашения по разработке Next.js проектов: архитектура и слои приложения, структура кода, организация модулей, стилизация, типизация и инфраструктура.
|
||||||
|
|
||||||
## Для ассистентов
|
|
||||||
|
|
||||||
Карта документации со ссылками на все разделы (формат [llmstxt.org](https://llmstxt.org)):
|
|
||||||
https://gromlab.ru/docs/nextjs-style-guide/raw/branch/main/generated/ru/llms.txt
|
|
||||||
|
|
||||||
## Структура документации
|
## Структура документации
|
||||||
|
|
||||||
|
|||||||
244
generate-llms.ts
244
generate-llms.ts
@@ -301,19 +301,173 @@ const copyMdFiles = (lang: Lang): void => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Собрать `nextjs-style-guide-{lang}.zip` со всеми `.md` локали и `VERSION`.
|
* Преобразовать sidebar `link` в относительный путь файла внутри архива
|
||||||
* Внутри архива — единая папка `nextjs-style-guide/`.
|
* (от корня папки `nextjs-style-guide/`). Это путь, по которому файл лежит
|
||||||
|
* в распакованной папке, без расширения добавляется `.md`.
|
||||||
|
*/
|
||||||
|
const linkToArchiveRel = (link: string, lang: Lang): string => {
|
||||||
|
const prefix = `/${lang}/`;
|
||||||
|
let rel = link.startsWith(prefix) ? link.slice(prefix.length) : link.replace(/^\//, '');
|
||||||
|
if (rel === '' || rel.endsWith('/')) {
|
||||||
|
rel += 'index.md';
|
||||||
|
} else {
|
||||||
|
rel += '.md';
|
||||||
|
}
|
||||||
|
return rel;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Заменить во всех `.md` архива ссылки `[text](/ru/foo)` на относительные
|
||||||
|
* пути от расположения файла. Без этого внутренние ссылки в распакованной
|
||||||
|
* папке не работают.
|
||||||
|
*/
|
||||||
|
const transformLinksInDir = (rootDir: string, lang: Lang): void => {
|
||||||
|
const linkRe = /\]\(\/([a-z]{2})\/([^)\s#]*)(#[^)]*)?\)/g;
|
||||||
|
|
||||||
|
const walk = (dir: string): void => {
|
||||||
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||||
|
const full = path.join(dir, entry.name);
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
walk(full);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!entry.isFile() || !entry.name.endsWith('.md')) continue;
|
||||||
|
|
||||||
|
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 targetAbs = path.join(rootDir, targetRel);
|
||||||
|
let rel = path.relative(fileDir, targetAbs);
|
||||||
|
if (!rel.startsWith('.')) rel = './' + rel;
|
||||||
|
return `](${rel}${hash})`;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (updated !== content) {
|
||||||
|
fs.writeFileSync(full, updated, 'utf8');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
walk(rootDir);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сгенерировать `README.md` — точка входа архива. Карта документации
|
||||||
|
* с относительными ссылками, описаниями из 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 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'),
|
||||||
|
);
|
||||||
|
const grouped = groupBySection(entries);
|
||||||
|
|
||||||
|
const lines: string[] = [];
|
||||||
|
lines.push(`# ${cfg.title}`);
|
||||||
|
lines.push('');
|
||||||
|
if (blockquote) {
|
||||||
|
lines.push(`> ${blockquote}`);
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
if (context) {
|
||||||
|
lines.push(context);
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
const heading = lang === 'ru' ? 'Содержание' : 'Contents';
|
||||||
|
lines.push(`## ${heading}`);
|
||||||
|
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));
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
const display = entry.prefix
|
||||||
|
? `${entry.prefix}: ${entry.text}`
|
||||||
|
: entry.text;
|
||||||
|
const descPart = description ? ` — ${description}` : '';
|
||||||
|
lines.push(`- [${display}](${targetRel})${descPart}`);
|
||||||
|
}
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push('---');
|
||||||
|
lines.push('');
|
||||||
|
lines.push(`Версия: ${VERSION} · Сборка: ${BUILD_DATE}`);
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
fs.writeFileSync(path.join(rootDir, 'README.md'), lines.join('\n'), 'utf8');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Собрать `nextjs-style-guide-{lang}.zip`. Внутри архива — единая папка
|
||||||
|
* `nextjs-style-guide/` с `.md`-файлами локали, README-точкой входа,
|
||||||
|
* `llms-full.txt` и `VERSION`. Внутренние ссылки в `.md` преобразуются
|
||||||
|
* в относительные.
|
||||||
*
|
*
|
||||||
* `llms.txt` в архив не кладём: его ссылки указывают на сайт и локально
|
* Веб-`index.md` локали из архива удаляется — его роль выполняет README.md.
|
||||||
* не работают. Структура папки сама по себе является картой документации.
|
|
||||||
*/
|
*/
|
||||||
const buildZip = (lang: Lang): void => {
|
const buildZip = (lang: Lang): void => {
|
||||||
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'nsg-'));
|
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'nsg-'));
|
||||||
const stage = path.join(tmpRoot, 'nextjs-style-guide');
|
const stage = path.join(tmpRoot, 'nextjs-style-guide');
|
||||||
fs.mkdirSync(stage, { recursive: true });
|
fs.mkdirSync(stage, { recursive: true });
|
||||||
|
|
||||||
|
// 1. Копируем все .md локали в staging.
|
||||||
copyDirSync(path.join('docs', lang), stage, (name) => name.endsWith('.md'));
|
copyDirSync(path.join('docs', lang), stage, (name) => name.endsWith('.md'));
|
||||||
|
|
||||||
|
// 2. Удаляем веб-index.md локали — в архиве он избыточен.
|
||||||
|
const indexPath = path.join(stage, 'index.md');
|
||||||
|
if (fs.existsSync(indexPath)) fs.unlinkSync(indexPath);
|
||||||
|
|
||||||
|
// 3. Преобразуем абсолютные ссылки `/ru/...` в относительные.
|
||||||
|
transformLinksInDir(stage, lang);
|
||||||
|
|
||||||
|
// 4. Генерируем точку входа README.md.
|
||||||
|
buildArchiveReadme(lang, stage);
|
||||||
|
|
||||||
|
// 5. Кладём llms-full.txt — удобно для одноразового чтения LLM.
|
||||||
|
const llmsFullSrc = path.join(PUBLIC_DIR, lang, 'llms-full.txt');
|
||||||
|
if (fs.existsSync(llmsFullSrc)) {
|
||||||
|
fs.copyFileSync(llmsFullSrc, path.join(stage, 'llms-full.txt'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Метаинформация сборки.
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
path.join(stage, 'VERSION'),
|
path.join(stage, 'VERSION'),
|
||||||
`${VERSION}\n${BUILD_DATE}\n`,
|
`${VERSION}\n${BUILD_DATE}\n`,
|
||||||
@@ -333,6 +487,84 @@ const buildZip = (lang: Lang): void => {
|
|||||||
console.log(`${outFile} создан (${VERSION})`);
|
console.log(`${outFile} создан (${VERSION})`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Удалить YAML frontmatter из исходника `.md`. */
|
||||||
|
const stripFrontmatter = (content: string): string =>
|
||||||
|
content.replace(/^---\n[\s\S]*?\n---\n*/, '');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сдвинуть уровень заголовков на 1 вниз (h1→h2, h2→h3, ...).
|
||||||
|
* Игнорирует строки внутри блоков кода.
|
||||||
|
*/
|
||||||
|
const shiftHeadings = (content: string): string => {
|
||||||
|
const lines = content.split('\n');
|
||||||
|
let inCodeBlock = false;
|
||||||
|
return lines
|
||||||
|
.map((line) => {
|
||||||
|
if (line.startsWith('```')) inCodeBlock = !inCodeBlock;
|
||||||
|
if (inCodeBlock) return line;
|
||||||
|
if (/^#{1,5}\s/.test(line)) return '#' + line;
|
||||||
|
return line;
|
||||||
|
})
|
||||||
|
.join('\n');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Собрать `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 entries = flattenSidebar(sidebar);
|
||||||
|
const blockquote = locale.llmsBlockquote ?? locale.description ?? '';
|
||||||
|
|
||||||
|
const parts: string[] = [];
|
||||||
|
parts.push(`# ${cfg.title}`);
|
||||||
|
parts.push('');
|
||||||
|
if (blockquote) parts.push(`> ${blockquote}`);
|
||||||
|
if (locale.llmsContext) {
|
||||||
|
parts.push('');
|
||||||
|
parts.push(locale.llmsContext);
|
||||||
|
}
|
||||||
|
parts.push('');
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
const filePath = linkToFilePath(entry.link, lang);
|
||||||
|
if (!fs.existsSync(filePath)) continue;
|
||||||
|
|
||||||
|
const raw = fs.readFileSync(filePath, 'utf8');
|
||||||
|
const content = shiftHeadings(stripFrontmatter(raw)).trim();
|
||||||
|
if (!content) continue;
|
||||||
|
|
||||||
|
// Мета-якорь: путь страницы для ориентации LLM
|
||||||
|
parts.push(`<!-- ${entry.link} -->`);
|
||||||
|
parts.push('');
|
||||||
|
parts.push(content);
|
||||||
|
parts.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
const outDir = path.join(PUBLIC_DIR, lang);
|
||||||
|
fs.mkdirSync(outDir, { recursive: true });
|
||||||
|
const outFile = path.join(outDir, 'llms-full.txt');
|
||||||
|
fs.writeFileSync(outFile, parts.join('\n'), 'utf8');
|
||||||
|
console.log(`${outFile} создан`);
|
||||||
|
};
|
||||||
|
|
||||||
/** Манифест сборки — для лендинга и внешних потребителей. */
|
/** Манифест сборки — для лендинга и внешних потребителей. */
|
||||||
const writeManifest = (): void => {
|
const writeManifest = (): void => {
|
||||||
const manifest = {
|
const manifest = {
|
||||||
@@ -341,10 +573,12 @@ const writeManifest = (): void => {
|
|||||||
languages: {
|
languages: {
|
||||||
ru: {
|
ru: {
|
||||||
llms: '/ru/llms.txt',
|
llms: '/ru/llms.txt',
|
||||||
|
llmsFull: '/ru/llms-full.txt',
|
||||||
zip: '/nextjs-style-guide-ru.zip',
|
zip: '/nextjs-style-guide-ru.zip',
|
||||||
},
|
},
|
||||||
en: {
|
en: {
|
||||||
llms: '/en/llms.txt',
|
llms: '/en/llms.txt',
|
||||||
|
llmsFull: '/en/llms-full.txt',
|
||||||
zip: '/nextjs-style-guide-en.zip',
|
zip: '/nextjs-style-guide-en.zip',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -373,6 +607,8 @@ const buildReadme = (lang: Lang, outFile: string): void => {
|
|||||||
|
|
||||||
buildLlms('ru');
|
buildLlms('ru');
|
||||||
buildLlms('en');
|
buildLlms('en');
|
||||||
|
buildLlmsFull('ru');
|
||||||
|
buildLlmsFull('en');
|
||||||
buildRootIndex();
|
buildRootIndex();
|
||||||
copyMdFiles('ru');
|
copyMdFiles('ru');
|
||||||
copyMdFiles('en');
|
copyMdFiles('en');
|
||||||
|
|||||||
Reference in New Issue
Block a user