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
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<PackageVersion Include="Asp.Versioning.OpenApi" Version="10.2.3" />
<PackageVersion Include="Azure.Core" Version="1.63.0" />
<PackageVersion Include="DotNet.ReproducibleBuilds" Version="2.0.5" />
<PackageVersion Include="Trellis.AgentDocs.Packaging" Version="0.1.0-preview.14" />
<PackageVersion Include="Trellis.AgentDocs.Packaging" Version="0.1.0-preview.17" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.12" />
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="10.0.12" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="10.0.12" />
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ run once from its Git root:

```powershell
dotnet new tool-manifest --output .config
dotnet tool install Trellis.AgentDocs --version 0.1.0-preview.14 --tool-manifest .config/dotnet-tools.json
dotnet tool install Trellis.AgentDocs --version 0.1.0-preview.17 --tool-manifest .config/dotnet-tools.json
dotnet tool run agentdocs init <solution-or-project>
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ run from its Git root:

```powershell
dotnet new tool-manifest --output .config
dotnet tool install Trellis.AgentDocs --version 0.1.0-preview.14 --tool-manifest .config/dotnet-tools.json
dotnet tool install Trellis.AgentDocs --version 0.1.0-preview.17 --tool-manifest .config/dotnet-tools.json
dotnet tool run agentdocs init <solution-or-project>
```

Expand Down
2 changes: 1 addition & 1 deletion Trellis.ServiceLevelIndicators.Asp/src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ run from its Git root:

```powershell
dotnet new tool-manifest --output .config
dotnet tool install Trellis.AgentDocs --version 0.1.0-preview.14 --tool-manifest .config/dotnet-tools.json
dotnet tool install Trellis.AgentDocs --version 0.1.0-preview.17 --tool-manifest .config/dotnet-tools.json
dotnet tool run agentdocs init <solution-or-project>
```

Expand Down
2 changes: 1 addition & 1 deletion Trellis.ServiceLevelIndicators/src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ run from its Git root:

```powershell
dotnet new tool-manifest --output .config
dotnet tool install Trellis.AgentDocs --version 0.1.0-preview.14 --tool-manifest .config/dotnet-tools.json
dotnet tool install Trellis.AgentDocs --version 0.1.0-preview.17 --tool-manifest .config/dotnet-tools.json
dotnet tool run agentdocs init <solution-or-project>
```

Expand Down
162 changes: 88 additions & 74 deletions build/test-guidance-packages.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -10,86 +10,100 @@ $references = [ordered]@{
'Trellis.ServiceLevelIndicators.Asp.ApiVersioning' = 'trellis-api-sli-apiversioning.md'
}

foreach ($id in $references.Keys) {
$packages = @(Get-ChildItem -LiteralPath $PackagesDirectory -Filter '*.nupkg' |
Where-Object { $_.Name -match "^$([regex]::Escape($id))\.\d" -and $_.Name -notlike '*.symbols.nupkg' })
if ($packages.Count -ne 1) {
throw "Expected one packed $id in $PackagesDirectory, found $($packages.Count)."
}
# The published validator checks the contract and discoverability (links that leave the package, front matter,
# size budgets); --strict makes a warning fail like an error. It is pinned to the packaging helper's version, which
# is published in lockstep with the tool.
$props = [xml] (Get-Content -LiteralPath (Join-Path (Join-Path $PSScriptRoot '..') 'Directory.Packages.props') -Raw)
$toolVersion = $props.SelectSingleNode('//PackageVersion[@Include="Trellis.AgentDocs.Packaging"]').Version
$toolDirectory = Join-Path ([System.IO.Path]::GetTempPath()) "agentdocs-validate-$([guid]::NewGuid().ToString('N'))"
$install = & dotnet tool install Trellis.AgentDocs --version $toolVersion --tool-path $toolDirectory 2>&1
if ($LASTEXITCODE -ne 0) { throw "Could not install Trellis.AgentDocs $toolVersion for validation:`n$($install | Out-String)" }

