feat: генерация llms.txt, лендинг с выбором языка и ZIP-архивов #1

Merged
gromov merged 1 commits from v3 into main 2026-04-25 18:10:35 +03:00
26 changed files with 1403 additions and 2386 deletions

View File

@@ -20,6 +20,9 @@ jobs:
echo "DOCKER_REGISTRY=$DOCKER_REGISTRY" >> $GITHUB_ENV
REGISTRY_IMAGE="$DOCKER_REGISTRY/$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')"
echo "REGISTRY_IMAGE=$REGISTRY_IMAGE" >> $GITHUB_ENV
# Версия сборки: тег если есть, иначе короткий SHA
BUILD_VERSION=$(git describe --tags --exact-match 2>/dev/null || git rev-parse --short HEAD)
echo "BUILD_VERSION=$BUILD_VERSION" >> $GITHUB_ENV
- name: Login to Container Registry
uses: docker/login-action@v3
@@ -47,6 +50,8 @@ jobs:
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
BUILD_VERSION=${{ env.BUILD_VERSION }}
provenance: false
sbom: false

4
.gitignore vendored
View File

@@ -135,3 +135,7 @@ dist
.vitepress/cache
.vitepress/dist
docs/.vitepress
# Генерируется через `npm run llms`
docs/public/
generated/

View File

