#requires -Modules powershell-yaml param( [string]$StartDir = (Split-Path -Parent $MyInvocation.MyCommand.Path) ) function Find-WorkspaceRoot($startDir) { $current = $startDir while ($true) { if (Test-Path -LiteralPath (Join-Path $current 'workspace.yml')) { return $current } $parent = Split-Path -Parent $current if ($parent -eq $current) { throw "workspace.yml not found" } $current = $parent } } function Parse-YamlFile($path) { try { return ConvertFrom-Yaml (Get-Content -LiteralPath $path -Raw) } catch { throw "Failed to parse YAML file $path : $_" } } function Strip-Quotes($value) { $trim = $value.Trim() if (($trim.StartsWith('"') -and $trim.EndsWith('"')) -or ($trim.StartsWith("'") -and $trim.EndsWith("'"))) { return $trim.Substring(1, $trim.Length - 2) } return $trim } function Parse-Workspace($workspacePath) { $lines = Get-Content -LiteralPath $workspacePath $collections = @() $inCollections = $false $current = $null foreach ($line in $lines) { $trim = $line.Trim() if (-not $inCollections -and $trim -eq 'collections:') { $inCollections = $true continue } if (-not $inCollections) { continue } if (-not ($line.StartsWith(' ') -or $line.StartsWith("`t"))) { break } if ($line -match '^\s*-\s+name:\s*(.+)$') { $name = Strip-Quotes $Matches[1] $current = [ordered]@{ name = $name } $collections += $current continue } if ($line -match '^\s*path:\s*(.+)$' -and $current) { $current.path = Strip-Quotes $Matches[1] } } return @{ collections = $collections } } function Walk-YamlFiles($rootDir) { Get-ChildItem -LiteralPath $rootDir -Recurse -File -Include *.yml, *.yaml | Where-Object { $_.Name -notmatch '^\.|node_modules' } | Select-Object -ExpandProperty FullName } function Ensure-Dir($path) { if (-not (Test-Path -LiteralPath $path)) { New-Item -ItemType Directory -Path $path | Out-Null } } function Clean-Folder($dir) { if (-not (Test-Path -LiteralPath $dir)) { return } foreach ($entry in Get-ChildItem -LiteralPath $dir) { if ($entry.PSIsContainer) { Clean-Folder $entry.FullName $children = Get-ChildItem -LiteralPath $entry.FullName if ($children.Count -eq 0) { Remove-Item -LiteralPath $entry.FullName -Force } } else { if (!$entry.PSIsContainer -and $entry.Name -match '\.js$|\.http$|\.env\.template$') { Remove-Item -LiteralPath $entry.FullName -Force } } } } function Parse-HttpBlock($text) { $parsed = ConvertFrom-Yaml $text $http = $parsed.http $headers = @() foreach ($h in ($http.headers | Where-Object { $_ })) { $headers += [ordered]@{ name = $h.name value = $h.value } } return @{ method = $http.method url = $http.url params = $http.params headers = $headers body = $http.body } } function Build-RequestContent($http, $name, $config, $dotenvVars) { $lines = @() $vars = @() $paramsVars = @() $commentVars = @() $seen = New-Object System.Collections.Generic.HashSet[string] function Add-Var($n, $v) { if (-not $n) { return } if ($seen.Contains($n)) { return } if ($dotenvVars.Contains($n)) { return } $seen.Add($n) | Out-Null $vars += @{ name = $n; value = $v } } function Add-ParamVar($n, $v) { if (-not $n) { return } if ($seen.Contains($n)) { return } if ($dotenvVars.Contains($n)) { return } $seen.Add($n) | Out-Null $paramsVars += @{ name = $n; value = $v } } function Add-CommentVar($n, $v) { if (-not $n) { return } $commentVars += @{ name = $n; value = $v } } # Variables from config foreach ($v in ($config.variables | Where-Object { $_ })) { Add-Var $v.name $v.value } # URL $url = $http.url if ($url) { $url = $url -replace ':(\w+)', '{{$1}}' $url = $url.Split('?')[0] } # Params $queryParams = @() $headers = @() foreach ($h in $http.headers) { $headers += $h } foreach ($p in $http.params) { $name = $p.name $value = $p.value $type = ($p.type).ToLower() $disabled = ($p.disabled -eq $true) if ($disabled) { Add-CommentVar $name $value continue } if ($type -eq 'header') { $headers += @{ name = $name; value = $value } } else { $queryParams += @{ name = $name; value = "{{$name}}" } Add-ParamVar $name $value } } # Auth if ($config.auth) { switch ($config.auth.type) { 'bearer' { $headers += @{ name = 'Authorization'; value = "Bearer $($config.auth.token)" } } 'basic' { $headers += @{ name = 'Authorization'; value = "Basic $($config.auth.username):$($config.auth.password)" } } } } # Commented vars if ($commentVars.Count -gt 0) { $lines += "# Other variables for $name" foreach ($v in $commentVars) { $lines += "# @$($v.name)=$($v.value)" } } # Parameter vars if ($paramsVars.Count -gt 0) { $lines += "# Parameter variables for $name" foreach ($v in $paramsVars) { $lines += "@$($v.name)=$($v.value)" } } # Vars if ($vars.Count -gt 0) { $lines += "# Variables for $name" foreach ($v in $vars) { $lines += "@$($v.name)=$($v.value)" } } # Build URL foreach ($qp in $queryParams) { $sep = ($url.Contains('?')) ? '&' : '?' $url = "$url$sep$($qp.name)=$($qp.value)" } $lines += "" $lines += "$($http.method.ToUpper()) $url" foreach ($h in $headers) { $lines += "$($h.name): $($h.value)" } if ($http.body) { if ($http.body.type -eq 'json') { $lines += "" try { $lines += ($http.body.data | ConvertFrom-Json -Depth 10 | ConvertTo-Json -Depth 10) } catch { <#Do this if a terminating exception happens#> Write-Warning "Failed to parse JSON for request $name, will use original content" $lines += $http.body.data } } } return ($lines -join "`n") } function Main { $workspaceRoot = Find-WorkspaceRoot $StartDir $workspace = Parse-Workspace (Join-Path $workspaceRoot 'workspace.yml') $collections = $workspace.collections $outputBase = Join-Path $workspaceRoot 'autogen/httpyac_ps1' Clean-Folder $outputBase foreach ($col in $collections) { $sourceDir = Join-Path $workspaceRoot $col.path if (-not (Test-Path -LiteralPath $sourceDir)) { Write-Warning "Skipping missing collection path: $($col.path)" continue } $outputRoot = Join-Path $outputBase $col.name Ensure-Dir $outputRoot # Copy JS $jsCount = 0 Get-ChildItem $sourceDir -Recurse -File -Include *.js | ForEach-Object { $rel = $_.FullName.Substring($sourceDir.Length).TrimStart('\') $dest = Join-Path $outputRoot $rel Ensure-Dir (Split-Path -Parent $dest) Copy-Item $_.FullName $dest -Force $jsCount++ } # Env templates $envDir = Join-Path $sourceDir 'environments' $dotenvVars = New-Object System.Collections.Generic.HashSet[string] if (Test-Path -LiteralPath $envDir) { $vars = @() foreach ($f in Get-ChildItem $envDir -File -Include *.yml, *.yaml) { $parsed = Parse-YamlFile $f.FullName foreach ($v in ($parsed.variables | Where-Object { $_ })) { if (-not $dotenvVars.Contains($v.name)) { $dotenvVars.Add($v.name) | Out-Null $vars += $v.name } } } $template = ($vars | ForEach-Object { "$_=`"EDIT_VALUE_HERE`"" }) -join "`n" Set-Content -LiteralPath (Join-Path $outputRoot '.env.template') -Value $template } # YAML → .http $yamlFiles = Walk-YamlFiles $sourceDir $count = 0 foreach ($file in $yamlFiles) { $content = Get-Content -LiteralPath $file -Raw $http = Parse-HttpBlock $content if (-not $http.url) { continue } $rel = $file.Substring($sourceDir.Length).TrimStart('\') $parsed = Split-Path $rel -LeafBase $dir = Split-Path $rel -Parent $targetDir = Join-Path $outputRoot $dir Ensure-Dir $targetDir $requestName = $parsed $config = @{ variables = @(); auth = $null } $dotenv = $dotenvVars $outFile = Join-Path $targetDir "$parsed.http" $reqContent = Build-RequestContent $http $requestName $config $dotenv Set-Content -LiteralPath $outFile -Value $reqContent $count++ } Write-Host ("{0,-33} => generated {1,3} .http file(s) and {2,3} .js file(s)" -f $col.name, $count, $jsCount) } } Main