366 lines
19 KiB
Python
366 lines
19 KiB
Python
#!/usr/bin/env python3
|
||
"""Generate k3s-test file reference markdown from repo tree."""
|
||
from __future__ import annotations
|
||
|
||
import os
|
||
import re
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parents[4] # k3s-test/
|
||
OUT = Path(__file__).resolve().parent
|
||
|
||
SKIP_PARTS = {
|
||
"node_modules", ".git", "vendor", "dist", ".vitepress/cache",
|
||
".vitepress/dist", ".terraform", ".tmp-chart", ".phpunit",
|
||
}
|
||
SKIP_NAMES = {".DS_Store"}
|
||
|
||
|
||
def should_skip(rel: str) -> bool:
|
||
parts = Path(rel).parts
|
||
if any(p in SKIP_PARTS for p in parts):
|
||
return True
|
||
if Path(rel).name in SKIP_NAMES:
|
||
return True
|
||
if "public/build" in rel:
|
||
return True
|
||
return False
|
||
|
||
|
||
def describe(rel: str) -> str:
|
||
name = Path(rel).name
|
||
p = rel.lower()
|
||
|
||
# Extension-based defaults
|
||
ext = Path(rel).suffix.lower()
|
||
ext_map = {
|
||
".sh": "Shell-скрипт",
|
||
".py": "Python-скрипт",
|
||
".yaml": "YAML-конфиг / Helm / K8s",
|
||
".yml": "YAML-конфиг / docker-compose",
|
||
".md": "Документация Markdown",
|
||
".json": "JSON-данные / конфиг",
|
||
".sql": "SQL (schema или seed)",
|
||
".php": "PHP (Symfony)",
|
||
".twig": "Twig-шаблон",
|
||
".js": "JavaScript",
|
||
".jsx": "React-компонент",
|
||
".scss": "SCSS-стили",
|
||
".css": "CSS-стили",
|
||
".svg": "SVG-иконка / изображение",
|
||
".png": "PNG-изображение",
|
||
".jpg": "JPEG-изображение",
|
||
".ico": "Favicon",
|
||
".pdf": "PDF-документ",
|
||
".pem": "TLS/JWT ключ",
|
||
".tf": "Terraform",
|
||
".hcl": "Terraform lock",
|
||
".tfvars": "Terraform переменные",
|
||
".tpl": "Helm template partial",
|
||
".tgz": "Vendored Helm subchart",
|
||
".lock": "Lockfile (Helm/composer/yarn)",
|
||
".html": "HTML",
|
||
".mts": "TypeScript (VitePress config)",
|
||
".ts": "TypeScript",
|
||
".mjs": "ES module (Node)",
|
||
".env": "Переменные окружения",
|
||
".xml": "XML-конфиг",
|
||
".zip": "Архив",
|
||
".tar": "Docker image tarball",
|
||
".woff": "Web-font",
|
||
".ttf": "TrueType font",
|
||
".eot": "Embedded OpenType font",
|
||
}
|
||
|
||
# Known files
|
||
known = {
|
||
"readme.md": "Главный README k3s-test",
|
||
"plan-sentry-redmine.md": "План Redmine (Sentry удалён из контура)",
|
||
"presentation-practical-guide.md": "Практическое руководство / демо для команды",
|
||
".gitignore": "Git ignore rules",
|
||
"dockerfile": "Docker multistage build",
|
||
"compose.yaml": "Docker Compose (Symfony)",
|
||
"compose.override.yaml": "Локальные overrides Compose",
|
||
"docker-compose.yml": "Docker Compose (моки / cabinet dev)",
|
||
"package.json": "NPM/Yarn зависимости и scripts",
|
||
"composer.json": "PHP Composer зависимости",
|
||
"composer.lock": "Composer lockfile",
|
||
"yarn.lock": "Yarn lockfile",
|
||
"webpack.config.js": "Webpack Encore конфиг",
|
||
"vite.config.js": "Vite bundler конфиг",
|
||
"nginx.conf": "nginx конфиг контейнера",
|
||
"index.php": "HTTP front controller Symfony",
|
||
"index.html": "HTML entry (Vite/React)",
|
||
"console": "Symfony CLI entry",
|
||
"phpunit": "PHPUnit runner wrapper",
|
||
"build.yml": "Gitea Actions CI pipeline",
|
||
"config.mts": "VitePress: nav, sidebar, mermaid",
|
||
"manifest.json": "Метаданные скриншотов",
|
||
"env.js": "Runtime API URL (adminpanel)",
|
||
"entrypoint.sh": "Container entrypoint (env injection)",
|
||
"all.yaml": "Combined Helm template (Deployment/Service/Ingress)",
|
||
"values.yaml": "Базовые Helm values",
|
||
"values-test.yaml": "Overrides для test-контура",
|
||
"values-stage.yaml": "Overrides для stage-контура",
|
||
"chart.yaml": "Helm chart metadata",
|
||
"app-of-apps.yaml": "ArgoCD root Application (sova-root)",
|
||
"platform-tools.yaml": "ArgoCD: monitoring, Gitea Actions, Redmine",
|
||
"test-contour.yaml": "ArgoCD: приложения test-контура",
|
||
"sova-projects.yaml": "ArgoCD GitOps: AppProject definitions",
|
||
"sova-project.yaml": "ArgoCD AppProject RBAC и namespaces",
|
||
"db-init-jobs.yaml": "K8s Jobs: schema → seed SQL",
|
||
"release-tag.sh": "Создать git tag → Gitea CI → GitOps",
|
||
"bootstrap-multipass.sh": "Полный bootstrap VM + k3s + platform",
|
||
"deploy-test-stack.sh": "Деплой приложений test-контура",
|
||
"prepare-db-init.sh": "Генерация schema/seed SQL из monorepo",
|
||
"sync-from-monorepo.sh": "Синхронизация кода из родительского monorepo",
|
||
"use-kubeconfig.sh": "export KUBECONFIG=~/.kube/sova-test-config",
|
||
"print-urls.sh": "URL всех сервисов и пароли platform",
|
||
"smoke-test.sh": "Smoke-проверки HTTP + kubectl",
|
||
"testsdk.js": "Stub WrSDK/SmartCaptcha для *.sova.local",
|
||
"slideshow_controller.js": "Stimulus: lazy import owl.carousel",
|
||
"services_stub.yaml": "Stub-клиенты SMS/Calltouch/Captcha для test",
|
||
"services.php": "PHP DI bindings по APP_ENV",
|
||
}
|
||
if name.lower() in known:
|
||
return known[name.lower()]
|
||
|
||
script_desc = {
|
||
"scripts/bootstrap-argocd.sh": "ArgoCD AppProject + Applications (sova-root, data-test, test-contour)",
|
||
"scripts/bootstrap-gitea-ci-secrets.sh": "Gitea Actions secrets: registry, deploy SSH key",
|
||
"scripts/bootstrap-gitea-runner.sh": "Установка Gitea Actions runner в k3s",
|
||
"scripts/bootstrap-gitea.sh": "Bootstrap Gitea: org sova, repos, admin user",
|
||
"scripts/bootstrap-redmine.sh": "REST API: проект sova-platform + задача #27",
|
||
"scripts/bootstrap-vm.sh": "Generic VM bootstrap helper",
|
||
"scripts/build-images.sh": "Сборка Docker-образов всех приложений (local-test/test tag)",
|
||
"scripts/configure-k3s-registry.sh": "k3s registries.yaml → pull из git.sova.local",
|
||
"scripts/deploy-monitoring-logs.sh": "Helm: Loki + Promtail (логи в Grafana)",
|
||
"scripts/deploy-platform-ingress.sh": "Ingress rules: Gitea, ArgoCD, Grafana, Redmine, docs",
|
||
"scripts/deploy-platform.sh": "Platform stack: ingress, ArgoCD, Gitea, Prometheus/Grafana",
|
||
"scripts/deploy-redmine.sh": "Helm Redmine + ingress + ArgoCD app redmine-test",
|
||
"scripts/deploy-sentry-redmine.sh": "Alias → deploy-redmine.sh (Sentry удалён)",
|
||
"scripts/import-images-to-vm.sh": "docker load .images/*.tar в Multipass VM",
|
||
"scripts/install-k3s-multipass.sh": "Установка k3s внутри Multipass VM",
|
||
"scripts/k3d-bootstrap.sh": "k3d cluster + ingress + apps (Mac без Multipass)",
|
||
"scripts/migrate-monorepo-branch.sh": "Demo: миграция ветки monorepo для issue #27",
|
||
"scripts/prepare-db-init.py": "Split SQL на schema/seed (helper для prepare-db-init.sh)",
|
||
"scripts/print-test-users.sh": "Тестовые логины/пароли приложений",
|
||
"scripts/release-test-tag.sh": "Wrapper → release-tag.sh (backward compat)",
|
||
"scripts/resize-multipass-vm.sh": "Увеличить CPU/RAM Multipass VM",
|
||
"scripts/setup-git-flow-branches.sh": "Создать prod/test/stage в Gitea repos",
|
||
"scripts/setup-gitea-branch-protection.sh": "Branch protection: PR-only на env-ветки",
|
||
"scripts/setup-gitea-redmine-integration.sh": "Инструкции Gitea ↔ Redmine (manual UI)",
|
||
"scripts/capture-platform-screenshots/capture.mjs": "Playwright: снять UI platform screenshots",
|
||
"scripts/capture-platform-screenshots/harness-check.mjs": "Проверка prerequisites screenshot harness",
|
||
"scripts/capture-platform-screenshots/reg-e2e.mjs": "E2E: сценарий регистрации cabinet",
|
||
"scripts/capture-platform-screenshots/run.sh": "Entry point: полный pipeline скриншотов",
|
||
}
|
||
if rel in script_desc:
|
||
return script_desc[rel]
|
||
|
||
if "wiremock/mappings" in p or "charts/mocks/mappings" in p:
|
||
stub = name.replace(".json", "").replace("-", " ")
|
||
return f"WireMock stub: {stub}"
|
||
if "/migrations/version" in p:
|
||
return "Doctrine migration"
|
||
if "/entity/" in p:
|
||
return f"Doctrine entity: {name.replace('.php', '')}"
|
||
if "/repository/" in p:
|
||
return f"Doctrine repository: {name.replace('.php', '')}"
|
||
if "/controller/" in p and ext == ".php":
|
||
return f"Symfony controller: {name.replace('.php', '')}"
|
||
if "/command/" in p and ext == ".php":
|
||
return f"Console command: {name.replace('.php', '')}"
|
||
if "/service/" in p and ext == ".php":
|
||
return f"Service: {name.replace('.php', '')}"
|
||
if "/dto/" in p or "/dto\\" in p:
|
||
return f"DTO: {name.replace('.php', '')}"
|
||
if "/form/" in p and ext == ".php":
|
||
return f"Form type: {name.replace('.php', '')}"
|
||
if "/messagehandler/" in p:
|
||
return f"Messenger handler: {name.replace('.php', '')}"
|
||
if "/message/" in p and ext == ".php":
|
||
return f"Messenger message: {name.replace('.php', '')}"
|
||
if "/controllers/" in p and name.endswith("_controller.js"):
|
||
return f"Stimulus controller: {name.replace('_controller.js', '')}"
|
||
if "/pages/" in p and ext == ".jsx":
|
||
return f"React page: {name.replace('.jsx', '')}"
|
||
if "/api/api" in p and ext == ".js":
|
||
return f"RTK Query API slice: {name.replace('.js', '')}"
|
||
if "/components/" in p and ext == ".jsx":
|
||
return f"React component: {name.replace('.jsx', '')}"
|
||
if "/hooks/" in p and ext == ".jsx":
|
||
return f"React hook: {name.replace('.jsx', '')}"
|
||
if "/templates/" in p and ext == ".twig":
|
||
return f"Twig template: {rel.split('templates/')[-1]}"
|
||
if "/config/packages/" in p:
|
||
return f"Symfony bundle config: {name}"
|
||
if "/assets/sass/" in p:
|
||
return f"SCSS entry: {name}"
|
||
if "/screenshots/" in p and ext == ".png":
|
||
return f"Скриншот UI: {name}"
|
||
if "/forms-screenshots/" in p and ext == ".png":
|
||
return f"Скриншот формы cabinet: {name}"
|
||
if "/backend-scenarios/" in p:
|
||
return f"Сценарий backend: {name.replace('.md', '')}"
|
||
if "/apps/" in p and ext == ".md":
|
||
return f"Документация приложения: {name.replace('.md', '')}"
|
||
if "/infrastructure/" in p and ext == ".md":
|
||
return f"Инфра-документация: {name.replace('.md', '')}"
|
||
if "platform-credentials.env" in p:
|
||
return "Сгенерированные креды platform (не в git)"
|
||
if ".images/" in p and ext == ".tar":
|
||
return f"Saved Docker image для import-images-to-vm.sh"
|
||
if "ci-ssh/" in p:
|
||
return "SSH-ключ Gitea Actions → sova-deploy GitOps"
|
||
|
||
if ext in ext_map:
|
||
return ext_map[ext]
|
||
return "Файл проекта"
|
||
|
||
|
||
def collect(prefix: str) -> list[tuple[str, str]]:
|
||
base = ROOT / prefix if prefix else ROOT
|
||
files: list[tuple[str, str]] = []
|
||
for path in sorted(base.rglob("*")):
|
||
if not path.is_file():
|
||
continue
|
||
rel = path.relative_to(ROOT).as_posix()
|
||
if should_skip(rel):
|
||
continue
|
||
if prefix and not rel.startswith(prefix.rstrip("/")):
|
||
continue
|
||
files.append((rel, describe(rel)))
|
||
return files
|
||
|
||
|
||
def md_table(rows: list[tuple[str, str]], path_col: str = "Путь") -> str:
|
||
if not rows:
|
||
return "_Нет файлов._\n"
|
||
lines = [f"| {path_col} | Назначение |", "|------|------------|"]
|
||
for rel, desc in rows:
|
||
lines.append(f"| `{rel}` | {desc} |")
|
||
return "\n".join(lines) + "\n"
|
||
|
||
|
||
def group_by_dir(files: list[tuple[str, str]]) -> dict[str, list[tuple[str, str]]]:
|
||
groups: dict[str, list[tuple[str, str]]] = {}
|
||
for rel, desc in files:
|
||
parent = str(Path(rel).parent)
|
||
groups.setdefault(parent if parent != "." else "(root)", []).append((rel, desc))
|
||
return dict(sorted(groups.items()))
|
||
|
||
|
||
def write_page(filename: str, title: str, intro: str, files: list[tuple[str, str]], grouped: bool = True) -> None:
|
||
body = [f"# {title}\n", intro, f"\n**Файлов:** {len(files)}\n"]
|
||
if grouped:
|
||
for dirname, rows in group_by_dir(files).items():
|
||
body.append(f"\n## `{dirname}/`\n\n")
|
||
body.append(md_table(rows, "Файл"))
|
||
else:
|
||
body.append("\n")
|
||
body.append(md_table(files))
|
||
(OUT / filename).write_text("\n".join(body), encoding="utf-8")
|
||
|
||
|
||
SECTIONS = [
|
||
("scripts.md", "scripts/", "Скрипты k3s-test", "Все shell/Python/Node скрипты bootstrap, deploy, CI и утилит. Запускать из каталога `k3s-test/`."),
|
||
("sova-deploy.md", "sova-deploy/", "sova-deploy: Helm и ArgoCD", "GitOps-репозиторий: Helm charts приложений, data layer, ArgoCD Applications и platform values."),
|
||
("sova-platform.md", "sova-platform/", "sova-platform: Terraform", "Terraform для Multipass VM и установки k3s на single-node."),
|
||
("sova-mocks.md", "sova-mocks/", "sova-mocks: WireMock и Mailpit", "Моки внешних API (MIS, Calltouch, Captcha) и SMTP для test-контура."),
|
||
("sova-redmine.md", "sova-redmine/", "sova-redmine: custom image", "Опциональный custom Docker-образ Redmine с plugin github_hook."),
|
||
("docs-pointers.md", "docs/", "docs/: указатели", "Короткие markdown-файлы со ссылками на VitePress."),
|
||
("sova-backend.md", "sova-backend/", "sova-backend: Symfony API", "Backend API для adminpanel и интеграций. Копия из monorepo + test-специфичные правки."),
|
||
("sova-adminpanel.md", "sova-adminpanel/", "sova-adminpanel: React admin", "React + Vite admin panel для CRUD контента и специалистов."),
|
||
("sova-cabinet.md", "sova-cabinet/", "sova-cabinet: Symfony ЛК", "Личный кабинет пациента: регистрация, запись, оплата."),
|
||
("sova-docs-repo.md", "sova-docs/", "sova-docs: сайт документации", "VitePress-сайт (монорепо docs + test-contour). Собирается в Docker и деплоится как docs-test."),
|
||
]
|
||
|
||
ROOT_FILES = [
|
||
"README.md", "plan-sentry-redmine.md", "presentation-practical-guide.md", ".gitignore",
|
||
]
|
||
|
||
|
||
def main() -> None:
|
||
for filename, prefix, title, intro in SECTIONS:
|
||
files = collect(prefix)
|
||
write_page(filename, title, intro, files)
|
||
|
||
# Root + generated + images
|
||
root_rows: list[tuple[str, str]] = []
|
||
for name in ROOT_FILES:
|
||
p = ROOT / name
|
||
if p.exists():
|
||
root_rows.append((name, describe(name)))
|
||
for prefix in [".generated/", ".images/"]:
|
||
root_rows.extend(collect(prefix))
|
||
|
||
write_page(
|
||
"root-and-artifacts.md",
|
||
"Корень k3s-test и артеfacts",
|
||
"Файлы в корне `k3s-test/`, сгенерированные креды и tar-образы для offline import.\n\n"
|
||
"> **Не документируем:** `.tmp-chart/` (legacy Sentry chart, 456 upstream-файлов), "
|
||
"`vendor/`, `node_modules/`, `public/build/` (сборка Webpack), `.terraform/providers/`.",
|
||
root_rows,
|
||
grouped=False,
|
||
)
|
||
|
||
# Index
|
||
total = sum(len(collect(p)) for _, p, _, _ in SECTIONS) + len(root_rows)
|
||
index = [
|
||
"# Справочник файлов k3s-test\n",
|
||
"Полный перечень файлов песочницы `k3s-test/` с кратким назначением каждого.",
|
||
"Для понимания **как пользоваться** контуром начните с [обзора test-контура](../) и [статьи «что сделано»](../test-contour-article).",
|
||
"Этот справочник отвечает на вопрос **«что лежит в каком файле»**.\n",
|
||
f"**Документировано файлов:** ~{total} (без vendor/node_modules/dist/.tmp-chart).\n",
|
||
"## Дерево каталогов\n",
|
||
"```",
|
||
"k3s-test/",
|
||
"├── README.md, plan-sentry-redmine.md, presentation-practical-guide.md",
|
||
"├── .generated/ # platform-credentials.env, CI SSH keys",
|
||
"├── .images/ # *.tar для import-images-to-vm.sh",
|
||
"├── docs/ # указатели на VitePress",
|
||
"├── scripts/ # bootstrap, deploy, CI, smoke",
|
||
"├── sova-adminpanel/ # React admin + Gitea CI",
|
||
"├── sova-backend/ # Symfony API + Gitea CI",
|
||
"├── sova-cabinet/ # Symfony ЛК + Gitea CI",
|
||
"├── sova-deploy/ # Helm + ArgoCD GitOps",
|
||
"├── sova-docs/ # VitePress documentation site",
|
||
"├── sova-mocks/ # WireMock + Mailpit",
|
||
"├── sova-platform/ # Terraform k3s bootstrap",
|
||
"└── sova-redmine/ # custom Redmine image (optional)",
|
||
"```\n",
|
||
"## Разделы справочника\n",
|
||
"| Раздел | Файлов | Описание |",
|
||
"|--------|--------|----------|",
|
||
]
|
||
for filename, prefix, title, _ in SECTIONS:
|
||
n = len(collect(prefix))
|
||
link = filename.replace(".md", "")
|
||
index.append(f"| [{title}](./{link}) | {n} | `{prefix}` |")
|
||
index.append(f"| [Корень и артеfacts](./root-and-artifacts) | {len(root_rows)} | README, `.generated/`, `.images/` |")
|
||
index.extend([
|
||
"\n## Типовые сценарии (какие скрипты вызывать)\n",
|
||
"| Сценарий | Команды |",
|
||
"|----------|---------|",
|
||
"| **Первый запуск (Multipass)** | `bootstrap-multipass.sh` → `build-images.sh` → `import-images-to-vm.sh` → `deploy-platform.sh` → `deploy-test-stack.sh` |",
|
||
"| **Первый запуск (k3d на Mac)** | `sync-from-monorepo.sh` → `build-images.sh` → `k3d-bootstrap.sh` |",
|
||
"| **Старт/стоп VM** | `multipass start sova-test` / `multipass stop sova-test` → `source scripts/use-kubeconfig.sh` |",
|
||
"| **Релиз приложения** | `scripts/release-tag.sh {backend\\|adminpanel\\|cabinet\\|docs} {tag} test` |",
|
||
"| **Обновить БД test** | `prepare-db-init.sh` → удалить job db-init → redeploy data-test |",
|
||
"| **Redmine** | `deploy-redmine.sh --bootstrap` |",
|
||
"| **Скриншоты для доков** | `capture-platform-screenshots/run.sh` |",
|
||
"\n## Связанная документация\n",
|
||
"- [Git-flow](../git-flow) — ветки prod/test/stage",
|
||
"- [Система тегов](../tags) — `{component}-v{semver}-{env}`",
|
||
"- [ArgoCD приложения](../argocd-apps) — sova-root, data-test",
|
||
"- [Gitea CI](./guides/gitea-ci) — pipeline и registry",
|
||
"- [Redmine](./guides/redmine) — issue tracker",
|
||
])
|
||
(OUT / "index.md").write_text("\n".join(index), encoding="utf-8")
|
||
print(f"Generated {len(SECTIONS)+2} pages, ~{total} files")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|