$archive = [System.IO.Compression.ZipFile]::OpenRead($packages[0].FullName)
try {
$manifestEntry = $archive.GetEntry('guidance/reference-manifest.json')
$referencePath = $references[$id]
$referenceEntry = $archive.GetEntry($referencePath)
if (-not $manifestEntry -or -not $referenceEntry) {
throw "$id must pack its guidance manifest and $referencePath."
}
if (@($archive.Entries | Where-Object {
$_.FullName.StartsWith('trellis/', [StringComparison]::OrdinalIgnoreCase)
}).Count -ne 0) {
throw "$id must not pack a trellis/ directory."
try {
foreach ($id in $references.Keys) {
$packages = @(Get-ChildItem -LiteralPath $PackagesDirectory -Filter '*.nupkg' |
Where-Object { $_.Name -match "^$([regex]::Escape($id))\.\d" -and $_.Name -notlike '*.symbols.nupkg' })
if ($packages.Count -ne 1) {
throw "Expected one packed $id in $PackagesDirectory, found $($packages.Count)."
}

$reader = [System.IO.StreamReader]::new($manifestEntry.Open())
try { $manifest = $reader.ReadToEnd() | ConvertFrom-Json }
finally { $reader.Dispose() }
$referenceBytes = [System.IO.MemoryStream]::new()
$archive = [System.IO.Compression.ZipFile]::OpenRead($packages[0].FullName)
try {
$referenceEntry.Open().CopyTo($referenceBytes)
$hash = [Convert]::ToHexString(
[System.Security.Cryptography.SHA256]::HashData($referenceBytes.ToArray())).ToLowerInvariant()
$referenceText = [System.Text.Encoding]::UTF8.GetString($referenceBytes.ToArray())
}
finally { $referenceBytes.Dispose() }
$manifestEntry = $archive.GetEntry('guidance/reference-manifest.json')
$referencePath = $references[$id]
$referenceEntry = $archive.GetEntry($referencePath)
if (-not $manifestEntry -or -not $referenceEntry) {
throw "$id must pack its guidance manifest and $referencePath."
}
if (@($archive.Entries | Where-Object {
$_.FullName.StartsWith('trellis/', [StringComparison]::OrdinalIgnoreCase)
}).Count -ne 0) {
throw "$id must not pack a trellis/ directory."
}

$description = [string] $manifest.documents[0].description
if ($manifest.schemaVersion -ne 1 -or @($manifest.documents).Count -ne 1 -or
$manifest.documents[0].path -ne $referencePath -or
$manifest.documents[0].sha256 -ne $hash -or
$manifest.documents[0].usage -ne 'onDemand' -or
$description.Length -eq 0 -or $description.Length -gt 200 -or $description -notmatch '^Open when ' -or
$null -ne $manifest.PSObject.Properties['entryPoints']) {
throw "$id manifest does not match the packed reference bytes, on-demand usage, and description."
}
# Each package installs into its own directory, so a relative link to a sibling package's reference breaks.
# Any non-absolute link to a trellis-*.md file counts, whatever its prefix (./, ../, subfolders) or anchor,
# in inline form [text](target) or reference form [id]: target.
if ($referenceText -match '\]\(\s*(?!https?:|mailto:|#)[^)\s]*trellis-[a-z0-9-]+\.md' -or
$referenceText -match '(?m)^\s*\[[^\]]+\]:\s*(?!https?:|mailto:|#)\S*trellis-[a-z0-9-]+\.md') {
throw "$id links to another package's reference file; refer to other packages by ID instead."
}
$reader = [System.IO.StreamReader]::new($manifestEntry.Open())
try { $manifest = $reader.ReadToEnd() | ConvertFrom-Json }
finally { $reader.Dispose() }
$referenceBytes = [System.IO.MemoryStream]::new()
try {
$referenceEntry.Open().CopyTo($referenceBytes)
$hash = [Convert]::ToHexString(
[System.Security.Cryptography.SHA256]::HashData($referenceBytes.ToArray())).ToLowerInvariant()
$referenceText = [System.Text.Encoding]::UTF8.GetString($referenceBytes.ToArray())
}
finally { $referenceBytes.Dispose() }

if (@($archive.Entries | Where-Object {
$_.FullName -match '^(build|buildTransitive)/'
}).Count -ne 0) {
throw "$id still ships a consumer build target."
}
$nuspec = $archive.GetEntry("$id.nuspec")
if (-not $nuspec) { throw "$id is missing its package metadata." }
$metadataReader = [System.IO.StreamReader]::new($nuspec.Open())
try { [xml] $metadata = $metadataReader.ReadToEnd() }
finally { $metadataReader.Dispose() }
if ($metadata.SelectSingleNode('//*[local-name()="dependency" and (@id="Trellis.AgentDocs.Packaging" or @id="Trellis.Core")]')) {
throw "$id leaks a publisher-only dependency or introduces a Core dependency."
}
if ($metadata.SelectSingleNode('//*[local-name()="readme"]')?.InnerText -ne 'README.md') {
throw "$id must declare its packed README.md as the NuGet readme."
}
$readmeEntry = $archive.GetEntry('README.md')
if (-not $readmeEntry) { throw "$id is missing its NuGet readme." }
$readmeReader = [System.IO.StreamReader]::new($readmeEntry.Open())
try { $readme = $readmeReader.ReadToEnd() }
finally { $readmeReader.Dispose() }
if ($readme -notmatch '(?m)^dotnet tool install Trellis\.AgentDocs --version \S+ --tool-manifest \.config/dotnet-tools\.json\r?$' -or
-not $readme.Contains('dotnet tool run agentdocs init <solution-or-project>')) {
throw "$id NuGet readme must explain how to install and initialize AgentDocs."
}
# Restoring a package never activates its guide: the readme must describe approval and sync.
if (-not $readme.Contains('approvedPackages') -or -not $readme.Contains('dotnet tool run agentdocs sync')) {
throw "$id NuGet readme must explain approving the package and running sync."
$description = [string] $manifest.documents[0].description
if ($manifest.schemaVersion -ne 1 -or @($manifest.documents).Count -ne 1 -or
$manifest.documents[0].path -ne $referencePath -or
$manifest.documents[0].sha256 -ne $hash -or
$manifest.documents[0].usage -ne 'onDemand' -or
$description.Length -eq 0 -or $description.Length -gt 200 -or $description -notmatch '^Open when ' -or
$null -ne $manifest.PSObject.Properties['entryPoints']) {
throw "$id manifest does not match the packed reference bytes, on-demand usage, and description."
}
# Each package installs into its own directory, so a relative link to a sibling package's reference breaks.
# Any non-absolute link to a trellis-*.md file counts, whatever its prefix (./, ../, subfolders) or anchor,
# in inline form [text](target) or reference form [id]: target.
if ($referenceText -match '\]\(\s*(?!https?:|mailto:|#)[^)\s]*trellis-[a-z0-9-]+\.md' -or
$referenceText -match '(?m)^\s*\[[^\]]+\]:\s*(?!https?:|mailto:|#)\S*trellis-[a-z0-9-]+\.md') {
throw "$id links to another package's reference file; refer to other packages by ID instead."
}

if (@($archive.Entries | Where-Object {
$_.FullName -match '^(build|buildTransitive)/'
}).Count -ne 0) {
throw "$id still ships a consumer build target."
}
$nuspec = $archive.GetEntry("$id.nuspec")
if (-not $nuspec) { throw "$id is missing its package metadata." }
$metadataReader = [System.IO.StreamReader]::new($nuspec.Open())
try { [xml] $metadata = $metadataReader.ReadToEnd() }
finally { $metadataReader.Dispose() }
if ($metadata.SelectSingleNode('//*[local-name()="dependency" and (@id="Trellis.AgentDocs.Packaging" or @id="Trellis.Core")]')) {
throw "$id leaks a publisher-only dependency or introduces a Core dependency."
}
if ($metadata.SelectSingleNode('//*[local-name()="readme"]')?.InnerText -ne 'README.md') {
throw "$id must declare its packed README.md as the NuGet readme."
}
$readmeEntry = $archive.GetEntry('README.md')
if (-not $readmeEntry) { throw "$id is missing its NuGet readme." }
$readmeReader = [System.IO.StreamReader]::new($readmeEntry.Open())
try { $readme = $readmeReader.ReadToEnd() }
finally { $readmeReader.Dispose() }
if ($readme -notmatch '(?m)^dotnet tool install Trellis\.AgentDocs --version \S+ --tool-manifest \.config/dotnet-tools\.json\r?$' -or
-not $readme.Contains('dotnet tool run agentdocs init <solution-or-project>')) {
throw "$id NuGet readme must explain how to install and initialize AgentDocs."
}
# Restoring a package never activates its guide: the readme must describe approval and sync.
if (-not $readme.Contains('approvedPackages') -or -not $readme.Contains('dotnet tool run agentdocs sync')) {
throw "$id NuGet readme must explain approving the package and running sync."
}
$validation = & (Join-Path $toolDirectory 'agentdocs') validate $packages[0].FullName --strict 2>&1
if ($LASTEXITCODE -ne 0) { throw "agentdocs validate --strict rejected ${id}:`n$($validation | Out-String)" }
Write-Host "PASS $($packages[0].Name) ships validated guidance without consumer targets"
}
Write-Host "PASS $($packages[0].Name) ships validated guidance without consumer targets"
finally { $archive.Dispose() }
}
finally { $archive.Dispose() }
}
finally { Remove-Item -LiteralPath $toolDirectory -Recurse -Force -ErrorAction SilentlyContinue }
Loading