From d05fec93d912c3993062bfacc9ca12e45070334f Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 19 Jul 2026 00:20:30 +0200 Subject: [PATCH 01/13] Make docs pipeline Zensical-only Remove mkdocs fallback from Build-Site, delete mkdocs fixture configs, and keep zensical.toml as the sole documentation configuration contract. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/Build-Site.yml | 58 +++++++++------- .github/zensical.toml | 69 +++++++++++++++++++ README.md | 2 + tests/srcTestRepo/mkdocs.yml | 75 --------------------- tests/srcTestRepo/zensical.toml | 69 +++++++++++++++++++ tests/srcWithManifestTestRepo/mkdocs.yml | 75 --------------------- tests/srcWithManifestTestRepo/zensical.toml | 69 +++++++++++++++++++ 7 files changed, 241 insertions(+), 176 deletions(-) create mode 100644 .github/zensical.toml delete mode 100644 tests/srcTestRepo/mkdocs.yml create mode 100644 tests/srcTestRepo/zensical.toml delete mode 100644 tests/srcWithManifestTestRepo/mkdocs.yml create mode 100644 tests/srcWithManifestTestRepo/zensical.toml diff --git a/.github/workflows/Build-Site.yml b/.github/workflows/Build-Site.yml index d128642e..fa629c1b 100644 --- a/.github/workflows/Build-Site.yml +++ b/.github/workflows/Build-Site.yml @@ -39,13 +39,10 @@ jobs: name: docs path: ${{ fromJson(inputs.Settings).WorkingDirectory }}/outputs/docs - - name: Install mkdocs-material + - name: Install Zensical shell: pwsh run: | - pip install mkdocs-material - pip install mkdocs-git-authors-plugin - pip install mkdocs-git-revision-date-localized-plugin - pip install mkdocs-git-committers-plugin-2 + pip install zensical - name: Structure site uses: PSModule/GitHub-Script@8083ec1f733f00357ee4d0db0c6056686e483bc0 # v1.9.0 @@ -111,49 +108,58 @@ jobs: Write-Host "Readme Target Path: $readmeTargetPath" } - LogGroup 'Build docs - Create mkdocs.yml' { + LogGroup 'Build docs - Create docs config' { $rootPath = Split-Path -Path $ModuleSourcePath -Parent $possiblePaths = @( - '.github/mkdocs.yml', - 'docs/mkdocs.yml', - 'mkdocs.yml' + '.github/zensical.toml', + 'docs/zensical.toml', + 'zensical.toml' ) - $mkdocsSourcePath = $null + $docsConfigSourcePath = $null foreach ($path in $possiblePaths) { $candidatePath = Join-Path -Path $rootPath -ChildPath $path if (Test-Path -Path $candidatePath) { - $mkdocsSourcePath = $candidatePath + $docsConfigSourcePath = $candidatePath break } } - if (-not $mkdocsSourcePath) { - throw "Mkdocs source file not found in any of the expected locations: $($possiblePaths -join ', ')" + if (-not $docsConfigSourcePath) { + throw "Documentation config file not found. Expected one of: $($possiblePaths -join ', ')" } - $mkdocsTargetPath = Join-Path -Path $SiteOutputPath -ChildPath 'mkdocs.yml' + $docsConfigFileName = [System.IO.Path]::GetFileName($docsConfigSourcePath) + $docsConfigTargetPath = Join-Path -Path $SiteOutputPath -ChildPath $docsConfigFileName - Write-Host "Mkdocs Source Path: $mkdocsSourcePath" - Write-Host "Mkdocs Target Path: $mkdocsTargetPath" + Write-Host "Docs Config Source: $docsConfigSourcePath" + Write-Host "Docs Config Target: $docsConfigTargetPath" - $mkdocsContent = Get-Content -Path $mkdocsSourcePath -Raw - $mkdocsContent = $mkdocsContent.Replace('-{{ REPO_NAME }}-', $ModuleName) - $mkdocsContent = $mkdocsContent.Replace('-{{ REPO_OWNER }}-', $env:GITHUB_REPOSITORY_OWNER) - $mkdocsContent | Set-Content -Path $mkdocsTargetPath -Force - Show-FileContent -Path $mkdocsTargetPath + $docsConfigContent = Get-Content -Path $docsConfigSourcePath -Raw + $docsConfigContent = $docsConfigContent.Replace('-{{ REPO_NAME }}-', $ModuleName) + $docsConfigContent = $docsConfigContent.Replace('-{{ REPO_OWNER }}-', $env:GITHUB_REPOSITORY_OWNER) + $docsConfigContent | Set-Content -Path $docsConfigTargetPath -Force + + Write-Host "Build Config Type: $docsConfigFileName" + Show-FileContent -Path $docsConfigTargetPath } - - name: Build mkdocs-material project + - name: Build documentation site with Zensical working-directory: ${{ fromJson(inputs.Settings).WorkingDirectory }}/outputs/site shell: pwsh run: | - LogGroup 'Build docs - mkdocs build - content' { - Show-FileContent -Path mkdocs.yml + if (-not (Test-Path -Path 'zensical.toml')) { + throw "No documentation config file found in outputs/site. Expected zensical.toml." + } + $configFile = 'zensical.toml' + + LogGroup 'Build docs - Zensical config content' { + Write-Host "Using config: $configFile" + Show-FileContent -Path $configFile } - LogGroup 'Build docs - mkdocs build' { - mkdocs build --config-file mkdocs.yml --site-dir ../../_site + LogGroup 'Build docs - Zensical build' { + zensical build --config-file $configFile --site-dir ../../_site } - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 diff --git a/.github/zensical.toml b/.github/zensical.toml new file mode 100644 index 00000000..650af9f7 --- /dev/null +++ b/.github/zensical.toml @@ -0,0 +1,69 @@ +[project] +site_name = "-{{ REPO_NAME }}-" +repo_name = "-{{ REPO_OWNER }}-/-{{ REPO_NAME }}-" +repo_url = "https://github.com/-{{ REPO_OWNER }}-/-{{ REPO_NAME }}-" + +[project.theme] +variant = "classic" +language = "en" +logo = "Assets/icon.png" +favicon = "Assets/icon.png" +features = [ + "navigation.instant", + "navigation.instant.progress", + "navigation.indexes", + "navigation.top", + "navigation.tracking", + "navigation.expand", + "search.suggest", + "search.highlight", + "content.code.copy" +] + +[[project.theme.palette]] +media = "(prefers-color-scheme)" +toggle.icon = "lucide/sun-moon" +toggle.name = "Switch to dark mode" + +[[project.theme.palette]] +media = "(prefers-color-scheme: dark)" +scheme = "slate" +primary = "black" +accent = "green" +toggle.icon = "lucide/moon" +toggle.name = "Switch to light mode" + +[[project.theme.palette]] +media = "(prefers-color-scheme: light)" +scheme = "default" +primary = "indigo" +accent = "green" +toggle.icon = "lucide/sun" +toggle.name = "Switch to system preference" + +[project.theme.icon] +repo = "fontawesome/brands/github" + +[project.markdown_extensions.toc] +permalink = true + +[project.markdown_extensions.attr_list] +[project.markdown_extensions.admonition] +[project.markdown_extensions.md_in_html] +[project.markdown_extensions.pymdownx.details] +[project.markdown_extensions.pymdownx.superfences] + +[[project.extra.social]] +icon = "fontawesome/brands/discord" +link = "https://discord.gg/jedJWCPAhD" +name = "-{{ REPO_OWNER }}- on Discord" + +[[project.extra.social]] +icon = "fontawesome/brands/github" +link = "https://github.com/-{{ REPO_OWNER }}-/" +name = "-{{ REPO_OWNER }}- on GitHub" + +[project.extra.consent] +title = "Cookie consent" +description = "We use cookies to recognize your repeated visits and preferences, as well as to measure the effectiveness of our documentation and whether users find what they're searching for. With your consent, you're helping us to make our documentation better." +actions = ["accept", "reject"] diff --git a/README.md b/README.md index 5e295557..db35db76 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ Process-PSModule is the corner-stone of the PSModule framework — an end-to-end GitHub Actions workflow that builds, tests, versions, documents, and publishes PowerShell modules to the PowerShell Gallery. +Documentation site generation is powered by Zensical. Repositories define site configuration in `zensical.toml`. + ## Documentation The full documentation lives on the MSX / Docs site: diff --git a/tests/srcTestRepo/mkdocs.yml b/tests/srcTestRepo/mkdocs.yml deleted file mode 100644 index df5e17ad..00000000 --- a/tests/srcTestRepo/mkdocs.yml +++ /dev/null @@ -1,75 +0,0 @@ -site_name: -{{ REPO_NAME }}- -theme: - name: material - language: en - font: - text: Roboto - code: Sono - logo: Assets/icon.png - favicon: Assets/icon.png - palette: - # Palette toggle for automatic mode - - media: "(prefers-color-scheme)" - toggle: - icon: material/link - name: Switch to dark mode - # Palette toggle for dark mode - - media: '(prefers-color-scheme: dark)' - scheme: slate - toggle: - primary: black - accent: green - icon: material/toggle-switch-off-outline - name: Switch to light mode - # Palette toggle for light mode - - media: '(prefers-color-scheme: light)' - scheme: default - toggle: - primary: indigo - accent: green - icon: material/toggle-switch - name: Switch to system preference - icon: - repo: material/github - features: - - navigation.instant - - navigation.instant.progress - - navigation.indexes - - navigation.top - - navigation.tracking - - navigation.expand - - search.suggest - - search.highlight - -repo_name: -{{ REPO_OWNER }}-/-{{ REPO_NAME }}- -repo_url: https://github.com/-{{ REPO_OWNER }}-/-{{ REPO_NAME }}- - -plugins: - - search - -markdown_extensions: - - toc: - permalink: true # Adds a link icon to headings - - attr_list - - admonition - - md_in_html - - pymdownx.details # Enables collapsible admonitions - -extra: - social: - - icon: fontawesome/brands/discord - link: https://discord.gg/jedJWCPAhD - name: -{{ REPO_OWNER }}- on Discord - - icon: fontawesome/brands/github - link: https://github.com/-{{ REPO_OWNER }}-/ - name: -{{ REPO_OWNER }}- on GitHub - consent: - title: Cookie consent - description: >- - We use cookies to recognize your repeated visits and preferences, as well - as to measure the effectiveness of our documentation and whether users - find what they're searching for. With your consent, you're helping us to - make our documentation better. - actions: - - accept - - reject diff --git a/tests/srcTestRepo/zensical.toml b/tests/srcTestRepo/zensical.toml new file mode 100644 index 00000000..650af9f7 --- /dev/null +++ b/tests/srcTestRepo/zensical.toml @@ -0,0 +1,69 @@ +[project] +site_name = "-{{ REPO_NAME }}-" +repo_name = "-{{ REPO_OWNER }}-/-{{ REPO_NAME }}-" +repo_url = "https://github.com/-{{ REPO_OWNER }}-/-{{ REPO_NAME }}-" + +[project.theme] +variant = "classic" +language = "en" +logo = "Assets/icon.png" +favicon = "Assets/icon.png" +features = [ + "navigation.instant", + "navigation.instant.progress", + "navigation.indexes", + "navigation.top", + "navigation.tracking", + "navigation.expand", + "search.suggest", + "search.highlight", + "content.code.copy" +] + +[[project.theme.palette]] +media = "(prefers-color-scheme)" +toggle.icon = "lucide/sun-moon" +toggle.name = "Switch to dark mode" + +[[project.theme.palette]] +media = "(prefers-color-scheme: dark)" +scheme = "slate" +primary = "black" +accent = "green" +toggle.icon = "lucide/moon" +toggle.name = "Switch to light mode" + +[[project.theme.palette]] +media = "(prefers-color-scheme: light)" +scheme = "default" +primary = "indigo" +accent = "green" +toggle.icon = "lucide/sun" +toggle.name = "Switch to system preference" + +[project.theme.icon] +repo = "fontawesome/brands/github" + +[project.markdown_extensions.toc] +permalink = true + +[project.markdown_extensions.attr_list] +[project.markdown_extensions.admonition] +[project.markdown_extensions.md_in_html] +[project.markdown_extensions.pymdownx.details] +[project.markdown_extensions.pymdownx.superfences] + +[[project.extra.social]] +icon = "fontawesome/brands/discord" +link = "https://discord.gg/jedJWCPAhD" +name = "-{{ REPO_OWNER }}- on Discord" + +[[project.extra.social]] +icon = "fontawesome/brands/github" +link = "https://github.com/-{{ REPO_OWNER }}-/" +name = "-{{ REPO_OWNER }}- on GitHub" + +[project.extra.consent] +title = "Cookie consent" +description = "We use cookies to recognize your repeated visits and preferences, as well as to measure the effectiveness of our documentation and whether users find what they're searching for. With your consent, you're helping us to make our documentation better." +actions = ["accept", "reject"] diff --git a/tests/srcWithManifestTestRepo/mkdocs.yml b/tests/srcWithManifestTestRepo/mkdocs.yml deleted file mode 100644 index df5e17ad..00000000 --- a/tests/srcWithManifestTestRepo/mkdocs.yml +++ /dev/null @@ -1,75 +0,0 @@ -site_name: -{{ REPO_NAME }}- -theme: - name: material - language: en - font: - text: Roboto - code: Sono - logo: Assets/icon.png - favicon: Assets/icon.png - palette: - # Palette toggle for automatic mode - - media: "(prefers-color-scheme)" - toggle: - icon: material/link - name: Switch to dark mode - # Palette toggle for dark mode - - media: '(prefers-color-scheme: dark)' - scheme: slate - toggle: - primary: black - accent: green - icon: material/toggle-switch-off-outline - name: Switch to light mode - # Palette toggle for light mode - - media: '(prefers-color-scheme: light)' - scheme: default - toggle: - primary: indigo - accent: green - icon: material/toggle-switch - name: Switch to system preference - icon: - repo: material/github - features: - - navigation.instant - - navigation.instant.progress - - navigation.indexes - - navigation.top - - navigation.tracking - - navigation.expand - - search.suggest - - search.highlight - -repo_name: -{{ REPO_OWNER }}-/-{{ REPO_NAME }}- -repo_url: https://github.com/-{{ REPO_OWNER }}-/-{{ REPO_NAME }}- - -plugins: - - search - -markdown_extensions: - - toc: - permalink: true # Adds a link icon to headings - - attr_list - - admonition - - md_in_html - - pymdownx.details # Enables collapsible admonitions - -extra: - social: - - icon: fontawesome/brands/discord - link: https://discord.gg/jedJWCPAhD - name: -{{ REPO_OWNER }}- on Discord - - icon: fontawesome/brands/github - link: https://github.com/-{{ REPO_OWNER }}-/ - name: -{{ REPO_OWNER }}- on GitHub - consent: - title: Cookie consent - description: >- - We use cookies to recognize your repeated visits and preferences, as well - as to measure the effectiveness of our documentation and whether users - find what they're searching for. With your consent, you're helping us to - make our documentation better. - actions: - - accept - - reject diff --git a/tests/srcWithManifestTestRepo/zensical.toml b/tests/srcWithManifestTestRepo/zensical.toml new file mode 100644 index 00000000..650af9f7 --- /dev/null +++ b/tests/srcWithManifestTestRepo/zensical.toml @@ -0,0 +1,69 @@ +[project] +site_name = "-{{ REPO_NAME }}-" +repo_name = "-{{ REPO_OWNER }}-/-{{ REPO_NAME }}-" +repo_url = "https://github.com/-{{ REPO_OWNER }}-/-{{ REPO_NAME }}-" + +[project.theme] +variant = "classic" +language = "en" +logo = "Assets/icon.png" +favicon = "Assets/icon.png" +features = [ + "navigation.instant", + "navigation.instant.progress", + "navigation.indexes", + "navigation.top", + "navigation.tracking", + "navigation.expand", + "search.suggest", + "search.highlight", + "content.code.copy" +] + +[[project.theme.palette]] +media = "(prefers-color-scheme)" +toggle.icon = "lucide/sun-moon" +toggle.name = "Switch to dark mode" + +[[project.theme.palette]] +media = "(prefers-color-scheme: dark)" +scheme = "slate" +primary = "black" +accent = "green" +toggle.icon = "lucide/moon" +toggle.name = "Switch to light mode" + +[[project.theme.palette]] +media = "(prefers-color-scheme: light)" +scheme = "default" +primary = "indigo" +accent = "green" +toggle.icon = "lucide/sun" +toggle.name = "Switch to system preference" + +[project.theme.icon] +repo = "fontawesome/brands/github" + +[project.markdown_extensions.toc] +permalink = true + +[project.markdown_extensions.attr_list] +[project.markdown_extensions.admonition] +[project.markdown_extensions.md_in_html] +[project.markdown_extensions.pymdownx.details] +[project.markdown_extensions.pymdownx.superfences] + +[[project.extra.social]] +icon = "fontawesome/brands/discord" +link = "https://discord.gg/jedJWCPAhD" +name = "-{{ REPO_OWNER }}- on Discord" + +[[project.extra.social]] +icon = "fontawesome/brands/github" +link = "https://github.com/-{{ REPO_OWNER }}-/" +name = "-{{ REPO_OWNER }}- on GitHub" + +[project.extra.consent] +title = "Cookie consent" +description = "We use cookies to recognize your repeated visits and preferences, as well as to measure the effectiveness of our documentation and whether users find what they're searching for. With your consent, you're helping us to make our documentation better." +actions = ["accept", "reject"] From a088ceaf71ac799fa02d437535b9de6a2800879d Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 19 Jul 2026 00:37:58 +0200 Subject: [PATCH 02/13] Fix Zensical build output handling Remove unsupported --site-dir flag, inject site_dir into zensical.toml when absent, and assert ../../_site output exists for Pages artifact upload. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/Build-Site.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/Build-Site.yml b/.github/workflows/Build-Site.yml index fa629c1b..35c6391f 100644 --- a/.github/workflows/Build-Site.yml +++ b/.github/workflows/Build-Site.yml @@ -138,6 +138,9 @@ jobs: $docsConfigContent = Get-Content -Path $docsConfigSourcePath -Raw $docsConfigContent = $docsConfigContent.Replace('-{{ REPO_NAME }}-', $ModuleName) $docsConfigContent = $docsConfigContent.Replace('-{{ REPO_OWNER }}-', $env:GITHUB_REPOSITORY_OWNER) + if ($docsConfigFileName -eq 'zensical.toml' -and $docsConfigContent -notmatch '(?m)^\s*site_dir\s*=') { + $docsConfigContent = $docsConfigContent -replace '(?m)^\[project\]\s*$', "[project]`nsite_dir = ""../../_site""" + } $docsConfigContent | Set-Content -Path $docsConfigTargetPath -Force Write-Host "Build Config Type: $docsConfigFileName" @@ -159,7 +162,13 @@ jobs: } LogGroup 'Build docs - Zensical build' { - zensical build --config-file $configFile --site-dir ../../_site + zensical build --config-file $configFile + } + + LogGroup 'Build docs - verify output' { + if (-not (Test-Path -Path '../../_site')) { + throw "Expected Zensical output at ../../_site but it was not created." + } } - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 From 75912aca33a0d100420e1f781195c95b4a413b74 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 19 Jul 2026 00:46:59 +0200 Subject: [PATCH 03/13] Constrain Zensical site_dir to project root Set zensical site_dir to _site in generated config, move built output to ../../_site for artifact upload, and keep Build-Site output contract intact. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/Build-Site.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/Build-Site.yml b/.github/workflows/Build-Site.yml index 35c6391f..575d8361 100644 --- a/.github/workflows/Build-Site.yml +++ b/.github/workflows/Build-Site.yml @@ -138,8 +138,12 @@ jobs: $docsConfigContent = Get-Content -Path $docsConfigSourcePath -Raw $docsConfigContent = $docsConfigContent.Replace('-{{ REPO_NAME }}-', $ModuleName) $docsConfigContent = $docsConfigContent.Replace('-{{ REPO_OWNER }}-', $env:GITHUB_REPOSITORY_OWNER) - if ($docsConfigFileName -eq 'zensical.toml' -and $docsConfigContent -notmatch '(?m)^\s*site_dir\s*=') { - $docsConfigContent = $docsConfigContent -replace '(?m)^\[project\]\s*$', "[project]`nsite_dir = ""../../_site""" + if ($docsConfigFileName -eq 'zensical.toml') { + if ($docsConfigContent -match '(?m)^\s*site_dir\s*=') { + $docsConfigContent = $docsConfigContent -replace '(?m)^\s*site_dir\s*=.*$', 'site_dir = "_site"' + } else { + $docsConfigContent = $docsConfigContent -replace '(?m)^\[project\]\s*$', "[project]`nsite_dir = ""_site""" + } } $docsConfigContent | Set-Content -Path $docsConfigTargetPath -Force @@ -166,6 +170,12 @@ jobs: } LogGroup 'Build docs - verify output' { + if (Test-Path -Path '_site') { + if (Test-Path -Path '../../_site') { + Remove-Item -Path '../../_site' -Recurse -Force + } + Move-Item -Path '_site' -Destination '../../_site' -Force + } if (-not (Test-Path -Path '../../_site')) { throw "Expected Zensical output at ../../_site but it was not created." } From 6f6c4f44f856307abff05d89a8affa6508d4a4dc Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 19 Jul 2026 00:51:58 +0200 Subject: [PATCH 04/13] Use PSModule helper imports Replace missing Helpers module imports with PSModule in publish and prerelease-cleanup actions. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/actions/Cleanup-PSModulePrereleases/src/cleanup.ps1 | 2 +- .github/actions/Publish-PSModule/src/publish.ps1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/Cleanup-PSModulePrereleases/src/cleanup.ps1 b/.github/actions/Cleanup-PSModulePrereleases/src/cleanup.ps1 index 546593a8..a10bdf1d 100644 --- a/.github/actions/Cleanup-PSModulePrereleases/src/cleanup.ps1 +++ b/.github/actions/Cleanup-PSModulePrereleases/src/cleanup.ps1 @@ -3,7 +3,7 @@ param() $PSStyle.OutputRendering = 'Ansi' -Import-Module -Name 'Helpers' -Force +Import-Module -Name 'PSModule' -Force #region Load inputs LogGroup 'Load inputs' { diff --git a/.github/actions/Publish-PSModule/src/publish.ps1 b/.github/actions/Publish-PSModule/src/publish.ps1 index d99b31be..b9f76254 100644 --- a/.github/actions/Publish-PSModule/src/publish.ps1 +++ b/.github/actions/Publish-PSModule/src/publish.ps1 @@ -27,7 +27,7 @@ param() $PSStyle.OutputRendering = 'Ansi' -Import-Module -Name 'Helpers' -Force +Import-Module -Name 'PSModule' -Force #region Load inputs LogGroup 'Load inputs' { From 2b5cc1c2bdda397b970fb4d063a34d93b8c2d519 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 19 Jul 2026 02:43:56 +0200 Subject: [PATCH 05/13] Inject nav-state script centrally Inject a centralized nav-state script into generated site HTML during Build-Site so first-level menu defaults and persisted fold state are applied for all repositories using Process-PSModule. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/Build-Site.yml | 97 ++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/.github/workflows/Build-Site.yml b/.github/workflows/Build-Site.yml index 575d8361..f3b752c6 100644 --- a/.github/workflows/Build-Site.yml +++ b/.github/workflows/Build-Site.yml @@ -179,6 +179,103 @@ jobs: if (-not (Test-Path -Path '../../_site')) { throw "Expected Zensical output at ../../_site but it was not created." } + + $navStateScript = @' +(() => { + const storageKey = "zensical-nav-state-v1"; + let lastSignature = ""; + + const getToggles = () => + Array.from(document.querySelectorAll("input.md-nav__toggle.md-toggle[id]")); + + const getPrimaryList = () => + document.querySelector("nav.md-nav--primary > ul.md-nav__list"); + + const applyDefaultTopLevelState = (toggles) => { + const primaryList = getPrimaryList(); + if (!primaryList) { + return; + } + + const topLevelToggles = Array.from( + primaryList.querySelectorAll( + ":scope > li > input.md-nav__toggle.md-toggle[id]" + ) + ); + const topLevelIds = new Set(topLevelToggles.map((toggle) => toggle.id)); + + for (const toggle of toggles) { + toggle.checked = topLevelIds.has(toggle.id); + } + }; + + const restoreState = (toggles) => { + const raw = localStorage.getItem(storageKey); + if (!raw) { + applyDefaultTopLevelState(toggles); + return; + } + + try { + const state = JSON.parse(raw); + for (const toggle of toggles) { + if (Object.prototype.hasOwnProperty.call(state, toggle.id)) { + toggle.checked = !!state[toggle.id]; + } + } + } catch { + applyDefaultTopLevelState(toggles); + } + }; + + const persistState = (toggles) => { + const state = {}; + for (const toggle of toggles) { + state[toggle.id] = !!toggle.checked; + } + localStorage.setItem(storageKey, JSON.stringify(state)); + }; + + const initialize = () => { + const toggles = getToggles(); + if (toggles.length === 0) { + return; + } + + const signature = toggles.map((toggle) => toggle.id).join("|"); + if (signature === lastSignature) { + return; + } + + lastSignature = signature; + restoreState(toggles); + for (const toggle of toggles) { + if (toggle.dataset.navStateBound === "true") { + continue; + } + + toggle.dataset.navStateBound = "true"; + toggle.addEventListener("change", () => persistState(getToggles())); + } + }; + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", initialize, { once: true }); + } else { + initialize(); + } + + setInterval(initialize, 500); +})(); +'@ + + Get-ChildItem -Path '../../_site' -Filter '*.html' -Recurse | ForEach-Object { + $html = Get-Content -Path $_.FullName -Raw + if ($html -notmatch 'zensical-nav-state-v1') { + $html = $html -replace '', "`n" + Set-Content -Path $_.FullName -Value $html -NoNewline + } + } } - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 From 528fed2855538ac54c8d7d17ea0dff972b54e9e3 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 19 Jul 2026 02:45:49 +0200 Subject: [PATCH 06/13] Fix Build-Site YAML script block Replace indented here-string with inline JavaScript string assignment so the workflow YAML parses correctly while retaining central nav-state injection. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/Build-Site.yml | 89 +------------------------------- 1 file changed, 1 insertion(+), 88 deletions(-) diff --git a/.github/workflows/Build-Site.yml b/.github/workflows/Build-Site.yml index f3b752c6..268ee11d 100644 --- a/.github/workflows/Build-Site.yml +++ b/.github/workflows/Build-Site.yml @@ -180,94 +180,7 @@ jobs: throw "Expected Zensical output at ../../_site but it was not created." } - $navStateScript = @' -(() => { - const storageKey = "zensical-nav-state-v1"; - let lastSignature = ""; - - const getToggles = () => - Array.from(document.querySelectorAll("input.md-nav__toggle.md-toggle[id]")); - - const getPrimaryList = () => - document.querySelector("nav.md-nav--primary > ul.md-nav__list"); - - const applyDefaultTopLevelState = (toggles) => { - const primaryList = getPrimaryList(); - if (!primaryList) { - return; - } - - const topLevelToggles = Array.from( - primaryList.querySelectorAll( - ":scope > li > input.md-nav__toggle.md-toggle[id]" - ) - ); - const topLevelIds = new Set(topLevelToggles.map((toggle) => toggle.id)); - - for (const toggle of toggles) { - toggle.checked = topLevelIds.has(toggle.id); - } - }; - - const restoreState = (toggles) => { - const raw = localStorage.getItem(storageKey); - if (!raw) { - applyDefaultTopLevelState(toggles); - return; - } - - try { - const state = JSON.parse(raw); - for (const toggle of toggles) { - if (Object.prototype.hasOwnProperty.call(state, toggle.id)) { - toggle.checked = !!state[toggle.id]; - } - } - } catch { - applyDefaultTopLevelState(toggles); - } - }; - - const persistState = (toggles) => { - const state = {}; - for (const toggle of toggles) { - state[toggle.id] = !!toggle.checked; - } - localStorage.setItem(storageKey, JSON.stringify(state)); - }; - - const initialize = () => { - const toggles = getToggles(); - if (toggles.length === 0) { - return; - } - - const signature = toggles.map((toggle) => toggle.id).join("|"); - if (signature === lastSignature) { - return; - } - - lastSignature = signature; - restoreState(toggles); - for (const toggle of toggles) { - if (toggle.dataset.navStateBound === "true") { - continue; - } - - toggle.dataset.navStateBound = "true"; - toggle.addEventListener("change", () => persistState(getToggles())); - } - }; - - if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", initialize, { once: true }); - } else { - initialize(); - } - - setInterval(initialize, 500); -})(); -'@ + $navStateScript = '(()=>{const k="zensical-nav-state-v1";let s="";const g=()=>Array.from(document.querySelectorAll("input.md-nav__toggle.md-toggle[id]"));const p=()=>document.querySelector("nav.md-nav--primary > ul.md-nav__list");const d=t=>{const l=p();if(!l)return;const n=new Set(Array.from(l.querySelectorAll(":scope > li > input.md-nav__toggle.md-toggle[id]")).map(x=>x.id));for(const x of t){x.checked=n.has(x.id)}};const r=t=>{const raw=localStorage.getItem(k);if(!raw){d(t);return;}try{const m=JSON.parse(raw);for(const x of t){if(Object.prototype.hasOwnProperty.call(m,x.id))x.checked=!!m[x.id];}}catch{d(t)}};const w=t=>{const m={};for(const x of t){m[x.id]=!!x.checked;}localStorage.setItem(k,JSON.stringify(m));};const i=()=>{const t=g();if(t.length===0)return;const sig=t.map(x=>x.id).join("|");if(sig===s)return;s=sig;r(t);for(const x of t){if(x.dataset.navStateBound==="true")continue;x.dataset.navStateBound="true";x.addEventListener("change",()=>w(g()));}};if(document.readyState==="loading"){document.addEventListener("DOMContentLoaded",i,{once:true});}else{i();}setInterval(i,500);})();' Get-ChildItem -Path '../../_site' -Filter '*.html' -Recurse | ForEach-Object { $html = Get-Content -Path $_.FullName -Raw From d5deb4d7c8999914351686312643497d0b95a0e4 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 19 Jul 2026 03:10:10 +0200 Subject: [PATCH 07/13] Extract site script injection action Move long inline PowerShell from Build-Site into a dedicated local action with src/inject-site-scripts.ps1, and switch to folder-based site injector scripts under .github/scripts/site-injectors/. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/actions/Inject-SiteScripts/action.yml | 21 +++++ .../src/inject-site-scripts.ps1 | 41 +++++++++ .github/scripts/site-injectors/nav-state.js | 86 +++++++++++++++++++ .github/workflows/Build-Site.yml | 16 ++-- 4 files changed, 154 insertions(+), 10 deletions(-) create mode 100644 .github/actions/Inject-SiteScripts/action.yml create mode 100644 .github/actions/Inject-SiteScripts/src/inject-site-scripts.ps1 create mode 100644 .github/scripts/site-injectors/nav-state.js diff --git a/.github/actions/Inject-SiteScripts/action.yml b/.github/actions/Inject-SiteScripts/action.yml new file mode 100644 index 00000000..c4dc1967 --- /dev/null +++ b/.github/actions/Inject-SiteScripts/action.yml @@ -0,0 +1,21 @@ +name: Inject-SiteScripts +description: Inject shared JavaScript snippets into generated site HTML files. + +inputs: + SitePath: + description: Path to the generated site output directory. + required: true + WorkflowPath: + description: Path to the checked out workflow repository root. + required: false + default: _wf + +runs: + using: composite + steps: + - name: Inject site scripts + shell: pwsh + run: | + & "${{ github.action_path }}/src/inject-site-scripts.ps1" ` + -SitePath "${{ inputs.SitePath }}" ` + -WorkflowPath "${{ inputs.WorkflowPath }}" diff --git a/.github/actions/Inject-SiteScripts/src/inject-site-scripts.ps1 b/.github/actions/Inject-SiteScripts/src/inject-site-scripts.ps1 new file mode 100644 index 00000000..3c10d48a --- /dev/null +++ b/.github/actions/Inject-SiteScripts/src/inject-site-scripts.ps1 @@ -0,0 +1,41 @@ +param( + [Parameter(Mandatory)] + [string]$SitePath, + + [Parameter(Mandatory)] + [string]$WorkflowPath +) + +$resolvedSitePath = Resolve-Path -Path $SitePath -ErrorAction Stop | Select-Object -ExpandProperty Path +$injectorsPath = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath "$WorkflowPath/.github/scripts/site-injectors" + +if (-not (Test-Path -Path $injectorsPath)) { + throw "Expected site injector folder at $injectorsPath but it was not found." +} + +$injectorScripts = Get-ChildItem -Path $injectorsPath -File -Filter '*.js' | Sort-Object -Property Name +if (-not $injectorScripts) { + Write-Host "No site injector scripts found under $injectorsPath." + exit 0 +} + +Get-ChildItem -Path $resolvedSitePath -Filter '*.html' -Recurse | ForEach-Object { + $html = Get-Content -Path $_.FullName -Raw + $modified = $false + + foreach ($injectorScript in $injectorScripts) { + $marker = "data-psmodule-site-injector=""$($injectorScript.Name)""" + if ($html -match [Regex]::Escape($marker)) { + continue + } + + $scriptContent = Get-Content -Path $injectorScript.FullName -Raw + $injectedScript = "" + $html = $html -replace '', "$injectedScript`n" + $modified = $true + } + + if ($modified) { + Set-Content -Path $_.FullName -Value $html -NoNewline + } +} diff --git a/.github/scripts/site-injectors/nav-state.js b/.github/scripts/site-injectors/nav-state.js new file mode 100644 index 00000000..f78a3f1d --- /dev/null +++ b/.github/scripts/site-injectors/nav-state.js @@ -0,0 +1,86 @@ +(() => { + const storageKey = "zensical-nav-state-v1"; + let lastSignature = ""; + + const getToggles = () => + Array.from(document.querySelectorAll("input.md-nav__toggle.md-toggle[id]")); + + const getPrimaryList = () => + document.querySelector("nav.md-nav--primary > ul.md-nav__list"); + + const applyDefaultTopLevelState = (toggles) => { + const primaryList = getPrimaryList(); + if (!primaryList) { + return; + } + + const topLevelToggles = Array.from( + primaryList.querySelectorAll( + ":scope > li > input.md-nav__toggle.md-toggle[id]" + ) + ); + const topLevelIds = new Set(topLevelToggles.map((toggle) => toggle.id)); + + for (const toggle of toggles) { + toggle.checked = topLevelIds.has(toggle.id); + } + }; + + const restoreState = (toggles) => { + const raw = localStorage.getItem(storageKey); + if (!raw) { + applyDefaultTopLevelState(toggles); + return; + } + + try { + const state = JSON.parse(raw); + for (const toggle of toggles) { + if (Object.prototype.hasOwnProperty.call(state, toggle.id)) { + toggle.checked = !!state[toggle.id]; + } + } + } catch { + applyDefaultTopLevelState(toggles); + } + }; + + const persistState = (toggles) => { + const state = {}; + for (const toggle of toggles) { + state[toggle.id] = !!toggle.checked; + } + localStorage.setItem(storageKey, JSON.stringify(state)); + }; + + const initialize = () => { + const toggles = getToggles(); + if (toggles.length === 0) { + return; + } + + const signature = toggles.map((toggle) => toggle.id).join("|"); + if (signature === lastSignature) { + return; + } + + lastSignature = signature; + restoreState(toggles); + for (const toggle of toggles) { + if (toggle.dataset.navStateBound === "true") { + continue; + } + + toggle.dataset.navStateBound = "true"; + toggle.addEventListener("change", () => persistState(getToggles())); + } + }; + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", initialize, { once: true }); + } else { + initialize(); + } + + setInterval(initialize, 500); +})(); diff --git a/.github/workflows/Build-Site.yml b/.github/workflows/Build-Site.yml index 268ee11d..603ddc30 100644 --- a/.github/workflows/Build-Site.yml +++ b/.github/workflows/Build-Site.yml @@ -179,18 +179,14 @@ jobs: if (-not (Test-Path -Path '../../_site')) { throw "Expected Zensical output at ../../_site but it was not created." } - - $navStateScript = '(()=>{const k="zensical-nav-state-v1";let s="";const g=()=>Array.from(document.querySelectorAll("input.md-nav__toggle.md-toggle[id]"));const p=()=>document.querySelector("nav.md-nav--primary > ul.md-nav__list");const d=t=>{const l=p();if(!l)return;const n=new Set(Array.from(l.querySelectorAll(":scope > li > input.md-nav__toggle.md-toggle[id]")).map(x=>x.id));for(const x of t){x.checked=n.has(x.id)}};const r=t=>{const raw=localStorage.getItem(k);if(!raw){d(t);return;}try{const m=JSON.parse(raw);for(const x of t){if(Object.prototype.hasOwnProperty.call(m,x.id))x.checked=!!m[x.id];}}catch{d(t)}};const w=t=>{const m={};for(const x of t){m[x.id]=!!x.checked;}localStorage.setItem(k,JSON.stringify(m));};const i=()=>{const t=g();if(t.length===0)return;const sig=t.map(x=>x.id).join("|");if(sig===s)return;s=sig;r(t);for(const x of t){if(x.dataset.navStateBound==="true")continue;x.dataset.navStateBound="true";x.addEventListener("change",()=>w(g()));}};if(document.readyState==="loading"){document.addEventListener("DOMContentLoaded",i,{once:true});}else{i();}setInterval(i,500);})();' - - Get-ChildItem -Path '../../_site' -Filter '*.html' -Recurse | ForEach-Object { - $html = Get-Content -Path $_.FullName -Raw - if ($html -notmatch 'zensical-nav-state-v1') { - $html = $html -replace '', "`n" - Set-Content -Path $_.FullName -Value $html -NoNewline - } - } } + - name: Inject shared site scripts + uses: ./_wf/.github/actions/Inject-SiteScripts + with: + SitePath: ${{ fromJson(inputs.Settings).WorkingDirectory }}/_site + WorkflowPath: _wf + - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 with: name: github-pages From 214dfb30ac454efdd7b5286f35cb9a5b735cc60b Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 19 Jul 2026 03:15:22 +0200 Subject: [PATCH 08/13] Extract all multiline Build-Site steps Move every multiline run/Script step in Build-Site into internal composite actions with src scripts: Install-Zensical, Structure-Site, Build-ZensicalSite, and keep Inject-SiteScripts as action-backed invocation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/actions/Build-ZensicalSite/action.yml | 14 ++ .../src/build-zensical-site.ps1 | 29 ++++ .github/actions/Inject-SiteScripts/action.yml | 5 +- .github/actions/Install-Zensical/action.yml | 9 ++ .../Install-Zensical/src/install-zensical.ps1 | 3 + .github/actions/Structure-Site/action.yml | 18 +++ .../Structure-Site/src/structure-site.ps1 | 77 ++++++++++ .github/workflows/Build-Site.yml | 140 +----------------- 8 files changed, 157 insertions(+), 138 deletions(-) create mode 100644 .github/actions/Build-ZensicalSite/action.yml create mode 100644 .github/actions/Build-ZensicalSite/src/build-zensical-site.ps1 create mode 100644 .github/actions/Install-Zensical/action.yml create mode 100644 .github/actions/Install-Zensical/src/install-zensical.ps1 create mode 100644 .github/actions/Structure-Site/action.yml create mode 100644 .github/actions/Structure-Site/src/structure-site.ps1 diff --git a/.github/actions/Build-ZensicalSite/action.yml b/.github/actions/Build-ZensicalSite/action.yml new file mode 100644 index 00000000..3aeaa607 --- /dev/null +++ b/.github/actions/Build-ZensicalSite/action.yml @@ -0,0 +1,14 @@ +name: Build-ZensicalSite +description: Build Zensical site output and normalize output folder. + +inputs: + WorkingDirectory: + description: Working directory for the repository build. + required: true + +runs: + using: composite + steps: + - name: Build documentation site + shell: pwsh + run: '& "${{ github.action_path }}/src/build-zensical-site.ps1" -WorkingDirectory "${{ inputs.WorkingDirectory }}"' diff --git a/.github/actions/Build-ZensicalSite/src/build-zensical-site.ps1 b/.github/actions/Build-ZensicalSite/src/build-zensical-site.ps1 new file mode 100644 index 00000000..090f1a09 --- /dev/null +++ b/.github/actions/Build-ZensicalSite/src/build-zensical-site.ps1 @@ -0,0 +1,29 @@ +param( + [Parameter(Mandatory)] + [string]$WorkingDirectory +) + +$ErrorActionPreference = 'Stop' + +$resolvedWorkingDirectory = Resolve-Path -Path $WorkingDirectory | Select-Object -ExpandProperty Path +$siteWorkingDirectory = Join-Path -Path $resolvedWorkingDirectory -ChildPath 'outputs/site' +$outputSitePath = Join-Path -Path $resolvedWorkingDirectory -ChildPath '_site' + +Set-Location -Path $siteWorkingDirectory + +if (-not (Test-Path -Path 'zensical.toml')) { + throw "No documentation config file found in outputs/site. Expected zensical.toml." +} + +zensical build --config-file 'zensical.toml' + +if (Test-Path -Path '_site') { + if (Test-Path -Path $outputSitePath) { + Remove-Item -Path $outputSitePath -Recurse -Force + } + Move-Item -Path '_site' -Destination $outputSitePath -Force +} + +if (-not (Test-Path -Path $outputSitePath)) { + throw "Expected Zensical output at $outputSitePath but it was not created." +} diff --git a/.github/actions/Inject-SiteScripts/action.yml b/.github/actions/Inject-SiteScripts/action.yml index c4dc1967..7e5fa2ab 100644 --- a/.github/actions/Inject-SiteScripts/action.yml +++ b/.github/actions/Inject-SiteScripts/action.yml @@ -15,7 +15,4 @@ runs: steps: - name: Inject site scripts shell: pwsh - run: | - & "${{ github.action_path }}/src/inject-site-scripts.ps1" ` - -SitePath "${{ inputs.SitePath }}" ` - -WorkflowPath "${{ inputs.WorkflowPath }}" + run: '& "${{ github.action_path }}/src/inject-site-scripts.ps1" -SitePath "${{ inputs.SitePath }}" -WorkflowPath "${{ inputs.WorkflowPath }}"' diff --git a/.github/actions/Install-Zensical/action.yml b/.github/actions/Install-Zensical/action.yml new file mode 100644 index 00000000..3a54443b --- /dev/null +++ b/.github/actions/Install-Zensical/action.yml @@ -0,0 +1,9 @@ +name: Install-Zensical +description: Install Zensical CLI used for documentation site generation. + +runs: + using: composite + steps: + - name: Install Zensical CLI + shell: pwsh + run: '& "${{ github.action_path }}/src/install-zensical.ps1"' diff --git a/.github/actions/Install-Zensical/src/install-zensical.ps1 b/.github/actions/Install-Zensical/src/install-zensical.ps1 new file mode 100644 index 00000000..a42e7982 --- /dev/null +++ b/.github/actions/Install-Zensical/src/install-zensical.ps1 @@ -0,0 +1,3 @@ +$ErrorActionPreference = 'Stop' + +pip install zensical diff --git a/.github/actions/Structure-Site/action.yml b/.github/actions/Structure-Site/action.yml new file mode 100644 index 00000000..842f8de3 --- /dev/null +++ b/.github/actions/Structure-Site/action.yml @@ -0,0 +1,18 @@ +name: Structure-Site +description: Structure generated docs and create site config for Zensical. + +inputs: + WorkingDirectory: + description: Working directory for the repository build. + required: true + Name: + description: Optional module name override. + required: false + default: '' + +runs: + using: composite + steps: + - name: Structure site content and config + shell: pwsh + run: '& "${{ github.action_path }}/src/structure-site.ps1" -WorkingDirectory "${{ inputs.WorkingDirectory }}" -Name "${{ inputs.Name }}"' diff --git a/.github/actions/Structure-Site/src/structure-site.ps1 b/.github/actions/Structure-Site/src/structure-site.ps1 new file mode 100644 index 00000000..68ece30b --- /dev/null +++ b/.github/actions/Structure-Site/src/structure-site.ps1 @@ -0,0 +1,77 @@ +param( + [Parameter(Mandatory)] + [string]$WorkingDirectory, + + [Parameter()] + [string]$Name +) + +$ErrorActionPreference = 'Stop' + +$resolvedWorkingDirectory = Resolve-Path -Path $WorkingDirectory | Select-Object -ExpandProperty Path +$siteOutputPath = Join-Path -Path $resolvedWorkingDirectory -ChildPath 'outputs/site' +$docsOutputPath = Join-Path -Path $resolvedWorkingDirectory -ChildPath 'outputs/docs' +$moduleSourcePath = Join-Path -Path $resolvedWorkingDirectory -ChildPath 'src' +$moduleName = if ([string]::IsNullOrEmpty($Name)) { $env:GITHUB_REPOSITORY_NAME } else { $Name } + +$functionDocsFolder = New-Item -Path (Join-Path -Path $siteOutputPath -ChildPath 'docs/Functions') -ItemType Directory -Force +Copy-Item -Path (Join-Path -Path $docsOutputPath -ChildPath '*') -Destination $functionDocsFolder.FullName -Recurse -Force + +Write-Host "Function Docs Folder: $($functionDocsFolder.FullName)" +Write-Host "Module Name: $moduleName" +Write-Host "Module Source Path: $moduleSourcePath" +Write-Host "Site Output Path: $siteOutputPath" + +$aboutDocsFolder = New-Item -Path (Join-Path -Path $siteOutputPath -ChildPath 'docs/About') -ItemType Directory -Force +$aboutSourceFolder = Join-Path -Path $moduleSourcePath -ChildPath 'en-US' +if (Test-Path -Path $aboutSourceFolder) { + Get-ChildItem -Path $aboutSourceFolder -Filter '*.txt' | Copy-Item -Destination $aboutDocsFolder.FullName -Force -PassThru | + Rename-Item -NewName { $_.Name -replace '\.txt$', '.md' } +} + +$assetsFolder = New-Item -Path (Join-Path -Path $siteOutputPath -ChildPath 'docs/Assets') -ItemType Directory -Force +$iconPath = Join-Path -Path $resolvedWorkingDirectory -ChildPath 'icon/icon.png' +if (Test-Path -Path $iconPath) { + Copy-Item -Path $iconPath -Destination $assetsFolder.FullName -Force +} + +$readmePath = Join-Path -Path $resolvedWorkingDirectory -ChildPath 'README.md' +$readmeTargetPath = Join-Path -Path $siteOutputPath -ChildPath 'docs/README.md' +Copy-Item -Path $readmePath -Destination $readmeTargetPath -Force + +$possiblePaths = @( + '.github/zensical.toml', + 'docs/zensical.toml', + 'zensical.toml' +) + +$docsConfigSourcePath = $null +foreach ($relativePath in $possiblePaths) { + $candidatePath = Join-Path -Path $resolvedWorkingDirectory -ChildPath $relativePath + if (Test-Path -Path $candidatePath) { + $docsConfigSourcePath = $candidatePath + break + } +} + +if (-not $docsConfigSourcePath) { + throw "Documentation config file not found. Expected one of: $($possiblePaths -join ', ')" +} + +$docsConfigFileName = [System.IO.Path]::GetFileName($docsConfigSourcePath) +$docsConfigTargetPath = Join-Path -Path $siteOutputPath -ChildPath $docsConfigFileName + +$docsConfigContent = Get-Content -Path $docsConfigSourcePath -Raw +$docsConfigContent = $docsConfigContent.Replace('-{{ REPO_NAME }}-', $moduleName) +$docsConfigContent = $docsConfigContent.Replace('-{{ REPO_OWNER }}-', $env:GITHUB_REPOSITORY_OWNER) + +if ($docsConfigFileName -eq 'zensical.toml') { + if ($docsConfigContent -match '(?m)^\s*site_dir\s*=') { + $docsConfigContent = $docsConfigContent -replace '(?m)^\s*site_dir\s*=.*$', 'site_dir = "_site"' + } else { + $docsConfigContent = $docsConfigContent -replace '(?m)^\[project\]\s*$', "[project]`nsite_dir = ""_site""" + } +} + +Set-Content -Path $docsConfigTargetPath -Value $docsConfigContent -Force +Write-Host "Build Config Type: $docsConfigFileName" diff --git a/.github/workflows/Build-Site.yml b/.github/workflows/Build-Site.yml index 603ddc30..cbf3b69f 100644 --- a/.github/workflows/Build-Site.yml +++ b/.github/workflows/Build-Site.yml @@ -40,146 +40,18 @@ jobs: path: ${{ fromJson(inputs.Settings).WorkingDirectory }}/outputs/docs - name: Install Zensical - shell: pwsh - run: | - pip install zensical + uses: ./_wf/.github/actions/Install-Zensical - name: Structure site - uses: PSModule/GitHub-Script@8083ec1f733f00357ee4d0db0c6056686e483bc0 # v1.9.0 + uses: ./_wf/.github/actions/Structure-Site with: - Debug: ${{ fromJson(inputs.Settings).Debug }} - Prerelease: ${{ fromJson(inputs.Settings).Prerelease }} - Verbose: ${{ fromJson(inputs.Settings).Verbose }} - Version: ${{ fromJson(inputs.Settings).Version }} WorkingDirectory: ${{ fromJson(inputs.Settings).WorkingDirectory }} - Script: | - LogGroup "Get folder structure" { - $functionDocsFolder = New-Item -Path "outputs/site/docs/Functions" -ItemType Directory -Force - Copy-Item -Path "outputs/docs/*" -Destination "outputs/site/docs/Functions" -Recurse -Force - $moduleName = [string]::IsNullOrEmpty('${{ fromJson(inputs.Settings).Name }}') ? $env:GITHUB_REPOSITORY_NAME : '${{ fromJson(inputs.Settings).Name }}' - $ModuleSourcePath = Resolve-Path 'src' | Select-Object -ExpandProperty Path - $SiteOutputPath = Resolve-Path 'outputs/site' | Select-Object -ExpandProperty Path - - Write-Host "Function Docs Folder: $functionDocsFolder" - Write-Host "Module Name: $moduleName" - Write-Host "Module Source Path: $ModuleSourcePath" - Write-Host "Site Output Path: $SiteOutputPath" - } - - LogGroup "Get folder structure" { - Get-ChildItem -Recurse | Select-Object -ExpandProperty FullName | Sort-Object | Format-List - } - - Get-ChildItem -Path $functionDocsFolder -Recurse -Force -Include '*.md' | ForEach-Object { - $fileName = $_.Name - LogGroup " - $fileName" { - Show-FileContent -Path $_ - } - } - - LogGroup 'Build docs - Process about topics' { - $aboutDocsFolderPath = Join-Path -Path $SiteOutputPath -ChildPath 'docs/About' - $aboutDocsFolder = New-Item -Path $aboutDocsFolderPath -ItemType Directory -Force - $aboutSourceFolder = Get-ChildItem -Path $ModuleSourcePath -Directory | Where-Object { $_.Name -eq 'en-US' } - Get-ChildItem -Path $aboutSourceFolder -Filter *.txt | Copy-Item -Destination $aboutDocsFolder -Force -Verbose -PassThru | - Rename-Item -NewName { $_.Name -replace '.txt', '.md' } - - Write-Host "About Docs Folder: $aboutDocsFolder" - Write-Host "About Source Folder: $aboutSourceFolder" - } - - LogGroup 'Build docs - Copy icon to assets' { - $assetsFolderPath = Join-Path -Path $SiteOutputPath -ChildPath 'docs/Assets' - $assetsFolder = New-Item -Path $assetsFolderPath -ItemType Directory -Force - $rootPath = Split-Path -Path $ModuleSourcePath -Parent - $iconPath = Resolve-Path 'icon\icon.png' | Select-Object -ExpandProperty Path - Copy-Item -Path $iconPath -Destination $assetsFolder -Force -Verbose - - Write-Host "Assets Folder: $assetsFolder" - Write-Host "Icon Path: $iconPath" - } - - LogGroup 'Build docs - Copy readme.md' { - $readmePath = Resolve-Path 'README.md' | Select-Object -ExpandProperty Path - $readmeTargetPath = Join-Path -Path $SiteOutputPath -ChildPath 'docs/README.md' - Copy-Item -Path $readmePath -Destination $readmeTargetPath -Force -Verbose - - Write-Host "Readme Path: $readmePath" - Write-Host "Readme Target Path: $readmeTargetPath" - } - - LogGroup 'Build docs - Create docs config' { - $rootPath = Split-Path -Path $ModuleSourcePath -Parent - $possiblePaths = @( - '.github/zensical.toml', - 'docs/zensical.toml', - 'zensical.toml' - ) - - $docsConfigSourcePath = $null - foreach ($path in $possiblePaths) { - $candidatePath = Join-Path -Path $rootPath -ChildPath $path - if (Test-Path -Path $candidatePath) { - $docsConfigSourcePath = $candidatePath - break - } - } - - if (-not $docsConfigSourcePath) { - throw "Documentation config file not found. Expected one of: $($possiblePaths -join ', ')" - } - - $docsConfigFileName = [System.IO.Path]::GetFileName($docsConfigSourcePath) - $docsConfigTargetPath = Join-Path -Path $SiteOutputPath -ChildPath $docsConfigFileName - - Write-Host "Docs Config Source: $docsConfigSourcePath" - Write-Host "Docs Config Target: $docsConfigTargetPath" - - $docsConfigContent = Get-Content -Path $docsConfigSourcePath -Raw - $docsConfigContent = $docsConfigContent.Replace('-{{ REPO_NAME }}-', $ModuleName) - $docsConfigContent = $docsConfigContent.Replace('-{{ REPO_OWNER }}-', $env:GITHUB_REPOSITORY_OWNER) - if ($docsConfigFileName -eq 'zensical.toml') { - if ($docsConfigContent -match '(?m)^\s*site_dir\s*=') { - $docsConfigContent = $docsConfigContent -replace '(?m)^\s*site_dir\s*=.*$', 'site_dir = "_site"' - } else { - $docsConfigContent = $docsConfigContent -replace '(?m)^\[project\]\s*$', "[project]`nsite_dir = ""_site""" - } - } - $docsConfigContent | Set-Content -Path $docsConfigTargetPath -Force - - Write-Host "Build Config Type: $docsConfigFileName" - Show-FileContent -Path $docsConfigTargetPath - } + Name: ${{ fromJson(inputs.Settings).Name }} - name: Build documentation site with Zensical - working-directory: ${{ fromJson(inputs.Settings).WorkingDirectory }}/outputs/site - shell: pwsh - run: | - if (-not (Test-Path -Path 'zensical.toml')) { - throw "No documentation config file found in outputs/site. Expected zensical.toml." - } - $configFile = 'zensical.toml' - - LogGroup 'Build docs - Zensical config content' { - Write-Host "Using config: $configFile" - Show-FileContent -Path $configFile - } - - LogGroup 'Build docs - Zensical build' { - zensical build --config-file $configFile - } - - LogGroup 'Build docs - verify output' { - if (Test-Path -Path '_site') { - if (Test-Path -Path '../../_site') { - Remove-Item -Path '../../_site' -Recurse -Force - } - Move-Item -Path '_site' -Destination '../../_site' -Force - } - if (-not (Test-Path -Path '../../_site')) { - throw "Expected Zensical output at ../../_site but it was not created." - } - } + uses: ./_wf/.github/actions/Build-ZensicalSite + with: + WorkingDirectory: ${{ fromJson(inputs.Settings).WorkingDirectory }} - name: Inject shared site scripts uses: ./_wf/.github/actions/Inject-SiteScripts From 5e8c7ffd456622c6cde0bd17deacefa5ad94e6e0 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 19 Jul 2026 03:21:39 +0200 Subject: [PATCH 09/13] Normalize action run blocks Replace remaining multiline run blocks in internal action.yml files with single-line invocations or commands, keeping executable logic in src scripts for better tooling and lint support. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/actions/Build-PSModule/action.yml | 4 +--- .github/actions/Cleanup-PSModulePrereleases/action.yml | 4 +--- .github/actions/Document-PSModule/action.yml | 4 +--- .github/actions/Expose-TestData/action.yml | 3 +-- .github/actions/Install-PSModule/action.yml | 4 +--- .github/actions/Publish-PSModule/action.yml | 3 +-- .github/actions/Resolve-PSModuleVersion/action.yml | 7 ++----- .github/actions/Test-PSModule/action.yml | 4 +--- 8 files changed, 9 insertions(+), 24 deletions(-) diff --git a/.github/actions/Build-PSModule/action.yml b/.github/actions/Build-PSModule/action.yml index 8143f3bd..ba3459da 100644 --- a/.github/actions/Build-PSModule/action.yml +++ b/.github/actions/Build-PSModule/action.yml @@ -40,9 +40,7 @@ runs: PSMODULE_BUILD_PSMODULE_INPUT_OutputFolder: ${{ inputs.OutputFolder }} PSMODULE_BUILD_PSMODULE_INPUT_Version: ${{ inputs.Version }} PSMODULE_BUILD_PSMODULE_INPUT_Prerelease: ${{ inputs.Prerelease }} - run: | - # Build-PSModule - ${{ github.action_path }}/src/main.ps1 + run: ${{ github.action_path }}/src/main.ps1 - name: Upload module artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/actions/Cleanup-PSModulePrereleases/action.yml b/.github/actions/Cleanup-PSModulePrereleases/action.yml index af728ffd..3c076b45 100644 --- a/.github/actions/Cleanup-PSModulePrereleases/action.yml +++ b/.github/actions/Cleanup-PSModulePrereleases/action.yml @@ -29,6 +29,4 @@ runs: env: PSMODULE_CLEANUP_PSMODULEPRERELEASES_INPUT_WhatIf: ${{ inputs.WhatIf }} PSMODULE_CLEANUP_PSMODULEPRERELEASES_CONTEXT_ReleaseTag: ${{ env.PSMODULE_PUBLISH_PSMODULE_CONTEXT_ReleaseTag }} - run: | - # Cleanup prerelease tags - ${{ github.action_path }}/src/cleanup.ps1 + run: ${{ github.action_path }}/src/cleanup.ps1 diff --git a/.github/actions/Document-PSModule/action.yml b/.github/actions/Document-PSModule/action.yml index f9d01a54..d82f83ab 100644 --- a/.github/actions/Document-PSModule/action.yml +++ b/.github/actions/Document-PSModule/action.yml @@ -27,6 +27,4 @@ runs: DOCUMENT_PSMODULE_INPUT_Name: ${{ inputs.Name }} DOCUMENT_PSMODULE_INPUT_ShowSummaryOnSuccess: ${{ inputs.ShowSummaryOnSuccess }} working-directory: ${{ inputs.WorkingDirectory }} - run: | - # Build-PSModuleDocumentation - ${{ github.action_path }}/src/main.ps1 + run: ${{ github.action_path }}/src/main.ps1 diff --git a/.github/actions/Expose-TestData/action.yml b/.github/actions/Expose-TestData/action.yml index 67d9e936..38acc14d 100644 --- a/.github/actions/Expose-TestData/action.yml +++ b/.github/actions/Expose-TestData/action.yml @@ -20,5 +20,4 @@ runs: shell: pwsh env: PSMODULE_TEST_DATA: ${{ inputs.TestData }} - run: | - Import-TestData + run: Import-TestData diff --git a/.github/actions/Install-PSModule/action.yml b/.github/actions/Install-PSModule/action.yml index 2f749694..0b3f3440 100644 --- a/.github/actions/Install-PSModule/action.yml +++ b/.github/actions/Install-PSModule/action.yml @@ -6,6 +6,4 @@ runs: steps: - name: Install-PSModule shell: pwsh - run: | - # Install-PSModule - ${{ github.action_path }}/src/main.ps1 + run: ${{ github.action_path }}/src/main.ps1 diff --git a/.github/actions/Publish-PSModule/action.yml b/.github/actions/Publish-PSModule/action.yml index c855fe20..0ff0a534 100644 --- a/.github/actions/Publish-PSModule/action.yml +++ b/.github/actions/Publish-PSModule/action.yml @@ -46,8 +46,7 @@ runs: - name: Update Microsoft.PowerShell.PSResourceGet shell: pwsh - run: | - Install-PSResource -Name Microsoft.PowerShell.PSResourceGet -Repository PSGallery -TrustRepository + run: Install-PSResource -Name Microsoft.PowerShell.PSResourceGet -Repository PSGallery -TrustRepository - name: Download module artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 diff --git a/.github/actions/Resolve-PSModuleVersion/action.yml b/.github/actions/Resolve-PSModuleVersion/action.yml index e9f8981e..20b41883 100644 --- a/.github/actions/Resolve-PSModuleVersion/action.yml +++ b/.github/actions/Resolve-PSModuleVersion/action.yml @@ -58,8 +58,7 @@ runs: - name: Install PSSemVer shell: pwsh - run: | - Install-PSResource -Name PSSemVer -Repository PSGallery -TrustRepository + run: Install-PSResource -Name PSSemVer -Repository PSGallery -TrustRepository - name: Resolve module version id: resolve @@ -71,6 +70,4 @@ runs: PSMODULE_RESOLVE_PSMODULEVERSION_INPUT_Name: ${{ inputs.Name }} PSMODULE_RESOLVE_PSMODULEVERSION_INPUT_EventJson: ${{ inputs.EventJson }} GITHUB_EVENT_PATH: ${{ inputs.EventPath || github.event_path }} - run: | - # Resolve module version - ${{ github.action_path }}/src/main.ps1 + run: ${{ github.action_path }}/src/main.ps1 diff --git a/.github/actions/Test-PSModule/action.yml b/.github/actions/Test-PSModule/action.yml index 4f7030d1..621c9032 100644 --- a/.github/actions/Test-PSModule/action.yml +++ b/.github/actions/Test-PSModule/action.yml @@ -276,9 +276,7 @@ runs: env: PSMODULE_TEST_PSMODULE_INPUT_Name: ${{ inputs.Name }} PSMODULE_TEST_PSMODULE_INPUT_Settings: ${{ inputs.Settings }} - run: | - # Get test paths - ${{ github.action_path }}/src/main.ps1 + run: ${{ github.action_path }}/src/main.ps1 - name: Invoke-Pester uses: PSModule/Invoke-Pester@4ff33199141fdf22568990b6107fe3148ae93a1c # v5.1.0 From 9388dde65d9363110924227fb65e56b44e680843 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 19 Jul 2026 03:24:53 +0200 Subject: [PATCH 10/13] Align new actions with MSX standards Apply MSXOrg/docs standards to newly introduced internal actions: use consistent src/main.ps1 entry points, add PowerShell script headers/cmdlet binding, and add action README documentation for discoverability and reuse. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/actions/Build-ZensicalSite/README.md | 16 ++++ .github/actions/Build-ZensicalSite/action.yml | 2 +- .../actions/Build-ZensicalSite/src/main.ps1 | 32 ++++++++ .github/actions/Inject-SiteScripts/README.md | 18 +++++ .github/actions/Inject-SiteScripts/action.yml | 2 +- .../actions/Inject-SiteScripts/src/main.ps1 | 44 ++++++++++ .github/actions/Install-Zensical/README.md | 14 ++++ .github/actions/Install-Zensical/action.yml | 2 +- .github/actions/Install-Zensical/src/main.ps1 | 8 ++ .github/actions/Structure-Site/README.md | 18 +++++ .github/actions/Structure-Site/action.yml | 2 +- .github/actions/Structure-Site/src/main.ps1 | 80 +++++++++++++++++++ 12 files changed, 234 insertions(+), 4 deletions(-) create mode 100644 .github/actions/Build-ZensicalSite/README.md create mode 100644 .github/actions/Build-ZensicalSite/src/main.ps1 create mode 100644 .github/actions/Inject-SiteScripts/README.md create mode 100644 .github/actions/Inject-SiteScripts/src/main.ps1 create mode 100644 .github/actions/Install-Zensical/README.md create mode 100644 .github/actions/Install-Zensical/src/main.ps1 create mode 100644 .github/actions/Structure-Site/README.md create mode 100644 .github/actions/Structure-Site/src/main.ps1 diff --git a/.github/actions/Build-ZensicalSite/README.md b/.github/actions/Build-ZensicalSite/README.md new file mode 100644 index 00000000..f4eb2bb9 --- /dev/null +++ b/.github/actions/Build-ZensicalSite/README.md @@ -0,0 +1,16 @@ +# Build-ZensicalSite + +Builds Zensical documentation site output and normalizes it to `/_site`. + +## Inputs + +- `WorkingDirectory` (required): Build working directory. + +## Usage + +```yaml +- name: Build documentation site with Zensical + uses: ./_wf/.github/actions/Build-ZensicalSite + with: + WorkingDirectory: . +``` diff --git a/.github/actions/Build-ZensicalSite/action.yml b/.github/actions/Build-ZensicalSite/action.yml index 3aeaa607..c168c151 100644 --- a/.github/actions/Build-ZensicalSite/action.yml +++ b/.github/actions/Build-ZensicalSite/action.yml @@ -11,4 +11,4 @@ runs: steps: - name: Build documentation site shell: pwsh - run: '& "${{ github.action_path }}/src/build-zensical-site.ps1" -WorkingDirectory "${{ inputs.WorkingDirectory }}"' + run: '& "${{ github.action_path }}/src/main.ps1" -WorkingDirectory "${{ inputs.WorkingDirectory }}"' diff --git a/.github/actions/Build-ZensicalSite/src/main.ps1 b/.github/actions/Build-ZensicalSite/src/main.ps1 new file mode 100644 index 00000000..25d3b782 --- /dev/null +++ b/.github/actions/Build-ZensicalSite/src/main.ps1 @@ -0,0 +1,32 @@ +#Requires -Version 7.0 + +[CmdletBinding()] +param( + [Parameter(Mandatory)] + [string]$WorkingDirectory +) + +$ErrorActionPreference = 'Stop' + +$resolvedWorkingDirectory = Resolve-Path -Path $WorkingDirectory | Select-Object -ExpandProperty Path +$siteWorkingDirectory = Join-Path -Path $resolvedWorkingDirectory -ChildPath 'outputs/site' +$outputSitePath = Join-Path -Path $resolvedWorkingDirectory -ChildPath '_site' + +Set-Location -Path $siteWorkingDirectory + +if (-not (Test-Path -Path 'zensical.toml')) { + throw "No documentation config file found in outputs/site. Expected zensical.toml." +} + +zensical build --config-file 'zensical.toml' + +if (Test-Path -Path '_site') { + if (Test-Path -Path $outputSitePath) { + Remove-Item -Path $outputSitePath -Recurse -Force + } + Move-Item -Path '_site' -Destination $outputSitePath -Force +} + +if (-not (Test-Path -Path $outputSitePath)) { + throw "Expected Zensical output at $outputSitePath but it was not created." +} diff --git a/.github/actions/Inject-SiteScripts/README.md b/.github/actions/Inject-SiteScripts/README.md new file mode 100644 index 00000000..9c75bdb1 --- /dev/null +++ b/.github/actions/Inject-SiteScripts/README.md @@ -0,0 +1,18 @@ +# Inject-SiteScripts + +Injects JavaScript snippets from `.github/scripts/site-injectors/*.js` into generated HTML files. + +## Inputs + +- `SitePath` (required): Path to generated site output. +- `WorkflowPath` (optional, default `_wf`): Path where workflow repository is checked out. + +## Usage + +```yaml +- name: Inject shared site scripts + uses: ./_wf/.github/actions/Inject-SiteScripts + with: + SitePath: ./_site + WorkflowPath: _wf +``` diff --git a/.github/actions/Inject-SiteScripts/action.yml b/.github/actions/Inject-SiteScripts/action.yml index 7e5fa2ab..8e21565f 100644 --- a/.github/actions/Inject-SiteScripts/action.yml +++ b/.github/actions/Inject-SiteScripts/action.yml @@ -15,4 +15,4 @@ runs: steps: - name: Inject site scripts shell: pwsh - run: '& "${{ github.action_path }}/src/inject-site-scripts.ps1" -SitePath "${{ inputs.SitePath }}" -WorkflowPath "${{ inputs.WorkflowPath }}"' + run: '& "${{ github.action_path }}/src/main.ps1" -SitePath "${{ inputs.SitePath }}" -WorkflowPath "${{ inputs.WorkflowPath }}"' diff --git a/.github/actions/Inject-SiteScripts/src/main.ps1 b/.github/actions/Inject-SiteScripts/src/main.ps1 new file mode 100644 index 00000000..c5edafd6 --- /dev/null +++ b/.github/actions/Inject-SiteScripts/src/main.ps1 @@ -0,0 +1,44 @@ +#Requires -Version 7.0 + +[CmdletBinding()] +param( + [Parameter(Mandatory)] + [string]$SitePath, + + [Parameter(Mandatory)] + [string]$WorkflowPath +) + +$resolvedSitePath = Resolve-Path -Path $SitePath -ErrorAction Stop | Select-Object -ExpandProperty Path +$injectorsPath = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath "$WorkflowPath/.github/scripts/site-injectors" + +if (-not (Test-Path -Path $injectorsPath)) { + throw "Expected site injector folder at $injectorsPath but it was not found." +} + +$injectorScripts = Get-ChildItem -Path $injectorsPath -File -Filter '*.js' | Sort-Object -Property Name +if (-not $injectorScripts) { + Write-Host "No site injector scripts found under $injectorsPath." + exit 0 +} + +Get-ChildItem -Path $resolvedSitePath -Filter '*.html' -Recurse | ForEach-Object { + $html = Get-Content -Path $_.FullName -Raw + $modified = $false + + foreach ($injectorScript in $injectorScripts) { + $marker = "data-psmodule-site-injector=""$($injectorScript.Name)""" + if ($html -match [Regex]::Escape($marker)) { + continue + } + + $scriptContent = Get-Content -Path $injectorScript.FullName -Raw + $injectedScript = "" + $html = $html -replace '', "$injectedScript`n" + $modified = $true + } + + if ($modified) { + Set-Content -Path $_.FullName -Value $html -NoNewline + } +} diff --git a/.github/actions/Install-Zensical/README.md b/.github/actions/Install-Zensical/README.md new file mode 100644 index 00000000..1939aba2 --- /dev/null +++ b/.github/actions/Install-Zensical/README.md @@ -0,0 +1,14 @@ +# Install-Zensical + +Installs the Zensical CLI used by site build workflows. + +## Inputs + +None. + +## Usage + +```yaml +- name: Install Zensical + uses: ./_wf/.github/actions/Install-Zensical +``` diff --git a/.github/actions/Install-Zensical/action.yml b/.github/actions/Install-Zensical/action.yml index 3a54443b..18a16b4b 100644 --- a/.github/actions/Install-Zensical/action.yml +++ b/.github/actions/Install-Zensical/action.yml @@ -6,4 +6,4 @@ runs: steps: - name: Install Zensical CLI shell: pwsh - run: '& "${{ github.action_path }}/src/install-zensical.ps1"' + run: '& "${{ github.action_path }}/src/main.ps1"' diff --git a/.github/actions/Install-Zensical/src/main.ps1 b/.github/actions/Install-Zensical/src/main.ps1 new file mode 100644 index 00000000..3f6cce54 --- /dev/null +++ b/.github/actions/Install-Zensical/src/main.ps1 @@ -0,0 +1,8 @@ +#Requires -Version 7.0 + +[CmdletBinding()] +param() + +$ErrorActionPreference = 'Stop' + +pip install zensical diff --git a/.github/actions/Structure-Site/README.md b/.github/actions/Structure-Site/README.md new file mode 100644 index 00000000..203f9564 --- /dev/null +++ b/.github/actions/Structure-Site/README.md @@ -0,0 +1,18 @@ +# Structure-Site + +Prepares site content and writes a resolved Zensical config file under `outputs/site`. + +## Inputs + +- `WorkingDirectory` (required): Build working directory. +- `Name` (optional): Module name override. + +## Usage + +```yaml +- name: Structure site + uses: ./_wf/.github/actions/Structure-Site + with: + WorkingDirectory: . + Name: MyModule +``` diff --git a/.github/actions/Structure-Site/action.yml b/.github/actions/Structure-Site/action.yml index 842f8de3..461a4b50 100644 --- a/.github/actions/Structure-Site/action.yml +++ b/.github/actions/Structure-Site/action.yml @@ -15,4 +15,4 @@ runs: steps: - name: Structure site content and config shell: pwsh - run: '& "${{ github.action_path }}/src/structure-site.ps1" -WorkingDirectory "${{ inputs.WorkingDirectory }}" -Name "${{ inputs.Name }}"' + run: '& "${{ github.action_path }}/src/main.ps1" -WorkingDirectory "${{ inputs.WorkingDirectory }}" -Name "${{ inputs.Name }}"' diff --git a/.github/actions/Structure-Site/src/main.ps1 b/.github/actions/Structure-Site/src/main.ps1 new file mode 100644 index 00000000..88224df6 --- /dev/null +++ b/.github/actions/Structure-Site/src/main.ps1 @@ -0,0 +1,80 @@ +#Requires -Version 7.0 + +[CmdletBinding()] +param( + [Parameter(Mandatory)] + [string]$WorkingDirectory, + + [Parameter()] + [string]$Name +) + +$ErrorActionPreference = 'Stop' + +$resolvedWorkingDirectory = Resolve-Path -Path $WorkingDirectory | Select-Object -ExpandProperty Path +$siteOutputPath = Join-Path -Path $resolvedWorkingDirectory -ChildPath 'outputs/site' +$docsOutputPath = Join-Path -Path $resolvedWorkingDirectory -ChildPath 'outputs/docs' +$moduleSourcePath = Join-Path -Path $resolvedWorkingDirectory -ChildPath 'src' +$moduleName = if ([string]::IsNullOrEmpty($Name)) { $env:GITHUB_REPOSITORY_NAME } else { $Name } + +$functionDocsFolder = New-Item -Path (Join-Path -Path $siteOutputPath -ChildPath 'docs/Functions') -ItemType Directory -Force +Copy-Item -Path (Join-Path -Path $docsOutputPath -ChildPath '*') -Destination $functionDocsFolder.FullName -Recurse -Force + +Write-Host "Function Docs Folder: $($functionDocsFolder.FullName)" +Write-Host "Module Name: $moduleName" +Write-Host "Module Source Path: $moduleSourcePath" +Write-Host "Site Output Path: $siteOutputPath" + +$aboutDocsFolder = New-Item -Path (Join-Path -Path $siteOutputPath -ChildPath 'docs/About') -ItemType Directory -Force +$aboutSourceFolder = Join-Path -Path $moduleSourcePath -ChildPath 'en-US' +if (Test-Path -Path $aboutSourceFolder) { + Get-ChildItem -Path $aboutSourceFolder -Filter '*.txt' | Copy-Item -Destination $aboutDocsFolder.FullName -Force -PassThru | + Rename-Item -NewName { $_.Name -replace '\.txt$', '.md' } +} + +$assetsFolder = New-Item -Path (Join-Path -Path $siteOutputPath -ChildPath 'docs/Assets') -ItemType Directory -Force +$iconPath = Join-Path -Path $resolvedWorkingDirectory -ChildPath 'icon/icon.png' +if (Test-Path -Path $iconPath) { + Copy-Item -Path $iconPath -Destination $assetsFolder.FullName -Force +} + +$readmePath = Join-Path -Path $resolvedWorkingDirectory -ChildPath 'README.md' +$readmeTargetPath = Join-Path -Path $siteOutputPath -ChildPath 'docs/README.md' +Copy-Item -Path $readmePath -Destination $readmeTargetPath -Force + +$possiblePaths = @( + '.github/zensical.toml', + 'docs/zensical.toml', + 'zensical.toml' +) + +$docsConfigSourcePath = $null +foreach ($relativePath in $possiblePaths) { + $candidatePath = Join-Path -Path $resolvedWorkingDirectory -ChildPath $relativePath + if (Test-Path -Path $candidatePath) { + $docsConfigSourcePath = $candidatePath + break + } +} + +if (-not $docsConfigSourcePath) { + throw "Documentation config file not found. Expected one of: $($possiblePaths -join ', ')" +} + +$docsConfigFileName = [System.IO.Path]::GetFileName($docsConfigSourcePath) +$docsConfigTargetPath = Join-Path -Path $siteOutputPath -ChildPath $docsConfigFileName + +$docsConfigContent = Get-Content -Path $docsConfigSourcePath -Raw +$docsConfigContent = $docsConfigContent.Replace('-{{ REPO_NAME }}-', $moduleName) +$docsConfigContent = $docsConfigContent.Replace('-{{ REPO_OWNER }}-', $env:GITHUB_REPOSITORY_OWNER) + +if ($docsConfigFileName -eq 'zensical.toml') { + if ($docsConfigContent -match '(?m)^\s*site_dir\s*=') { + $docsConfigContent = $docsConfigContent -replace '(?m)^\s*site_dir\s*=.*$', 'site_dir = "_site"' + } else { + $docsConfigContent = $docsConfigContent -replace '(?m)^\[project\]\s*$', "[project]`nsite_dir = ""_site""" + } +} + +Set-Content -Path $docsConfigTargetPath -Value $docsConfigContent -Force +Write-Host "Build Config Type: $docsConfigFileName" From a69daa593d708b9292dc817070c82292b35d0321 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 19 Jul 2026 03:29:07 +0200 Subject: [PATCH 11/13] Document action scripts per MSX standard Add comment-based help blocks and inline parameter documentation to new action PowerShell entry scripts to align with MSXOrg/docs script standards. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../actions/Build-ZensicalSite/src/main.ps1 | 18 ++++++++++++++++++ .../actions/Inject-SiteScripts/src/main.ps1 | 19 +++++++++++++++++++ .github/actions/Install-Zensical/src/main.ps1 | 16 ++++++++++++++++ .github/actions/Structure-Site/src/main.ps1 | 19 +++++++++++++++++++ 4 files changed, 72 insertions(+) diff --git a/.github/actions/Build-ZensicalSite/src/main.ps1 b/.github/actions/Build-ZensicalSite/src/main.ps1 index 25d3b782..080a8dea 100644 --- a/.github/actions/Build-ZensicalSite/src/main.ps1 +++ b/.github/actions/Build-ZensicalSite/src/main.ps1 @@ -1,7 +1,25 @@ #Requires -Version 7.0 +<# + .SYNOPSIS + Builds the Zensical site and normalizes output to the expected _site path. + + .DESCRIPTION + Runs zensical build from outputs/site and moves the generated _site directory + to /_site for downstream workflow steps. + + .EXAMPLE + ./main.ps1 -WorkingDirectory '.' + + .INPUTS + None. + + .OUTPUTS + None. +#> [CmdletBinding()] param( + # Build working directory containing outputs/site and destination _site. [Parameter(Mandatory)] [string]$WorkingDirectory ) diff --git a/.github/actions/Inject-SiteScripts/src/main.ps1 b/.github/actions/Inject-SiteScripts/src/main.ps1 index c5edafd6..6fcdad0d 100644 --- a/.github/actions/Inject-SiteScripts/src/main.ps1 +++ b/.github/actions/Inject-SiteScripts/src/main.ps1 @@ -1,10 +1,29 @@ #Requires -Version 7.0 +<# + .SYNOPSIS + Injects shared JavaScript snippets into generated site HTML files. + + .DESCRIPTION + Reads scripts from .github/scripts/site-injectors in the checked out workflow + repository and injects each script into every HTML file under SitePath, once per file. + + .EXAMPLE + ./main.ps1 -SitePath './_site' -WorkflowPath '_wf' + + .INPUTS + None. + + .OUTPUTS + None. +#> [CmdletBinding()] param( + # Path to generated site output directory containing HTML files. [Parameter(Mandatory)] [string]$SitePath, + # Relative path to the checked out workflow repository root. [Parameter(Mandatory)] [string]$WorkflowPath ) diff --git a/.github/actions/Install-Zensical/src/main.ps1 b/.github/actions/Install-Zensical/src/main.ps1 index 3f6cce54..936e4886 100644 --- a/.github/actions/Install-Zensical/src/main.ps1 +++ b/.github/actions/Install-Zensical/src/main.ps1 @@ -1,5 +1,21 @@ #Requires -Version 7.0 +<# + .SYNOPSIS + Installs the Zensical CLI used by site build workflows. + + .DESCRIPTION + Installs the zensical Python package in the current runner environment. + + .EXAMPLE + ./main.ps1 + + .INPUTS + None. + + .OUTPUTS + None. +#> [CmdletBinding()] param() diff --git a/.github/actions/Structure-Site/src/main.ps1 b/.github/actions/Structure-Site/src/main.ps1 index 88224df6..44c8d20c 100644 --- a/.github/actions/Structure-Site/src/main.ps1 +++ b/.github/actions/Structure-Site/src/main.ps1 @@ -1,10 +1,29 @@ #Requires -Version 7.0 +<# + .SYNOPSIS + Structures generated documentation content and writes a resolved site config. + + .DESCRIPTION + Copies generated docs, README, and assets into outputs/site and resolves zensical.toml + placeholders and site_dir for build execution. + + .EXAMPLE + ./main.ps1 -WorkingDirectory '.' -Name 'MyModule' + + .INPUTS + None. + + .OUTPUTS + None. +#> [CmdletBinding()] param( + # Build working directory where src/, README.md, and outputs/ exist. [Parameter(Mandatory)] [string]$WorkingDirectory, + # Optional module name override used for placeholder replacement. [Parameter()] [string]$Name ) From 3edbe44f7816588ad56d9990ff139e7fc045b224 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 19 Jul 2026 03:42:11 +0200 Subject: [PATCH 12/13] Harden composite action input handling Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/actions/Build-ZensicalSite/action.yml | 4 +++- .github/actions/Inject-SiteScripts/action.yml | 5 ++++- .github/actions/Structure-Site/action.yml | 5 ++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/actions/Build-ZensicalSite/action.yml b/.github/actions/Build-ZensicalSite/action.yml index c168c151..5031f0a9 100644 --- a/.github/actions/Build-ZensicalSite/action.yml +++ b/.github/actions/Build-ZensicalSite/action.yml @@ -11,4 +11,6 @@ runs: steps: - name: Build documentation site shell: pwsh - run: '& "${{ github.action_path }}/src/main.ps1" -WorkingDirectory "${{ inputs.WorkingDirectory }}"' + env: + INPUT_WORKING_DIRECTORY: ${{ inputs.WorkingDirectory }} + run: '& "${{ github.action_path }}/src/main.ps1" -WorkingDirectory "$env:INPUT_WORKING_DIRECTORY"' diff --git a/.github/actions/Inject-SiteScripts/action.yml b/.github/actions/Inject-SiteScripts/action.yml index 8e21565f..0a280e76 100644 --- a/.github/actions/Inject-SiteScripts/action.yml +++ b/.github/actions/Inject-SiteScripts/action.yml @@ -15,4 +15,7 @@ runs: steps: - name: Inject site scripts shell: pwsh - run: '& "${{ github.action_path }}/src/main.ps1" -SitePath "${{ inputs.SitePath }}" -WorkflowPath "${{ inputs.WorkflowPath }}"' + env: + INPUT_SITE_PATH: ${{ inputs.SitePath }} + INPUT_WORKFLOW_PATH: ${{ inputs.WorkflowPath }} + run: '& "${{ github.action_path }}/src/main.ps1" -SitePath "$env:INPUT_SITE_PATH" -WorkflowPath "$env:INPUT_WORKFLOW_PATH"' diff --git a/.github/actions/Structure-Site/action.yml b/.github/actions/Structure-Site/action.yml index 461a4b50..cb6d5cd2 100644 --- a/.github/actions/Structure-Site/action.yml +++ b/.github/actions/Structure-Site/action.yml @@ -15,4 +15,7 @@ runs: steps: - name: Structure site content and config shell: pwsh - run: '& "${{ github.action_path }}/src/main.ps1" -WorkingDirectory "${{ inputs.WorkingDirectory }}" -Name "${{ inputs.Name }}"' + env: + INPUT_WORKING_DIRECTORY: ${{ inputs.WorkingDirectory }} + INPUT_NAME: ${{ inputs.Name }} + run: '& "${{ github.action_path }}/src/main.ps1" -WorkingDirectory "$env:INPUT_WORKING_DIRECTORY" -Name "$env:INPUT_NAME"' From 8dedc1a208e87bb87e6b389dc51d48e0586f333b Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 19 Jul 2026 03:48:16 +0200 Subject: [PATCH 13/13] Fix nav-state JavaScript lint issues Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/scripts/site-injectors/nav-state.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/scripts/site-injectors/nav-state.js b/.github/scripts/site-injectors/nav-state.js index f78a3f1d..1150a1da 100644 --- a/.github/scripts/site-injectors/nav-state.js +++ b/.github/scripts/site-injectors/nav-state.js @@ -16,8 +16,8 @@ const topLevelToggles = Array.from( primaryList.querySelectorAll( - ":scope > li > input.md-nav__toggle.md-toggle[id]" - ) + ":scope > li > input.md-nav__toggle.md-toggle[id]", + ), ); const topLevelIds = new Set(topLevelToggles.map((toggle) => toggle.id)); @@ -36,7 +36,7 @@ try { const state = JSON.parse(raw); for (const toggle of toggles) { - if (Object.prototype.hasOwnProperty.call(state, toggle.id)) { + if (Object.hasOwn(state, toggle.id)) { toggle.checked = !!state[toggle.id]; } }