Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ param()

$PSStyle.OutputRendering = 'Ansi'

Import-Module -Name 'Helpers' -Force
Import-Module -Name 'PSModule' -Force

#region Load inputs
LogGroup 'Load inputs' {
Expand Down
21 changes: 21 additions & 0 deletions .github/actions/Inject-SiteScripts/action.yml
Original file line number Diff line number Diff line change
@@ -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" `

Check warning

Code scanning / CodeQL

Code injection Medium

Potential code injection in
${ github.action_path }
, which may be controlled by an external user.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
-SitePath "${{ inputs.SitePath }}" `

Check warning

Code scanning / CodeQL

Code injection Medium

Potential code injection in
${ inputs.SitePath }
, which may be controlled by an external user.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
-WorkflowPath "${{ inputs.WorkflowPath }}"

Check warning

Code scanning / CodeQL

Code injection Medium

Potential code injection in
${ inputs.WorkflowPath }
, which may be controlled by an external user.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
41 changes: 41 additions & 0 deletions .github/actions/Inject-SiteScripts/src/inject-site-scripts.ps1
Original file line number Diff line number Diff line change
@@ -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 = "<script $marker>$scriptContent</script>"
$html = $html -replace '</body>', "$injectedScript`n</body>"
$modified = $true
}

if ($modified) {
Set-Content -Path $_.FullName -Value $html -NoNewline
}
}
2 changes: 1 addition & 1 deletion .github/actions/Publish-PSModule/src/publish.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ param()

$PSStyle.OutputRendering = 'Ansi'

Import-Module -Name 'Helpers' -Force
Import-Module -Name 'PSModule' -Force

#region Load inputs
LogGroup 'Load inputs' {
Expand Down
86 changes: 86 additions & 0 deletions .github/scripts/site-injectors/nav-state.js
Original file line number Diff line number Diff line change
@@ -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);
})();
83 changes: 57 additions & 26 deletions .github/workflows/Build-Site.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -111,51 +108,85 @@ 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)
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: 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 - mkdocs build' {
mkdocs build --config-file mkdocs.yml --site-dir ../../_site
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."
}
}

- 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
Expand Down
69 changes: 69 additions & 0 deletions .github/zensical.toml
Original file line number Diff line number Diff line change
@@ -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"]
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading
Loading