fix: исправить доступность артефактов документации
- добавлены HTML-подсказки для обнаружения llms.txt агентами - обновлена карточка скачивания спецификации и архива - добавлен раздел с порядком чтения спецификации - исправлена генерация ссылок для single-file, Markdown и ZIP - обновлены сгенерированные README.md и ARCHITECTURE.md
This commit is contained in:
@@ -20,6 +20,12 @@ export default defineConfig({
|
|||||||
description: 'Правила и стандарты архитектуры проекта',
|
description: 'Правила и стандарты архитектуры проекта',
|
||||||
base: '/docs/',
|
base: '/docs/',
|
||||||
cleanUrls: true,
|
cleanUrls: true,
|
||||||
|
head: [
|
||||||
|
['meta', { name: 'llms', content: '/llms.txt' }],
|
||||||
|
['link', { rel: 'alternate llms', type: 'text/plain', href: '/llms.txt', title: 'llms.txt' }],
|
||||||
|
['link', { rel: 'alternate', type: 'text/plain', href: '/llms-full.txt', title: 'llms-full.txt' }],
|
||||||
|
['link', { rel: 'alternate', type: 'text/markdown', href: '/ARCHITECTURE.md', title: 'ARCHITECTURE.md' }],
|
||||||
|
],
|
||||||
|
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
sidebar,
|
sidebar,
|
||||||
|
|||||||
10
README.md
10
README.md
@@ -1,6 +1,16 @@
|
|||||||
# SLM Design
|
# SLM Design
|
||||||
Scoped Layered Module Design — модульная архитектура фронтенд-приложений. Код организован по слоям ответственности, а модуль содержит всё, что ему нужно: компоненты, хуки, сторы, типы, стили.
|
Scoped Layered Module Design — модульная архитектура фронтенд-приложений. Код организован по слоям ответственности, а модуль содержит всё, что ему нужно: компоненты, хуки, сторы, типы, стили.
|
||||||
|
|
||||||
|
## Разделы спецификации
|
||||||
|
|
||||||
|
Спецификация SLM Design состоит из нескольких связанных разделов. Этот обзор даёт общий контекст, а детальные правила описаны дальше:
|
||||||
|
|
||||||
|
- [Слои](docs/architecture/layers.md) — уровни организации `src/`, направление зависимостей и зона ответственности каждого слоя.
|
||||||
|
- [Модули](docs/architecture/modules.md) — границы ответственности, публичный API, типы модулей и отличие модуля от компонента.
|
||||||
|
- [Сегменты](docs/architecture/segments.md) — внутренние папки модуля (`ui/`, `parts/`, `hooks/`, `types/` и другие) и правила размещения файлов.
|
||||||
|
|
||||||
|
Рекомендуемый порядок чтения: обзор → слои → модули → сегменты.
|
||||||
|
|
||||||
## Преимущества
|
## Преимущества
|
||||||
|
|
||||||
### Вертикальная организация домена
|
### Вертикальная организация домена
|
||||||
|
|||||||
@@ -6,6 +6,16 @@ description: Назначение архитектуры, ключевые пр
|
|||||||
# SLM Design
|
# SLM Design
|
||||||
Scoped Layered Module Design — модульная архитектура фронтенд-приложений. Код организован по слоям ответственности, а модуль содержит всё, что ему нужно: компоненты, хуки, сторы, типы, стили.
|
Scoped Layered Module Design — модульная архитектура фронтенд-приложений. Код организован по слоям ответственности, а модуль содержит всё, что ему нужно: компоненты, хуки, сторы, типы, стили.
|
||||||
|
|
||||||
|
## Разделы спецификации
|
||||||
|
|
||||||
|
Спецификация SLM Design состоит из нескольких связанных разделов. Этот обзор даёт общий контекст, а детальные правила описаны дальше:
|
||||||
|
|
||||||
|
- [Слои](/architecture/layers) — уровни организации `src/`, направление зависимостей и зона ответственности каждого слоя.
|
||||||
|
- [Модули](/architecture/modules) — границы ответственности, публичный API, типы модулей и отличие модуля от компонента.
|
||||||
|
- [Сегменты](/architecture/segments) — внутренние папки модуля (`ui/`, `parts/`, `hooks/`, `types/` и другие) и правила размещения файлов.
|
||||||
|
|
||||||
|
Рекомендуемый порядок чтения: обзор → слои → модули → сегменты.
|
||||||
|
|
||||||
## Преимущества
|
## Преимущества
|
||||||
|
|
||||||
### Вертикальная организация домена
|
### Вертикальная организация домена
|
||||||
|
|||||||
69
generate.ts
69
generate.ts
@@ -65,6 +65,35 @@ function fileRelToMdUrl(file: string): string {
|
|||||||
return `${DOC_ROUTE_PREFIX}/${file}`;
|
return `${DOC_ROUTE_PREFIX}/${file}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ARCHITECTURE_LINK_RE = /\]\((\/architecture(?:\/[^)\s#]*)?)(#[^)\s]*)?\)/g;
|
||||||
|
|
||||||
|
function architectureRouteToFileRel(route: string): string {
|
||||||
|
if (route.replace(/\/$/, "") === "/architecture") return "architecture/index.md";
|
||||||
|
return linkToFileRel(route);
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformArchitectureLinks(
|
||||||
|
content: string,
|
||||||
|
toHref: (route: string, hash: string) => string,
|
||||||
|
): string {
|
||||||
|
return content.replace(ARCHITECTURE_LINK_RE, (_match, route: string, hash = "") => {
|
||||||
|
return `](${toHref(route, hash)})`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformArchiveLinks(content: string): string {
|
||||||
|
return transformArchitectureLinks(content, (route, hash) => {
|
||||||
|
const fileName = path.basename(architectureRouteToFileRel(route));
|
||||||
|
return `./${fileName}${hash}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformSiteMarkdownLinks(content: string): string {
|
||||||
|
return transformArchitectureLinks(content, (route, hash) => {
|
||||||
|
return `${fileRelToMdUrl(architectureRouteToFileRel(route))}${hash}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function getAllFiles(): string[] {
|
function getAllFiles(): string[] {
|
||||||
return SIDEBAR.flatMap((g) => g.items.map((item) => linkToFileRel(item.link)));
|
return SIDEBAR.flatMap((g) => g.items.map((item) => linkToFileRel(item.link)));
|
||||||
}
|
}
|
||||||
@@ -75,6 +104,38 @@ const stripFrontmatter = (content: string) =>
|
|||||||
const stripRulesLink = (content: string) =>
|
const stripRulesLink = (content: string) =>
|
||||||
content.replace(/<!-- rules-link -->[\s\S]*?<!-- \/rules-link -->\n*/g, "");
|
content.replace(/<!-- rules-link -->[\s\S]*?<!-- \/rules-link -->\n*/g, "");
|
||||||
|
|
||||||
|
function slugifyHeading(heading: string): string {
|
||||||
|
return heading
|
||||||
|
.trim()
|
||||||
|
.replace(/[`*_~[\]()]/g, "")
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[^\p{L}\p{N}\s-]/gu, "")
|
||||||
|
.trim()
|
||||||
|
.replace(/\s+/g, "-");
|
||||||
|
}
|
||||||
|
|
||||||
|
function fileRelToSingleFileAnchor(file: string): string {
|
||||||
|
const filePath = path.join(SRC_DIR, file);
|
||||||
|
if (!fs.existsSync(filePath)) return slugifyHeading(path.basename(file, ".md"));
|
||||||
|
|
||||||
|
const raw = stripFrontmatter(fs.readFileSync(filePath, "utf8"));
|
||||||
|
const title = raw.match(/^#\s+(.+)$/m)?.[1];
|
||||||
|
return slugifyHeading(title ?? path.basename(file, ".md"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformSingleFileLinks(content: string): string {
|
||||||
|
return transformArchitectureLinks(content, (route, hash) => {
|
||||||
|
if (hash) return hash;
|
||||||
|
return `#${fileRelToSingleFileAnchor(architectureRouteToFileRel(route))}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformReadmeLinks(content: string): string {
|
||||||
|
return transformArchitectureLinks(content, (route, hash) => {
|
||||||
|
return `docs/${architectureRouteToFileRel(route)}${hash}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const shiftHeadings = (content: string) => {
|
const shiftHeadings = (content: string) => {
|
||||||
const lines = content.split("\n");
|
const lines = content.split("\n");
|
||||||
let inCodeBlock = false;
|
let inCodeBlock = false;
|
||||||
@@ -100,7 +161,8 @@ const buildArchitectureMarkdown = (routePrefix: string) => {
|
|||||||
if (!content) continue;
|
if (!content) continue;
|
||||||
|
|
||||||
const route = routePrefix + fileRelToRoute(file).replace(DOC_ROUTE_PREFIX, "");
|
const route = routePrefix + fileRelToRoute(file).replace(DOC_ROUTE_PREFIX, "");
|
||||||
const processed = file.endsWith("index.md") ? content : shiftHeadings(content);
|
const shifted = file.endsWith("index.md") ? content : shiftHeadings(content);
|
||||||
|
const processed = transformSingleFileLinks(shifted);
|
||||||
parts.push(`<!-- ${route} -->\n${processed}`);
|
parts.push(`<!-- ${route} -->\n${processed}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,9 +213,10 @@ function copyMarkdownFiles() {
|
|||||||
const src = path.join(SRC_DIR, file);
|
const src = path.join(SRC_DIR, file);
|
||||||
if (!fs.existsSync(src)) continue;
|
if (!fs.existsSync(src)) continue;
|
||||||
|
|
||||||
|
const content = transformSiteMarkdownLinks(fs.readFileSync(src, "utf8"));
|
||||||
const dest = path.join(DOCS_PUBLIC_DIR, file);
|
const dest = path.join(DOCS_PUBLIC_DIR, file);
|
||||||
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
||||||
fs.copyFileSync(src, dest);
|
fs.writeFileSync(dest, content, "utf8");
|
||||||
copied++;
|
copied++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,6 +242,7 @@ function buildZip() {
|
|||||||
if (!fs.existsSync(src)) continue;
|
if (!fs.existsSync(src)) continue;
|
||||||
let content = fs.readFileSync(src, "utf8");
|
let content = fs.readFileSync(src, "utf8");
|
||||||
content = stripRulesLink(stripFrontmatter(content)).trim();
|
content = stripRulesLink(stripFrontmatter(content)).trim();
|
||||||
|
content = transformArchiveLinks(content);
|
||||||
const destName = path.basename(file);
|
const destName = path.basename(file);
|
||||||
fs.writeFileSync(path.join(tmpDir, destName), content, "utf8");
|
fs.writeFileSync(path.join(tmpDir, destName), content, "utf8");
|
||||||
}
|
}
|
||||||
@@ -205,6 +269,7 @@ function buildReadme() {
|
|||||||
|
|
||||||
let content = stripFrontmatter(fs.readFileSync(indexPath, "utf8"));
|
let content = stripFrontmatter(fs.readFileSync(indexPath, "utf8"));
|
||||||
content = content.replace(/<!-- rules-link -->[\s\S]*?<!-- \/rules-link -->\n*/g, "");
|
content = content.replace(/<!-- rules-link -->[\s\S]*?<!-- \/rules-link -->\n*/g, "");
|
||||||
|
content = transformReadmeLinks(content);
|
||||||
fs.writeFileSync("./README.md", content, "utf8");
|
fs.writeFileSync("./README.md", content, "utf8");
|
||||||
console.log("README.md создан");
|
console.log("README.md создан");
|
||||||
}
|
}
|
||||||
|
|||||||
20
index.html
20
index.html
@@ -5,10 +5,28 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>SLM Design</title>
|
<title>SLM Design</title>
|
||||||
<meta name="description" content="Scoped Layered Module Design — модульная архитектура фронтенд-приложений" />
|
<meta name="description" content="Scoped Layered Module Design — модульная архитектура фронтенд-приложений" />
|
||||||
|
<meta name="llms" content="/llms.txt" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
|
<link rel="alternate llms" type="text/plain" href="/llms.txt" title="llms.txt" />
|
||||||
|
<link rel="alternate" type="text/plain" href="/llms-full.txt" title="llms-full.txt" />
|
||||||
|
<link rel="alternate" type="text/markdown" href="/ARCHITECTURE.md" title="ARCHITECTURE.md" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root">
|
||||||
|
<main>
|
||||||
|
<h1>SLM Design</h1>
|
||||||
|
<p>Scoped Layered Module Design — модульная архитектура фронтенд-приложений.</p>
|
||||||
|
<nav aria-label="Карта сайта и AI-артефакты">
|
||||||
|
<ul>
|
||||||
|
<li><a href="/docs/">Документация</a></li>
|
||||||
|
<li><a href="/llms.txt" rel="alternate" type="text/plain">llms.txt</a></li>
|
||||||
|
<li><a href="/llms-full.txt" rel="alternate" type="text/plain">llms-full.txt</a></li>
|
||||||
|
<li><a href="/ARCHITECTURE.md" rel="alternate" type="text/markdown">ARCHITECTURE.md</a></li>
|
||||||
|
<li><a href="/slm-design.zip" download>slm-design.zip</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -6,6 +6,16 @@
|
|||||||
# SLM Design
|
# SLM Design
|
||||||
Scoped Layered Module Design — модульная архитектура фронтенд-приложений. Код организован по слоям ответственности, а модуль содержит всё, что ему нужно: компоненты, хуки, сторы, типы, стили.
|
Scoped Layered Module Design — модульная архитектура фронтенд-приложений. Код организован по слоям ответственности, а модуль содержит всё, что ему нужно: компоненты, хуки, сторы, типы, стили.
|
||||||
|
|
||||||
|
## Разделы спецификации
|
||||||
|
|
||||||
|
Спецификация SLM Design состоит из нескольких связанных разделов. Этот обзор даёт общий контекст, а детальные правила описаны дальше:
|
||||||
|
|
||||||
|
- [Слои](#слои) — уровни организации `src/`, направление зависимостей и зона ответственности каждого слоя.
|
||||||
|
- [Модули](#модули) — границы ответственности, публичный API, типы модулей и отличие модуля от компонента.
|
||||||
|
- [Сегменты](#сегменты) — внутренние папки модуля (`ui/`, `parts/`, `hooks/`, `types/` и другие) и правила размещения файлов.
|
||||||
|
|
||||||
|
Рекомендуемый порядок чтения: обзор → слои → модули → сегменты.
|
||||||
|
|
||||||
## Преимущества
|
## Преимущества
|
||||||
|
|
||||||
### Вертикальная организация домена
|
### Вертикальная организация домена
|
||||||
@@ -498,7 +508,7 @@ backend-api/
|
|||||||
└── index.ts # публичный API
|
└── index.ts # публичный API
|
||||||
```
|
```
|
||||||
|
|
||||||
Подробное описание сегментов — в разделе [Сегменты](/architecture/segments).
|
Подробное описание сегментов — в разделе [Сегменты](#сегменты).
|
||||||
|
|
||||||
### Публичный API
|
### Публичный API
|
||||||
|
|
||||||
@@ -675,7 +685,7 @@ export const HomeScreen = () => {
|
|||||||
- Не получает данные самостоятельно, не выбирает источник данных и не композирует данные.
|
- Не получает данные самостоятельно, не выбирает источник данных и не композирует данные.
|
||||||
- Не содержит бизнес-логику или сценарную логику.
|
- Не содержит бизнес-логику или сценарную логику.
|
||||||
|
|
||||||
Если UI-сущности нужно что-то за пределами этих ограничений, она должна быть оформлена как модуль. Полная граница описана в разделе [Компонент](/architecture/modules#компонент).
|
Если UI-сущности нужно что-то за пределами этих ограничений, она должна быть оформлена как модуль. Полная граница описана в разделе [Компонент](#компонент).
|
||||||
|
|
||||||
Корневой файл модуля в `ui/` не размещается. Он лежит в корне модуля: `{module-name}.tsx`.
|
Корневой файл модуля в `ui/` не размещается. Он лежит в корне модуля: `{module-name}.tsx`.
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,19 @@ export const homeCards = [
|
|||||||
cta: 'Открыть →',
|
cta: 'Открыть →',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'ARCHITECTURE.md',
|
title: 'Скачать',
|
||||||
description: 'Полная версия архитектуры в одном файле',
|
description: 'Локальная копия спецификации и архив документации.',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
href: '/ARCHITECTURE.md',
|
href: '/ARCHITECTURE.md',
|
||||||
cta: 'Открыть →',
|
label: 'ARCHITECTURE.md',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: '/slm-design.zip',
|
||||||
|
label: 'slm-design.zip',
|
||||||
|
download: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Ассистенту',
|
title: 'Ассистенту',
|
||||||
|
|||||||
@@ -48,7 +48,12 @@ export function HomeScreen() {
|
|||||||
<p>{card.description}</p>
|
<p>{card.description}</p>
|
||||||
<div className={styles.cardActions}>
|
<div className={styles.cardActions}>
|
||||||
{card.actions.map((action) => (
|
{card.actions.map((action) => (
|
||||||
<a className={styles.cardAction} href={action.href} key={action.href}>
|
<a
|
||||||
|
className={styles.cardAction}
|
||||||
|
download={'download' in action ? action.download : undefined}
|
||||||
|
href={action.href}
|
||||||
|
key={action.href}
|
||||||
|
>
|
||||||
{action.label}
|
{action.label}
|
||||||
</a>
|
</a>
|
||||||
))}
|
))}
|
||||||
|
|||||||
Reference in New Issue
Block a user