fix: исправить ссылки markdown-артефактов
- исправлена генерация ссылок для architecture и examples - сохранена структура папок в архиве slm-design.zip - обновлён сгенерированный ARCHITECTURE.md
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: Монорепозитории
|
title: Монорепозитории
|
||||||
description: "Правила применения SLM Design для frontend-проектов, находящихся в монорепозитории"
|
description: Правила применения SLM Design для frontend-проектов, находящихся в монорепозитории
|
||||||
---
|
---
|
||||||
|
|
||||||
# Монорепозитории
|
# Монорепозитории
|
||||||
|
|||||||
60
generate.ts
60
generate.ts
@@ -65,32 +65,37 @@ function fileRelToMdUrl(file: string): string {
|
|||||||
return `${DOC_ROUTE_PREFIX}/${file}`;
|
return `${DOC_ROUTE_PREFIX}/${file}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ARCHITECTURE_LINK_RE = /\]\((\/architecture(?:\/[^)\s#]*)?)(#[^)\s]*)?\)/g;
|
const DOC_LINK_RE = /\]\((\/(?:architecture|examples)(?:\/[^)\s#]*)?)(#[^)\s]*)?\)/g;
|
||||||
|
|
||||||
function architectureRouteToFileRel(route: string): string {
|
function docRouteToFileRel(route: string): string {
|
||||||
if (route.replace(/\/$/, "") === "/architecture") return "architecture/index.md";
|
if (route.replace(/\/$/, "") === "/architecture") return "architecture/index.md";
|
||||||
|
if (route.replace(/\/$/, "") === "/examples") return "examples/index.md";
|
||||||
return linkToFileRel(route);
|
return linkToFileRel(route);
|
||||||
}
|
}
|
||||||
|
|
||||||
function transformArchitectureLinks(
|
function transformDocLinks(
|
||||||
content: string,
|
content: string,
|
||||||
toHref: (route: string, hash: string) => string,
|
toHref: (route: string, hash: string) => string,
|
||||||
): string {
|
): string {
|
||||||
return content.replace(ARCHITECTURE_LINK_RE, (_match, route: string, hash = "") => {
|
return content.replace(DOC_LINK_RE, (_match, route: string, hash = "") => {
|
||||||
return `](${toHref(route, hash)})`;
|
return `](${toHref(route, hash)})`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function transformArchiveLinks(content: string): string {
|
function formatRelativeMarkdownPath(fromFile: string, toFile: string): string {
|
||||||
return transformArchitectureLinks(content, (route, hash) => {
|
const relative = path.relative(path.dirname(fromFile), toFile).split(path.sep).join("/");
|
||||||
const fileName = path.basename(architectureRouteToFileRel(route));
|
return relative.startsWith(".") ? relative : `./${relative}`;
|
||||||
return `./${fileName}${hash}`;
|
}
|
||||||
|
|
||||||
|
function transformArchiveLinks(content: string, fromFile: string): string {
|
||||||
|
return transformDocLinks(content, (route, hash) => {
|
||||||
|
return `${formatRelativeMarkdownPath(fromFile, docRouteToFileRel(route))}${hash}`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function transformSiteMarkdownLinks(content: string): string {
|
function transformSiteMarkdownLinks(content: string): string {
|
||||||
return transformArchitectureLinks(content, (route, hash) => {
|
return transformDocLinks(content, (route, hash) => {
|
||||||
return `${fileRelToMdUrl(architectureRouteToFileRel(route))}${hash}`;
|
return `${fileRelToMdUrl(docRouteToFileRel(route))}${hash}`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,35 +109,15 @@ 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 {
|
function transformSingleFileLinks(content: string): string {
|
||||||
return transformArchitectureLinks(content, (route, hash) => {
|
return transformDocLinks(content, (route, hash) => {
|
||||||
if (hash) return hash;
|
return `${fileRelToMdUrl(docRouteToFileRel(route))}${hash}`;
|
||||||
return `#${fileRelToSingleFileAnchor(architectureRouteToFileRel(route))}`;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function transformReadmeLinks(content: string): string {
|
function transformReadmeLinks(content: string): string {
|
||||||
return transformArchitectureLinks(content, (route, hash) => {
|
return transformDocLinks(content, (route, hash) => {
|
||||||
return `docs/${architectureRouteToFileRel(route)}${hash}`;
|
return `docs/${docRouteToFileRel(route)}${hash}`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,9 +227,10 @@ 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);
|
content = transformArchiveLinks(content, file);
|
||||||
const destName = path.basename(file);
|
const dest = path.join(tmpDir, file);
|
||||||
fs.writeFileSync(path.join(tmpDir, destName), content, "utf8");
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
||||||
|
fs.writeFileSync(dest, content, "utf8");
|
||||||
}
|
}
|
||||||
|
|
||||||
const pkg = JSON.parse(fs.readFileSync("./package.json", "utf8"));
|
const pkg = JSON.parse(fs.readFileSync("./package.json", "utf8"));
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ Scoped Layered Module Design — модульная архитектура фр
|
|||||||
|
|
||||||
Спецификация SLM Design состоит из нескольких связанных разделов. Этот обзор даёт общий контекст, а детальные правила описаны дальше:
|
Спецификация SLM Design состоит из нескольких связанных разделов. Этот обзор даёт общий контекст, а детальные правила описаны дальше:
|
||||||
|
|
||||||
- [Слои](#слои) — уровни организации `src/`, направление зависимостей и зона ответственности каждого слоя.
|
- [Слои](/docs/architecture/layers.md) — уровни организации `src/`, направление зависимостей и зона ответственности каждого слоя.
|
||||||
- [Модули](#модули) — границы ответственности, публичный API, типы модулей и отличие модуля от компонента.
|
- [Модули](/docs/architecture/modules.md) — границы ответственности, публичный API, типы модулей и отличие модуля от компонента.
|
||||||
- [Сегменты](#сегменты) — внутренние папки модуля (`ui/`, `parts/`, `hooks/`, `types/` и другие) и правила размещения файлов.
|
- [Сегменты](/docs/architecture/segments.md) — внутренние папки модуля (`ui/`, `parts/`, `hooks/`, `types/` и другие) и правила размещения файлов.
|
||||||
- [Монорепозитории](#монорепозитории) — применение SLM в `apps/` и `packages/`, правила выноса общих слоёв и ограничения для business.
|
- [Монорепозитории](/docs/architecture/monorepo.md) — применение SLM в `apps/` и `packages/`, правила выноса общих слоёв и ограничения для business.
|
||||||
|
|
||||||
Рекомендуемый порядок чтения: обзор → слои → модули → сегменты → монорепозитории.
|
Рекомендуемый порядок чтения: обзор → слои → модули → сегменты → монорепозитории.
|
||||||
|
|
||||||
@@ -513,7 +513,7 @@ backend-api/
|
|||||||
└── index.ts # публичный API
|
└── index.ts # публичный API
|
||||||
```
|
```
|
||||||
|
|
||||||
Подробное описание сегментов — в разделе [Сегменты](#сегменты).
|
Подробное описание сегментов — в разделе [Сегменты](/docs/architecture/segments.md).
|
||||||
|
|
||||||
### Публичный API
|
### Публичный API
|
||||||
|
|
||||||
@@ -558,9 +558,9 @@ Business-модуль всегда экспортирует фабрику. Фа
|
|||||||
|
|
||||||
#### Примеры
|
#### Примеры
|
||||||
|
|
||||||
Пример реализации фабрики в React см. в [Создание фабрики](/examples/react/factory).
|
Пример реализации фабрики в React см. в [Создание фабрики](/docs/examples/react/factory.md).
|
||||||
|
|
||||||
Пример композиции фабрик в React screen-модуле см. в [Композиция фабрик](/examples/react/factory-composition).
|
Пример композиции фабрик в React screen-модуле см. в [Композиция фабрик](/docs/examples/react/factory-composition.md).
|
||||||
|
|
||||||
### Жизненный цикл
|
### Жизненный цикл
|
||||||
|
|
||||||
@@ -614,7 +614,7 @@ Business-модуль всегда экспортирует фабрику. Фа
|
|||||||
- Не получает данные самостоятельно, не выбирает источник данных и не композирует данные.
|
- Не получает данные самостоятельно, не выбирает источник данных и не композирует данные.
|
||||||
- Не содержит бизнес-логику или сценарную логику.
|
- Не содержит бизнес-логику или сценарную логику.
|
||||||
|
|
||||||
Если UI-сущности нужно что-то за пределами этих ограничений, она должна быть оформлена как модуль. Полная граница описана в разделе [Компонент](#компонент).
|
Если UI-сущности нужно что-то за пределами этих ограничений, она должна быть оформлена как модуль. Полная граница описана в разделе [Компонент](/docs/architecture/modules.md#компонент).
|
||||||
|
|
||||||
Корневой файл модуля в `ui/` не размещается. Он лежит в корне модуля: `{module-name}.tsx`.
|
Корневой файл модуля в `ui/` не размещается. Он лежит в корне модуля: `{module-name}.tsx`.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user