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
|
echo "DOCKER_REGISTRY=$DOCKER_REGISTRY" >> $GITHUB_ENV
|
||||||
REGISTRY_IMAGE="$DOCKER_REGISTRY/$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')"
|
REGISTRY_IMAGE="$DOCKER_REGISTRY/$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')"
|
||||||
echo "REGISTRY_IMAGE=$REGISTRY_IMAGE" >> $GITHUB_ENV
|
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
|
- name: Login to Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
@@ -47,6 +50,8 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
build-args: |
|
||||||
|
BUILD_VERSION=${{ env.BUILD_VERSION }}
|
||||||
provenance: false
|
provenance: false
|
||||||
sbom: false
|
sbom: false
|
||||||
|
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -135,3 +135,7 @@ dist
|
|||||||
.vitepress/cache
|
.vitepress/cache
|
||||||
.vitepress/dist
|
.vitepress/dist
|
||||||
docs/.vitepress
|
docs/.vitepress
|
||||||
|
|
||||||
|
# Генерируется через `npm run llms`
|
||||||
|
docs/public/
|
||||||
|
generated/
|
||||||
@@ -3,45 +3,45 @@ import { defineConfig } from 'vitepress';
|
|||||||
const ruSidebar = [
|
const ruSidebar = [
|
||||||
{
|
{
|
||||||
text: 'Workflow',
|
text: 'Workflow',
|
||||||
link: '/workflow',
|
link: '/ru/workflow',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Базовые правила',
|
text: 'Базовые правила',
|
||||||
items: [
|
items: [
|
||||||
{ text: 'Технологии и библиотеки', link: '/basics/tech-stack' },
|
{ text: 'Технологии и библиотеки', link: '/ru/basics/tech-stack' },
|
||||||
{ text: 'Именование', link: '/basics/naming' },
|
{ text: 'Именование', link: '/ru/basics/naming' },
|
||||||
{
|
{
|
||||||
text: 'Архитектура',
|
text: 'Архитектура',
|
||||||
collapsed: true,
|
collapsed: true,
|
||||||
items: [
|
items: [
|
||||||
{ text: 'Обзор', link: '/basics/architecture/' },
|
{ text: 'Обзор', link: '/ru/basics/architecture/' },
|
||||||
{ text: 'Слои', link: '/basics/architecture/reference/layers' },
|
{ text: 'Слои', link: '/ru/basics/architecture/reference/layers' },
|
||||||
{ text: 'Модули', link: '/basics/architecture/reference/modules' },
|
{ text: 'Модули', link: '/ru/basics/architecture/reference/modules' },
|
||||||
{ text: 'Сегменты', link: '/basics/architecture/reference/segments' },
|
{ text: 'Сегменты', link: '/ru/basics/architecture/reference/segments' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{ text: 'Стиль кода', link: '/basics/code-style' },
|
{ text: 'Стиль кода', link: '/ru/basics/code-style' },
|
||||||
{ text: 'Документирование', link: '/basics/documentation' },
|
{ text: 'Документирование', link: '/ru/basics/documentation' },
|
||||||
{ text: 'Типизация', link: '/basics/typing' },
|
{ text: 'Типизация', link: '/ru/basics/typing' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Прикладные разделы',
|
text: 'Прикладные разделы',
|
||||||
items: [
|
items: [
|
||||||
{ text: 'Структура проекта', link: '/applied/project-structure' },
|
{ text: 'Структура проекта', link: '/ru/applied/project-structure' },
|
||||||
{ text: 'Компоненты', link: '/applied/components' },
|
{ text: 'Компоненты', link: '/ru/applied/components' },
|
||||||
{ text: 'Страницы (App Router)', link: '/applied/page-level' },
|
{ text: 'Страницы (App Router)', link: '/ru/applied/page-level' },
|
||||||
{ text: 'Шаблоны и генерация кода', link: '/applied/templates-generation' },
|
{ text: 'Шаблоны и генерация кода', link: '/ru/applied/templates-generation' },
|
||||||
{ text: 'Стили', link: '/applied/styles' },
|
{ text: 'Стили', link: '/ru/applied/styles' },
|
||||||
{ text: 'Изображения', link: '/applied/images-sprites' },
|
{ text: 'Изображения', link: '/ru/applied/images-sprites' },
|
||||||
{ text: 'SVG-спрайты', link: '/applied/svg-sprites' },
|
{ text: 'SVG-спрайты', link: '/ru/applied/svg-sprites' },
|
||||||
{ text: 'Видео', link: '/applied/video' },
|
{ text: 'Видео', link: '/ru/applied/video' },
|
||||||
{ text: 'API', link: '/applied/api' },
|
{ text: 'API', link: '/ru/applied/api' },
|
||||||
{ text: 'Stores', link: '/applied/stores' },
|
{ text: 'Stores', link: '/ru/applied/stores' },
|
||||||
{ text: 'Хуки', link: '/applied/hooks' },
|
{ text: 'Хуки', link: '/ru/applied/hooks' },
|
||||||
{ text: 'Шрифты', link: '/applied/fonts' },
|
{ text: 'Шрифты', link: '/ru/applied/fonts' },
|
||||||
{ text: 'Локализация', link: '/applied/localization' },
|
{ text: 'Локализация', link: '/ru/applied/localization' },
|
||||||
{ text: 'Настройка VS Code', link: '/applied/vscode' },
|
{ 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({
|
export default defineConfig({
|
||||||
srcDir: 'docs',
|
srcDir: 'docs',
|
||||||
|
// `docs/public/` содержит сгенерированные `.md`-копии и `llms.txt` для LLM
|
||||||
|
// (попадают в корень `dist/` как статика). Исключаем из сканирования
|
||||||
|
// страниц, иначе VitePress рендерит их как HTML-страницы.
|
||||||
|
srcExclude: ['public/**'],
|
||||||
title: 'NextJS Style Guide',
|
title: 'NextJS Style Guide',
|
||||||
description: 'Правила и стандарты разработки на NextJS и TypeScript',
|
description: 'Правила и стандарты разработки на NextJS и TypeScript',
|
||||||
|
|
||||||
rewrites: {
|
vite: {
|
||||||
'ru/:rest*': ':rest*',
|
plugins: [utf8TextPlugin],
|
||||||
|
define: {
|
||||||
|
__BUILD_VERSION__: JSON.stringify(process.env.BUILD_VERSION || 'dev'),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
locales: {
|
locales: {
|
||||||
root: {
|
root: {
|
||||||
|
label: 'Languages',
|
||||||
|
lang: 'en',
|
||||||
|
},
|
||||||
|
ru: {
|
||||||
label: 'Русский',
|
label: 'Русский',
|
||||||
lang: 'ru-RU',
|
lang: 'ru-RU',
|
||||||
|
link: '/ru/',
|
||||||
|
description: 'Стандарты разработки на Next.js + TypeScript с архитектурой SLM',
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
sidebar: ruSidebar,
|
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: {
|
en: {
|
||||||
label: 'English',
|
label: 'English',
|
||||||
lang: 'en-US',
|
lang: 'en-US',
|
||||||
link: '/en/',
|
link: '/en/',
|
||||||
|
description: 'Next.js + TypeScript development standards with SLM architecture',
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
sidebar: enSidebar,
|
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.
|
При работе с документацией следовать правилам из CONTRIBUTING.md.
|
||||||
|
|
||||||
- Язык документации и коммитов — русский.
|
- Язык документации и коммитов — русский.
|
||||||
- После изменений в `.md`-файлах — запустить `npm run docs` для обновления RULES.md.
|
- После изменений в `.md`-файлах — запустить `npm run llms` для обновления `llms.txt` и README.
|
||||||
- При добавлении нового раздела — обновить сайдбар (`.vitepress/config.ts`)
|
- При добавлении нового раздела — обновить сайдбар (`.vitepress/config.ts`):
|
||||||
и порядок файлов (`concat-md.js`).
|
он же является источником порядка и группировки для `llms.txt`.
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
|---------|-----------|
|
|---------|-----------|
|
||||||
| `npm run dev` | Локальный сервер разработки |
|
| `npm run dev` | Локальный сервер разработки |
|
||||||
| `npm run build` | Сборка статического сайта |
|
| `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/
|
.vitepress/
|
||||||
├── config.ts # Конфигурация VitePress, сайдбары, локали
|
├── config.ts # Конфигурация VitePress, сайдбары, локали
|
||||||
generated/
|
generated/
|
||||||
├── ru/RULES.md # Сгенерированный единый файл (ru)
|
├── ru/llms.txt # Карта документации для LLM (ru, llmstxt.org)
|
||||||
└── en/RULES.md # Сгенерированный единый файл (en)
|
└── en/llms.txt # Карта документации для LLM (en, llmstxt.org)
|
||||||
concat-md.js # Скрипт генерации RULES.md
|
generate-llms.ts # Скрипт генерации llms.txt и README
|
||||||
```
|
```
|
||||||
|
|
||||||
### Добавление нового раздела
|
### Добавление нового раздела
|
||||||
|
|
||||||
1. Создать `.md`-файл в нужной папке (`basics/` или `applied/`).
|
1. Создать `.md`-файл в нужной папке (`basics/` или `applied/`).
|
||||||
2. Добавить пункт в сайдбар — `.vitepress/config.ts` (оба языка, если есть перевод).
|
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 {
|
:8080 {
|
||||||
root * /srv
|
root * /srv
|
||||||
|
|
||||||
|
# Кириллица в .txt и .md ломается без явного charset
|
||||||
|
@text path *.txt *.md
|
||||||
|
header @text Content-Type "text/plain; charset=utf-8"
|
||||||
|
|
||||||
file_server
|
file_server
|
||||||
try_files {path} /index.html
|
try_files {path} /index.html
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
FROM node:24-alpine AS build
|
FROM node:24-alpine AS build
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
# zip нужен для упаковки nextjs-style-guide-{lang}.zip
|
||||||
|
RUN apk add --no-cache zip
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
RUN npm ci
|
RUN npm ci
|
||||||
COPY . .
|
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
|
FROM caddy:2-alpine
|
||||||
COPY Caddyfile /etc/caddy/Caddyfile
|
COPY Caddyfile /etc/caddy/Caddyfile
|
||||||
|
|||||||
@@ -54,4 +54,5 @@ Rules and standards for NextJS and TypeScript development: architecture, typing,
|
|||||||
|
|
||||||
## For Assistants
|
## 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
|
## 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`.
|
Правила написания React-компонентов: файловая структура модуля, типизация пропсов, документирование и реализация. Раздел охватывает компоненты всех слоёв — от `shared/ui` до `screens`.
|
||||||
|
|
||||||
Архитектурные слои и их назначение описаны в разделе [Архитектура](/basics/architecture/).
|
Архитектурные слои и их назначение описаны в разделе [Архитектура](/ru/basics/architecture/).
|
||||||
|
|
||||||
|
|
||||||
## Правила организации
|
## Правила организации
|
||||||
@@ -43,7 +43,7 @@ container/
|
|||||||
- **`type` вместо `interface`** — гибче для пропсов: поддерживает union, intersection, mapped types. Declaration merging пропсам не нужно.
|
- **`type` вместо `interface`** — гибче для пропсов: поддерживает union, intersection, mapped types. Declaration merging пропсам не нужно.
|
||||||
- **Без `FC`** — неявно добавляет `children`, усложняет дженерики, не даёт преимуществ перед аннотацией параметра.
|
- **Без `FC`** — неявно добавляет `children`, усложняет дженерики, не даёт преимуществ перед аннотацией параметра.
|
||||||
- **Типы в `types/`, а не в `.tsx`** — предотвращает циклические зависимости (компонент импортирует хук, хук импортирует тип из компонента) и разделяет ответственность: `.tsx` для рендера, `.type.ts` для данных.
|
- **Типы в `types/`, а не в `.tsx`** — предотвращает циклические зависимости (компонент импортирует хук, хук импортирует тип из компонента) и разделяет ответственность: `.tsx` для рендера, `.type.ts` для данных.
|
||||||
- **Без возвращаемого типа** — TypeScript выводит из JSX. Осознанное исключение из [базового правила](/basics/typing).
|
- **Без возвращаемого типа** — TypeScript выводит из JSX. Осознанное исключение из [базового правила](/ru/basics/typing).
|
||||||
|
|
||||||
## Реализация
|
## Реализация
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ auth/
|
|||||||
└── index.ts # публичный API
|
└── index.ts # публичный API
|
||||||
```
|
```
|
||||||
|
|
||||||
Подробное описание каждого сегмента — в разделе [Сегменты](/basics/architecture/reference/segments).
|
Подробное описание каждого сегмента — в разделе [Сегменты](/ru/basics/architecture/reference/segments).
|
||||||
|
|
||||||
## Публичный API
|
## Публичный API
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ title: Технологии и библиотеки
|
|||||||
- `Next.js` — для продуктовых сайтов.
|
- `Next.js` — для продуктовых сайтов.
|
||||||
|
|
||||||
### Архитектура
|
### Архитектура
|
||||||
- `SLM Design` — собственная модульная архитектура проекта. Подробнее в разделе [Архитектура](/basics/architecture/).
|
- `SLM Design` — собственная модульная архитектура проекта. Подробнее в разделе [Архитектура](/ru/basics/architecture/).
|
||||||
|
|
||||||
### UI компоненты
|
### UI компоненты
|
||||||
- `Mantine UI` — базовые 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. Заполнить модуль логикой и стилями.
|
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. Заполнить экран логикой и стилями.
|
2. Заполнить экран логикой и стилями.
|
||||||
|
|
||||||
@@ -20,8 +20,8 @@ title: Добавление страницы
|
|||||||
|
|
||||||
## Правила
|
## Правила
|
||||||
|
|
||||||
- Ручное создание файловой структуры экрана запрещено — только [генерация](/applied/templates-generation) из шаблона.
|
- Ручное создание файловой структуры экрана запрещено — только [генерация](/ru/applied/templates-generation) из шаблона.
|
||||||
- Логика, стили и зависимости размещаются в экране, не в `page.tsx`.
|
- Логика, стили и зависимости размещаются в экране, не в `page.tsx`.
|
||||||
- Каждая страница содержит `metadata` с `title` и `description`.
|
- Каждая страница содержит `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**.
|
**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/` запрещены.
|
- **Глобальные стили** вне `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",
|
"name": "nextjs-style-guide",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"tsx": "^4.19.2",
|
||||||
"vitepress": "^1.6.3"
|
"vitepress": "^1.6.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -643,6 +644,23 @@
|
|||||||
"node": ">=12"
|
"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": {
|
"node_modules/@esbuild/netbsd-x64": {
|
||||||
"version": "0.21.5",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
|
||||||
@@ -660,6 +678,23 @@
|
|||||||
"node": ">=12"
|
"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": {
|
"node_modules/@esbuild/openbsd-x64": {
|
||||||
"version": "0.21.5",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
|
||||||
@@ -677,6 +712,23 @@
|
|||||||
"node": ">=12"
|
"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": {
|
"node_modules/@esbuild/sunos-x64": {
|
||||||
"version": "0.21.5",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
|
"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": "^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": {
|
"node_modules/hast-util-to-html": {
|
||||||
"version": "9.0.5",
|
"version": "9.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz",
|
||||||
@@ -2088,6 +2153,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/rfdc": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
|
||||||
@@ -2248,6 +2323,459 @@
|
|||||||
"url": "https://github.com/sponsors/wooorm"
|
"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": {
|
"node_modules/unist-util-position": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz",
|
||||||
|
|||||||
@@ -4,13 +4,14 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"docs": "node ./concat-md.js",
|
"llms": "tsx ./generate-llms.ts",
|
||||||
"dev": "vitepress dev .",
|
"dev": "vitepress dev .",
|
||||||
"build": "vitepress build .",
|
"build": "vitepress build .",
|
||||||
"serve": "vitepress serve ."
|
"serve": "vitepress serve ."
|
||||||
},
|
},
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"tsx": "^4.19.2",
|
||||||
"vitepress": "^1.6.3"
|
"vitepress": "^1.6.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user