Skip to content

Commit a307cb8

Browse files
committed
Refactor to package-wide versioning with sidebar selector
- Change min version for pathsim from 0.5 to 0.7 - Add versionStore for persisting version selection per package - Generate per-version search and crossref indexes in build.py - Add dynamic index loading (indexLoader.ts) - Update search.ts and crossref.ts for dynamic indexes - Create new versioned route structure: [package]/[version]/api and examples - Add version selector dropdown to Sidebar - Update DocLayout to pass version context - Make PackageOverview version-aware with availability checks - Update navigation config for versioned paths - Remove old optional version routes and static indexes
1 parent e2c250d commit a307cb8

28 files changed

Lines changed: 1690 additions & 15639 deletions

scripts/build-indexes.py

Lines changed: 36 additions & 255 deletions
Original file line numberDiff line numberDiff line change
@@ -1,278 +1,59 @@
11
#!/usr/bin/env python3
22
"""
3-
Build search and crossref indexes from API data and notebook manifests.
3+
Regenerate search and crossref indexes for all existing versions.
44
5-
This script generates pre-built indexes at build time, eliminating
6-
the need for runtime index construction in the browser.
5+
This script is a utility to regenerate per-version indexes without
6+
rebuilding API docs or notebooks. Useful after updating index generation logic.
77
8-
Outputs:
9-
- src/lib/api/generated/search-index.json
10-
- src/lib/api/generated/crossref-index.json
8+
Indexes are generated per-version in: static/{package}/{tag}/
9+
- search-index.json
10+
- crossref-index.json
1111
"""
1212

1313
import json
14+
import sys
1415
from pathlib import Path
15-
from typing import Any
1616

17-
# Paths relative to script location
18-
SCRIPT_DIR = Path(__file__).parent
19-
PROJECT_ROOT = SCRIPT_DIR.parent
20-
API_DIR = PROJECT_ROOT / "src" / "lib" / "api" / "generated"
21-
NOTEBOOKS_DIR = PROJECT_ROOT / "static" / "notebooks"
17+
# Add scripts directory to path for imports
18+
sys.path.insert(0, str(Path(__file__).parent))
2219

