#!/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()