Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
14 changes: 14 additions & 0 deletions .github/actions/Build-ZensicalSite/action.yml
Original file line number Diff line number Diff line change
@@ -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 }}"'

Check warning

Code scanning / CodeQL

Code injection Medium

Potential code injection in
${ github.action_path }
, which may be controlled by an external user.

Check warning

Code scanning / CodeQL

Code injection Medium

Potential code injection in
${ inputs.WorkingDirectory }
, which may be controlled by an external user.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
29 changes: 29 additions & 0 deletions .github/actions/Build-ZensicalSite/src/build-zensical-site.ps1
Original file line number Diff line number Diff line change
@@ -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."
}
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
18 changes: 18 additions & 0 deletions .github/actions/Inject-SiteScripts/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
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 }}"'

Check warning

Code scanning / CodeQL

Code injection Medium

Potential code injection in
${ github.action_path }
, which may be controlled by an external user.

Check warning

Code scanning / CodeQL

Code injection Medium

Potential code injection in
${ inputs.SitePath }
, which may be controlled by an external user.

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
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
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
}
}
9 changes: 9 additions & 0 deletions .github/actions/Install-Zensical/action.yml
Original file line number Diff line number Diff line change
@@ -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"'

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
3 changes: 3 additions & 0 deletions .github/actions/Install-Zensical/src/install-zensical.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
$ErrorActionPreference = 'Stop'

pip install zensical
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
18 changes: 18 additions & 0 deletions .github/actions/Structure-Site/action.yml
Original file line number Diff line number Diff line change
@@ -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 }}"'

Check warning

Code scanning / CodeQL

Code injection Medium

Potential code injection in
${ github.action_path }
, which may be controlled by an external user.

Check warning

Code scanning / CodeQL

Code injection Medium

Potential code injection in
${ inputs.WorkingDirectory }
, which may be controlled by an external user.

Check warning

Code scanning / CodeQL

Code injection Medium

Potential code injection in
${ inputs.Name }
, which may be controlled by an external user.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
77 changes: 77 additions & 0 deletions .github/actions/Structure-Site/src/structure-site.ps1
Original file line number Diff line number Diff line change
@@ -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"
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);
})();
Loading
Loading