23-
# Package configuration (mirrors src/lib/config/packages.ts)
24-
PACKAGES = {
25-
"pathsim": {
26-
"name": "PathSim",
27-
"description": "Block-diagram based simulation framework for dynamical systems"
28-
},
29-
"chem": {
30-
"name": "PathSim-Chem",
31-
"description": "Chemical reaction network simulation"
32-
},
33-
"vehicle": {
34-
"name": "PathSim-Vehicle",
35-
"description": "Vehicle dynamics simulation"
36-
}
37-
}
38-
39-
PACKAGE_ORDER = ["pathsim", "chem", "vehicle"]
40-
41-
42-
def load_api_data() -> dict[str, Any]:
43-
"""Load all API JSON files."""
44-
api_data = {}
45-
for pkg_id in PACKAGE_ORDER:
46-
json_path = API_DIR / f"{pkg_id}.json"
47-
if json_path.exists():
48-
with open(json_path, "r", encoding="utf-8") as f:
49-
api_data[pkg_id] = json.load(f)
50-
return api_data
51-
52-
53-
def load_notebook_manifests() -> dict[str, list[dict]]:
54-
"""Load all notebook manifests."""
55-
manifests = {}
56-
for pkg_id in PACKAGE_ORDER:
57-
manifest_path = NOTEBOOKS_DIR / pkg_id / "manifest.json"
58-
if manifest_path.exists():
59-
with open(manifest_path, "r", encoding="utf-8") as f:
60-
data = json.load(f)
61-
manifests[pkg_id] = data.get("notebooks", [])
62-
return manifests
63-
64-
65-
def build_search_index(api_data: dict, manifests: dict) -> list[dict]:
66-
"""Build the search index from API data and notebook manifests."""
67-
results = []
68-
69-
# Track which packages have examples
70-
packages_with_examples = set(pkg_id for pkg_id, notebooks in manifests.items() if notebooks)
71-
72-
# 1. Add page-level results
73-
for pkg_id in PACKAGE_ORDER:
74-
pkg = PACKAGES.get(pkg_id, {})
75-
pkg_name = pkg.get("name", pkg_id)
76-
pkg_desc = pkg.get("description", "")
77-
78-
# Overview page
79-
results.append({
80-
"type": "page",
81-
"name": pkg_name,
82-
"description": pkg_desc,
83-
"path": pkg_id,
84-
"packageId": pkg_id,
85-
"moduleName": ""
86-
})
87-
88-
# API page
89-
results.append({
90-
"type": "page",
91-
"name": f"{pkg_name} API",
92-
"description": f"API reference for {pkg_name}",
93-
"path": f"{pkg_id}/api",
94-
"packageId": pkg_id,
95-
"moduleName": ""
96-
})
97-
98-
# Examples page (only if package has examples)
99-
if pkg_id in packages_with_examples:
100-
results.append({
101-
"type": "page",
102-
"name": f"{pkg_name} Examples",
103-
"description": f"Example notebooks for {pkg_name}",
104-
"path": f"{pkg_id}/examples",
105-
"packageId": pkg_id,
106-
"moduleName": ""
107-
})
108-
109-
# 2. Add API items (modules, classes, methods, functions)
110-
for pkg_id, pkg_data in api_data.items():
111-
base_path = f"{pkg_id}/api"
112-
modules = pkg_data.get("modules", {})
113-
114-
for module_name, module in modules.items():
115-
# Module
116-
results.append({
117-
"type": "module",
118-
"name": module_name,
119-
"description": module.get("description", ""),
120-
"path": f"{base_path}#{module_name}",
121-
"packageId": pkg_id,
122-
"moduleName": module_name
123-
})
124-
125-
# Classes
126-
for cls in module.get("classes", []):
127-
results.append({
128-
"type": "class",
129-
"name": cls["name"],
130-
"description": cls.get("description", ""),
131-
"path": f"{base_path}#{cls['name']}",
132-
"packageId": pkg_id,
133-
"moduleName": module_name
134-
})
135-
136-
# Methods
137-
for method in cls.get("methods", []):
138-
results.append({
139-
"type": "method",
140-
"name": method["name"],
141-
"description": method.get("description", ""),
142-
"path": f"{base_path}#{cls['name']}.{method['name']}",
143-
"packageId": pkg_id,
144-
"moduleName": module_name,
145-
"parentClass": cls["name"]
146-
})
147-
148-
# Functions
149-
for func in module.get("functions", []):
150-
results.append({
151-
"type": "function",
152-
"name": func["name"],
153-
"description": func.get("description", ""),
154-
"path": f"{base_path}#{func['name']}",
155-
"packageId": pkg_id,
156-
"moduleName": module_name
157-
})
158-
159-
# 3. Add examples from notebook manifests
160-
for pkg_id, notebooks in manifests.items():
161-
for notebook in notebooks:
162-
results.append({
163-
"type": "example",
164-
"name": notebook.get("title", ""),
165-
"description": notebook.get("description", ""),
166-
"path": f"{pkg_id}/examples/{notebook.get('slug', '')}",
167-
"packageId": pkg_id,
168-
"moduleName": notebook.get("category", ""),
169-
"tags": notebook.get("tags", [])
170-
})
171-
172-
return results
173-
174-
175-
def build_crossref_index(api_data: dict) -> dict[str, dict]:
176-
"""Build the crossref index mapping names to targets."""
177-
index = {}
178-
179-
for pkg_id, pkg_data in api_data.items():
180-
api_path = f"{pkg_id}/api"
181-
modules = pkg_data.get("modules", {})
182-
183-
for module_name, module in modules.items():
184-
# Module
185-
module_target = {
186-
"name": module_name,
187-
"type": "module",
188-
"packageId": pkg_id,
189-
"moduleName": module_name,
190-
"path": f"{api_path}#{module_name.replace('.', '-')}"
191-
}
192-
index[module_name] = module_target
193-
194-
# Classes
195-
for cls in module.get("classes", []):
196-
class_target = {
197-
"name": cls["name"],
198-
"type": "class",
199-
"packageId": pkg_id,
200-
"moduleName": module_name,
201-
"path": f"{api_path}#{cls['name']}"
202-
}
203-
204-
# Index by multiple keys for flexible lookup
205-
full_module_path = f"{module_name}.{cls['name']}"
206-
index[cls["name"]] = class_target # Just class name
207-
index[full_module_path] = class_target # Full path
208-
index[f"{pkg_id}.{cls['name']}"] = class_target # package.ClassName
209-
210-
# Methods
211-
for method in cls.get("methods", []):
212-
method_target = {
213-
"name": method["name"],
214-
"type": "method",
215-
"packageId": pkg_id,
216-
"moduleName": module_name,
217-
"parentClass": cls["name"],
218-
"path": f"{api_path}#{cls['name']}.{method['name']}"
219-
}
220-
# Index by ClassName.method_name
221-
index[f"{cls['name']}.{method['name']}"] = method_target
222-
223-
# Functions
224-
for func in module.get("functions", []):
225-
func_target = {
226-
"name": func["name"],
227-
"type": "function",
228-
"packageId": pkg_id,
229-
"moduleName": module_name,
230-
"path": f"{api_path}#{func['name']}"
231-
}
232-
full_module_path = f"{module_name}.{func['name']}"
233-
index[func["name"]] = func_target
234-
index[full_module_path] = func_target
235-
236-
return index
237-
238-
239-
def write_json(data: Any, path: Path) -> None:
240-
"""Write data as JSON."""
241-
path.parent.mkdir(parents=True, exist_ok=True)
242-
with open(path, "w", encoding="utf-8") as f:
243-
json.dump(data, f, indent=2, ensure_ascii=False)
244-
print(f" Written: {path}")
20+
from build import generate_version_indexes
21+
from lib.config import PACKAGES, STATIC_DIR
24522