@@ -3,45 +3,45 @@ import { defineConfig } from 'vitepress';
const ruSidebar = [
{
text: 'Workflow',
link: '/workflow',
link: '/ru/workflow',
},
{
text: 'Базовые правила',
items: [
{ text: 'Технологии и библиотеки', link: '/basics/tech-stack' },
{ text: 'Именование', link: '/basics/naming' },
{ text: 'Технологии и библиотеки', link: '/ru/basics/tech-stack' },
{ text: 'Именование', link: '/ru/basics/naming' },
{
text: 'Архитектура',
collapsed: true,
items: [
{ text: 'Обзор', link: '/basics/architecture/' },
{ text: 'Слои', link: '/basics/architecture/reference/layers' },
{ text: 'Модули', link: '/basics/architecture/reference/modules' },
{ text: 'Сегменты', link: '/basics/architecture/reference/segments' },
{ 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: '/basics/code-style' },
{ text: 'Документирование', link: '/basics/documentation' },
{ text: 'Типизация', link: '/basics/typing' },
{ text: 'Стиль кода', link: '/ru/basics/code-style' },
{ text: 'Документирование', link: '/ru/basics/documentation' },
{ text: 'Типизация', link: '/ru/basics/typing' },
],
},
{
text: 'Прикладные разделы',
items: [
{ text: 'Структура проекта', link: '/applied/project-structure' },
{ text: 'Компоненты', link: '/applied/components' },
{ text: 'Страницы (App Router)', link: '/applied/page-level' },
{ text: 'Шаблоны и генерация кода', link: '/applied/templates-generation' },
{ text: 'Стили', link: '/applied/styles' },
{ text: 'Изображения', link: '/applied/images-sprites' },
{ text: 'SVG-спрайты', link: '/applied/svg-sprites' },
{ text: 'Видео', link: '/applied/video' },
{ text: 'API', link: '/applied/api' },
{ text: 'Stores', link: '/applied/stores' },
{ text: 'Хуки', link: '/applied/hooks' },
{ text: 'Шрифты', link: '/applied/fonts' },
{ text: 'Локализация', link: '/applied/localization' },
{ text: 'Настройка VS Code', link: '/applied/vscode' },
{ 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' },
],
},
];
@@ -92,30 +92,71 @@ const enSidebar = [
},
];
/**
* Vite-плагин: отдаёт `.txt` и `.md` с явной кодировкой UTF-8.
* Без этого браузер декодирует как ISO-8859-1 и кириллица ломается.
*/
const utf8TextPlugin = {
name: 'utf8-text-files',
configureServer(server: any) {
server.middlewares.use((req: any, res: any, next: any) => {
const url: string = req.url || '';
if (url.endsWith('.txt') || url.endsWith('.md')) {
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
}
next();
});
},
};
export default defineConfig({
srcDir: 'docs',
// `docs/public/` содержит сгенерированные `.md`-копии и `llms.txt` для LLM
// (попадают в корень `dist/` как статика). Исключаем из сканирования
// страниц, иначе VitePress рендерит их как HTML-страницы.
srcExclude: ['public/**'],
title: 'NextJS Style Guide',
description: 'Правила и стандарты разработки на NextJS и TypeScript',
rewrites: {
'ru/:rest*': ':rest*',
vite: {
plugins: [utf8TextPlugin],
define: {
__BUILD_VERSION__: JSON.stringify(process.env.BUILD_VERSION || 'dev'),
},
},
locales: {
root: {
label: 'Languages',
lang: 'en',
},
ru: {
label: 'Русский',
lang: 'ru-RU',
link: '/ru/',
description: 'Стандарты разработки на Next.js + TypeScript с архитектурой SLM',
themeConfig: {
sidebar: ruSidebar,
},
// Расширенный блок описания для llms.txt — даёт LLM полный
// технический контекст: стек, методология, охват тем.
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,
},
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.',
},
},
});

View File

@@ -3,6 +3,6 @@
При работе с документацией следовать правилам из CONTRIBUTING.md.
- Язык документации и коммитов — русский.
- После изменений в `.md`-файлах — запустить `npm run docs` для обновления RULES.md.
- При добавлении нового раздела — обновить сайдбар (`.vitepress/config.ts`)
и порядок файлов (`concat-md.js`).
- После изменений в `.md`-файлах — запустить `npm run llms` для обновления `llms.txt` и README.
- При добавлении нового раздела — обновить сайдбар (`.vitepress/config.ts`):
он же является источником порядка и группировки для `llms.txt`.

View File

@@ -17,7 +17,7 @@
|---------|-----------|
| `npm run dev` | Локальный сервер разработки |
| `npm run build` | Сборка статического сайта |
| `npm run docs` | Генерация `generated/{lang}/RULES.md` — единый файл для AI-ассистентов |
| `npm run llms` | Генерация `generated/{lang}/llms.txt` (карта документации для LLM) и README |
## Структура файлов
@@ -51,16 +51,17 @@ docs/
.vitepress/
├── config.ts # Конфигурация VitePress, сайдбары, локали
generated/
├── ru/RULES.md # Сгенерированный единый файл (ru)
└── en/RULES.md # Сгенерированный единый файл (en)
concat-md.js # Скрипт генерации RULES.md
├── ru/llms.txt # Карта документации для LLM (ru, llmstxt.org)
└── en/llms.txt # Карта документации для LLM (en, llmstxt.org)
generate-llms.ts # Скрипт генерации llms.txt и README
```
### Добавление нового раздела
1. Создать `.md`-файл в нужной папке (`basics/` или `applied/`).
2. Добавить пункт в сайдбар — `.vitepress/config.ts` (оба языка, если есть перевод).
3. Добавить файл в массив `fileOrder``concat-md.js` (для генерации RULES.md).
Сайдбар — единственный источник порядка и группировки для `llms.txt`.
3. Запустить `npm run llms` для обновления `generated/{lang}/llms.txt`.
## Два типа документации

View File

@@ -1,5 +1,10 @@
:8080 {
root * /srv
# Кириллица в .txt и .md ломается без явного charset
@text path *.txt *.md
header @text Content-Type "text/plain; charset=utf-8"
file_server
try_files {path} /index.html
}

View File

@@ -1,9 +1,13 @@
FROM node:24-alpine AS build
WORKDIR /app
# zip нужен для упаковки nextjs-style-guide-{lang}.zip
RUN apk add --no-cache zip
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
ARG BUILD_VERSION=dev
ENV BUILD_VERSION=${BUILD_VERSION}
RUN npm run llms && npm run build
FROM caddy:2-alpine
COPY Caddyfile /etc/caddy/Caddyfile

View File

@@ -54,4 +54,5 @@ Rules and standards for NextJS and TypeScript development: architecture, typing,
## For Assistants
Full documentation in a single MD file: https://gromlab.ru/docs/frontend-style-guide/raw/branch/main/generated/en/RULES.md
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

@@ -4,7 +4,8 @@
## Для ассистентов
Полная документация в одном MD файле: https://gromlab.ru/docs/nextjs-style-guide/raw/branch/main/generated/ru/RULES.md
Карта документации со ссылками на все разделы (формат [llmstxt.org](https://llmstxt.org)):
https://gromlab.ru/docs/nextjs-style-guide/raw/branch/main/generated/ru/llms.txt
## Структура документации

View File

@@ -1,110 +0,0 @@
import path from "path";
import fs from "fs";
// Явный порядок файлов внутри каждого языка
const fileOrder = [
// index
"index.md",
// workflow
"workflow.md",
// basics
"basics/tech-stack.md",
"basics/naming.md",
"basics/architecture/index.md",
"basics/architecture/reference/layers.md",
"basics/architecture/reference/modules.md",
"basics/architecture/reference/segments.md",
"basics/code-style.md",
"basics/documentation.md",
"basics/typing.md",
// applied
"applied/project-structure.md",
"applied/components.md",
"applied/page-level.md",
"applied/templates-generation.md",
"applied/styles.md",
"applied/images-sprites.md",
"applied/svg-sprites.md",
"applied/video.md",
"applied/api.md",
"applied/stores.md",
"applied/hooks.md",
"applied/fonts.md",
"applied/localization.md",
"applied/vscode.md",
];
// Удалить frontmatter из содержимого md-файла
const stripFrontmatter = (content) =>
content.replace(/^---[\s\S]*?---\n*/m, "");
// Сдвинуть уровень заголовков на 1 вниз (h1→h2, h2→h3, ...)
// Не трогает заголовки внутри блоков кода
const shiftHeadings = (content) => {
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");
};
// Собрать RULES.md с мета-якорями для каждого файла
const buildRules = (lang) => {
const srcDir = `./docs/${lang}`;
const outDir = `./generated/${lang}`;
const outFile = path.join(outDir, "RULES.md");
if (!fs.existsSync(srcDir)) {
console.log(`Пропуск ${lang}: папка ${srcDir} не найдена`);
return;
}
fs.mkdirSync(outDir, { recursive: true });
const parts = [];
for (const file of fileOrder) {
const filePath = path.join(srcDir, file);
if (!fs.existsSync(filePath)) continue;
const raw = fs.readFileSync(filePath, "utf8");
const content = stripFrontmatter(raw).trim();
if (!content) continue;
// Мета-якорь: путь VitePress без расширения
const route = "/" + file.replace(/\.md$/, "");
// index.md остаётся без сдвига (его h1 — главный заголовок документа)
const processed = file === "index.md" ? content : shiftHeadings(content);
parts.push(`<!-- ${route} -->\n${processed}`);
}
fs.writeFileSync(outFile, parts.join("\n\n"), "utf8");
console.log(`RULES.md (${lang}) создан: ${outFile}`);
};
// Собираем RULES.md для обоих языков
buildRules("ru");
buildRules("en");
// Генерируем README из index.md
const buildReadme = (lang, outFile) => {
const indexPath = `./docs/${lang}/index.md`;
if (!fs.existsSync(indexPath)) {
console.log(`Пропуск README (${lang}): ${indexPath} не найден`);
return;
}
const content = stripFrontmatter(fs.readFileSync(indexPath, "utf8"));
fs.writeFileSync(outFile, content, "utf8");
console.log(`${outFile} создан из ${indexPath}`);
};
buildReadme("en", "./README.md");
buildReadme("ru", "./README_RU.md");

View File

@@ -54,4 +54,5 @@ Rules and standards for NextJS and TypeScript development: architecture, typing,
## For Assistants
Full documentation in a single MD file: https://gromlab.ru/docs/frontend-style-guide/raw/branch/main/generated/en/RULES.md
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

372
docs/index.md Normal file
View File

@@ -0,0 +1,372 @@
---
layout: false
---
<script setup>
import { ref, computed, 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 + TypeScript-проектов — чтобы команда писала одинаково, а новые разработчики включались в проект быстрее.',
langLabel: 'Язык',
themeLabel: 'Тема',
themes: { auto: 'Авто', light: 'Светлая', dark: 'Тёмная' },
cards: {
docs: {
title: 'Документация',
desc: 'Все разделы: процессы разработки, базовые правила, прикладные руководства.',
href: './ru/',
cta: 'Открыть',
},
ai: {
title: 'Ассистенту',
desc: 'Карта документации в формате llms.txt для AI-агентов.',
href: './ru/llms.txt',
cta: 'Открыть',
},
zip: {
title: 'Скачать правила',
desc: 'Архив всех Markdown-файлов одним ZIP.',
href: './nextjs-style-guide-ru.zip',
cta: 'Скачать',
},
},
},
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.',
langLabel: 'Language',
themeLabel: 'Theme',
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.',
href: '#',
cta: 'Open',
badge: 'in development',
},
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') {
localStorage.removeItem(THEME_KEY)
} else {
localStorage.setItem(THEME_KEY, value)
}
const isDark = value === 'dark' || (value === 'auto' && matchMedia('(prefers-color-scheme: dark)').matches)
document.documentElement.classList.toggle('dark', isDark)
}
/**
* Клик по кнопке темы:
* - по активной → переключение в auto;
* - по неактивной → выбор этого варианта.
*/
function toggleTheme(value) {
setTheme(theme.value === value ? 'auto' : value)
}
</script>
<div class="landing">
<section class="landing__hero">
<h1 class="landing__title">NextJS Style Guide</h1>
<ClientOnly>
<p class="landing__tagline">{{ t.tagline }}</p>
<div class="landing__controls">
<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">
<button
type="button"
class="seg__btn"
:class="{ 'seg__btn--active': theme === 'light' }"
:aria-pressed="theme === 'light'"
:title="t.themes.light"
@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>
</button>
<button
type="button"
class="seg__btn"
:class="{ 'seg__btn--active': theme === 'dark' }"
:aria-pressed="theme === 'dark'"
:title="t.themes.dark"
@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>
</button>
</div>
</div>
</ClientOnly>
</section>
<ClientOnly>
<section class="landing__cards">
<a
v-for="key in ['docs', 'ai', 'zip']"
:key="key"
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>
</section>
</ClientOnly>
<p class="landing__version">v{{ buildVersion }}</p>
</div>
<style scoped>
.landing {
min-height: 100vh;
padding: 48px 32px;
background: var(--vp-c-bg);
color: var(--vp-c-text-1);
font-family: var(--vp-font-family-base);
display: flex;
flex-direction: column;
justify-content: center;
gap: 64px;
}
.landing__controls {
display: flex;
justify-content: center;
align-items: center;
gap: 12px;
margin-top: 28px;
flex-wrap: wrap;
}
.seg {
display: inline-flex;
align-items: stretch;
padding: 4px;
background: var(--vp-c-bg-soft);
border: 1px solid var(--vp-c-divider);
border-radius: 999px;
gap: 2px;
}
.seg__btn {
appearance: none;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 6px 14px;
font-size: 13px;
font-weight: 500;
font-family: inherit;
line-height: 1;
color: var(--vp-c-text-2);
background: transparent;
border: none;
border-radius: 999px;
cursor: pointer;
transition: color 0.15s, background-color 0.15s;
}
.seg__btn:hover {
color: var(--vp-c-text-1);
}
.seg__btn--active {
background: var(--vp-c-bg);
color: var(--vp-c-text-1);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
}
.seg--icons .seg__btn {
padding: 6px 10px;
}
.seg__btn svg {
display: block;
}
.landing__hero {
text-align: center;
}
.landing__title {
font-size: 56px;
font-weight: 700;
line-height: 1;
margin: 0 0 16px;
letter-spacing: -0.02em;
background: linear-gradient(120deg, var(--vp-c-brand-1), var(--vp-c-brand-2));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.landing__tagline {
font-size: 18px;
line-height: 1.55;
color: var(--vp-c-text-2);
margin: 0 auto;
max-width: 720px;
}
.landing__cards {
max-width: 1100px;
width: 100%;
margin: 0 auto;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}
.landing__card {
display: flex;
flex-direction: column;
gap: 8px;
padding: 24px;
border-radius: 12px;
background: var(--vp-c-bg-soft);
border: 1px solid var(--vp-c-divider);
text-decoration: none;
color: inherit;
transition: border-color 0.2s, transform 0.2s;
}
.landing__card:hover {
border-color: var(--vp-c-brand-1);
transform: translateY(-2px);
}
.landing__card--soon {
opacity: 0.55;
cursor: not-allowed;
pointer-events: none;
}
.landing__card h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: var(--vp-c-text-1);
}
.landing__card p {
margin: 0;
font-size: 14px;
line-height: 1.5;
color: var(--vp-c-text-2);
}
.landing__cta {
margin-top: auto;
font-size: 14px;
font-weight: 500;
color: var(--vp-c-brand-1);
}
.landing__version {
text-align: center;
margin: 24px 0 0;
font-size: 12px;
color: var(--vp-c-text-3);
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: 16px 16px 48px;
gap: 32px;
}
.landing__title {
font-size: 36px;
}
.landing__cards {
grid-template-columns: 1fr;
}
.landing__controls {
gap: 8px;
}
}
</style>

View File

@@ -6,7 +6,7 @@ title: Компоненты
Правила написания React-компонентов: файловая структура модуля, типизация пропсов, документирование и реализация. Раздел охватывает компоненты всех слоёв — от `shared/ui` до `screens`.
Архитектурные слои и их назначение описаны в разделе [Архитектура](/basics/architecture/).
Архитектурные слои и их назначение описаны в разделе [Архитектура](/ru/basics/architecture/).
## Правила организации
@@ -43,7 +43,7 @@ container/
- **`type` вместо `interface`** — гибче для пропсов: поддерживает union, intersection, mapped types. Declaration merging пропсам не нужно.
- **Без `FC`** — неявно добавляет `children`, усложняет дженерики, не даёт преимуществ перед аннотацией параметра.
- **Типы в `types/`, а не в `.tsx`** — предотвращает циклические зависимости (компонент импортирует хук, хук импортирует тип из компонента) и разделяет ответственность: `.tsx` для рендера, `.type.ts` для данных.
- **Без возвращаемого типа** — TypeScript выводит из JSX. Осознанное исключение из [базового правила](/basics/typing).
- **Без возвращаемого типа** — TypeScript выводит из JSX. Осознанное исключение из [базового правила](/ru/basics/typing).
## Реализация

View File

@@ -52,7 +52,7 @@ auth/
└── index.ts # публичный API
```
Подробное описание каждого сегмента — в разделе [Сегменты](/basics/architecture/reference/segments).
Подробное описание каждого сегмента — в разделе [Сегменты](/ru/basics/architecture/reference/segments).
## Публичный API

View File

@@ -13,7 +13,7 @@ title: Технологии и библиотеки
- `Next.js` — для продуктовых сайтов.
### Архитектура
- `SLM Design` — собственная модульная архитектура проекта. Подробнее в разделе [Архитектура](/basics/architecture/).
- `SLM Design` — собственная модульная архитектура проекта. Подробнее в разделе [Архитектура](/ru/basics/architecture/).
### UI компоненты
- `Mantine UI` — базовые UI-компоненты.

View File

@@ -4,7 +4,8 @@
## Для ассистентов
Полная документация в одном MD файле: https://gromlab.ru/docs/nextjs-style-guide/raw/branch/main/generated/ru/RULES.md
Карта документации со ссылками на все разделы (формат [llmstxt.org](https://llmstxt.org)):
https://gromlab.ru/docs/nextjs-style-guide/raw/branch/main/generated/ru/llms.txt
## Структура документации

View File

@@ -28,4 +28,4 @@ title: Генерация кода
- Повторяющаяся структура появляется больше одного раза.
- Существующий шаблон не покрывает нужный тип модуля.
Инструменты и синтаксис шаблонов — [Шаблоны и генерация кода](/applied/templates-generation).
Инструменты и синтаксис шаблонов — [Шаблоны и генерация кода](/ru/applied/templates-generation).

View File

@@ -12,11 +12,11 @@ title: Добавление UI-модуля
## Порядок действий
1. [Сгенерировать](/applied/templates-generation) модуль из соответствующего шаблона в целевой слой.
1. [Сгенерировать](/ru/applied/templates-generation) модуль из соответствующего шаблона в целевой слой.
2. Заполнить модуль логикой и стилями.
## Дочерние компоненты
Если модулю нужны внутренние подкомпоненты — [генерировать](/applied/templates-generation) их из шаблона `component` в папку `ui/` внутри родительского модуля. Дочерние компоненты не экспортируются через `index.ts` родителя.
Если модулю нужны внутренние подкомпоненты — [генерировать](/ru/applied/templates-generation) их из шаблона `component` в папку `ui/` внутри родительского модуля. Дочерние компоненты не экспортируются через `index.ts` родителя.
Правила написания компонентов — [Компоненты](/applied/components).
Правила написания компонентов — [Компоненты](/ru/applied/components).

View File

@@ -12,7 +12,7 @@ title: Добавление страницы
## Порядок действий
1. [Сгенерировать](/applied/templates-generation) экран из шаблона `screen` в папку `src/screens/`.
1. [Сгенерировать](/ru/applied/templates-generation) экран из шаблона `screen` в папку `src/screens/`.
2. Заполнить экран логикой и стилями.
@@ -20,8 +20,8 @@ title: Добавление страницы
## Правила
- Ручное создание файловой структуры экрана запрещено — только [генерация](/applied/templates-generation) из шаблона.
- Ручное создание файловой структуры экрана запрещено — только [генерация](/ru/applied/templates-generation) из шаблона.
- Логика, стили и зависимости размещаются в экране, не в `page.tsx`.
- Каждая страница содержит `metadata` с `title` и `description`.
Примеры `page.tsx` и `metadata` — [Page-level компоненты](/applied/page-level).
Примеры `page.tsx` и `metadata` — [Page-level компоненты](/ru/applied/page-level).

View File

@@ -10,7 +10,7 @@ title: Начало работы
**Next.js** (App Router), **Mantine**, **Zustand**, **SLM Design**.
Подробнее — [Технологии и библиотеки](/basics/tech-stack).
Подробнее — [Технологии и библиотеки](/ru/basics/tech-stack).
## Ключевые особенности
@@ -19,4 +19,4 @@ title: Начало работы
## Настройка окружения
Открыть проект в VS Code и установить рекомендуемые расширения — редактор предложит это автоматически. Подробнее — [Настройка VS Code](/applied/vscode).
Открыть проект в VS Code и установить рекомендуемые расширения — редактор предложит это автоматически. Подробнее — [Настройка VS Code](/ru/applied/vscode).

View File

@@ -20,4 +20,4 @@ title: Стилизация
- **Магические значения** — произвольные цвета, отступы и скругления запрещены, использовать токены.
- **Глобальные стили** вне `app/styles/` запрещены.
Правила написания CSS, вложенность, медиа-запросы и токены — [Стили](/applied/styles).
Правила написания CSS, вложенность, медиа-запросы и токены — [Стили](/ru/applied/styles).

383
generate-llms.ts Normal file
View File

@@ -0,0 +1,383 @@
import path from 'node:path';
import fs from 'node:fs';
import os from 'node:os';
import { execFileSync } from 'node:child_process';
import config from './.vitepress/config';
/** Версия сборки. Передаётся CI через ENV; локально — `dev`. */
const VERSION = process.env.BUILD_VERSION || 'dev';
const BUILD_DATE = new Date().toISOString();
/** Корневая папка для генерируемой статики (попадает в build dist). */
const PUBLIC_DIR = 'docs/public';
type Lang = 'ru' | 'en';
interface SidebarItem {
text: string;
link?: string;
items?: SidebarItem[];
collapsed?: boolean;
}
interface Entry {
/** Название группы верхнего уровня (sidebar[].text) */
section: string;
/** Префикс из вложенной группы (например "Архитектура") */
prefix: string | null;
/** Текст пункта в sidebar */
text: string;
/** Ссылка из sidebar */
link: string;
}
/** Разобрать YAML frontmatter (плоский, без вложенностей) */
const parseFrontmatter = (
content: string,
): { data: Record<string, string>; body: string } => {
const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
if (!match) return { data: {}, body: content };
const data: Record<string, string> = {};
for (const line of match[1].split('\n')) {
const lineMatch = line.match(/^([^:]+):\s*(.*)$/);
if (!lineMatch) continue;
let value = lineMatch[2].trim();
if (
(value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))
) {
value = value.slice(1, -1);
}
data[lineMatch[1].trim()] = value;
}
return { data, body: match[2] };
};
/** Первый абзац после h1 — однострочное описание для llms.txt */
const firstParagraphAfterH1 = (body: string): string | null => {
const lines = body.split('\n');
const h1Idx = lines.findIndex((l) => /^#\s/.test(l));
if (h1Idx === -1) return null;
let i = h1Idx + 1;
while (i < lines.length && lines[i].trim() === '') i++;
const para: string[] = [];
while (
i < lines.length &&
lines[i].trim() !== '' &&
!lines[i].startsWith('#')
) {
para.push(lines[i].trim());
i++;
}
return para.join(' ').trim() || null;
};
/**
* Преобразовать sidebar `link` в относительный путь файла внутри
* `docs/{lang}/`. Sidebar links содержат полный префикс локали
* (`/ru/...`, `/en/...`) — отрезаем его.
*/
const linkToRel = (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;
};
const linkToFilePath = (link: string, lang: Lang): string =>
path.join('docs', lang, linkToRel(link, lang));
/**
* Абсолютный путь от корня сайта к `.md`-копии страницы.
* После build файлы лежат в `dist/{lang}/...md` (через `docs/public/`).
*/
const linkToSiteUrl = (link: string, lang: Lang): string =>
`/${lang}/${linkToRel(link, lang)}`;
/**
* Развернуть sidebar в плоский список с сохранением группы и
* опционального префикса вложенной группы.
*/
const flattenSidebar = (sidebar: SidebarItem[]): Entry[] => {
const entries: Entry[] = [];
for (const top of sidebar) {
const section = top.text;
if (top.link && !top.items) {
entries.push({ section, prefix: null, text: top.text, link: top.link });
continue;
}
if (!top.items) continue;
for (const item of top.items) {
if (item.items) {
for (const sub of item.items) {
if (!sub.link) continue;
entries.push({
section,
prefix: item.text,
text: sub.text,
link: sub.link,
});
}
} else if (item.link) {
entries.push({
section,
prefix: null,
text: item.text,
link: item.link,
});
}
}
}
return entries;
};
const groupBySection = (entries: Entry[]): Map<string, Entry[]> => {
const map = new Map<string, Entry[]>();
for (const entry of entries) {
const list = map.get(entry.section);
if (list) list.push(entry);
else map.set(entry.section, [entry]);
}
return map;
};
const buildLlms = (lang: Lang): void => {
const localeKey = lang;
// VitePress-конфиг типизирован как `UserConfig`, но обращаемся к
// фактически переданным значениям — сужаем тип через any.
const cfg = config as unknown as {
title: string;
description: string;
locales: Record<
string,
{
description?: string;
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 entries = flattenSidebar(sidebar);
const grouped = groupBySection(entries);
const lines: string[] = [];
lines.push(`# ${cfg.title}`);
lines.push('');
lines.push(`> ${blockquote}`);
lines.push('');
if (context) {
lines.push(context);
lines.push('');
}
for (const [section, items] of grouped) {
lines.push(`## ${section}`);
lines.push('');
for (const entry of items) {
const filePath = linkToFilePath(entry.link, lang);
const url = linkToSiteUrl(entry.link, lang);
// Текст ссылки берём из 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}`);
}
const display = entry.prefix
? `${entry.prefix}: ${entry.text}`
: entry.text;
const descPart = description ? `: ${description}` : '';
lines.push(`- [${display}](${url})${descPart}`);
}
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');
console.log(`${outFile} создан`);
};
/** Рекурсивно скопировать дерево, фильтруя по предикату. */
const copyDirSync = (
src: string,
dest: string,
filter: (name: string) => boolean = () => true,
): number => {
let count = 0;
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
const srcPath = path.join(src, entry.name);
const destPath = path.join(dest, entry.name);
if (entry.isDirectory()) {
fs.mkdirSync(destPath, { recursive: true });
count += copyDirSync(srcPath, destPath, filter);
} else if (entry.isFile() && filter(entry.name)) {
fs.mkdirSync(dest, { recursive: true });
fs.copyFileSync(srcPath, destPath);
count++;
}
}
return count;
};
/**
* Скопировать все `.md`-файлы локали в `docs/public/{lang}/`,
* чтобы они попали в build `dist/` и были доступны по URL `/lang/path.md`.
*/
const copyMdFiles = (lang: Lang): void => {
const srcDir = path.join('docs', lang);
const destDir = path.join(PUBLIC_DIR, lang);
if (!fs.existsSync(srcDir)) return;
const copied = copyDirSync(srcDir, destDir, (name) => name.endsWith('.md'));
console.log(`[${lang}] скопировано ${copied} .md-файлов в ${destDir}`);
};
/**
* Собрать `nextjs-style-guide-{lang}.zip` со всеми `.md` локали и `VERSION`.
* Внутри архива — единая папка `nextjs-style-guide/`.
*
* `llms.txt` в архив не кладём: его ссылки указывают на сайт и локально
* не работают. Структура папки сама по себе является картой документации.
*/
const buildZip = (lang: Lang): void => {
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'nsg-'));
const stage = path.join(tmpRoot, 'nextjs-style-guide');
fs.mkdirSync(stage, { recursive: true });
copyDirSync(path.join('docs', lang), stage, (name) => name.endsWith('.md'));
fs.writeFileSync(
path.join(stage, 'VERSION'),
`${VERSION}\n${BUILD_DATE}\n`,
);
const outFile = path.resolve(
PUBLIC_DIR,
`nextjs-style-guide-${lang}.zip`,
);
fs.rmSync(outFile, { force: true });
execFileSync('zip', ['-rq', outFile, 'nextjs-style-guide'], {
cwd: tmpRoot,
});
fs.rmSync(tmpRoot, { recursive: true, force: true });
console.log(`${outFile} создан (${VERSION})`);
};
/** Манифест сборки — для лендинга и внешних потребителей. */
const writeManifest = (): void => {
const manifest = {
version: VERSION,
buildDate: BUILD_DATE,
languages: {
ru: {
llms: '/ru/llms.txt',
zip: '/nextjs-style-guide-ru.zip',
},
en: {
llms: '/en/llms.txt',
zip: '/nextjs-style-guide-en.zip',
},
},
};
fs.mkdirSync(PUBLIC_DIR, { recursive: true });
fs.writeFileSync(
path.join(PUBLIC_DIR, 'manifest.json'),
JSON.stringify(manifest, null, 2),
'utf8',
);
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');
if (!fs.existsSync(indexPath)) {
console.warn(`Пропуск ${outFile}: ${indexPath} не найден`);
return;
}
const raw = fs.readFileSync(indexPath, 'utf8');
const { body } = parseFrontmatter(raw);
fs.writeFileSync(outFile, body.trimStart(), 'utf8');
console.log(`${outFile} обновлён из ${indexPath}`);
};
buildLlms('ru');
buildLlms('en');
buildRootIndex();
copyMdFiles('ru');
copyMdFiles('en');
buildZip('ru');
buildZip('en');
writeManifest();
buildReadme('en', 'README.md');
buildReadme('ru', 'README_RU.md');

View File

@@ -1,132 +0,0 @@
<!-- /index -->
# NextJS Style Guide
Rules and standards for NextJS and TypeScript development: architecture, typing, styles, components, API, and infrastructure.
## 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 FSD 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)_ |
## For Assistants
Full documentation in a single MD file: https://gromlab.ru/docs/frontend-style-guide/raw/branch/main/generated/en/RULES.md
<!-- /basics/tech-stack -->
## Tech Stack
Base technology stack and libraries used in projects.
<!-- /basics/naming -->
## Naming
Naming should be predictable, concise, and reflect the meaning of the entity.
<!-- /basics/code-style -->
## Code Style
Unified code formatting rules: indentation, line breaks, quotes, import order, and readability.
<!-- /basics/documentation -->
## Documentation
Documentation should help understand the purpose of an entity, not duplicate its types or obvious details.
<!-- /basics/typing -->
## Typing
Typing is required for all public interfaces, functions, and components.
<!-- /applied/project-structure -->
## Project Structure
Base project structure and principles of module organization at folder and file level.
<!-- /applied/components -->
## Components
Rules for creating UI components across all FSD layers.
<!-- /applied/page-level -->
## 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`.
<!-- /applied/templates-generation -->
## Templates & Code Generation
Template tools, syntax, and examples for code generation.
<!-- /applied/styles -->
## Styles
CSS writing rules: PostCSS Modules, nesting, media queries, variables, formatting.
<!-- /applied/images-sprites -->
## Images
<!-- /applied/svg-sprites -->
## SVG Sprites
<!-- /applied/video -->
## Video
<!-- /applied/api -->
## API
<!-- /applied/stores -->
## Stores
<!-- /applied/hooks -->
## Hooks
<!-- /applied/fonts -->
## Fonts
<!-- /applied/localization -->
## Localization

File diff suppressed because it is too large Load Diff

528
package-lock.json generated
View File

@@ -8,6 +8,7 @@
"name": "nextjs-style-guide",
"version": "0.0.0",
"devDependencies": {
"tsx": "^4.19.2",
"vitepress": "^1.6.3"
}
},
@@ -643,6 +644,23 @@
"node": ">=12"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz",
"integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
@@ -660,6 +678,23 @@
"node": ">=12"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz",
"integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
@@ -677,6 +712,23 @@
"node": ">=12"
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz",
"integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
@@ -1659,6 +1711,19 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/get-tsconfig": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz",
"integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==",
"dev": true,
"license": "MIT",
"dependencies": {
"resolve-pkg-maps": "^1.0.0"
},
"funding": {
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
"node_modules/hast-util-to-html": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz",
@@ -2088,6 +2153,16 @@
"dev": true,
"license": "MIT"
},
"node_modules/resolve-pkg-maps": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
},
"node_modules/rfdc": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
@@ -2248,6 +2323,459 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/tsx": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "~0.27.0",
"get-tsconfig": "^4.7.5"
},
"bin": {
"tsx": "dist/cli.mjs"
},
"engines": {
"node": ">=18.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
}
},
"node_modules/tsx/node_modules/@esbuild/aix-ppc64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
"integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/android-arm": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz",
"integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/android-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz",
"integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/android-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz",
"integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/darwin-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz",
"integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/darwin-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz",
"integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/freebsd-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz",
"integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/freebsd-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz",
"integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-arm": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz",
"integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz",
"integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-ia32": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz",
"integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-loong64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz",
"integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-mips64el": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz",
"integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-ppc64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz",
"integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-riscv64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz",
"integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-s390x": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz",
"integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz",
"integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/netbsd-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz",
"integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/openbsd-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz",
"integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/sunos-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz",
"integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/win32-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz",
"integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/win32-ia32": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz",
"integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/win32-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz",
"integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/esbuild": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
"integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.27.7",
"@esbuild/android-arm": "0.27.7",
"@esbuild/android-arm64": "0.27.7",
"@esbuild/android-x64": "0.27.7",
"@esbuild/darwin-arm64": "0.27.7",
"@esbuild/darwin-x64": "0.27.7",
"@esbuild/freebsd-arm64": "0.27.7",
"@esbuild/freebsd-x64": "0.27.7",
"@esbuild/linux-arm": "0.27.7",
"@esbuild/linux-arm64": "0.27.7",
"@esbuild/linux-ia32": "0.27.7",
"@esbuild/linux-loong64": "0.27.7",
"@esbuild/linux-mips64el": "0.27.7",
"@esbuild/linux-ppc64": "0.27.7",
"@esbuild/linux-riscv64": "0.27.7",
"@esbuild/linux-s390x": "0.27.7",
"@esbuild/linux-x64": "0.27.7",
"@esbuild/netbsd-arm64": "0.27.7",
"@esbuild/netbsd-x64": "0.27.7",
"@esbuild/openbsd-arm64": "0.27.7",
"@esbuild/openbsd-x64": "0.27.7",
"@esbuild/openharmony-arm64": "0.27.7",
"@esbuild/sunos-x64": "0.27.7",
"@esbuild/win32-arm64": "0.27.7",
"@esbuild/win32-ia32": "0.27.7",
"@esbuild/win32-x64": "0.27.7"
}
},
"node_modules/unist-util-position": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz",

View File

@@ -4,13 +4,14 @@
"type": "module",
"version": "0.0.0",
"scripts": {
"docs": "node ./concat-md.js",
"llms": "tsx ./generate-llms.ts",
"dev": "vitepress dev .",
"build": "vitepress build .",
"serve": "vitepress serve ."
},
"dependencies": {},
"devDependencies": {
"tsx": "^4.19.2",
"vitepress": "^1.6.3"
}
}