Skip to content

Commit 581a3b0

Browse files
style: psturtle.com ( Fixes #134 )
Initial website content and scripts
1 parent acffdad commit 581a3b0

File tree

7 files changed

+1671
-0
lines changed

7 files changed

+1671
-0
lines changed

.github/workflows/deploy.yml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Simple workflow for deploying static content to GitHub Pages
2+
name: Deploy
3+
4+
on:
5+
# Runs on pushes
6+
push:
7+
# and run on a schedule
8+
schedule:
9+
# for those that don't speak cron, this would try to run 42 minutes past the hour, every hour
10+
# - cron: '42 0-23 * * *'
11+
# Allows you to run this workflow manually from the Actions tab
12+
workflow_dispatch:
13+
14+
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
15+
permissions:
16+
contents: read
17+
pages: write
18+
id-token: write
19+
20+
# Allow only one concurrent deployment, and cancel any in-progress deployments if a new one is triggered
21+
concurrency:
22+
group: "pages"
23+
cancel-in-progress: true
24+
25+
jobs:
26+
# Single deploy job since we're just deploying
27+
deploy:
28+
environment:
29+
name: github-pages
30+
url: ${{ steps.deployment.outputs.page_url }}
31+
runs-on: ubuntu-latest
32+
steps:
33+
- name: Checkout
34+
uses: actions/checkout@main
35+
with:
36+
# Using fetch-depth: 0 to ensure we get the full history of the repository
37+
fetch-depth: 0
38+
- name: Setup Pages
39+
uses: actions/configure-pages@main
40+
- name: Build Pages
41+
shell: pwsh
42+
run: ./psturtle.com/build.ps1
43+
- name: Upload artifact
44+
uses: actions/upload-pages-artifact@main
45+
with:
46+
# Upload the contents of the domain directory to the GitHub Pages artifact
47+
path: './psturtle.com/'
48+
- name: Deploy to GitHub Pages
49+
id: deployment
50+
uses: actions/deploy-pages@main

psturtle.com/build.ps1

Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
<#
2+
.SYNOPSIS
3+
Builds the website.
4+
.DESCRIPTION
5+
Builds a static site using PowerShell.
6+
7+
* The site will be configured using any `config.*` files.
8+
* Functions and filters will be loaded from any `functions.*` or `filters.*` files.
9+
* All files will be processed using `buildFile.ps1` (any `*.*.ps1` file should be run).
10+
.EXAMPLE
11+
./build.ps1
12+
#>
13+
param(
14+
[string[]]
15+
$FilePath,
16+
17+
[string]
18+
$Root = $PSScriptRoot
19+
)
20+
21+
# Push into the script root directory
22+
if ($PSScriptRoot) { Push-Location $PSScriptRoot }
23+
24+
# Creation of a sitewide object to hold configuration information.
25+
$Site = [Ordered]@{}
26+
$Site.Files =
27+
if ($filePath) { Get-ChildItem -Recurse -File -Path $FilePath }
28+
else { Get-ChildItem -Recurse -File }
29+
30+
$Site.PSScriptRoot = "$PSScriptRoot"
31+
32+
#region Common Functions and Filters
33+
$functionFileNames = 'functions', 'function', 'filters', 'filter'
34+
$functionPattern = "(?>$($functionFileNames -join '|'))\.ps1$"
35+
$functionFiles = Get-ChildItem -Path $Site.PSScriptRoot |
36+
Where-Object Name -Match $functionPattern
37+
38+
foreach ($file in $functionFiles) {
39+
# If we have a file with the name function or functions, we'll use it to set the site configuration.
40+
. $file.FullName
41+
}
42+
#endregion Common Functions and Filters
43+
44+
# Set an alias to buildPage.ps1
45+
Set-Alias BuildPage ./buildPage.ps1
46+
47+
# If we have an event path,
48+
$gitHubEvent =
49+
if ($env:GITHUB_EVENT_PATH) {
50+
# all we need to do to serve it is copy it.
51+
Copy-Item $env:GITHUB_EVENT_PATH .\gitHubEvent.json
52+
53+
# and we can assign it to a variable, so you we can use it in any files we build.
54+
Get-Content -Path .\gitHubEvent.json -Raw | ConvertFrom-Json
55+
}
56+
57+
# If we have a CNAME, read it, trim it, and update the site object.
58+
if (Test-Path 'CNAME') {
59+
$Site.CNAME = $CNAME = (Get-Content -Path 'CNAME' -Raw).Trim()
60+
$Site.RootUrl = "https://$CNAME/"
61+
} elseif (
62+
($site.PSScriptRoot | Split-Path -Leaf) -like '*.*'
63+
) {
64+
$site.CNAME = $CNAME = ($site.PSScriptRoot | Split-Path -Leaf)
65+
$site.RootUrl = "https://$CNAME/"
66+
}
67+
68+
# If we have a config.json file, it can be used to set the site configuration.
69+
if (Test-Path 'config.json') {
70+
$siteConfig = Get-Content -Path 'config.json' -Raw | ConvertFrom-Json
71+
foreach ($property in $siteConfig.psobject.properties) {
72+
$Site[$property.Name] = $property.Value
73+
}
74+
}
75+
76+
# If we have a config.psd1 file, we'll use it to set the site configuration.
77+
if (Test-Path 'config.psd1') {
78+
$siteConfig = Import-LocalizedData -FileName 'config.psd1' -BaseDirectory $PSScriptRoot
79+
foreach ($property in $siteConfig.GetEnumerator()) {
80+
$Site[$property.Key] = $property.Value
81+
}
82+
}
83+
84+
# If we have a config yaml, things
85+
if (Test-Path 'config.yaml') {
86+
$siteConfig = Get-Item 'config.yaml' | from_yaml
87+
foreach ($property in $siteConfig.GetEnumerator()) {
88+
$Site[$property.Name] = $property.Value
89+
}
90+
}
91+
92+
# If we have a config.ps1 file,
93+
if (Test-Path 'config.ps1') {
94+
# Get the script command
95+
$configScript = Get-Command -Name './config.ps1'
96+
# and install any requirements it has.
97+
$configScript | RequireModule
98+
# run it, and let it configure anything it chooses to.
99+
. $configScript
100+
}
101+
102+
# Start the clock
103+
$site['LastBuildTime'] = $lastBuildTime = [DateTime]::Now
104+
#region Build Files
105+
106+
# Start the clock on the build process
107+
$buildStart = [DateTime]::Now
108+
# pipe every file we find to buildFile
109+
$Site.Files | . buildPage
110+
# and stop the clock
111+
$buildEnd = [DateTime]::Now
112+
113+
#endregion Build Files
114+
115+
# If we have changed directories, we need to push back into the script root directory.
116+
if ($PSScriptRoot -and "$PSScriptRoot" -ne "$pwd") {
117+
Push-Location $psScriptRoot
118+
}
119+
120+
#region lastBuild.json
121+
# We create a new object each time, so we can use it to compare to the last build.
122+
$newLastBuild = [Ordered]@{
123+
LastBuildTime = $lastBuildTime
124+
BuildDuration = $buildEnd - $buildStart
125+
Message =
126+
if ($gitHubEvent.commits) {
127+
$gitHubEvent.commits[-1].Message
128+
} elseif ($gitHubEvent.schedule) {
129+
"Ran at $([DateTime]::Now.ToString('o')) on $($gitHubEvent.schedule)"
130+
} else {
131+
'On Demand'
132+
}
133+
}
134+
135+
# If we have a CNAME, we can use it to get the last build time from the server.
136+
$lastBuild =
137+
try {
138+
Invoke-RestMethod -Uri "https://$CNAME/lastBuild.json" -ErrorAction Ignore
139+
} catch {
140+
Write-Verbose ($_ | Out-String)
141+
}
142+
143+
# If we could get the last build time, we can use it to calculate the time since the last build.
144+
if ($lastBuild) {
145+
$newLastBuild.TimeSinceLastBuild = $lastBuildTime - $lastBuild.LastBuildTime
146+
}
147+
148+
# Save the build time to a file.
149+
$newLastBuild | ConvertTo-Json -Depth 2 > lastBuild.json
150+
#endregion lastBuild.json
151+
152+
#region sitemap.xml
153+
if (-not $Site.NoSitemap) {
154+
$siteMapXml = @(
155+
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'
156+
:nextPage foreach ($key in $site.PagesByUrl.Keys | Sort-Object { "$_".Length}) {
157+
$keyUri = $key -as [Uri]
158+
$page = $site.PagesByUrl[$key]
159+
if ($site.Disallow) {
160+
foreach ($disallow in $site.Disallow) {
161+
if ($keyUri.LocalPath -like "*$disallow*") { continue nextPage }
162+
if ($keyUri.AbsoluteUri -like "*$disallow*") { continue nextPage }
163+
}
164+
}
165+
if ($page.NoIndex) { continue }
166+
if ($page.NoSitemap) { continue }
167+
if ($page.OutputFile.Extension -ne '.html') { continue }
168+
"<url>"
169+
"<loc>$key</loc>"
170+
if ($site.PagesByUrl[$key].Date -is [DateTime]) {
171+
"<lastmod>$($site.PagesByUrl[$key].Date.ToString('yyyy-MM-dd'))</lastmod>"
172+
}
173+
"</url>"
174+
}
175+
'</urlset>'
176+
) -join ' ' -as [xml]
177+
if ($siteMapXml) {
178+
$siteMapXml.Save((
179+
Join-Path $site.PSScriptRoot sitemap.xml
180+
))
181+
}
182+
}
183+
#endregion sitemap.xml
184+
185+
#region index.rss
186+
if (-not $Site.NoRss) {
187+
$pagesByDate = @($site.PagesByUrl.GetEnumerator() |
188+
Sort-Object { $_.Value.Date } -Descending)
189+
$lastPubDate = if ($pagesByDate.Values.Date) {
190+
$pagesByDate[0].Value.Date.ToString('R')
191+
} else {
192+
$lastBuildTime.ToString('R')
193+
}
194+
$rssXml = @(
195+
'<rss version="2.0">'
196+
'<channel>'
197+
"<title>$([Security.SecurityElement]::Escape($(
198+
if ($site.Title) { $site.Title } else { $site.CNAME }
199+
)))</title>"
200+
"<link>$($site.RootUrl)</link>"
201+
"<description>$([Security.SecurityElement]::Escape($(
202+
if ($site.Description) { $site.Description } else { $site.Title }
203+
)))</description>"
204+
"<pubDate>$($lastPubDate)</pubDate>"
205+
"<lastBuildDate>$($lastBuildTime.ToString('R'))</lastBuildDate>"
206+
"<language>$([Security.SecurityElement]::Escape($site.Language))</language>"
207+
:nextPage foreach ($keyValue in $pagesByDate) {
208+
$key = $keyValue.Key
209+
$keyUri = $key -as [Uri]
210+
$page = $keyValue.Value
211+
if ($site.Disallow) {
212+
foreach ($disallow in $site.Disallow) {
213+
if ($keyUri.LocalPath -like "*$disallow*") { continue nextPage }
214+
if ($keyUri.AbsoluteUri -like "*$disallow*") { continue nextPage }
215+
}
216+
}
217+
if ($site.PagesByUrl[$key].NoIndex) { continue }
218+
if ($site.PagesByUrl[$key].NoSitemap) { continue }
219+
if ($site.PagesByUrl[$key].OutputFile.Extension -ne '.html') { continue }
220+
"<item>"
221+
"<title>$([Security.SecurityElement]::Escape($(
222+
if ($page.Title) { $page.Title }
223+
elseif ($site.Title) { $site.Title }
224+
else { $site.CNAME }
225+
)))</title>"
226+
if ($site.PagesByUrl[$key].Date -is [DateTime]) {
227+
"<pubDate>$($site.PagesByUrl[$key].Date.ToString('R'))</pubDate>"
228+
}
229+
"<description>$([Security.SecurityElement]::Escape($(
230+
if ($page.Description) { $page.Description }
231+
elseif ($site.Description) { $site.Description }
232+
)))</description>"
233+
"<link>$key</link>"
234+
"<guid isPermaLink='true'>$key</guid>"
235+
"</item>"
236+
}
237+
'</channel>'
238+
'</rss>'
239+
) -join ' ' -as [xml]
240+
241+
if ($rssXml) {
242+
$rssOutputPath = Join-Path $site.PSScriptRoot 'RSS' | Join-Path -ChildPath 'index.rss'
243+
if (-not (Test-Path $rssOutputPath)) {
244+
# Create the file if it doesn't exist
245+
$null = New-Item -ItemType File -Force $rssOutputPath
246+
}
247+
$rssXml.Save($rssOutputPath)
248+
}
249+
}
250+
251+
252+
#endregion index.rss
253+
254+
#region robots.txt
255+
if (-not $Site.NoRobots) {
256+
@(
257+
"User-agent: *"
258+
if ($site.Disallow) {
259+
foreach ($disallow in $site.Disallow) {
260+
"Disallow: $disallow"
261+
}
262+
}
263+
if ($site.Allow) {
264+
foreach ($allow in $site.Allow) {
265+
"Allow: $allow"
266+
}
267+
}
268+
if ($site.CNAME -and -not $site.NoSitemap) {
269+
"Sitemap: https://$($site.CNAME)/sitemap.xml"
270+
}
271+
) > robots.txt
272+
}
273+
#endregion robots.txt
274+
275+
#region index.json
276+
if (-not $Site.NoIndex) {
277+
$fileIndex =
278+
if ($filePath) { Get-ChildItem -Recurse -File -Path $FilePath }
279+
else { Get-ChildItem -Recurse -File }
280+
281+
$replacement =
282+
if ($filePath) {
283+
"^" + ([regex]::Escape($filePath) -replace '\*','.{0,}?')
284+
} else {
285+
"^" + [regex]::Escape("$pwd")
286+
}
287+
288+
$indexObject = [Ordered]@{}
289+
$gitCommand = $ExecutionContext.SessionState.InvokeCommand.GetCommand('git', 'Application')
290+
foreach ($file in $fileIndex) {
291+
$gitDates =
292+
try {
293+
(& $gitCommand log --follow --format=%ci --date default $file.FullName *>&1) -as [datetime[]]
294+
} catch {
295+
$null
296+
}
297+
$LASTEXITCODE = 0
298+
299+
$indexObject[$file.FullName -replace $replacement] = [Ordered]@{
300+
Name = $file.Name
301+
Length = $file.Length
302+
Extension = $file.Extension
303+
CreatedAt =
304+
if ($gitDates) {
305+
$gitDates[-1]
306+
} else {
307+
$file.CreationTime
308+
}
309+
LastWriteTime =
310+
if ($gitDates) {
311+
$gitDates[0]
312+
} else {
313+
$file.LastWriteTime
314+
}
315+
}
316+
}
317+
318+
foreach ($indexKey in $indexObject.Keys) {
319+
if (-not $indexObject[$indexKey].CreatedAt) {
320+
if ($indexObject["$indexKey.ps1"].CreatedAt) {
321+
$indexObject[$indexKey].CreatedAt = $indexObject["$indexKey.ps1"].CreatedAt
322+
}
323+
}
324+
if (-not $indexObject[$indexKey].LastWriteTime) {
325+
if ($indexObject["$indexKey.ps1"].LastWriteTime) {
326+
$indexObject[$indexKey].LastWriteTime = $indexObject["$indexKey.ps1"].LastWriteTime
327+
}
328+
}
329+
}
330+
331+
$indexObject | ConvertTo-Json -Depth 4 > index.json
332+
}
333+
#endregion index.json
334+
335+
#region archive.zip
336+
if ($site.Archive) {
337+
# Create an archive of the current deployment.
338+
Compress-Archive -Path $pwd -DestinationPath "archive.zip" -CompressionLevel Optimal -Force
339+
}
340+
#endregion archive.zip
341+
if ($PSScriptRoot) { Pop-Location }

0 commit comments

Comments
 (0)