Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 5 additions & 3 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ root = true
# separate editorconfig.
#
# .gitattributes is the single authority for line endings (it transforms bytes
# on checkout); end_of_line here is an editor hint only, and editorconfig-checker
# leaves the end-of-line check disabled. See modules/editorconfig/.
# on checkout); end_of_line here is an editor hint only, and the
# editorconfig-checker ruleset disables its end-of-line check to avoid
# double-enforcement.

[*]
indent_style = space
Expand Down Expand Up @@ -37,7 +38,8 @@ indent_size = 2
[*.{ps1,psm1,psd1}]
indent_size = 4

# Windows batch — cmd.exe requires CRLF.
# Windows batch — CRLF is the safe default (cmd.exe goto/call label seeks can
# break with LF).
[*.{cmd,bat}]
end_of_line = crlf

Expand Down
13 changes: 7 additions & 6 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,20 @@
*.bash diff=bash

###############################################################################
# PowerShell — LF is deliberate. Verified to run on both PowerShell 7 and
# Windows PowerShell 5.1. The PowerShell repo pins .ps1 to eol=lf; the popular
# community gitattributes template pins crlf — they target different eras. Listed
# explicitly so it is not "corrected" to crlf without a requirement to round-trip
# pre-existing CRLF-signed scripts unchanged.
# PowerShell — LF is deliberate. Verified empirically to run on both PowerShell 7
# and Windows PowerShell 5.1. The popular community gitattributes templates pin
# *.ps1 to crlf; we choose lf for a uniform-LF repo. Listed explicitly so it is
# not "corrected" to crlf without a requirement to round-trip pre-existing
# CRLF-signed scripts unchanged.
###############################################################################

*.ps1 text eol=lf
*.psm1 text eol=lf
*.psd1 text eol=lf

###############################################################################
# Windows-native — cmd.exe requires CRLF (overrides the LF default above).
# Windows-native — cmd.exe is reliable only with CRLF (LF can break goto/call
# label seeks), so override the LF default above.
###############################################################################

*.cmd text eol=crlf
Expand Down
5 changes: 4 additions & 1 deletion .github/actions/lychee-offline/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ runs:
run: |
set -euo pipefail
# --offline skips all network requests, so only local file links and
# #anchors are checked — fully deterministic.
# (when the ruleset sets include_fragments, as the standard lychee.toml
# does) #anchors are checked — fully deterministic. lychee's own CLI
# default for fragment checking is off, so anchor integrity is ruleset-
# driven; the config is intentionally not overridden here.
args=(--offline --no-progress --config "$CONFIG")
for e in $EXCLUDE; do
args+=(--exclude-path "$e")
Expand Down
11 changes: 6 additions & 5 deletions .github/actions/powershell/Invoke-Pssa.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@

