From 104b491813039e4b9c279342d5ff2d73410dcf18 Mon Sep 17 00:00:00 2001 From: Joe Heck Date: Wed, 19 Aug 2026 10:15:39 -0700 Subject: [PATCH 1/2] removing un-used descriptive block of sources.json schema, and update to use 'latest' for the immediate slug for publication --- scripts/README.md | 15 ++++-- scripts/build_docs.py | 10 +--- scripts/hacking-synthesized-landing-page.md | 53 +++++---------------- scripts/sources.json | 3 +- scripts/test_build_docs.py | 23 ++++----- 5 files changed, 34 insertions(+), 70 deletions(-) diff --git a/scripts/README.md b/scripts/README.md index a97259d3..8231a5b6 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -12,13 +12,18 @@ python3 -m http.server 8123 --directory .build-output Then in another terminal: ```bash -open http://localhost:8123/main/documentation/ +open http://localhost:8123/latest/documentation/ ``` -Serve from `.build-output` (the parent), not `.build-output/main`: the build -bakes a `/main/` hosting base path into every asset URL, so the `/main/` prefix -must map to the `main/` directory. This example avoids port 8000, which can be -commonly used by other apps or examples. +Serve from `.build-output` (the parent), not `.build-output/latest`: the build +bakes a `/latest/` hosting base path into every asset URL, so the `/latest/` +prefix must map to the `latest/` directory. This example avoids port 8000, +which can be commonly used by other apps or examples. + +> Note: Ensure that you have a version of Swift available that will build the +> content that you point at in sources.json. For latest development tree builds, +> that typically requires either the latest Xcode beta or a Swift nightly development +> build to support Swift, SwiftPM, swift-testing, and other core projects. ## Navigation manifest (combined sidebar curation) diff --git a/scripts/build_docs.py b/scripts/build_docs.py index d43dbb8d..a8c225f2 100755 --- a/scripts/build_docs.py +++ b/scripts/build_docs.py @@ -148,18 +148,10 @@ def validate_sources(config): if "version" not in config: errors.append("Top-level 'version' field is missing") elif not isinstance(config["version"], dict): - errors.append( - "Top-level 'version' field must be an object with 'slug' and " - "'descriptive-name'" - ) + errors.append("Top-level 'version' field must be an object with 'slug'") else: if not config["version"].get("slug"): errors.append("Top-level 'version' object is missing 'slug'") - # 'descriptive-name' is validated and recorded in build-manifest.json but - # not otherwise consumed by the build; reserved for a future narrative use - # (e.g. re-introducing a landing-page blurb — see hacking-synthesized-landing-page.md). - if not config["version"].get("descriptive-name"): - errors.append("Top-level 'version' object is missing 'descriptive-name'") if "sources" not in config: errors.append("Top-level 'sources' field is missing") diff --git a/scripts/hacking-synthesized-landing-page.md b/scripts/hacking-synthesized-landing-page.md index 6f4075b8..1cb66d1d 100644 --- a/scripts/hacking-synthesized-landing-page.md +++ b/scripts/hacking-synthesized-landing-page.md @@ -3,9 +3,7 @@ > **Status: not currently wired up.** `set_version_paragraph()` and its call > from `_finalize_combined_archive()` have been removed from the build > scripts — the combined archive's landing page no longer gets a "Swift -> version: X" paragraph injected. `sources.json`'s `version.descriptive-name` -> is still validated and recorded in `build-manifest.json`, but nothing reads -> it to alter documentation content. The mechanics below (verified against +> version: X" paragraph injected. The mechanics below (verified against > `swift-docc` source) remain accurate if this is revisited. Companion to `hacking-index-json.md` (sidebar/navigator). This file covers the @@ -101,43 +99,18 @@ This renders as normal running prose **above the Topics section** and below the title/role-heading — it is body content, not the short one-line `abstract` teaser (a separate, also-currently-absent top-level key). -## Where this lives in our pipeline - -`scripts/curate_navigator.py` → `set_version_paragraph(archive_path, text)`: -reads `data/documentation.json`, unconditionally replaces -`doc["primaryContentSections"]` with a single content section containing one -paragraph, writes atomically. No-op if the file is missing. Because it -replaces the array wholesale rather than appending, calling it repeatedly -(e.g. re-running the build) is idempotent — it does not accumulate -paragraphs. - -Called from `scripts/build_docs.py` → `_finalize_combined_archive()`, -**unconditionally** (not gated on `navigation.json` existing, unlike -`curate_navigator()`/topic-section curation), right after `merge_archives()` -succeeds and before the static-hosting transform: - -```python -descriptive_name = (version or {}).get("descriptive-name") -if descriptive_name: - set_version_paragraph(combined_output, f"Swift version: {descriptive_name}") -``` - -`version` is the full `sources.json` `"version"` object -(`{"slug": ..., "descriptive-name": ...}`); the text is built here in -`build_docs.py`, not inside `curate_navigator.py`, so `set_version_paragraph` -stays a generic "set this text as the landing page body" primitive. - -### Why it must run before the static-hosting transform +## Ordering constraint: run before the static-hosting transform -`transform_static_hosting()` runs `docc process-archive -transform-for-static-hosting`, which produces a **fresh archive** at a temp -path and then replaces the original wholesale (`shutil.rmtree` + -`shutil.move`). Whatever is in `data/documentation.json` at the time that -command runs is what gets baked into the final per-route HTML; editing the -JSON afterward doesn't help because the static-hosting archive may not carry -the same `data/*.json` structure forward. Same ordering constraint as -`curate_navigator()`'s landing-page section rewriting — see -`hacking-index-json.md`. +Any script that edits `data/documentation.json` — a future reimplementation +of this recipe included — must run before `transform_static_hosting()`. +That function runs `docc process-archive transform-for-static-hosting`, +which produces a **fresh archive** at a temp path and then replaces the +original wholesale (`shutil.rmtree` + `shutil.move`). Whatever is in +`data/documentation.json` at the time that command runs is what gets baked +into the final per-route HTML; editing the JSON afterward doesn't help +because the static-hosting archive may not carry the same `data/*.json` +structure forward. Same ordering constraint as `curate_navigator()`'s +landing-page section rewriting — see `hacking-index-json.md`. ## Extending this further @@ -172,7 +145,7 @@ published at . the title) — also omitted when empty on the synthesized landing page. Don't conflate it with `primaryContentSections`; this doc's approach targets body prose, not the teaser line. -- **Ordering vs. navigator curation:** `set_version_paragraph()` and +- **Ordering vs. navigator curation:** a landing-page-body rewrite and `curate_navigator()` both rewrite `data/documentation.json` but touch disjoint keys (`primaryContentSections` vs. `topicSections`/`references`), so call order between them doesn't matter — verified by both being simple diff --git a/scripts/sources.json b/scripts/sources.json index eab8d8aa..48ca05e0 100644 --- a/scripts/sources.json +++ b/scripts/sources.json @@ -1,7 +1,6 @@ { "version": { - "slug": "main", - "descriptive-name": "prototype" + "slug": "latest" }, "sources": [ { diff --git a/scripts/test_build_docs.py b/scripts/test_build_docs.py index 517879c6..605d0caf 100644 --- a/scripts/test_build_docs.py +++ b/scripts/test_build_docs.py @@ -49,7 +49,7 @@ def _validate(config): def _wrap(entry): return { - "version": {"slug": "main", "descriptive-name": "prototype"}, + "version": {"slug": "main"}, "sources": [entry], } @@ -67,11 +67,11 @@ def _config(self, version): } def test_valid_version_object(self): - output = _validate(self._config({"slug": "main", "descriptive-name": "prototype"})) + output = _validate(self._config({"slug": "main"})) self.assertIsNone(output) def test_version_missing_is_rejected(self): - config = self._config({"slug": "main", "descriptive-name": "prototype"}) + config = self._config({"slug": "main"}) del config["version"] output = _validate(config) self.assertIsNotNone(output) @@ -83,15 +83,10 @@ def test_version_as_plain_string_is_rejected(self): self.assertIn("version", output) def test_version_missing_slug_is_rejected(self): - output = _validate(self._config({"descriptive-name": "prototype"})) + output = _validate(self._config({})) self.assertIsNotNone(output) self.assertIn("slug", output) - def test_version_missing_descriptive_name_is_rejected(self): - output = _validate(self._config({"slug": "main"})) - self.assertIsNotNone(output) - self.assertIn("descriptive-name", output) - class ValidateArchiveType(unittest.TestCase): def test_minimal_valid(self): @@ -1632,7 +1627,7 @@ def _children_of(archive, lang="swift"): class ValidateNavigation(unittest.TestCase): SOURCES = { - "version": {"slug": "main", "descriptive-name": "prototype"}, + "version": {"slug": "main"}, "sources": [{"id": "a"}, {"id": "b"}], } @@ -2298,7 +2293,7 @@ def test_unlisted_module_raises(self): class AutoArchiveVersionSlug(unittest.TestCase): - """_auto_archive must resolve the {slug, descriptive-name} version object.""" + """_auto_archive must resolve the {slug} version object.""" def _sources_path(self, tmp_path): scripts_dir = tmp_path / "scripts" @@ -2310,7 +2305,7 @@ def test_uses_slug_from_version_object(self): tmp_path = Path(tmp) sources_path = self._sources_path(tmp_path) sources = { - "version": {"slug": "main", "descriptive-name": "prototype"}, + "version": {"slug": "main"}, "sources": [], } index_dir = tmp_path / ".build-output" / "main" / "index" @@ -2325,7 +2320,7 @@ def test_missing_slug_returns_none(self): tmp_path = Path(tmp) sources_path = self._sources_path(tmp_path) sources = { - "version": {"descriptive-name": "prototype"}, + "version": {}, "sources": [], } self.assertIsNone(validate_navigation_cli._auto_archive(sources, sources_path)) @@ -2352,7 +2347,7 @@ def _setup(self, tmp, nav, sources, archive_modules=None): return argv SOURCES = { - "version": {"slug": "main", "descriptive-name": "prototype"}, + "version": {"slug": "main"}, "sources": [{"id": "a"}, {"id": "b"}], } From a4a9064f8fa112fd8a14d3c690e622e3238159f8 Mon Sep 17 00:00:00 2001 From: Joe Heck Date: Wed, 19 Aug 2026 10:34:32 -0700 Subject: [PATCH 2/2] enabling CI for the combined doc build and adjusting --- .github/workflows/pull_request.yml | 9 +++++++++ scripts/README.md | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 7b837de0..67f57f44 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -19,3 +19,12 @@ jobs: broken_symlink_check_enabled: true shell_check_enabled: true yamllint_check_enabled: true + + docs-build-tests: + name: docs-build-tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - name: Run scripts test suite + run: python3 -m unittest discover -s scripts -p 'test_*.py' -v diff --git a/scripts/README.md b/scripts/README.md index 8231a5b6..e75da0b7 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -36,7 +36,7 @@ from the main page's module list as well as the sidebar. Each entry names a in the merged index must be either placed in a group or listed under `hidden` — `build_docs.py` validates and applies this automatically (the `navigator-curation` build step), and fails the build on any mismatch or -uncovered module. See `../hacking-index-json.md` for the underlying mechanics. +uncovered module. See `hacking-index-json.md` for the underlying mechanics. A group's `title` is optional — omit it (or set it to `null`) for a headerless group whose modules render with no section label in either the sidebar or the