Skip to content

Commit 385314e

Browse files
authored
Merge pull request #160 from posit-dev/feat-site-url-option
feat: add `site_url` option
2 parents e8d0152 + 15c13e3 commit 385314e

7 files changed

Lines changed: 207 additions & 0 deletions

File tree

great_docs/_versioned_build.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1260,6 +1260,13 @@ def _rewrite_quarto_yml_for_version(
12601260
# (it defaults to _site anyway, but be explicit)
12611261
config.setdefault("project", {})["output-dir"] = "_site"
12621262

1263+
# Adjust site-url for non-latest versions: append the /v/<tag>/ prefix
1264+
# so that Quarto generates correct root-relative asset paths for versioned subpaths.
1265+
existing_site_url = config.get("website", {}).get("site-url")
1266+
if entry.tag != latest_tag and not entry.latest and existing_site_url:
1267+
base = existing_site_url.rstrip("/")
1268+
config.setdefault("website", {})["site-url"] = f"{base}/v/{entry.tag}/"
1269+
12631270
# Set a version-specific title suffix
12641271
if entry.tag != latest_tag and not entry.latest:
12651272
title = config.get("website", {}).get("title", "")

great_docs/config.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
"no_auto_exclude": False, # Bypass the built-in AUTO_EXCLUDE list entirely
2424
# GitHub integration
2525
"repo": None, # GitHub repository URL override (e.g., "https://github.com/owner/repo")
26+
# Site URL for subdirectory deployments (sets website.site-url in _quarto.yml)
27+
# e.g., "http://myserver:3838/data-team/mypackage/"
28+
"site_url": None,
2629
"github_style": "widget", # "widget" (shows stars) or "icon"
2730
# Source link configuration
2831
"source": {
@@ -497,6 +500,11 @@ def repo(self) -> str | None:
497500
"""Get the GitHub repository URL override."""
498501
return self.get("repo")
499502

503+
@property
504+
def site_url(self) -> str | None:
505+
"""Get the site URL for subdirectory deployments."""
506+
return self.get("site_url")
507+
500508
@property
501509
def github_style(self) -> str:
502510
"""Get the GitHub link style."""

great_docs/core.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10055,6 +10055,10 @@ def _update_quarto_config(self) -> None:
1005510055
if "website" not in config:
1005610056
config["website"] = {}
1005710057

10058+
# Set site-url for subdirectory deployments (from great-docs.yml)
10059+
if self._config.site_url:
10060+
config["website"]["site-url"] = self._config.site_url
10061+
1005810062
# Enable page navigation for TOC
1005910063
if "page-navigation" not in config["website"]:
1006010064
config["website"]["page-navigation"] = True
@@ -13532,6 +13536,7 @@ def _on_renders_done() -> None:
1353213536
quarto_env=quarto_env,
1353313537
version_tags=version_tags,
1353413538
latest_only=latest_only,
13539+
site_url=self._config.site_url,
1353513540
progress_callback=_progress_cb,
1353613541
on_renders_done=_on_renders_done,
1353713542
badge_expiry_raw=self._config.get("new_is_old"),

tests/test_great_docs.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15497,6 +15497,71 @@ def test_update_quarto_config_version_badge_no_releases():
1549715497
assert not meta_path.exists()
1549815498

1549915499

15500+
def test_update_quarto_config_site_url():
15501+
"""Test _update_quarto_config injects site-url from great-docs.yml."""
15502+
15503+
with tempfile.TemporaryDirectory() as tmp_dir:
15504+
docs = GreatDocs(project_path=tmp_dir)
15505+
15506+
pyproject = Path(tmp_dir) / "pyproject.toml"
15507+
pyproject.write_text('[project]\nname = "mypkg"\n', encoding="utf-8")
15508+
15509+
gd_yml = Path(tmp_dir) / "great-docs.yml"
15510+
gd_yml.write_text(
15511+
'site_url: "http://myserver:3838/data-team/mypackage/"\n',
15512+
encoding="utf-8",
15513+
)
15514+
docs._config = Config(Path(tmp_dir))
15515+
15516+
quarto_yml = docs.project_path / "_quarto.yml"
15517+
quarto_yml.parent.mkdir(parents=True, exist_ok=True)
15518+
15519+
base_config = {
15520+
"project": {"type": "website", "resources": []},
15521+
"website": {"navbar": {"left": []}, "sidebar": []},
15522+
"format": {"html": {}},
15523+
}
15524+
15525+
with open(quarto_yml, "w") as f:
15526+
write_yaml(base_config, f)
15527+
15528+
docs._update_quarto_config()
15529+
15530+
with open(quarto_yml, "r") as f:
15531+
result = read_yaml(f)
15532+
15533+
assert result["website"]["site-url"] == "http://myserver:3838/data-team/mypackage/"
15534+
15535+
15536+
def test_update_quarto_config_site_url_not_set():
15537+
"""Test _update_quarto_config does not inject site-url when not configured."""
15538+
15539+
with tempfile.TemporaryDirectory() as tmp_dir:
15540+
docs = GreatDocs(project_path=tmp_dir)
15541+
15542+
pyproject = Path(tmp_dir) / "pyproject.toml"
15543+
pyproject.write_text('[project]\nname = "mypkg"\n', encoding="utf-8")
15544+
15545+
quarto_yml = docs.project_path / "_quarto.yml"
15546+
quarto_yml.parent.mkdir(parents=True, exist_ok=True)
15547+
15548+
base_config = {
15549+
"project": {"type": "website", "resources": []},
15550+
"website": {"navbar": {"left": []}, "sidebar": []},
15551+
"format": {"html": {}},
15552+
}
15553+
15554+
with open(quarto_yml, "w") as f:
15555+
write_yaml(base_config, f)
15556+
15557+
docs._update_quarto_config()
15558+
15559+
with open(quarto_yml, "r") as f:
15560+
result = read_yaml(f)
15561+
15562+
assert "site-url" not in result["website"]
15563+
15564+
1550015565
def test_create_index_from_readme_citation_parsing():
1550115566
"""Test _create_index_from_readme produces citation.qmd from CITATION.cff."""
1550215567

tests/test_versioned_build.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1188,6 +1188,82 @@ def test_no_site_url_no_canonical(self, tmp_path: Path):
11881188
assert "canonical" not in content
11891189

11901190

1191+
# ---------------------------------------------------------------------------
1192+
# site-url adjustment for versioned builds
1193+
# ---------------------------------------------------------------------------
1194+
1195+
1196+
class TestSiteUrlVersionAdjustment:
1197+
def test_non_latest_adjusts_site_url(self, tmp_path: Path):
1198+
"""Non-latest versions get site-url with /v/<tag>/ appended."""
1199+
from great_docs._versioned_build import _rewrite_quarto_yml_for_version
1200+
1201+
dest = tmp_path / "v02"
1202+
dest.mkdir()
1203+
(dest / "_quarto.yml").write_text(
1204+
"project:\n type: website\n output-dir: _site\n"
1205+
"format:\n html: {}\nwebsite:\n title: Test\n"
1206+
" site-url: 'http://myserver:3838/data-team/mypkg/'\n",
1207+
encoding="utf-8",
1208+
)
1209+
entry = _make_entry("0.2")
1210+
_rewrite_quarto_yml_for_version(
1211+
dest, entry, "0.3", site_url="http://myserver:3838/data-team/mypkg/"
1212+
)
1213+
1214+
from yaml12 import read_yaml
1215+
1216+
with open(dest / "_quarto.yml") as f:
1217+
result = read_yaml(f)
1218+
1219+
assert result["website"]["site-url"] == "http://myserver:3838/data-team/mypkg/v/0.2/"
1220+
1221+
def test_latest_keeps_site_url_unchanged(self, tmp_path: Path):
1222+
"""Latest version keeps the original site-url as-is."""
1223+
from great_docs._versioned_build import _rewrite_quarto_yml_for_version
1224+
1225+
dest = tmp_path / "root"
1226+
dest.mkdir()
1227+
(dest / "_quarto.yml").write_text(
1228+
"project:\n type: website\n output-dir: _site\n"
1229+
"format:\n html: {}\nwebsite:\n title: Test\n"
1230+
" site-url: 'http://myserver:3838/data-team/mypkg/'\n",
1231+
encoding="utf-8",
1232+
)
1233+
entry = _make_entry("0.3", latest=True)
1234+
_rewrite_quarto_yml_for_version(
1235+
dest, entry, "0.3", site_url="http://myserver:3838/data-team/mypkg/"
1236+
)
1237+
1238+
from yaml12 import read_yaml
1239+
1240+
with open(dest / "_quarto.yml") as f:
1241+
result = read_yaml(f)
1242+
1243+
assert result["website"]["site-url"] == "http://myserver:3838/data-team/mypkg/"
1244+
1245+
def test_no_site_url_in_config_no_adjustment(self, tmp_path: Path):
1246+
"""When site-url is not in _quarto.yml, nothing is injected."""
1247+
from great_docs._versioned_build import _rewrite_quarto_yml_for_version
1248+
1249+
dest = tmp_path / "v02"
1250+
dest.mkdir()
1251+
(dest / "_quarto.yml").write_text(
1252+
"project:\n type: website\n output-dir: _site\n"
1253+
"format:\n html: {}\nwebsite:\n title: Test\n",
1254+
encoding="utf-8",
1255+
)
1256+
entry = _make_entry("0.2")
1257+
_rewrite_quarto_yml_for_version(dest, entry, "0.3")
1258+
1259+
from yaml12 import read_yaml
1260+
1261+
with open(dest / "_quarto.yml") as f:
1262+
result = read_yaml(f)
1263+
1264+
assert "site-url" not in result.get("website", {})
1265+
1266+
11911267
# ---------------------------------------------------------------------------
11921268
# Snapshot cache path
11931269
# ---------------------------------------------------------------------------

user_guide/05-configuration.qmd

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -893,6 +893,9 @@ exclude:
893893
repo: https://github.com/your-org/your-package # Override auto-detect
894894
github_style: widget
895895
896+
# Site URL (for subdirectory deployments)
897+
# site_url: "https://internal.example.com/docs/mypackage/"
898+
896899
# Source Links
897900
source:
898901
enabled: true

user_guide/14-deployment.qmd

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,49 @@ The built site is in `great-docs/_site/`. Upload this directory to your hosting
168168
- **AWS S3**: Sync the `great-docs/_site/` folder to your bucket
169169
- **Any static host**: Copy contents of `great-docs/_site/`
170170

171+
## Subdirectory Deployments
172+
173+
If your site is hosted at a subpath rather than a domain root (for example,
174+
`https://internal.example.com/docs/mypackage/`), you need to tell Quarto the base URL so it
175+
generates correct root-relative paths for CSS, JS, and navigation links.
176+
177+
Set `site_url` in `great-docs.yml`:
178+
179+
```{.yaml filename="great-docs.yml"}
180+
site_url: "https://internal.example.com/docs/mypackage/"
181+
```
182+
183+
This writes `website.site-url` into the generated `_quarto.yml` during every
184+
build, so you never have to patch the config manually.
185+
186+
### What it fixes
187+
188+
Without `site_url`, Quarto generates root-relative asset paths like `/site_libs/...` and
189+
`/reference/...`. When the site lives at a subpath, those paths resolve to the wrong location and
190+
the site appears broken (missing styles, broken navigation, 404s on the reference section, etc.).
191+
192+
### Versioned sites
193+
194+
For multi-version builds, Great Docs automatically appends the version prefix to `site-url` for
195+
non-latest versions. If your `site_url` is:
196+
197+
```
198+
https://internal.example.com/docs/mypackage/
199+
```
200+
201+
then version `0.2` (served at `/v/0.2/`) will use:
202+
203+
```
204+
https://internal.example.com/docs/mypackage/v/0.2/
205+
```
206+
207+
No extra configuration is needed.
208+
209+
### When you don't need it
210+
211+
If you deploy to a domain root (e.g., `https://yourpackage.github.io/` or a custom domain), you do
212+
not need to set `site_url`. Quarto's defaults work correctly in that case.
213+
171214
## Custom Domain
172215

173216
To use a custom domain with GitHub Pages:

0 commit comments

Comments
 (0)