24623

24724
def main():
248-
print("Building search and crossref indexes")
25+
print("Regenerating per-version indexes")
24926
print("=" * 40)
25027

251-
# Load source data
252-
print("\nLoading API data...")
253-
api_data = load_api_data()
254-
print(f" Loaded {len(api_data)} package(s)")
28+
total_versions = 0
29+
30+
for pkg_id in PACKAGES.keys():
31+
pkg_dir = STATIC_DIR / pkg_id
32+
33+
if not pkg_dir.exists():
34+
print(f"\n{pkg_id}: No static directory found")
35+
continue
25536

256-
print("\nLoading notebook manifests...")
257-
manifests = load_notebook_manifests()
258-
total_notebooks = sum(len(n) for n in manifests.values())
259-
print(f" Loaded {total_notebooks} notebook(s) from {len(manifests)} package(s)")
37+
# Find all version directories
38+
version_dirs = [
39+
d for d in pkg_dir.iterdir()
40+
if d.is_dir() and d.name.startswith("v")
41+
]
26042

261-
# Build indexes
262-
print("\nBuilding search index...")
263-
search_index = build_search_index(api_data, manifests)
264-
print(f" Generated {len(search_index)} search entries")
43+
if not version_dirs:
44+
print(f"\n{pkg_id}: No versions found")
45+
continue
26546

266-
print("\nBuilding crossref index...")
267-
crossref_index = build_crossref_index(api_data)
268-
print(f" Generated {len(crossref_index)} crossref entries")
47+
print(f"\n{pkg_id}: {len(version_dirs)} version(s)")
26948

270-
# Write outputs
271-
print("\nWriting indexes...")
272-
write_json(search_index, API_DIR / "search-index.json")
273-
write_json(crossref_index, API_DIR / "crossref-index.json")
49+
for version_dir in sorted(version_dirs, key=lambda d: d.name):
50+
tag = version_dir.name
51+
print(f" {tag}:")
52+
generate_version_indexes(pkg_id, tag, version_dir)
53+
total_versions += 1
27454

275-
print("\nDone!")
55+
print(f"\n{'=' * 40}")
56+
print(f"Regenerated indexes for {total_versions} version(s)")
27657

27758

27859
if __name__ == "__main__":

0 commit comments

Comments
 (0)