feat: генерация llms.txt, лендинг с выбором языка и ZIP-архивов
- удалён concat-md.js: вместо единого RULES.md теперь llms.txt
- добавлен generate-llms.ts: собирает llms.txt из sidebar config, копирует
.md-файлы для отдачи LLM и упаковывает ZIP-архивы по локалям
- добавлен корневой /llms.txt как роутер на /ru/llms.txt и /en/llms.txt
- добавлен манифест /manifest.json со ссылками и версией сборки
- добавлен лендинг docs/index.md (layout: false) с автоопределением
языка, переключателями языка и темы
- английская локаль временно заблокирована: карточки как заглушки,
ссылка на /en/ в роутере без href
- добавлены поля llmsBlockquote и llmsContext в локали для
технодокументационного описания в llms.txt
- разделены VitePress-локали: root (только лендинг), ru (/ru/), en (/en/)
- добавлен srcExclude: ['public/**'] чтобы VitePress не рендерил
сгенерированные .md как страницы
- добавлен Vite-плагин для отдачи .txt и .md с charset=utf-8
- добавлена секция в Caddyfile для текстовых файлов
- BUILD_VERSION пробрасывается из Gitea CI через docker --build-arg
и подставляется в лендинг через Vite define
- Dockerfile: установка zip, npm run llms перед npm run build
- обновлены внутренние ссылки в docs/ru/**/*.md на префикс /ru/
- обновлены AGENTS.md и CONTRIBUTING.md под новый процесс
- README/README_RU генерируются из docs/{lang}/index.md, остаются в репо
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -134,4 +134,8 @@ dist
|
||||
# VitePress
|
||||
.vitepress/cache
|
||||
.vitepress/dist
|
||||
docs/.vitepress
|
||||
docs/.vitepress
|
||||
|
||||
# Генерируется через `npm run llms`
|
||||
docs/public/
|
||||
generated/
|
||||
@@ -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.',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
## Два типа документации
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
## Структура документации
|
||||
|
||||
|
||||
110
concat-md.js
110
concat-md.js
@@ -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");
|
||||
@@ -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
372
docs/index.md
Normal 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>
|
||||
@@ -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).
|
||||
|
||||
## Реализация
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ auth/
|
||||
└── index.ts # публичный API
|
||||
```
|
||||
|
||||
Подробное описание каждого сегмента — в разделе [Сегменты](/basics/architecture/reference/segments).
|
||||
Подробное описание каждого сегмента — в разделе [Сегменты](/ru/basics/architecture/reference/segments).
|
||||
|
||||
## Публичный API
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ title: Технологии и библиотеки
|
||||
- `Next.js` — для продуктовых сайтов.
|
||||
|
||||
### Архитектура
|
||||
- `SLM Design` — собственная модульная архитектура проекта. Подробнее в разделе [Архитектура](/basics/architecture/).
|
||||
- `SLM Design` — собственная модульная архитектура проекта. Подробнее в разделе [Архитектура](/ru/basics/architecture/).
|
||||
|
||||
### UI компоненты
|
||||
- `Mantine UI` — базовые UI-компоненты.
|
||||
|
||||
@@ -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
|
||||
|
||||
## Структура документации
|
||||
|
||||
|
||||
@@ -28,4 +28,4 @@ title: Генерация кода
|
||||
- Повторяющаяся структура появляется больше одного раза.
|
||||
- Существующий шаблон не покрывает нужный тип модуля.
|
||||
|
||||
Инструменты и синтаксис шаблонов — [Шаблоны и генерация кода](/applied/templates-generation).
|
||||
Инструменты и синтаксис шаблонов — [Шаблоны и генерация кода](/ru/applied/templates-generation).
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -20,4 +20,4 @@ title: Стилизация
|
||||
- **Магические значения** — произвольные цвета, отступы и скругления запрещены, использовать токены.
|
||||
- **Глобальные стили** вне `app/styles/` запрещены.
|
||||
|
||||
Правила написания CSS, вложенность, медиа-запросы и токены — [Стили](/applied/styles).
|
||||
Правила написания CSS, вложенность, медиа-запросы и токены — [Стили](/ru/applied/styles).
|
||||
|
||||
383
generate-llms.ts
Normal file
383
generate-llms.ts
Normal 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');
|
||||
@@ -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
528
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user