build/Build-DFToolIdentities.ps1
|
#Requires -Version 7.0 <# .SYNOPSIS Builds data/tool-identities.json: verified cross-catalog identity links for Tools/*.json's curated tools, plus any hand-authored build/identities/*.jsonc fragments. Author-side tooling — never loaded by the DotForge module. .PARAMETER ToolsPath Tools/*.json directory (default: Tools/ at the repo root). .PARAMETER IdentitiesDir Directory of hand-authored *.jsonc fragments (default: build/identities next to this script). May be empty or absent — all v1 seed data comes from Tools/*.json; this is the extension point for future manual entries. .PARAMETER OutPath Output file (default: data/tool-identities.json at the repo root). .PARAMETER Fresh Bypass catalog/GitHub caches during resolution — use for the real published-artifact build; omit for a quick local dry run against whatever's already cached. .PARAMETER ResolveLinkage Override the per-tool linkage resolver (default: a thin wrapper around the real Resolve-DFToolIdentityLinkage). Tests inject a canned scriptblock here to avoid live network access. .EXAMPLE ./build/Build-DFToolIdentities.ps1 -Fresh Regenerates data/tool-identities.json with live, uncached resolution. #> [CmdletBinding()] param( [string]$ToolsPath = (Join-Path $PSScriptRoot '../Tools'), [string]$IdentitiesDir = (Join-Path $PSScriptRoot 'identities'), [string]$OutPath = (Join-Path $PSScriptRoot '../data/tool-identities.json'), [switch]$Fresh, [scriptblock]$ResolveLinkage ) if (-not (Get-Command Test-DFToolIdentityGuideSchema -ErrorAction Ignore)) { # Test-DFToolIdentityGuideSchema / Resolve-DFToolIdentityLinkage are # Private (not exported) per repo convention, so Import-Module alone # would not make them visible here — dot-source Private/*.ps1 directly, # mirroring how DotForge.psm1 loads them into the module's own session. Get-ChildItem -Path (Join-Path $PSScriptRoot '../Private') -Filter '*.ps1' | ForEach-Object { . $_.FullName } } if (-not $ResolveLinkage) { $ResolveLinkage = { param($ToolName, $Packages, $Fresh) Resolve-DFToolIdentityLinkage -ToolName $ToolName -Packages $Packages -Fresh:$Fresh } } function Read-DFIdentityFragment { param([string]$Path) $raw = Get-Content $Path -Raw $stripped = ($raw -split "`n" | ForEach-Object { if ($_ -match '^(?<code>(?:[^"]|"[^"]*")*?)//') { $Matches.code } else { $_ } }) -join "`n" $stripped | ConvertFrom-Json } $tools = [ordered]@{} $seenPairs = @{} function Add-DFIdentityEntry { param([string]$Key, $Entry, [string]$SourceLabel) if ($tools.Contains($Key)) { throw "Build-DFToolIdentities: duplicate tool key '$Key' from $SourceLabel (already defined)" } foreach ($prop in $Entry.packages.PSObject.Properties) { if (-not $prop.Value) { continue } $pairKey = "$($prop.Name.ToLowerInvariant()):$(([string]$prop.Value).ToLowerInvariant())" if ($seenPairs.ContainsKey($pairKey)) { throw "Build-DFToolIdentities: $pairKey claimed by both '$($seenPairs[$pairKey])' and '$Key' ($SourceLabel)" } $seenPairs[$pairKey] = $Key } $tools[$Key] = $Entry } # Seed from Tools/*.json — the authoritative, already-curated 32. The build # pass VERIFIES this existing curation (attaching automated repo/homepage # evidence where possible) rather than discovering new links from scratch. foreach ($file in (Get-ChildItem $ToolsPath -Filter '*.json')) { $tool = Get-Content $file.FullName -Raw | ConvertFrom-Json if (-not $tool.packages -or @($tool.packages.PSObject.Properties).Count -eq 0) { continue } $linkage = & $ResolveLinkage $tool.name $tool.packages $Fresh.IsPresent $entry = [ordered]@{ packages = $tool.packages; linkedVia = $linkage.LinkedVia } if ($linkage.Repo) { $entry.repo = $linkage.Repo } Add-DFIdentityEntry -Key $tool.name -Entry ([pscustomobject]$entry) -SourceLabel "Tools/$($file.Name)" } # Hand-authored additions (empty for v1; mechanism for future growth). if (Test-Path $IdentitiesDir) { foreach ($file in (Get-ChildItem $IdentitiesDir -Filter '*.jsonc' -ErrorAction Ignore)) { $fragment = Read-DFIdentityFragment -Path $file.FullName foreach ($prop in $fragment.PSObject.Properties) { Add-DFIdentityEntry -Key $prop.Name -Entry $prop.Value -SourceLabel $file.Name } } } $sortedTools = [ordered]@{} foreach ($key in ($tools.Keys | Sort-Object)) { $sortedTools[$key] = $tools[$key] } $doc = [pscustomobject]@{ schemaVersion = 1 updated = [datetime]::UtcNow.ToString('yyyy-MM-dd') # Cast to PSCustomObject so Test-DFToolIdentityGuideSchema's # PSObject.Properties enumeration walks tool keys, not the ordered # dictionary's own .NET members. tools = [pscustomobject]$sortedTools } $errs = $null if (-not (Test-DFToolIdentityGuideSchema -Database $doc -Errors ([ref]$errs))) { throw "Build-DFToolIdentities: generated document failed validation:`n$($errs -join "`n")" } $doc | ConvertTo-Json -Depth 8 | Set-Content -Path $OutPath -Encoding UTF8 Write-Host "Wrote $OutPath ($($sortedTools.Count) tools)" |