docs: inventory current application architecture
This commit is contained in:
@@ -0,0 +1,317 @@
|
|||||||
|
{
|
||||||
|
"schema_version": 1,
|
||||||
|
"captured_from": "323c734",
|
||||||
|
"runtime": {
|
||||||
|
"http_server": "http.server.ThreadingHTTPServer",
|
||||||
|
"application_processes": 1,
|
||||||
|
"database": "SQLite WAL",
|
||||||
|
"frontend": "build-free HTML/CSS/JavaScript",
|
||||||
|
"container_port": 8765
|
||||||
|
},
|
||||||
|
"counts": {
|
||||||
|
"primary_pages": 16,
|
||||||
|
"api_exact_paths": 53,
|
||||||
|
"api_prefixes": 0,
|
||||||
|
"api_patterns": 11,
|
||||||
|
"database_tables": 34
|
||||||
|
},
|
||||||
|
"pages": [
|
||||||
|
{
|
||||||
|
"id": "sentimentCycleView",
|
||||||
|
"title": "情绪周期"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "limitPool",
|
||||||
|
"title": "涨停池"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "brokenView",
|
||||||
|
"title": "炸板池"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "downView",
|
||||||
|
"title": "跌停板"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "yesterdayView",
|
||||||
|
"title": "昨日涨停"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "performanceView",
|
||||||
|
"title": "涨停表现"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ladderView",
|
||||||
|
"title": "市场天梯"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rotationView",
|
||||||
|
"title": "板块轮动"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "auctionView",
|
||||||
|
"title": "集合竞价"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "themeLibraryView",
|
||||||
|
"title": "题材库"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "popularityView",
|
||||||
|
"title": "人气热榜"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dragonView",
|
||||||
|
"title": "龙虎榜"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "screenerView",
|
||||||
|
"title": "智能选股"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mentorView",
|
||||||
|
"title": "问师"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "heavenView",
|
||||||
|
"title": "问天"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reviewWorkspaceView",
|
||||||
|
"title": "我的复盘"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"api": {
|
||||||
|
"exact": [
|
||||||
|
"/api/account/birth-profile",
|
||||||
|
"/api/account/password",
|
||||||
|
"/api/account/status",
|
||||||
|
"/api/admin/membership",
|
||||||
|
"/api/admin/refresh",
|
||||||
|
"/api/admin/settings",
|
||||||
|
"/api/admin/settings/test",
|
||||||
|
"/api/alerts",
|
||||||
|
"/api/alerts/read-all",
|
||||||
|
"/api/assistant/chat",
|
||||||
|
"/api/assistant/messages",
|
||||||
|
"/api/auction",
|
||||||
|
"/api/auth/login",
|
||||||
|
"/api/auth/logout",
|
||||||
|
"/api/auth/me",
|
||||||
|
"/api/auth/register",
|
||||||
|
"/api/backfill",
|
||||||
|
"/api/chart/intraday",
|
||||||
|
"/api/dashboard",
|
||||||
|
"/api/dragon-tiger",
|
||||||
|
"/api/dragon-tiger/profiles",
|
||||||
|
"/api/health",
|
||||||
|
"/api/heaven/hexagram",
|
||||||
|
"/api/heaven/interpret",
|
||||||
|
"/api/heaven/personal",
|
||||||
|
"/api/heaven/readings",
|
||||||
|
"/api/heaven/sector-phases",
|
||||||
|
"/api/heaven/setup",
|
||||||
|
"/api/mentors/chat",
|
||||||
|
"/api/mentors/messages",
|
||||||
|
"/api/mentors/preferences",
|
||||||
|
"/api/mentors/setup",
|
||||||
|
"/api/notes",
|
||||||
|
"/api/popularity",
|
||||||
|
"/api/realtime-aggregate/health",
|
||||||
|
"/api/reasons",
|
||||||
|
"/api/rotation/history",
|
||||||
|
"/api/rotation/members",
|
||||||
|
"/api/screener/compile",
|
||||||
|
"/api/screener/run",
|
||||||
|
"/api/screener/setup",
|
||||||
|
"/api/screener/strategies",
|
||||||
|
"/api/screener/sync",
|
||||||
|
"/api/screener/tracking",
|
||||||
|
"/api/screener/tracking/refresh",
|
||||||
|
"/api/search",
|
||||||
|
"/api/search/detail",
|
||||||
|
"/api/seat-aliases",
|
||||||
|
"/api/sentiment/history",
|
||||||
|
"/api/themes",
|
||||||
|
"/api/themes/detail",
|
||||||
|
"/api/trades",
|
||||||
|
"/api/watchlist"
|
||||||
|
],
|
||||||
|
"prefixes": [],
|
||||||
|
"patterns": [
|
||||||
|
"/api/alerts/(\\d+)",
|
||||||
|
"/api/alerts/(\\d+)/read",
|
||||||
|
"/api/heaven/readings/(\\d+)",
|
||||||
|
"/api/heaven/sector-phases/(.+)",
|
||||||
|
"/api/notes/(\\d+)",
|
||||||
|
"/api/screener/strategies/(\\d+)",
|
||||||
|
"/api/screener/tracking/(\\d+)",
|
||||||
|
"/api/stock/(\\d{6})",
|
||||||
|
"/api/stock/(\\d{6})/preview",
|
||||||
|
"/api/trades/(\\d+)",
|
||||||
|
"/api/watchlist/(\\d{6})"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"database_tables": [
|
||||||
|
"users",
|
||||||
|
"user_sessions",
|
||||||
|
"user_credentials",
|
||||||
|
"user_birth_profiles",
|
||||||
|
"system_settings",
|
||||||
|
"llm_usage",
|
||||||
|
"dashboard_snapshots",
|
||||||
|
"sync_runs",
|
||||||
|
"data_snapshots",
|
||||||
|
"watchlist",
|
||||||
|
"review_notes",
|
||||||
|
"reason_overrides",
|
||||||
|
"seat_aliases",
|
||||||
|
"sector_phase_overrides",
|
||||||
|
"stock_master",
|
||||||
|
"daily_bars",
|
||||||
|
"benchmark_bars",
|
||||||
|
"daily_indicators",
|
||||||
|
"fundamental_indicators",
|
||||||
|
"moneyflow_daily",
|
||||||
|
"auction_factors",
|
||||||
|
"earnings_events",
|
||||||
|
"popularity_factors",
|
||||||
|
"lhb_institution_daily",
|
||||||
|
"screener_strategies",
|
||||||
|
"screener_runs",
|
||||||
|
"mentor_messages",
|
||||||
|
"mentor_preferences",
|
||||||
|
"wencai_saved_queries",
|
||||||
|
"strategy_tracks",
|
||||||
|
"alerts",
|
||||||
|
"trade_entries",
|
||||||
|
"assistant_messages",
|
||||||
|
"heaven_readings"
|
||||||
|
],
|
||||||
|
"background_job_methods": [
|
||||||
|
"_background_refresh_loop",
|
||||||
|
"_run_background_sync",
|
||||||
|
"_schedule_automatic_screeners",
|
||||||
|
"_schedule_ifind_event_enrichment",
|
||||||
|
"run_automatic_screeners"
|
||||||
|
],
|
||||||
|
"external_data_adapters": [
|
||||||
|
{
|
||||||
|
"provider": "tushare",
|
||||||
|
"path": "tushare_client.py",
|
||||||
|
"runtime_role": "primary deterministic market data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"provider": "ifind",
|
||||||
|
"path": "ifind_client.py",
|
||||||
|
"runtime_role": "realtime, charts, snapshots, enrichment"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"provider": "eastmoney",
|
||||||
|
"path": "chart_data_provider.py",
|
||||||
|
"runtime_role": "display chart fallback"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"provider": "eastmoney",
|
||||||
|
"path": "realtime_aggregator.py",
|
||||||
|
"runtime_role": "isolated realtime observation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"provider": "tencent",
|
||||||
|
"path": "realtime_aggregator.py",
|
||||||
|
"runtime_role": "index observation fallback"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"llm_entrypoints": [
|
||||||
|
{
|
||||||
|
"function": "stream_with_mentor",
|
||||||
|
"path": "mentor_agent.py"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"function": "interpret_heaven",
|
||||||
|
"path": "heaven_agent.py"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"function": "stream_review_assistant",
|
||||||
|
"path": "assistant_agent.py"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"function": "compile_strategy_with_llm",
|
||||||
|
"path": "llm_strategy.py"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"function": "test_llm_connection",
|
||||||
|
"path": "llm_strategy.py"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"css_layers": [
|
||||||
|
"/styles.css",
|
||||||
|
"/renovation.css?v=20260725-5",
|
||||||
|
"/redesign-v2.css?v=20260728-1",
|
||||||
|
"/design-system.css?v=20260728-4",
|
||||||
|
"/theme.css?v=20260728-2",
|
||||||
|
"/wentian-v2.css?v=20260728-7"
|
||||||
|
],
|
||||||
|
"code_hotspots": [
|
||||||
|
{
|
||||||
|
"path": "static/app.js",
|
||||||
|
"bytes": 449457,
|
||||||
|
"lines": 9447
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "static/styles.css",
|
||||||
|
"bytes": 364385,
|
||||||
|
"lines": 15549
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "server.py",
|
||||||
|
"bytes": 269167,
|
||||||
|
"lines": 5965
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "static/redesign-v2.css",
|
||||||
|
"bytes": 264960,
|
||||||
|
"lines": 8616
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "static/index.html",
|
||||||
|
"bytes": 133569,
|
||||||
|
"lines": 1871
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "database.py",
|
||||||
|
"bytes": 121468,
|
||||||
|
"lines": 2839
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "screener.py",
|
||||||
|
"bytes": 108535,
|
||||||
|
"lines": 2213
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "tushare_client.py",
|
||||||
|
"bytes": 94312,
|
||||||
|
"lines": 2175
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "static/renovation.css",
|
||||||
|
"bytes": 86245,
|
||||||
|
"lines": 1636
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "static/wentian-v2.css",
|
||||||
|
"bytes": 73222,
|
||||||
|
"lines": 1084
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "market_insights.py",
|
||||||
|
"bytes": 58066,
|
||||||
|
"lines": 1312
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "static/theme.css",
|
||||||
|
"bytes": 41279,
|
||||||
|
"lines": 1407
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# Stage 02: Architecture Inventory
|
||||||
|
|
||||||
|
Date: 2026-07-29
|
||||||
|
|
||||||
|
The machine-readable inventory is `architecture-inventory.json`. Regenerate it with:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python tools/build_architecture_inventory.py
|
||||||
|
python tools/build_architecture_inventory.py --check
|
||||||
|
```
|
||||||
|
|
||||||
|
## Current Shape
|
||||||
|
|
||||||
|
- The product is a single-container modular monolith with one Python process and SQLite WAL.
|
||||||
|
- The browser is a build-free single-page application served by the same process.
|
||||||
|
- The primary navigation exposes 16 workspaces.
|
||||||
|
- The current database bootstrap defines 34 tables.
|
||||||
|
- HTTP routing and service orchestration are concentrated in `server.py`.
|
||||||
|
- Schema creation, inline migrations, and all persistence methods are concentrated in
|
||||||
|
`database.py`.
|
||||||
|
- The frontend loads six ordered CSS layers and one global application script.
|
||||||
|
- Background refresh and automatic screener work execute as daemon threads in the HTTP
|
||||||
|
process.
|
||||||
|
|
||||||
|
## Ownership Inventory
|
||||||
|
|
||||||
|
### Shared market and system data
|
||||||
|
|
||||||
|
Dashboard snapshots, synchronization runs, stock master data, daily bars, benchmark bars,
|
||||||
|
daily indicators, fundamental indicators, money flow, auction factors, earnings events,
|
||||||
|
popularity factors, institutional list data, public screener strategies, reason overrides,
|
||||||
|
seat aliases, sector phase overrides, and encrypted system settings are shared.
|
||||||
|
|
||||||
|
### User-owned data
|
||||||
|
|
||||||
|
Sessions, personal credentials, birth profiles, watchlists, review notes, custom strategies,
|
||||||
|
private screener runs, mentor messages and preferences, saved queries, tracking entries,
|
||||||
|
alerts, trades, assistant messages, and heaven readings are user-scoped. Every repository
|
||||||
|
extracted in later stages must preserve this boundary explicitly.
|
||||||
|
|
||||||
|
## Runtime Entrypoints
|
||||||
|
|
||||||
|
- HTTP and application bootstrap: `server.py`
|
||||||
|
- Persistence and migrations: `database.py`
|
||||||
|
- Access policy: `api_access.py`
|
||||||
|
- Market providers: `tushare_client.py`, `ifind_client.py`, `chart_data_provider.py`,
|
||||||
|
`realtime_aggregator.py`
|
||||||
|
- Deterministic computation: `sentiment_engine.py`, `screener.py`, `advanced_strategies.py`,
|
||||||
|
`market_insights.py`, `heaven_engine.py`
|
||||||
|
- LLM calls: `mentor_agent.py`, `heaven_agent.py`, `assistant_agent.py`, `llm_strategy.py`
|
||||||
|
- Browser shell and pages: `static/index.html`, `static/app.js`, and the ordered CSS files
|
||||||
|
|
||||||
|
## Primary Structural Risks
|
||||||
|
|
||||||
|
1. Request routing, use cases, scheduling, provider selection, serialization, and account
|
||||||
|
context coexist in one service module.
|
||||||
|
2. Database migrations cannot be reviewed independently from repository behavior.
|
||||||
|
3. Several business paths instantiate provider clients directly, so source policy is not
|
||||||
|
centrally enforceable.
|
||||||
|
4. LLM authorization, quota accounting, model selection, streaming, and error handling are
|
||||||
|
distributed across multiple adapters.
|
||||||
|
5. Global DOM state and page behavior share one JavaScript file, increasing cross-page
|
||||||
|
regression risk.
|
||||||
|
6. CSS correctness depends on load order and late overrides rather than explicit layer
|
||||||
|
ownership.
|
||||||
|
7. In-process daemon jobs have no durable queue and some failure paths are not observable.
|
||||||
|
|
||||||
|
## Inventory Limits
|
||||||
|
|
||||||
|
The JSON inventory records source-level declarations and references. It does not claim that
|
||||||
|
every declared endpoint is exercised or every table is populated. Runtime coverage and data
|
||||||
|
quality are separate contracts introduced in later governance stages.
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import ast
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
OUTPUT = ROOT / "docs" / "governance" / "architecture-inventory.json"
|
||||||
|
|
||||||
|
|
||||||
|
def relative(path: Path) -> str:
|
||||||
|
return path.relative_to(ROOT).as_posix()
|
||||||
|
|
||||||
|
|
||||||
|
def source(path: str) -> str:
|
||||||
|
return (ROOT / path).read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def page_inventory(html: str) -> list[dict[str, str]]:
|
||||||
|
items: list[dict[str, str]] = []
|
||||||
|
seen: set[str] = set()
|
||||||
|
pattern = re.compile(
|
||||||
|
r'<button[^>]+data-view="(?P<id>[^"]+)"[^>]+title="(?P<title>[^"]+)"',
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
|
for match in pattern.finditer(html):
|
||||||
|
page_id = match.group("id")
|
||||||
|
if page_id in seen:
|
||||||
|
continue
|
||||||
|
seen.add(page_id)
|
||||||
|
items.append({"id": page_id, "title": match.group("title")})
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
def api_inventory(server: str) -> dict[str, list[str]]:
|
||||||
|
exact = sorted(set(re.findall(r'parsed\.path\s*==\s*"(/api/[^"]+)"', server)))
|
||||||
|
prefixes = sorted(
|
||||||
|
set(re.findall(r'parsed\.path\.startswith\(\s*"(/api/[^"]+)"', server))
|
||||||
|
)
|
||||||
|
patterns = sorted(
|
||||||
|
set(
|
||||||
|
item
|
||||||
|
for item in re.findall(r'r?["\']([^"\']*?/api/[^"\']+)["\']', server)
|
||||||
|
if "\\d" in item or ".+" in item or "(?P" in item
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return {"exact": exact, "prefixes": prefixes, "patterns": patterns}
|
||||||
|
|
||||||
|
|
||||||
|
def database_inventory(database: str) -> list[str]:
|
||||||
|
return re.findall(r"CREATE TABLE IF NOT EXISTS\s+([a-zA-Z0-9_]+)", database)
|
||||||
|
|
||||||
|
|
||||||
|
def python_functions(path: str, prefixes: tuple[str, ...]) -> list[str]:
|
||||||
|
tree = ast.parse(source(path), filename=path)
|
||||||
|
result: list[str] = []
|
||||||
|
for node in ast.walk(tree):
|
||||||
|
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name.startswith(prefixes):
|
||||||
|
result.append(node.name)
|
||||||
|
return sorted(set(result))
|
||||||
|
|
||||||
|
|
||||||
|
def css_layers(html: str) -> list[str]:
|
||||||
|
return re.findall(r'<link[^>]+rel="stylesheet"[^>]+href="([^"]+)"', html)
|
||||||
|
|
||||||
|
|
||||||
|
def code_hotspots() -> list[dict[str, Any]]:
|
||||||
|
candidates = [
|
||||||
|
"server.py",
|
||||||
|
"database.py",
|
||||||
|
"screener.py",
|
||||||
|
"market_insights.py",
|
||||||
|
"tushare_client.py",
|
||||||
|
"static/index.html",
|
||||||
|
"static/app.js",
|
||||||
|
"static/styles.css",
|
||||||
|
"static/redesign-v2.css",
|
||||||
|
"static/renovation.css",
|
||||||
|
"static/theme.css",
|
||||||
|
"static/wentian-v2.css",
|
||||||
|
]
|
||||||
|
rows = []
|
||||||
|
for name in candidates:
|
||||||
|
path = ROOT / name
|
||||||
|
rows.append(
|
||||||
|
{
|
||||||
|
"path": name,
|
||||||
|
"bytes": path.stat().st_size,
|
||||||
|
"lines": len(path.read_text(encoding="utf-8").splitlines()),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return sorted(rows, key=lambda item: item["bytes"], reverse=True)
|
||||||
|
|
||||||
|
|
||||||
|
def build() -> dict[str, Any]:
|
||||||
|
html = source("static/index.html")
|
||||||
|
server = source("server.py")
|
||||||
|
database = source("database.py")
|
||||||
|
pages = page_inventory(html)
|
||||||
|
api = api_inventory(server)
|
||||||
|
tables = database_inventory(database)
|
||||||
|
return {
|
||||||
|
"schema_version": 1,
|
||||||
|
"captured_from": "323c734",
|
||||||
|
"runtime": {
|
||||||
|
"http_server": "http.server.ThreadingHTTPServer",
|
||||||
|
"application_processes": 1,
|
||||||
|
"database": "SQLite WAL",
|
||||||
|
"frontend": "build-free HTML/CSS/JavaScript",
|
||||||
|
"container_port": 8765,
|
||||||
|
},
|
||||||
|
"counts": {
|
||||||
|
"primary_pages": len(pages),
|
||||||
|
"api_exact_paths": len(api["exact"]),
|
||||||
|
"api_prefixes": len(api["prefixes"]),
|
||||||
|
"api_patterns": len(api["patterns"]),
|
||||||
|
"database_tables": len(tables),
|
||||||
|
},
|
||||||
|
"pages": pages,
|
||||||
|
"api": api,
|
||||||
|
"database_tables": tables,
|
||||||
|
"background_job_methods": python_functions(
|
||||||
|
"server.py", ("_background", "_run_background", "_schedule_", "run_automatic")
|
||||||
|
),
|
||||||
|
"external_data_adapters": [
|
||||||
|
{"provider": "tushare", "path": "tushare_client.py", "runtime_role": "primary deterministic market data"},
|
||||||
|
{"provider": "ifind", "path": "ifind_client.py", "runtime_role": "realtime, charts, snapshots, enrichment"},
|
||||||
|
{"provider": "eastmoney", "path": "chart_data_provider.py", "runtime_role": "display chart fallback"},
|
||||||
|
{"provider": "eastmoney", "path": "realtime_aggregator.py", "runtime_role": "isolated realtime observation"},
|
||||||
|
{"provider": "tencent", "path": "realtime_aggregator.py", "runtime_role": "index observation fallback"},
|
||||||
|
],
|
||||||
|
"llm_entrypoints": [
|
||||||
|
{"function": "stream_with_mentor", "path": "mentor_agent.py"},
|
||||||
|
{"function": "interpret_heaven", "path": "heaven_agent.py"},
|
||||||
|
{"function": "stream_review_assistant", "path": "assistant_agent.py"},
|
||||||
|
{"function": "compile_strategy_with_llm", "path": "llm_strategy.py"},
|
||||||
|
{"function": "test_llm_connection", "path": "llm_strategy.py"},
|
||||||
|
],
|
||||||
|
"css_layers": css_layers(html),
|
||||||
|
"code_hotspots": code_hotspots(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description="Build the current architecture inventory")
|
||||||
|
parser.add_argument("--check", action="store_true", help="fail when the committed inventory is stale")
|
||||||
|
args = parser.parse_args()
|
||||||
|
rendered = json.dumps(build(), ensure_ascii=False, indent=2) + "\n"
|
||||||
|
if args.check:
|
||||||
|
if not OUTPUT.exists() or OUTPUT.read_text(encoding="utf-8") != rendered:
|
||||||
|
raise SystemExit("architecture inventory is stale; run tools/build_architecture_inventory.py")
|
||||||
|
print("Architecture inventory is current.")
|
||||||
|
return 0
|
||||||
|
OUTPUT.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
OUTPUT.write_text(rendered, encoding="utf-8")
|
||||||
|
print(relative(OUTPUT))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Reference in New Issue
Block a user