Compare commits

...

3 Commits

Author SHA1 Message Date
464c709859 docs: убрать «воду» из вводных абзацев разделов
All checks were successful
CI/CD Pipeline / docker (push) Successful in 54s
CI/CD Pipeline / deploy (push) Successful in 6s
- удалены обороты «Раздел описывает», «Этот раздел описывает» из
  10 файлов docs/ru
- вводные абзацы переписаны в формате «тема: категории/области»
  без перечисления конкретного содержимого раздела
- удалён frontmatter description из basics/architecture/index.md
  (подтягивается первый абзац после h1 — про SLM Design)
- в CONTRIBUTING.md добавлен раздел «Вводный абзац» с правилами
  и блоками «Хорошо/Плохо»: что делать, чего избегать, проверка
  на излишнюю конкретику
2026-04-25 20:15:10 +03:00
64db18917b fix: синхронизировать EN-лендинг с русским
All checks were successful
CI/CD Pipeline / docker (push) Successful in 54s
CI/CD Pipeline / deploy (push) Successful in 6s
- обновлён EN tagline на лендинге под новый стиль (RU уже был обновлён)
- английская карточка «For Assistant» приведена к структуре русской:
  две кнопки llms.txt и llms-full.txt с заглушечными ссылками (#)
  и бейджем «in development»
- шаблон карточки теперь поддерживает badge при наличии buttons:
  карточка приглушается, кнопки не кликаются
2026-04-25 20:00:52 +03:00
ae103e962e feat: llms-full.txt, README архива и доработка лендинга
All checks were successful
CI/CD Pipeline / docker (push) Successful in 46s
CI/CD Pipeline / deploy (push) Successful in 8s
- добавлен генератор llms-full.txt: вся документация локали в одном
  файле с мета-якорями, порядок повторяет sidebar
- архив теперь содержит README.md как точку входа: карта документации
  с относительными ссылками, описаниями и метаинфо сборки
- ссылки /ru/... в .md-файлах архива преобразуются в относительные
  пути (через path.relative) — внутренняя навигация работает локально
- веб-index.md удаляется из архива (его роль выполняет README.md)
- llms-full.txt добавлен в архив для одноразового чтения LLM
- в sidebar добавлен пункт «Главная» / «Home» со ссылкой на корень локали
- карточка «Ассистенту» на лендинге: две кнопки llms.txt и llms-full.txt
  с открытием в новой вкладке
- активирована карточка «Скачать правила» (ru) с ссылкой на zip-архив
- удалён устаревший блок «Для ассистентов» из docs/{ru,en}/index.md
- обновлены описания на главных локалей и заменён FSD на SLM в EN
- в манифесте появилось поле llmsFull рядом с llms
2026-04-25 19:56:44 +03:00
19 changed files with 385 additions and 65 deletions

View File

@@ -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,

View File

@@ -185,6 +185,50 @@ title: Название раздела
- Подсекции внутри `h2``h3`. - Подсекции внутри `h2``h3`.
- `h4` не используется. - `h4` не используется.
### Вводный абзац
Абзац сразу после `h1` отвечает на вопрос «о чём этот раздел?».
Он попадает в `llms.txt` и `README.md` архива как краткое описание,
поэтому должен быть плотным и без воды.
**Правила:**
- Не начинать с «Раздел описывает», «Этот раздел», «В этом разделе»,
«Здесь рассмотрено», «В этом документе».
- Начинать с подлежащего — самой темы (`Слои SLM:`, `Соглашения об именовании:`).
- Двоеточие или тире для перечисления **категорий и областей**, а не
конкретных значений из содержимого.
- Не дублировать содержимое: если внутри раздела 12 правил —
не перечислять их во вводном абзаце.
- Не аргументировать («единые правила делают код предсказуемым»).
- 12 предложения.
**Проверка:** если при добавлении нового правила/инструмента/раздела
вводный абзац придётся править — он слишком конкретный.
**Хорошо:**
```markdown
Слои SLM: назначение, классификация, направление зависимостей, правила.
```
```markdown
Базовый стек проекта по областям: UI, архитектура, данные, состояние,
локализация, тестирование, стили, генерация кода.
```
**Плохо:**
```markdown
Раздел описывает слои SLM: что такое слой, какие бывают, как между
ними направлены зависимости и какие правила действуют на каждом.
```
```markdown
Этот раздел описывает базовый стек технологий и библиотек, принятый в
проекте. React, TypeScript, Next.js, SWR, Zustand, i18next.
```
### Примеры кода ### Примеры кода
- Блоки кода с указанием языка: ` ```tsx `, ` ```css `, ` ```bash `, ` ```text `. - Блоки кода с указанием языка: ` ```tsx `, ` ```css `, ` ```bash `, ` ```text `.

View File

@@ -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

View File

@@ -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
## Структура документации ## Структура документации

View File

@@ -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

View File

@@ -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;

View File

@@ -4,7 +4,7 @@ title: Структура проекта
# Структура проекта # Структура проекта
Раздел описывает расположение файлов и папок в проекте Next.js (App Router). Файловая организация Next.js-проекта по архитектуре SLM.
## Корень репозитория ## Корень репозитория

View File

@@ -4,7 +4,7 @@ title: Стили
# Стили # Стили
Раздел описывает правила написания CSS: PostCSS Modules, вложенность, медиа-запросы, переменные, форматирование. Правила написания CSS: PostCSS Modules, форматирование, переменные.
## Общие правила ## Общие правила

View File

@@ -1,6 +1,5 @@
--- ---
title: Архитектура title: Архитектура
description: "Раздел описывает архитектуру проекта: из каких слоёв состоит приложение, как организован код внутри слоёв и какие правила управляют зависимостями."
--- ---
# SLM Design # SLM Design

View File

@@ -4,7 +4,7 @@ title: Слои
# Слои # Слои
Раздел описывает слои SLM: что такое слой, какие бывают, как между ними направлены зависимости и какие правила действуют на каждом. Слои SLM: назначение, классификация, направление зависимостей, правила.
## Определение ## Определение

View File

@@ -4,7 +4,7 @@ title: Модули
# Модули # Модули
Раздел описывает модули SLM: что такое модуль, из чего он состоит и как взаимодействует с остальным кодом. Модули SLM: состав, границы, взаимодействие с остальным кодом.
## Определение ## Определение

View File

@@ -4,7 +4,7 @@ title: Сегменты
# Сегменты # Сегменты
Раздел описывает сегменты SLM: что такое сегмент, какие бывают и что в каждом из них лежит. Сегменты SLM: типы, назначение, что лежит внутри каждого.
## Определение ## Определение

View File

@@ -4,7 +4,7 @@ title: Стиль кода
# Стиль кода # Стиль кода
Раздел описывает единые правила оформления кода: отступы, переносы, кавычки, порядок импортов и базовую читаемость. Единые правила оформления кода: форматирование, импорты, читаемость.
## Отступы ## Отступы

View File

@@ -4,8 +4,7 @@ title: Документирование
# Документирование # Документирование
Этот раздел описывает правила документирования кода: когда и как писать Правила документирования кода: что и когда документировать через JSDoc.
комментарии к компонентам, функциям, типам и интерфейсам.
## Общие правила ## Общие правила

View File

@@ -4,7 +4,7 @@ title: Именование
# Именование # Именование
Этот раздел описывает соглашения об именовании в проекте. Единые правила делают код предсказуемым и упрощают навигацию по проекту. Соглашения об именовании в коде: что и как называть.
## Базовые правила ## Базовые правила

View File

@@ -4,7 +4,7 @@ title: Технологии и библиотеки
# Технологии и библиотеки # Технологии и библиотеки
Этот раздел описывает базовый стек технологий и библиотек, принятый в проекте. Базовый стек проекта по областям: UI, архитектура, данные, состояние, локализация, тестирование, стили, генерация кода.
## Что используем ## Что используем

View File

@@ -4,7 +4,7 @@ title: Типизация
# Типизация # Типизация
Этот раздел описывает правила типизации: как типизировать компоненты, функции и работу с `any`/`unknown`. Правила типизации в TypeScript: общие принципы и работа с динамическими типами.
## Общие правила ## Общие правила

View File

@@ -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
## Структура документации ## Структура документации

View File

@@ -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');