create-caspian-app 1.6.4 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -46,6 +46,8 @@ sys.path.insert(0, str(ROOT))
46
46
  # Keep the app in non-production mode: dev generates an ephemeral session secret,
47
47
  # so the build does not require production secrets to be present.
48
48
  os.environ.setdefault("APP_ENV", "development")
49
+ # Starting the app's lifespan for the export must not start the task scheduler.
50
+ os.environ["CASPIAN_STATIC_EXPORT"] = "1"
49
51
 
50
52
  import httpx2 # noqa: E402 (the app's HTTP client; drives the ASGI app in-process)
51
53
 
@@ -96,8 +98,12 @@ def _fill_rule(fastapi_rule: str, params) -> str:
96
98
  params = {names[0]: params} if names else {}
97
99
  url = fastapi_rule
98
100
  for key, value in params.items():
101
+ if value is None or value == "":
102
+ # An optional catch-all (`[[...slug]]`) exports its base URL.
103
+ url = url.replace(f"/{{{key}:path}}", "")
104
+ continue
99
105
  url = url.replace(f"{{{key}:path}}", str(value)).replace(f"{{{key}}}", str(value))
100
- return url
106
+ return url or "/"
101
107
 
102
108
 
103
109
  def _out_path_for(url_path: str) -> Path:
@@ -242,6 +248,19 @@ def build() -> int:
242
248
 
243
249
  await export_one(client, url)
244
250
 
251
+ # sitemap.py / robots.py -> static/sitemap.xml, static/robots.txt
252
+ for url in getattr(main, "SEO_ROUTES", []):
253
+ resp = await client.get(url)
254
+ if resp.status_code != 200:
255
+ skipped.append((url, f"returned HTTP {resp.status_code}"))
256
+ continue
257
+ out_file = OUT_DIR / url.lstrip("/")
258
+ out_file.write_text(resp.text, encoding="utf-8")
259
+ exported.append(url)
260
+ if not os.getenv("APP_BASE_URL"):
261
+ seo_warnings.append(url)
262
+
263
+ seo_warnings: list[str] = []
245
264
  asyncio.run(render_all())
246
265
 
247
266
  _copy_assets()
@@ -249,9 +268,20 @@ def build() -> int:
249
268
  # ---- Report ----
250
269
  print(f"\n{GREEN}Exported {len(exported)} page(s):{RESET}")
251
270
  for url in exported:
252
- rel = _out_path_for(url).relative_to(ROOT)
271
+ target = (
272
+ OUT_DIR / url.lstrip("/")
273
+ if url in getattr(main, "SEO_ROUTES", [])
274
+ else _out_path_for(url)
275
+ )
276
+ rel = target.relative_to(ROOT)
253
277
  print(f" {GREEN}OK{RESET} {url} -> {rel}")
254
278
 
279
+ if seo_warnings:
280
+ print(
281
+ f"\n{YELLOW}Note: APP_BASE_URL is not set{RESET}{DIM} -- {', '.join(seo_warnings)} "
282
+ "contain http://testserver URLs. Set APP_BASE_URL to the public origin and rebuild.{RESET}"
283
+ )
284
+
255
285
  if rpc_warnings:
256
286
  print(f"\n{YELLOW}Note: these pages appear to use pp.rpc()/server actions{RESET}")
257
287
  print(f"{DIM} (they render, but those interactions are dead without the backend):{RESET}")