From bdf35bd7f56100b23772595fa0301cbc28be6cc4 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Mon, 2 Nov 2026 17:42:33 +0000 Subject: [PATCH 1/4] Update Validation docs and remove `.lycheeignore` Move `github.com/SpineEventEngine/validation/docs` to `2754355a`, the merge of SpineEventEngine/validation#341, which fixes the broken links in the Validation docs. Lychee passes over the built site without the patterns that `.lycheeignore` held, so the file goes. Co-Authored-By: Claude Opus 5.5 --- .lycheeignore | 17 ----------------- docs/_preview/go.mod | 2 +- docs/_preview/go.sum | 2 ++ docs/go.mod | 2 +- docs/go.sum | 2 ++ 5 files changed, 6 insertions(+), 19 deletions(-) delete mode 100644 .lycheeignore diff --git a/.lycheeignore b/.lycheeignore deleted file mode 100644 index 16e4602..0000000 --- a/.lycheeignore +++ /dev/null @@ -1,17 +0,0 @@ -# Lychee skips the URLs matching these patterns. It reads this file from the -# directory it runs in, the repository root. `lychee.toml` comes from `config` -# and `./config/pull` overwrites it, so links specific to this site go here. -# -# These links are broken in the docs of `SpineEventEngine/validation`, which -# `docs/hugo.toml` imports as a module, so they can only be fixed there. -# Remove a pattern once the module update that fixes its link lands here. -# See https://github.com/SpineEventEngine/documentation/issues/97. -^https://github\.com/SpineEventEngine/validation/blob/master/\.agents/running-builds\.md$ -^https://github\.com/SpineEventEngine/validation/blob/master/\.agents/documentation-guidelines\.md$ -^https://github\.com/SpineEventEngine/embed-code-go/blob/master/EMBEDDING\.md$ -^https://github\.com/SpineEventEngine/embed-code-go/tree/master/bin$ -^https://plugins\.gradle\.org/plugin/io\.spine\.validation$ -# Relative links that resolve outside the module's pages. -^http://localhost:[0-9]+/06-developers-guide/$ -^http://localhost:[0-9]+/developer/$ -^http://localhost:[0-9]+/typical_custom_option\.jpg$ diff --git a/docs/_preview/go.mod b/docs/_preview/go.mod index 4d64e36..d0df99b 100644 --- a/docs/_preview/go.mod +++ b/docs/_preview/go.mod @@ -4,7 +4,7 @@ go 1.27.3 require ( github.com/SpineEventEngine/site-commons v0.0.0-20261030172811-9216e48eb656 // indirect - github.com/SpineEventEngine/validation/docs v0.0.0-20260902132507-6465029c83b9 // indirect + github.com/SpineEventEngine/validation/docs v0.0.0-20261102173850-2754355aee88 // indirect github.com/gohugoio/hugo-mod-bootstrap-scss/v5 v5.20300.20800 // indirect github.com/gohugoio/hugo-mod-jslibs-dist/popperjs/v2 v2.21100.20000 // indirect github.com/twbs/bootstrap v5.3.8+incompatible // indirect diff --git a/docs/_preview/go.sum b/docs/_preview/go.sum index 84161e8..ee17981 100644 --- a/docs/_preview/go.sum +++ b/docs/_preview/go.sum @@ -8,6 +8,8 @@ github.com/SpineEventEngine/validation/docs v0.0.0-20260523180634-dd65da5abb65 h github.com/SpineEventEngine/validation/docs v0.0.0-20260523180634-dd65da5abb65/go.mod h1:4RnP1hlrfBI7ZlTsJkllaOEyluhzmz4mOBrRgbc/tts= github.com/SpineEventEngine/validation/docs v0.0.0-20260902132507-6465029c83b9 h1:Bsgd16jNYCBdqtpEyW/CYV2yFjO7Y4nAPSlYz0zjFb0= github.com/SpineEventEngine/validation/docs v0.0.0-20260902132507-6465029c83b9/go.mod h1:4RnP1hlrfBI7ZlTsJkllaOEyluhzmz4mOBrRgbc/tts= +github.com/SpineEventEngine/validation/docs v0.0.0-20261102173850-2754355aee88 h1:Cl2OrO7wwgnH/tfRFMkHxph+5lz0CWzXfk/jtAKcp/Y= +github.com/SpineEventEngine/validation/docs v0.0.0-20261102173850-2754355aee88/go.mod h1:4RnP1hlrfBI7ZlTsJkllaOEyluhzmz4mOBrRgbc/tts= github.com/gohugoio/hugo-mod-bootstrap-scss/v5 v5.20300.20400 h1:L6+F22i76xmeWWwrtijAhUbf3BiRLmpO5j34bgl1ggU= github.com/gohugoio/hugo-mod-bootstrap-scss/v5 v5.20300.20400/go.mod h1:uekq1D4ebeXgduLj8VIZy8TgfTjrLdSl6nPtVczso78= github.com/gohugoio/hugo-mod-bootstrap-scss/v5 v5.20300.20800 h1:j5myhhzYwIHTr5ctK96Elfgp5uRROvrlTzYwwe1nF8o= diff --git a/docs/go.mod b/docs/go.mod index 638b7d3..454da5d 100644 --- a/docs/go.mod +++ b/docs/go.mod @@ -2,4 +2,4 @@ module github.com/SpineEventEngine/documentation/docs go 1.22.0 -require github.com/SpineEventEngine/validation/docs v0.0.0-20260902132507-6465029c83b9 // indirect +require github.com/SpineEventEngine/validation/docs v0.0.0-20261102173850-2754355aee88 // indirect diff --git a/docs/go.sum b/docs/go.sum index eb9e24b..95e14eb 100644 --- a/docs/go.sum +++ b/docs/go.sum @@ -2,3 +2,5 @@ github.com/SpineEventEngine/validation/docs v0.0.0-20260523180634-dd65da5abb65 h github.com/SpineEventEngine/validation/docs v0.0.0-20260523180634-dd65da5abb65/go.mod h1:4RnP1hlrfBI7ZlTsJkllaOEyluhzmz4mOBrRgbc/tts= github.com/SpineEventEngine/validation/docs v0.0.0-20260902132507-6465029c83b9 h1:Bsgd16jNYCBdqtpEyW/CYV2yFjO7Y4nAPSlYz0zjFb0= github.com/SpineEventEngine/validation/docs v0.0.0-20260902132507-6465029c83b9/go.mod h1:4RnP1hlrfBI7ZlTsJkllaOEyluhzmz4mOBrRgbc/tts= +github.com/SpineEventEngine/validation/docs v0.0.0-20261102173850-2754355aee88 h1:Cl2OrO7wwgnH/tfRFMkHxph+5lz0CWzXfk/jtAKcp/Y= +github.com/SpineEventEngine/validation/docs v0.0.0-20261102173850-2754355aee88/go.mod h1:4RnP1hlrfBI7ZlTsJkllaOEyluhzmz4mOBrRgbc/tts= From eb609e5c216d723df95d5f6fa8b07fbf3a395da6 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Mon, 2 Nov 2026 17:45:14 +0000 Subject: [PATCH 2/4] Update `config` and `.agents/shared` Run `./config/pull`, which moves `config` to `3a0d13a` and `.agents/shared` to `551ad4f`, the tips of their `master` branches. The `.agents/shared` pin is force-added, because `.gitmodules` sets `ignore = all` for it. The pull updates only `check-links.yml`, which had no local edits: - Check Links also runs when `.lycheeignore` changes. - Site detection also finds a Hugo config in `_preview/`. Here it still picks `docs/_preview` as the work directory. - Newer `setup-node`, `cache`, and `release-downloader` actions. - Lychee's report is added to the run summary. Co-Authored-By: Claude Opus 5.5 --- .agents/shared | 2 +- .github/workflows/check-links.yml | 54 ++++++++++++++++++++++++------- config | 2 +- 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/.agents/shared b/.agents/shared index 017c8d3..551ad4f 160000 --- a/.agents/shared +++ b/.agents/shared @@ -1 +1 @@ -Subproject commit 017c8d38ff02352de1c5a3a4e00f09a65b04d074 +Subproject commit 551ad4fc2d7871d8919eaaff8a37c529249830d6 diff --git a/.github/workflows/check-links.yml b/.github/workflows/check-links.yml index 3f9b156..cf02c5d 100644 --- a/.github/workflows/check-links.yml +++ b/.github/workflows/check-links.yml @@ -8,6 +8,7 @@ on: - 'docs/**' - 'site/**' - 'lychee.toml' + - '.lycheeignore' - '.github/workflows/check-links.yml' workflow_dispatch: @@ -44,6 +45,11 @@ jobs: # Detect the Hugo site root (`docs/` or `site/`) by looking for a Hugo # config file. Hugo config may live directly in the site root or in a # `config/` or `config/_default/` subdirectory (both layouts are valid). + # A repo may also keep its only Hugo config in the `_preview/` sub-tree + # (e.g. `docs/_preview/hugo.toml`), so the same paths count there too. + # Hugo finds such a config only when it runs in `$dir/_preview`, so it + # needs `$dir/_preview/package-lock.json`: the `$dir` fallback below + # is for site-root configs only. # Outputs `present=true|false` and `work_dir` (the directory where # `npm ci` / `hugo` commands should run — either `$dir/_preview` for # repos that use a separate preview sub-tree, or `$dir` for repos whose @@ -57,19 +63,22 @@ jobs: for dir in docs site; do for cfg in hugo.toml hugo.yaml \ config/hugo.toml config/hugo.yaml \ - config/_default/hugo.toml config/_default/hugo.yaml; do + config/_default/hugo.toml config/_default/hugo.yaml \ + _preview/hugo.toml _preview/hugo.yaml \ + _preview/config/hugo.toml _preview/config/hugo.yaml \ + _preview/config/_default/hugo.toml _preview/config/_default/hugo.yaml; do if [ -f "$dir/$cfg" ]; then if [ -f "$dir/_preview/package-lock.json" ]; then echo "work_dir=$dir/_preview" >> "$GITHUB_OUTPUT" echo "present=true" >> "$GITHUB_OUTPUT" echo "::notice::Hugo site found under $dir/ (work_dir: $dir/_preview)" - elif [ -f "$dir/package-lock.json" ]; then + elif [ -f "$dir/package-lock.json" ] && [[ $cfg != _preview/* ]]; then echo "work_dir=$dir" >> "$GITHUB_OUTPUT" echo "present=true" >> "$GITHUB_OUTPUT" echo "::notice::Hugo site found under $dir/ (work_dir: $dir)" else echo "present=false" >> "$GITHUB_OUTPUT" - echo "::notice::Hugo config found in $dir/ but no package-lock.json found — skipping link check." + echo "::notice::Found $dir/$cfg but no package-lock.json to build it with — skipping link check." fi exit 0 fi @@ -110,13 +119,13 @@ jobs: if: steps.docs.outputs.present == 'true' run: hugo env | grep 'github.com/sass/dart-sass' - # `actions/setup-node@v4` ships with built-in npm caching that hashes + # `actions/setup-node` ships with built-in npm caching that hashes # the lockfile and restores `~/.npm`. We use that instead of a - # standalone `actions/cache@v4` block so there is only one source of + # standalone `actions/cache` block so there is only one source of # truth for the cache key (no drift between two layers). - name: Setup Node if: steps.docs.outputs.present == 'true' - uses: actions/setup-node@v4 + uses: actions/setup-node@v7 with: node-version: '26' cache: 'npm' @@ -128,7 +137,7 @@ jobs: # invalidates the cache deterministically regardless of site root. - name: Cache Hugo Modules if: steps.docs.outputs.present == 'true' - uses: actions/cache@v4 + uses: actions/cache@v6 with: path: /tmp/hugo_cache key: ${{ runner.os }}-hugomod-${{ hashFiles('docs/**/go.sum', 'site/**/go.sum') }} @@ -152,7 +161,7 @@ jobs: # `max_cache_age` expires. - name: Cache Lychee results if: steps.docs.outputs.present == 'true' - uses: actions/cache@v4 + uses: actions/cache@v6 with: path: .lycheecache key: cache-lychee-${{ runner.os }}-${{ hashFiles('lychee.toml') }} @@ -167,7 +176,7 @@ jobs: - name: Cache Lychee executable if: steps.docs.outputs.present == 'true' id: cache-lychee - uses: actions/cache@v4 + uses: actions/cache@v6 with: path: lychee key: ${{ runner.os }}-${{ env.LYCHEE_VERSION_TAG }}-${{ env.LYCHEE_RELEASE }} @@ -187,7 +196,7 @@ jobs: # string when the file is absent, so this guard runs the download iff # neither the exact key nor any restore-key restored the binary. - name: Download Lychee executable - uses: robinraju/release-downloader@v1.7 + uses: robinraju/release-downloader@v1.13 if: steps.docs.outputs.present == 'true' && hashFiles('lychee/lychee') == '' with: repository: "lycheeverse/lychee" @@ -239,9 +248,32 @@ jobs: exit 1 fi + # Lychee prints broken links to this step's log only; the run page + # shows just "Process completed with exit code 2". `tee` keeps the + # report in the log and saves a copy, which the next step adds to the + # run summary. (`--output` would not do: it moves the report from the + # log to the file.) `pipefail` keeps Lychee's exit code. Without it, + # the step would take `tee`'s 0 and pass despite broken links. - name: Check links if: steps.docs.outputs.present == 'true' run: | + set -o pipefail ./lychee/lychee --config lychee.toml --timeout 60 \ --base-url http://localhost:1313/ \ - '${{ steps.docs.outputs.work_dir }}/public/**/*.html' + '${{ steps.docs.outputs.work_dir }}/public/**/*.html' \ + | tee lychee-report.txt + + # Runs after a failed `Check links` too, which is when the report + # matters most. `hashFiles` skips this step when Lychee never ran. + # `!cancelled()` needs the `${{ }}` wrapper: a bare leading `!` is + # a YAML tag. + - name: Publish link report + if: ${{ !cancelled() && hashFiles('lychee-report.txt') != '' }} + run: | + { + echo '## Link check' + echo + echo '```text' + cat lychee-report.txt + echo '```' + } >> "$GITHUB_STEP_SUMMARY" diff --git a/config b/config index f5a8d40..3a0d13a 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit f5a8d4093cd9d54b39a13f4da61c2932cc12a909 +Subproject commit 3a0d13a6bc72f1d3b873a6c5f300aa9bf26ffe0a From 707066ceda810d78109485796a15beab16f4db9a Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Mon, 2 Nov 2026 17:59:03 +0000 Subject: [PATCH 3/4] Rename the root Gradle project to `documentation` The name now matches the repository. Co-Authored-By: Claude Opus 5.5 --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index 7b0cfaf..0c303e8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -12,7 +12,7 @@ * and limitations under the License. */ -rootProject.name = "spine-docs" +rootProject.name = "documentation" includeBuild("./docs/_code/samples") From 07a00435865eb0b8fd3ef4e0be917078d5667aa8 Mon Sep 17 00:00:00 2001 From: alexander-yevsyukov Date: Mon, 2 Nov 2026 18:15:02 +0000 Subject: [PATCH 4/4] Check out missing examples before embedding code The code samples come from the `docs/_code/examples` submodules, which `./init-submodules` leaves to this repository and a fresh clone or worktree lacks. `checkSamples` failed there, and `embedCode` embedded from empty directories, because `git submodule update --remote` skips submodules that were never initialized. The new `checkOutExamples` task, which both tasks depend on, checks out each missing example at its pinned commit. Examples that are already checked out stay as they are. Gradle writes a `.gradle` directory into an empty included build, which blocks the clone, so the script removes it when it is all there is. Co-Authored-By: Claude Opus 5.5 --- README.md | 3 +++ build.gradle.kts | 9 +++++++++ docs/_script/check-out-examples | 27 +++++++++++++++++++++++++++ 3 files changed, 39 insertions(+) create mode 100755 docs/_script/check-out-examples diff --git a/README.md b/README.md index 0478ea6..2994b33 100644 --- a/README.md +++ b/README.md @@ -180,6 +180,9 @@ To verify that the source code samples embedded into the pages are up-to-date, r ./gradlew :checkSamples ``` +Both tasks first check out the example submodules under `docs/_code/examples` +that are missing, at the commits this repository pins. + ## Product release To release a new version of Spine documentation, see the [SPINE_RELEASE.md](SPINE_RELEASE.md). diff --git a/build.gradle.kts b/build.gradle.kts index a10759d..6654ebf 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -35,10 +35,18 @@ task("installDependencies") { commandLine("./docs/_script/install-dependencies") } +/** + * Checks out the example submodules that the code samples come from, if they are missing. + */ +task("checkOutExamples") { + commandLine("./docs/_script/check-out-examples") +} + /** * Embeds the code samples into pages of the site. */ task("embedCode") { + dependsOn("checkOutExamples") commandLine("./docs/_script/embed-code") } @@ -46,6 +54,7 @@ task("embedCode") { * Verifies that the source code samples embedded into the pages are up-to-date. */ task("checkSamples") { + dependsOn("checkOutExamples") commandLine("./docs/_script/check-samples") } diff --git a/docs/_script/check-out-examples b/docs/_script/check-out-examples new file mode 100755 index 0000000..9f38413 --- /dev/null +++ b/docs/_script/check-out-examples @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +# Checks out the example submodules under `docs/_code/examples` that are not +# checked out yet, at the commits this repository pins. +# +# The embedded code samples come from these examples, but a fresh clone or +# worktree starts without them. Examples that are already checked out stay as +# they are, so this never rewinds one that `embed-code` moved to a newer commit. + +set -e + +git submodule status -- docs/_code/examples | while read -r status path _; do + # `git submodule status` marks a submodule that is not initialized with `-`. + case "$status" in + -*) ;; + *) continue ;; + esac + + # `settings.gradle.kts` includes some examples as builds, and Gradle writes its + # `.gradle` directory even into an empty one. Git does not clone into a + # directory that is not empty, so remove `.gradle` when it is all there is. + if [ "$(ls -A "$path")" = ".gradle" ]; then + rm -rf "${path:?}/.gradle" + fi + + git submodule update --init -- "$path" +done