feat: llms-full.txt, README архива и доработка лендинга
- добавлен генератор 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
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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: 'Скачать правила',
|
||||||
@@ -164,21 +166,39 @@ 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 && !t.cards[key].badge"
|
||||||
class="landing__card"
|
class="landing__card landing__card--multi"
|
||||||
:class="{ 'landing__card--soon': t.cards[key].badge }"
|
>
|
||||||
:href="t.cards[key].href"
|
<h3>{{ t.cards[key].title }}</h3>
|
||||||
:aria-disabled="t.cards[key].badge ? 'true' : null"
|
<p>{{ t.cards[key].desc }}</p>
|
||||||
>
|
<div class="landing__buttons">
|
||||||
<h3>
|
<a
|
||||||
{{ t.cards[key].title }}
|
v-for="btn in t.cards[key].buttons"
|
||||||
<span v-if="t.cards[key].badge" class="landing__badge">{{ t.cards[key].badge }}</span>
|
:key="btn.href"
|
||||||
</h3>
|
class="landing__button"
|
||||||
<p>{{ t.cards[key].desc }}</p>
|
:href="btn.href"
|
||||||
<span class="landing__cta">{{ t.cards[key].cta }} →</span>
|
target="_blank"
|
||||||
</a>
|
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 +352,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;
|
||||||
|
|||||||
@@ -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