feat(packages): add duf to the scoop manifest #278
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| workflow_dispatch: | |
| # Cancel superseded runs on the same ref so the queue stays current. | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: true | |
| # Pinned dependency versions live in one place so a bump is a one-line, reviewable | |
| # change (and Dependabot can PR action SHAs). Actions are pinned to a full commit | |
| # SHA β a moved tag can't silently change what runs in CI (supply-chain gate). | |
| env: | |
| PESTER_VERSION: "5.6.1" | |
| PSSA_VERSION: "1.22.0" | |
| LUACHECK_VERSION: "1.2.0" | |
| permissions: | |
| contents: read | |
| jobs: | |
| # --- incremental gate ------------------------------------------------------- | |
| # Decide whether the heavy Windows jobs need to run at all. A docs/markdown-only | |
| # change still gets the fast Linux gate, but doesn't spin up two windows-latest | |
| # runners for nothing. `code` is true when any changed file is NOT pure docs. | |
| detect-changes: | |
| name: Detect code changes | |
| runs-on: ubuntu-latest | |
| outputs: | |
| code: ${{ steps.filter.outputs.code }} | |
| lua: ${{ steps.filter.outputs.lua }} | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| fetch-depth: 0 | |
| - id: filter | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| ZERO='0000000000000000000000000000000000000000' | |
| if [ "${{ github.event_name }}" = "pull_request" ]; then | |
| base='${{ github.event.pull_request.base.sha }}' | |
| else | |
| base='${{ github.event.before }}' | |
| fi | |
| # An empty / all-zero (new branch) / unreachable base can't anchor a diff. | |
| # Fall back to HEAD~1; if even that is missing (root commit, shallow clone), | |
| # we have no usable base. | |
| if [ -z "$base" ] || [ "$base" = "$ZERO" ] || ! git cat-file -e "${base}^{commit}" 2>/dev/null; then | |
| if git rev-parse HEAD~1 >/dev/null 2>&1; then base='HEAD~1'; else base=''; fi | |
| fi | |
| # FAIL SAFE: when the base is unknown, run EVERYTHING rather than skipping | |
| # the heavy jobs on a guess β the old HEAD~1 fallback could yield an empty | |
| # diff and silently skip tests on a root commit / new branch / force-push. | |
| if [ -z "$base" ]; then | |
| echo "no usable diff base (root commit / new branch) β running all jobs" | |
| echo 'code=true' >> "$GITHUB_OUTPUT" | |
| echo 'lua=true' >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| changed="$(git diff --name-only "${base}...HEAD")" | |
| echo "diff base: $base"; echo "changed files:"; echo "$changed" | |
| # code=true if at least one changed path is NOT a docs/markdown file | |
| # (anything under docs/ or any *.md is treated as docs-only). | |
| if printf '%s\n' "$changed" | grep -qvE '^(docs/.*|.*\.md)$'; then | |
| echo 'code=true' >> "$GITHUB_OUTPUT" | |
| else | |
| echo 'code=false' >> "$GITHUB_OUTPUT" | |
| fi | |
| # lua=true when any Neovim Lua config (or its luacheck config) changed, | |
| # so the Lua lint only spins up when it can actually find something. | |
| if printf '%s\n' "$changed" | grep -qE '(^nvim/|\.lua$|\.luacheckrc$)'; then | |
| echo 'lua=true' >> "$GITHUB_OUTPUT" | |
| else | |
| echo 'lua=false' >> "$GITHUB_OUTPUT" | |
| fi | |
| # --- fast, dependency-free gate --------------------------------------------- | |
| # Runs on Linux pwsh in seconds and fails the build on any syntax/JSON/format | |
| # regression before the heavier Windows jobs spin up. Always runs. | |
| lint-fast: | |
| name: Syntax, manifest & format gate (fast) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Validate (parser + JSON + editorconfig, no Gallery needed) | |
| shell: pwsh | |
| run: ./tests/Invoke-Validation.ps1 | |
| # --- Lua lint (nvim config) ------------------------------------------------- | |
| # The nvim/ tree had a .luacheckrc but nothing ran it, so the config could rot | |
| # silently. Runs only when Lua actually changed (see detect-changes.lua). | |
| lua-lint: | |
| name: Luacheck (nvim) | |
| runs-on: ubuntu-latest | |
| needs: [lint-fast, detect-changes] | |
| if: needs.detect-changes.outputs.lua == 'true' | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Install lua + luarocks | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| # lua5.1 + luarocks provide the runtime luacheck needs to execute, so | |
| # they come from apt every run (the cached rock below can't run without | |
| # them). The slow, non-hermetic part β compiling the luacheck rock β is | |
| # what we pin and cache. | |
| sudo apt-get update | |
| # liblua5.1-0-dev so luarocks can compile luacheck's C deps (luafilesystem) | |
| # against the SAME Lua 5.1 we standardize on below. | |
| sudo apt-get install -y lua5.1 liblua5.1-0-dev luarocks | |
| - name: Cache luacheck rock | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: ~/.luarocks | |
| # Key on the runner image (ImageOS) and the Lua line too: the cached tree | |
| # is built against the runner's lua5.1/luarocks, so a runner-image bump or | |
| # Lua change must invalidate it rather than silently reuse a mismatched rock. | |
| key: luacheck-${{ runner.os }}-${{ env.ImageOS }}-lua5.1-${{ env.LUACHECK_VERSION }} | |
| - name: Install luacheck (pinned) | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| # Pin the exact version (matches the SHA-pinned Actions discipline) AND the | |
| # Lua line (--lua-version 5.1) so the build target is deterministic instead | |
| # of luarocks' default; install to the cached user tree, so a cache hit | |
| # skips the recompile. | |
| if ! "$HOME/.luarocks/bin/luacheck" --version 2>/dev/null | grep -q "$LUACHECK_VERSION"; then | |
| luarocks install --local --lua-version 5.1 luacheck "$LUACHECK_VERSION" | |
| fi | |
| echo "$HOME/.luarocks/bin" >> "$GITHUB_PATH" | |
| - name: Luacheck | |
| shell: bash | |
| # Run from nvim/ so luacheck discovers nvim/.luacheckrc by searching upward. | |
| run: luacheck . | |
| working-directory: nvim | |
| # --- nvim <-> Core parity (B1) ---------------------------------------------- | |
| # nvim/ is vendored from dotfiles-core; this fails if the vendored tree drifts | |
| # from the Core commit recorded in nvim/.core-ref (clone Core @ that commit and | |
| # diff). Self-skips until a sync stamps the ref. Same nvim-only gate as luacheck. | |
| nvim-parity: | |
| name: nvim β Core parity | |
| runs-on: ubuntu-latest | |
| needs: [lint-fast, detect-changes] | |
| if: needs.detect-changes.outputs.lua == 'true' | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Assert nvim/ matches Core at the recorded ref | |
| shell: pwsh | |
| run: ./tests/Assert-NvimParity.ps1 | |
| analyze: | |
| name: PSScriptAnalyzer | |
| runs-on: windows-latest | |
| needs: [lint-fast, detect-changes] | |
| if: needs.detect-changes.outputs.code == 'true' | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Cache PowerShell modules | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: ~/Documents/PowerShell/Modules | |
| key: pssa-${{ runner.os }}-${{ env.PSSA_VERSION }} | |
| - name: Install PSScriptAnalyzer (pinned) | |
| shell: pwsh | |
| run: | | |
| if (-not (Get-Module -ListAvailable PSScriptAnalyzer | | |
| Where-Object Version -eq ([version]$env:PSSA_VERSION))) { | |
| Set-PSRepository PSGallery -InstallationPolicy Trusted | |
| Install-Module PSScriptAnalyzer -RequiredVersion $env:PSSA_VERSION ` | |
| -Scope CurrentUser -Force | |
| } | |
| # Supply-chain gate: -RequiredVersion above pins the version and Install-Module | |
| # enforces the publisher check, but nothing asserted the installed bits are | |
| # actually Microsoft-signed (a poisoned cache or a compromised feed would slip | |
| # through). Verify the module manifest's Authenticode signature is Valid AND | |
| # Microsoft-issued before trusting the analyzer β the Windows analogue of the | |
| # SHA-256 verification the Linux gate tools get from setup-core-tools. | |
| - name: Verify PSScriptAnalyzer is Microsoft-signed | |
| shell: pwsh | |
| run: | | |
| $mod = Get-Module -ListAvailable PSScriptAnalyzer | | |
| Where-Object Version -eq ([version]$env:PSSA_VERSION) | | |
| Select-Object -First 1 | |
| if (-not $mod) { throw "PSScriptAnalyzer $($env:PSSA_VERSION) not installed" } | |
| $sig = Get-AuthenticodeSignature -FilePath $mod.Path | |
| "status=$($sig.Status)" | Write-Host | |
| # Check Status FIRST: on a non-Valid status (e.g. NotSigned) SignerCertificate | |
| # is $null, so reading .Subject before this would mask the real failure. | |
| if ($sig.Status -ne 'Valid') { | |
| throw "PSScriptAnalyzer signature is '$($sig.Status)', expected 'Valid'" | |
| } | |
| $subject = $sig.SignerCertificate.Subject | |
| "signer=$subject" | Write-Host | |
| if ($subject -notmatch 'Microsoft Corporation') { | |
| throw "PSScriptAnalyzer signer '$subject' is not Microsoft Corporation" | |
| } | |
| "PSScriptAnalyzer $($env:PSSA_VERSION) verified: Valid + Microsoft-signed" | Write-Host | |
| - name: Analyze | |
| shell: pwsh | |
| run: | | |
| Import-Module PSScriptAnalyzer -RequiredVersion $env:PSSA_VERSION | |
| $results = Invoke-ScriptAnalyzer -Path . -Recurse ` | |
| -Settings ./tests/PSScriptAnalyzerSettings.psd1 | |
| if ($results) { | |
| $results | Format-Table -AutoSize | Out-String | Write-Host | |
| } | |
| $errors = @($results | Where-Object Severity -eq 'Error') | |
| "Analyzer findings: $($results.Count) total, $($errors.Count) error(s)" | Write-Host | |
| if ($errors.Count) { throw "PSScriptAnalyzer reported $($errors.Count) error(s)" } | |
| test: | |
| name: Pester | |
| runs-on: windows-latest | |
| needs: [lint-fast, detect-changes] | |
| if: needs.detect-changes.outputs.code == 'true' | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Cache PowerShell modules | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: ~/Documents/PowerShell/Modules | |
| key: pester-${{ runner.os }}-${{ env.PESTER_VERSION }} | |
| - name: Install Pester (pinned) | |
| shell: pwsh | |
| run: | | |
| if (-not (Get-Module -ListAvailable Pester | | |
| Where-Object Version -eq ([version]$env:PESTER_VERSION))) { | |
| Set-PSRepository PSGallery -InstallationPolicy Trusted | |
| Install-Module Pester -RequiredVersion $env:PESTER_VERSION ` | |
| -Scope CurrentUser -Force -SkipPublisherCheck | |
| } | |
| - name: Run tests (with coverage gate) | |
| shell: pwsh | |
| run: | | |
| Import-Module Pester -RequiredVersion $env:PESTER_VERSION | |
| # The coverage % bar and the test-case FLOOR come from the versioned, | |
| # generated baseline (tests/coverage-baseline.json) β not hand-edited | |
| # literals here. Refresh it with tests/Update-CoverageBaseline.ps1 after | |
| # intentionally removing tests (B5). Read-CoverageBaseline throws if the | |
| # file is missing/corrupt, so the gate can't silently go soft. | |
| . ./tests/CoverageGate.ps1 | |
| $baseline = Read-CoverageBaseline (Get-Content ./tests/coverage-baseline.json -Raw) | |
| $cfg = New-PesterConfiguration | |
| $testPath = './tests' # single source for the run path + the test-file glob | |
| $cfg.Run.Path = $testPath | |
| # PassThru (not Run.Exit) so we get the result object back and can gate on | |
| # BOTH failures and coverage ourselves with clear messages. | |
| $cfg.Run.PassThru = $true | |
| $cfg.Output.Verbosity = 'Detailed' | |
| # Regression gate on the whole pure-helper surface: 05-lib.ps1 plus the | |
| # Dotfiles module's extracted helper files (B7), each fully exercised by | |
| # Lib/Wsl/Doctor/Help/Module tests. A drop here means a test was deleted | |
| # or a branch was added without a covering test. Grow this list as more | |
| # pure helpers reach full coverage. | |
| $cfg.CodeCoverage.Enabled = $true | |
| $cfg.CodeCoverage.Path = @( | |
| './powershell/core/05-lib.ps1' | |
| './powershell/Dotfiles/Wsl.Helpers.ps1' | |
| './powershell/Dotfiles/Doctor.Helpers.ps1' | |
| './powershell/Dotfiles/Help.Helpers.ps1' | |
| './powershell/Dotfiles/Modules.Helpers.ps1' | |
| ) | |
| $cfg.CodeCoverage.CoveragePercentTarget = $baseline.CoveragePercentTarget | |
| $r = Invoke-Pester -Configuration $cfg | |
| $pct = [math]::Round($r.CodeCoverage.CoveragePercent, 1) | |
| "Tests: $($r.PassedCount) passed, $($r.FailedCount) failed, $($r.TotalCount) total across $($r.Containers.Count) file(s). Coverage (pure-helper surface): $pct% (target $($baseline.CoveragePercentTarget)%)" | Write-Host | |
| # The test-FILE count is auto-derived (not stored): count *.Tests.ps1 | |
| # under the run path RECURSIVELY (matching Pester's own discovery) and | |
| # assert Pester ran EXACTLY that many β a suite that fails to load, or is | |
| # removed/renamed, is caught with no number to maintain (issue #29). | |
| $expectedFiles = @(Get-ChildItem $testPath -Recurse -File -Filter *.Tests.ps1 -ErrorAction Stop).Count | |
| # One pure decision over the whole result: hard failures, the coverage | |
| # bar, the exact file match, and the test-case floor. The floor also | |
| # guards suites NOT in CoveragePaths (install/uninstall/packages), whose | |
| # removed tests wouldn't show up as a coverage drop. TotalCount runs well | |
| # past the floor (two -ForEach blocks expand it); it only trips on a real | |
| # deletion. | |
| $gate = Get-CoverageGateResult -CoveragePercent $r.CodeCoverage.CoveragePercent ` | |
| -TotalCount $r.TotalCount -FileCount $r.Containers.Count -ExpectedFileCount $expectedFiles ` | |
| -FailedCount $r.FailedCount -Baseline $baseline | |
| if (-not $gate.Passed) { | |
| $gate.Failures | ForEach-Object { Write-Host "::error::$_" } | |
| throw ("coverage gate failed:`n " + ($gate.Failures -join "`n ")) | |
| } |