Skip to content

fix: a rebuild no longer deletes the installers it is about to copy (… #126

fix: a rebuild no longer deletes the installers it is about to copy (…

fix: a rebuild no longer deletes the installers it is about to copy (… #126

Workflow file for this run

name: checks
on:
pull_request:
push:
branches: [main]
tags: ['v*']
# Read-only token: nothing here writes to the repo.
permissions:
contents: read
# A new push to the same branch makes the previous run pointless.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
powershell:
runs-on: windows-latest
# A hung job is not a failed job: without this it holds a runner until the
# six-hour default. Pester 6 hangs this suite (see the Pester step below),
# which is exactly the shape of failure a timeout has to cover.
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
# `shell: powershell` is Windows PowerShell 5.1; `pwsh` would be 7.
# DiscWright requires 5.1, so 5.1 is the only parser whose verdict means
# anything here - 7 accepts syntax that 5.1 rejects.
- name: Parse every script under Windows PowerShell 5.1
shell: powershell
run: |
$failed = $false
foreach ($f in Get-ChildItem -Recurse -Filter *.ps1) {
$errors = $null
[void][System.Management.Automation.Language.Parser]::ParseFile(
$f.FullName, [ref]$null, [ref]$errors)
if ($errors -and $errors.Count) {
$failed = $true
Write-Host "::error file=$($f.Name)::$($errors.Count) parse error(s)"
foreach ($e in $errors) {
Write-Host " line $($e.Extent.StartLineNumber): $($e.Message)"
}
} else {
Write-Host "ok $($f.Name)"
}
}
if ($failed) { exit 1 }
# The ISO build tests skip themselves if the runner has no IMAPI2FS, and the
# ISO content tests skip if it has no 7-Zip, so the job still means something
# on a machine that cannot do either.
- name: Pester
shell: powershell
run: |
# Pester 5, deliberately, and not merely "5 or later" - the same
# pin tests/Invoke-Tests.ps1 makes, for the reason written out there:
# under 6.1.0 every file in this suite hangs in BeforeAll. Hosted
# runners ship 5.x today, so -MinimumVersion 5.0 happens to import 5.x
# today. It would import 6.x the day that changes, and a hung job just
# runs until the timeout above rather than telling anybody why.
$p5 = Get-Module -ListAvailable Pester |
Where-Object { $_.Version.Major -eq 5 } |
Sort-Object Version -Descending | Select-Object -First 1
if (-not $p5) {
Set-PSRepository PSGallery -InstallationPolicy Trusted
Install-Module Pester -Force -SkipPublisherCheck -Scope CurrentUser -MinimumVersion 5.0 -MaximumVersion 5.999.999
$p5 = Get-Module -ListAvailable Pester |
Where-Object { $_.Version.Major -eq 5 } |
Sort-Object Version -Descending | Select-Object -First 1
}
Import-Module $p5.Path -Force
Write-Host "Pester $((Get-Module Pester).Version)"
$cfg = New-PesterConfiguration
$cfg.Run.Path = 'tests'
# tests/ui drives the real window: it launches the app, moves the
# pointer and needs a desktop that behaves like a person's. A hosted
# runner has a virtual screen, so the suite's own "is there a desktop"
# guard says yes and the tests then fail on a display nothing can
# really use. Excluded by path AND by tag, because Run.Path is
# recursive and would otherwise pick up the file again the moment
# anything is renamed.
$cfg.Run.ExcludePath = @((Join-Path $PWD 'tests\ui'))
$cfg.Filter.ExcludeTag = @('UI')
$cfg.Run.Exit = $true
$cfg.Output.Verbosity = 'Detailed'
$cfg.TestResult.Enabled = $true
$cfg.TestResult.OutputPath = 'pester-results.xml'
Invoke-Pester -Configuration $cfg
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: pester-results
path: pester-results.xml
if-no-files-found: ignore
# Fails the build on Errors only. Warnings are printed but tolerated: the
# default rule set has strong opinions about WinForms code that are not
# worth rewriting a working app over, and a check that always fails is a
# check everybody learns to ignore. Which rules run is in
# PSScriptAnalyzerSettings.psd1, with the reasoning for each exclusion.
- name: PSScriptAnalyzer
shell: powershell
run: |
if (-not (Get-Module -ListAvailable PSScriptAnalyzer)) {
Set-PSRepository PSGallery -InstallationPolicy Trusted
Install-Module PSScriptAnalyzer -Force -Scope CurrentUser
}
# Invoke-ScriptAnalyzer does find the settings file on its own when it
# sits in the analyzed path, but naming it means the result does not
# depend on where the job happens to be standing.
# docs/research holds throwaway harnesses preserved exactly as they were
# run, as the evidence behind a decision recorded in ROADMAP.md. They are
# not part of DiscWright and will never be edited - tidying them would
# make them no longer the scripts that produced the numbers. Linting them
# buys nothing and costs two dozen warnings on every run, and a check
# that always shouts is a check everybody learns to ignore.
$results = @(Invoke-ScriptAnalyzer -Path . -Recurse -Settings .\PSScriptAnalyzerSettings.psd1 |
Where-Object { $_.ScriptPath -notlike '*\docs\research\*' })
$errors = @($results | Where-Object { $_.Severity -eq 'Error' })
$warnings = @($results | Where-Object { $_.Severity -eq 'Warning' })
foreach ($w in $warnings) {
Write-Host "::warning file=$($w.ScriptName),line=$($w.Line)::$($w.RuleName): $($w.Message)"
}
foreach ($e in $errors) {
Write-Host "::error file=$($e.ScriptName),line=$($e.Line)::$($e.RuleName): $($e.Message)"
}
Write-Host ""
Write-Host "$($errors.Count) error(s), $($warnings.Count) warning(s)"
if ($errors.Count) { exit 1 }
# GitHub serves repository files without a BOM. Windows PowerShell 5.1
# decodes a BOM-less file as the system ANSI codepage rather than UTF-8, so
# one stray non-ASCII byte in a script silently mangles on any machine whose
# codepage is not the author's. Everything executable is ASCII today; this
# keeps it that way. Documentation is exempt - GitHub renders it as UTF-8.
- name: Executable files must stay pure ASCII
shell: powershell
run: |
$failed = $false
foreach ($f in Get-ChildItem -Recurse -Include *.ps1,*.vbs,*.cmd) {
$bytes = [IO.File]::ReadAllBytes($f.FullName)
$bad = @()
for ($i = 0; $i -lt $bytes.Length; $i++) {
if ($bytes[$i] -gt 127) { $bad += $i }
}
if ($bad.Count) {
$failed = $true
Write-Host "::error file=$($f.Name)::$($bad.Count) non-ASCII byte(s), first at offset $($bad[0])"
} else {
Write-Host "ok $($f.Name)"
}
}
if ($failed) { exit 1 }
# There is deliberately no job here for tests/ui.
#
# Those tests drive the real window, and a hosted runner's desktop is 1024x768
# with nobody sitting at it. The window needs 1032px of height, so it opens
# scrolled, controls sit off-screen, and a synthesized click lands on nothing.
# Measured: every window test that only READS state passes there, and every one
# that has to CLICK something below the fold fails.
#
# Running it anyway behind continue-on-error was worse than not running it. The
# check still reported red, and a red check that everyone is told to ignore
# teaches people to ignore red checks.
#
# They run locally, where the desktop is real and they are trustworthy:
#
# .\tests\Invoke-Tests.ps1 both suites
# Invoke-Pester tests/ui the window alone
#
# A version constant maintained by hand drifts, because bumping it is a step
# that gets forgotten. This runs only when a tag is pushed and fails the tag if
# the app would report a version it is not.
version-matches-tag:
if: startsWith(github.ref, 'refs/tags/v')
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: App version must match the tag
shell: powershell
run: |
# GITHUB_REF_NAME rather than an expression, so the tag arrives as data
# instead of being pasted into the script before it runs.
$tag = $env:GITHUB_REF_NAME
$expected = $tag -replace '^v',''
$m = [regex]::Match((Get-Content DiscWright.ps1 -Raw), '\$APP_VERSION\s*=\s*''([^'']+)''')
if (-not $m.Success) {
Write-Host "::error::no `$APP_VERSION assignment found in DiscWright.ps1"
exit 1
}
$actual = $m.Groups[1].Value
Write-Host "tag: $tag expects: $expected DiscWright.ps1 says: $actual"
if ($actual -ne $expected) {
Write-Host "::error::`$APP_VERSION is '$actual' but the tag is '$tag' - bump the constant before tagging"
exit 1
}
Write-Host "ok"