.DESCRIPTION
Lints .ps1/.psm1 files against a PSScriptAnalyzerSettings.psd1 ruleset. Each
file is analyzed in a fresh pwsh subprocess to avoid the PSScriptAnalyzer
CommandInfoCache / RunspacePool race that surfaces (notably on Linux) when
many files are analyzed in one process.
file is analyzed in a fresh pwsh subprocess to sidestep an intermittent
NullReferenceException in PSScriptAnalyzer's CommandInfoCache that surfaces
when many files are analyzed in one process (PSScriptAnalyzer issue #1708).

File discovery uses Get-ChildItem -Force so dot-prefixed directories (for
example .github) are not silently skipped by Linux pwsh.
Expand All @@ -22,8 +22,9 @@
only resolves if a ruleset sits next to this script.

.PARAMETER AnalyzerVersion
Required minimum PSScriptAnalyzer version. Default 1.25.0 — the floor that
fixes the Linux NRE under pwsh 7.4.14+.
Required minimum PSScriptAnalyzer version. Default 1.25.0, which resolves an
Import-Module assembly-version mismatch on newer pwsh 7.4.x
(PSScriptAnalyzer issue #2106 / PR #2107).

.PARAMETER ExcludePath
Path substrings to skip (matched against forward-slash-normalized full
Expand Down
13 changes: 13 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,22 @@ updates:
# .github/workflows; the glob target is required to also scan the composite
# actions' own action.yml files (Dependabot does not descend into
# .github/actions/<name>/ from "/" alone).
#
# Dependabot only updates `uses:` action SHAs. The tool versions pinned inside
# the actions' run scripts and input defaults — markdownlint-cli2, Node.js,
# PSScriptAnalyzer, and the checksum-pinned ShellCheck and lychee binaries —
# have no ecosystem manifest here and are tracked manually at the weekly review.
- package-ecosystem: github-actions
directories:
- /
- /.github/actions/*
schedule:
interval: weekly
open-pull-requests-limit: 10
commit-message:
prefix: build
# Collapse all action bumps into a single PR to cut review noise.
groups:
github-actions:
patterns:
- '*'
12 changes: 10 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,22 @@ jobs:
- name: Analyze PowerShell
uses: ./.github/actions/powershell

links:
runs-on: ubuntu-latest
steps:
- name: Check out
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Check links and anchors (offline)
uses: ./.github/actions/lychee-offline

ci-status:
if: always()
needs: [markdown, powershell]
needs: [markdown, powershell, links]
runs-on: ubuntu-latest
steps:
- name: Aggregate lane results
env:
RESULTS: ${{ needs.markdown.result }} ${{ needs.powershell.result }}
RESULTS: ${{ needs.markdown.result }} ${{ needs.powershell.result }} ${{ needs.links.result }}
run: |
for r in $RESULTS; do
case "$r" in
Expand Down
22 changes: 20 additions & 2 deletions .github/workflows/link-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,31 @@ jobs:
args: ${{ inputs.args }}
fail: false
env:
# Lifts the github.com link rate limit to 1,000 req/hour/repo.
# Authenticates lychee's github.com requests so they use the higher
# authenticated rate limit instead of the stricter unauthenticated one.
GITHUB_TOKEN: ${{ github.token }}

# create-issue-from-file does not deduplicate by title: with no issue-number
# it always opens a NEW issue. Resolve the existing open tracking issue
# first so the next step UPDATES it, keeping a single rolling issue rather
# than filing a fresh one on every scheduled failure.
- name: Find existing tracking issue
id: tracking
if: steps.lychee.outputs.exit_code != '0'
env:
GH_TOKEN: ${{ github.token }}
TITLE: ${{ inputs.issue-title }}
run: |
number=$(gh issue list --state open --label link-check \
--search "in:title \"$TITLE\"" --json number,title \
--jq ".[] | select(.title == \"$TITLE\") | .number" | head -n1)
echo "number=$number" >> "$GITHUB_OUTPUT"

- name: Open or update tracking issue
if: steps.lychee.outputs.exit_code != '0'
uses: peter-evans/create-issue-from-file@fca9117c27cdc29c6c4db3b86c48e4115a786710 # v6.0.0
with:
title: ${{ inputs.issue-title }}
content-filepath: ./lychee/out.md
issue-number: ${{ steps.tracking.outputs.number }}
content-filepath: lychee/out.md
labels: automated, link-check
29 changes: 29 additions & 0 deletions modules/lychee/lychee.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# lychee ruleset — repo-agnostic link-checking config.
# The offline CI lane runs with --offline (external URLs skipped); the online
# advisory lane reuses this file with the network enabled.
# Ref: https://github.com/lycheeverse/lychee/blob/master/lychee.example.toml

# Verify #fragment/anchor targets resolve, not just the file path ("full"
# checks both anchor and text fragments).
include_fragments = "full"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use a v0.23-compatible fragment setting

When the scheduled link-check workflow runs with its defaults, it reuses this new config via --config modules/lychee/lychee.toml, but the pinned lycheeverse/lychee-action@8646... is v2.8.0 and its action metadata defaults to lycheeVersion: v0.23.0; I checked the v0.23.0 example config, where include_fragments is still boolean (include_fragments = true), while the "full" value is documented for current v0.24.x. As a result the online reusable workflow will fail parsing the config instead of checking links unless you either pin the action to a lychee 0.24+ binary or keep this setting compatible with v0.23.

Useful? React with 👍 / 👎.


# Build/dependency trees — not authored content. Values are regex (single-quoted
# TOML literals, so backslashes are not double-escaped).
exclude_path = [
'node_modules',
'\.venv',
'/bin/',
'/obj/',
]

# URL excludes for the online lane (the offline lane skips URLs entirely):
# auth-walled hosts that block automated checkers, loopback, and placeholders.
exclude = [
'^https?://(www\.)?x\.com/',
'^https?://twitter\.com/',
'^https?://(www\.)?linkedin\.com/',
'^https?://bsky\.app/',
'^https?://localhost',
'^https?://127\.0\.0\.1',
'^https?://example\.(com|org)',
]
6 changes: 3 additions & 3 deletions modules/markdown/.markdownlint-cli2.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@
"MD046": { "style": "fenced" }, // Fenced (not indented) code blocks
"MD048": { "style": "backtick" }, // Backtick (not tilde) code fences
"MD024": { "siblings_only": true }, // Allow duplicate headings under different parents
"MD055": { "style": "consistent" }, // Consistent table pipe style within a file
"MD060": false, // Don't enforce one table-column style across mixed compact/padded tables
"MD060": false, // Table column style — disabled; allow any inner-pipe spacing without flagging

// --- Relaxed for GFM / prose ---
"MD013": false, // No hard line-length limit (tables and code exceed 80)
Expand All @@ -31,7 +30,8 @@
"MD033": false, // Allow inline HTML (<details>, <summary>, <br>, ...)
"MD034": false, // Allow bare URLs (GFM autolinks them)
"MD036": false, // Allow bold text used as a pseudo-heading
"MD040": false, // Fenced code need not declare a language
"MD041": false // First line need not be a top-level heading (frontmatter)
// MD040 (fenced-code language) left at its default (enabled): GFM relies on
// the language tag for syntax highlighting; use ```text for plain blocks.
}
}
3 changes: 3 additions & 0 deletions modules/powershell/PSScriptAnalyzerSettings.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@

# --- Compatibility ---

# TargetVersions documents the supported window (7.4 LTS, 7.6 current).
# The rule matches on major version only, so both entries map to "7" —
# the pair is intentional documentation, not two distinct checks.
PSUseCompatibleSyntax = @{
Enable = $true
TargetVersions = @('7.4', '7.6')
Expand Down