diff --git a/scripts/generate-http-docs.js b/scripts/generate-http-docs.js index d1ece61..19e8907 100644 --- a/scripts/generate-http-docs.js +++ b/scripts/generate-http-docs.js @@ -199,6 +199,47 @@ function formatVariableValue(value, renderContext = {}) { return JSON.stringify(value); } +export function mergeRequestConfig(base, updates) { + if (!updates || typeof updates !== 'object') { + return base; + } + + const merged = { ...(base || {}) }; + if (Object.prototype.hasOwnProperty.call(updates, 'auth')) { + if (updates.auth === 'inherit') { + if (merged.auth && typeof merged.auth === 'object') { + merged.auth = merged.auth; + } else { + delete merged.auth; + } + } else if (typeof updates.auth === 'object' && updates.auth !== null) { + merged.auth = updates.auth; + } else { + delete merged.auth; + } + } else { + delete merged.auth; + } + + if (Array.isArray(updates.variables)) { + const variables = [...(Array.isArray(base.variables) ? base.variables : [])]; + const byName = new Map(); + for (const variable of variables) { + if (variable && variable.name) { + byName.set(String(variable.name), variable); + } + } + for (const variable of updates.variables) { + if (variable && variable.name) { + byName.set(String(variable.name), variable); + } + } + merged.variables = [...byName.values()]; + } + + return merged; +} + export function getRequestConfigForFile(yamlFile, sourceDir) { const resolved = []; const seenFiles = new Set(); @@ -267,46 +308,6 @@ export function getRequestConfigForFile(yamlFile, sourceDir) { return resolved.reduce((result, config) => mergeRequestConfig(result, config), {}); } -export function mergeRequestConfig(base, updates) { - if (!updates || typeof updates !== 'object') { - return base; - } - - const merged = { ...(base || {}) }; - if (Object.prototype.hasOwnProperty.call(updates, 'auth')) { - if (updates.auth === 'inherit') { - if (merged.auth && typeof merged.auth === 'object') { - merged.auth = merged.auth; - } else { - delete merged.auth; - } - } else if (typeof updates.auth === 'object' && updates.auth !== null) { - merged.auth = updates.auth; - } else { - delete merged.auth; - } - } else { - delete merged.auth; - } - - if (Array.isArray(updates.variables)) { - const variables = [...(Array.isArray(base.variables) ? base.variables : [])]; - const byName = new Map(); - for (const variable of variables) { - if (variable && variable.name) { - byName.set(String(variable.name), variable); - } - } - for (const variable of updates.variables) { - if (variable && variable.name) { - byName.set(String(variable.name), variable); - } - } - merged.variables = [...byName.values()]; - } - - return merged; -} export function buildRequestContent(request, requestName, requestConfig = {}, dotenvVariables = new Set()) { const lines = []; diff --git a/scripts/generate-http-docs.ps1 b/scripts/generate-http-docs.ps1 index c831801..8ea907d 100644 --- a/scripts/generate-http-docs.ps1 +++ b/scripts/generate-http-docs.ps1 @@ -1,5 +1,3 @@ -#requires -Modules powershell-yaml - param( [string]$StartDir = (Split-Path -Parent $MyInvocation.MyCommand.Path) ) @@ -18,15 +16,19 @@ function Find-WorkspaceRoot($startDir) { } } -function Parse-YamlFile($path) { +function Parse-Yaml($text) { try { - $parsedYaml = ConvertFrom-Yaml (Get-Content -LiteralPath $path -Raw) + $parsedYaml = ConvertFrom-Yaml $text return $parsedYaml ?? @{} } catch { - throw "Failed to parse YAML file $path : $_" + throw "Failed to parse YAML content : $_" } } +function Parse-YamlFile($path) { + return Parse-Yaml (Get-Content -LiteralPath $path -Raw) +} + function Strip-Quotes($value) { $trim = $value.Trim() if (($trim.StartsWith('"') -and $trim.EndsWith('"')) -or @@ -71,37 +73,576 @@ function Parse-Workspace($workspacePath) { return @{ collections = $collections } } -function Sanitize-VariableName([string]$name) { - $sanitized = $name.Trim() - -replace '[{}]','' - -replace '[^A-Za-z0-9_]','_' - -replace '^([0-9])','_$1' +function Sanitize-VarName($name) { + $sanitized = ([string]$name).Trim() -replace '[{}]', '' -replace '[^A-Za-z0-9_]', '_' -replace '^([0-9])', '_$1' - return $sanitized ?? 'value' + if ([string]::IsNullOrWhiteSpace($sanitized)) { + return 'value' + } + return $sanitized } -function Parse-PlaceholderContent([string]$content) { - $trimmed = $content.Trim(); - $dotenvMatch = $trimmed -imatch '^\$dotenv\s+(?.+)$' - if ($dotenvMatch) { - return @{ name = $Matches.matched.Trim(); isDotEnv = true } +function Parse-PlaceholderContent($content) { + $trimmed = ([string]$content).Trim(); + $dotenvMatch = [regex]::Match($trimmed, '^\$dotenv\s+(.+)$', 'IgnoreCase') + if ($dotenvMatch.Success) { + return @{ + name = $dotenvMatch.Groups[1].value.Trim() + isDotenv = $true + } + } + return @{ + name = $trimmed + isDotenv = $false } - return @{ name = $trimmed; isDotEnv = false } } function Collect-Placeholders($value) { - # if (typeof value !== 'string') { - # return []; - # } + # Se non è stringa → restituisci array vuoto + if ($value -isnot [string]) { + return @() + } $placeholders = @() - $placeholders = Select-String "\{\{([^{}]+)\}\}" -InputObject $value -AllMatches | ForEach-Object matches | ForEach-Object (Parse-PlaceholderContent Value) + $regex = [regex]'\{\{([^{}]+)\}\}' + $foundMatches = $regex.Matches($value) + foreach ($m in $foundMatches) { + $inner = $m.Groups[1].value + $parsed = Parse-PlaceholderContent $inner + $placeholders += $parsed.name + } return $placeholders } -function Walk-YamlFiles($rootDir) { - Get-ChildItem -LiteralPath $rootDir -Recurse -File -Include *.yml, *.yaml | - Where-Object { $_.Name -notmatch '^\.|node_modules' } | - Select-Object -ExpandProperty FullName +function Find-Block ([string[]]$lines, $keyName) { + for ($i = 0; $i -lt $lines.Count; $i++) { + $trimmed = $lines[$i].Trim() + if ($trimmed -ne $keyName -and -not $trimmed.StartsWith("$($keyName):")) { + continue + } + + $lineIndent = ([regex]::Match($lines[$i], '^\s*')).value.Length + $block = @() + for ($j = $i + 1; $j -lt $lines.Count; $j++) { + $currentLine = $lines[$j] + $currentTrimmed = $currentLine.Trim() + if ([string]::IsNullOrWhiteSpace($currentTrimmed)) { + $block += $currentLine + continue + } + $currentIndent = ([regex]::Match($currentLine, '^\s*')).value.Length + # Caso 1: indentazione <= indentazione della chiave e NON inizia con spazio → fine blocco + if ($currentIndent -le $lineIndent -and -not $currentLine.StartsWith(' ')) { + break + } + # Caso 2: indentazione <= indentazione della chiave e riga commento → includi + if ($currentIndent -le $lineIndent -and $currentTrimmed.StartsWith('#')) { + $block += $currentLine + continue + } + # Caso 3: indentazione <= indentazione della chiave → fine blocco + if ($currentIndent -le $lineIndent) { + break + } + # Altrimenti la riga fa parte del blocco + $block += $currentLine + } + return $block + } + return @() +} + +function Parse-RequestInfo ($text) { + $lines = $text -split '\r?\n' + $infoLines = Find-Block -Lines $lines -KeyName 'info' + $joined = ($infoLines -join "`n") + $nameMatch = [regex]::Match($joined, '^\s*name:\s*(.+)$', 'Multiline') + if ($nameMatch.Success) { + return Strip-Quotes $nameMatch.Groups[1].value + } + return '' +} + +function Parse-HttpBlock ($text) { + # ConvertFrom-Yaml restituisce $null se il testo è vuoto o non valido + $parsed = Parse-Yaml $text + if (-not $parsed) { $parsed = @{} } + + $http = $parsed.http + if (-not $http) { $http = @{} } + + $headers = @() + $headerList = @() + if ($http.headers -is [System.Collections.IEnumerable]) { + $headerList = $http.headers + } + foreach ($header in $headerList) { + if (-not $header -or $header -isnot [psobject] -and $header -isnot [hashtable]) { + continue + } + $name = ([string]($header.name ?? '')).Trim() + $value = ([string]($header.value ?? '')).Trim() + $headers += @{ + name = $name + value = $value + } + } + + $params = @() + if ($http.params -is [System.Collections.IEnumerable]) { + $params = $http.params + } + + $body = $null + if ($http.body -and ($http.body -is [psobject] -or $http.body -is [hashtable])) { + $body = $http.body + } + + return @{ + method = $http.method ?? 'GET' + url = $http.url ?? '' + params = $params + headers = $headers + body = $body + } +} + +function Format-VariableValue ($value, [hashtable]$RenderContext = @{}) { + if ($null -eq $value) { + return "''" + } + + if ($value -is [string]) { + if ($value.Trim() -eq '') { + return "''" + } + + $renderedValue = $value + if ($RenderContext.ContainsKey('renderValue') -and $RenderContext.renderValue) { + $renderedValue = $RenderContext.renderValue.Invoke($value) + } + if ($renderedValue -match '\s') { + $escaped = $renderedValue -replace '"', '\"' + return '"' + $escaped + '"' + } + return $renderedValue + } + + return ($value | ConvertTo-Json -Depth 20 -Compress) +} + +function Merge-RequestConfig ($base, $updates) { + if (-not $updates -or ($updates -isnot [psobject] -and $updates -isnot [hashtable])) { + return $base + } + + # Clona base (shallow clone) + $merged = @{} + if ($base -is [psobject] -or $base -is [hashtable]) { + foreach ($key in $base.Keys) { + $merged[$key] = $base[$key] + } + } + + if ($updates.ContainsKey('auth')) { + $auth = $updates.auth + if ($auth -eq 'inherit') { + if ($merged.ContainsKey('auth') -and ($merged.auth -is [psobject] -or $merged.auth -is [hashtable])) { + $merged.auth = $merged.auth + } + else { + $merged.Remove('auth') + } + } + elseif ($auth -is [psobject] -or $auth -is [hashtable]) { + $merged.auth = $auth + } + else { + $merged.Remove('auth') + } + } + else { + $merged.Remove('auth') + } + + if ($updates.variables -is [System.Collections.IEnumerable]) { + $variables = @() + if ($base.variables -is [System.Collections.IEnumerable]) { + $variables = @($base.variables) + } + # Mappa per nome + $byName = @{} + foreach ($variable in $variables) { + if ($variable -and $variable.name) { + $byName[[string]$variable.name] = $variable + } + } + foreach ($variable in $updates.variables) { + if ($variable -and $variable.name) { + $byName[[string]$variable.name] = $variable + } + } + $merged.variables = $byName.Values + } + + return $merged +} + +function Get-RequestConfigForFile ($yamlFile, $sourceDir) { + $resolved = @() + $seenFiles = New-Object System.Collections.Generic.HashSet[string] + + function Add-File ($FilePath) { + if (-not $FilePath -or $seenFiles.Contains($FilePath)) { + return + } + $seenFiles.Add($FilePath) + if (-not (Test-Path $FilePath)) { + return + } + + try { + $parsed = (Get-Content -Raw $FilePath | ConvertFrom-Yaml) + if (-not $parsed) { $parsed = @{} } + $requestConfig = $null + + # Caso 1: parsed.request esiste ed è un oggetto + if ($parsed.request -and ($parsed.request -is [psobject] -or $parsed.request -is [hashtable])) { + $requestConfig = $parsed.request + } + # Caso 2: parsed è un oggetto e contiene auth/variables + elseif ($parsed -is [psobject] -or $parsed -is [hashtable]) { + $config = @{} + # auth + if ($parsed.ContainsKey('auth')) { + $config.auth = $parsed.auth + } + elseif ($parsed.http -and ($parsed.http -is [psobject] -or $parsed.http -is [hashtable]) -and $parsed.http.ContainsKey('auth')) { + $config.auth = $parsed.http.auth + } + # variables + if ($parsed.variables -is [System.Collections.IEnumerable]) { + $config.variables = $parsed.variables + } + if ($config.Count -gt 0) { + $requestConfig = $config + } + } + + if ($null -ne $requestConfig) { + $resolved += $requestConfig + } + elseif ((Resolve-Path $FilePath).Path -eq (Resolve-Path $yamlFile).Path) { + $resolved += @{} + } + } + catch { + # Ignora file YAML non validi + } + } + + # Costruisci la catena delle directory + $dirChain = @() + $currentDir = Split-Path -Parent $yamlFile + while ($true) { + $dirChain = ,$currentDir + $dirChain + if ($currentDir -eq $sourceDir) { + break + } + $parentDir = Split-Path -Parent $currentDir + if ($parentDir -eq $currentDir) { + break + } + $currentDir = $parentDir + } + + foreach ($dir in $dirChain) { + Add-File (Join-Path $dir 'opencollection.yml') + Add-File (Join-Path $dir 'folder.yml') + } + + # Aggiungi il file principale + Add-File $yamlFile + + $result = @{} + foreach ($config in $resolved) { + $result = Merge-RequestConfig $result $config + } + + return $result +} + +function Build-RequestContent ( + $request, + $requestName, + $requestConfig = @{}, + [System.Collections.Generic.HashSet[string]]$dotenvVariables = $(New-Object System.Collections.Generic.HashSet[string]) + ) { + + $lines = @() + $variableDefinitions = @() + $commentedVariableDefinitions = @() + $parameterVariableDefinitions = @() + $seenVariables = New-Object System.Collections.Generic.HashSet[string] + + function Add-Variable ($Name, $Value) { + if (-not $Name) { return } + $normalized = ([string]$Name).Trim() + if (-not $normalized) { return } + if ($seenVariables.Contains($normalized)) { return } + if ($dotenvVariables.Contains($normalized)) { return } + $seenVariables.Add($normalized) + $variableDefinitions += @{ name=$normalized; value=$Value } + } + + function Add-ParameterVariable ($Name, $Value) { + if (-not $Name) { return } + $normalized = ([string]$Name).Trim() + if (-not $normalized) { return } + if ($seenVariables.Contains($normalized)) { return } + if ($dotenvVariables.Contains($normalized)) { return } + $seenVariables.Add($normalized) + $parameterVariableDefinitions += @{ name=$normalized; value=$Value } + } + + function Add-CommentedVariable ($Name, $Value) { + if (-not $Name) { return } + $normalized = ([string]$Name).Trim() + if (-not $normalized) { return } + $commentedVariableDefinitions += @{ name=$normalized; value=$Value } + } + + function Add-ReferencedVariables ($Value, $FallbackValue = 'YOUR_VALUE_HERE') { + foreach ($placeholder in Collect-Placeholders ([string]$Value)) { + Add-Variable $placeholder $FallbackValue + } + } + + function RenderJsonValue ($Value) { + if ($Value -is [string]) { + return Render-Value $Value + } + elseif ($Value -is [System.Collections.IEnumerable]) { + return @($Value | ForEach-Object { RenderJsonValue $_ }) + } + elseif ($Value -is [psobject] -or $Value -is [hashtable]) { + $result = @{} + foreach ($key in $Value.Keys) { + $result[$key] = RenderJsonValue $Value[$key] + } + return $result + } + return $Value + } + + function Add-ParameterVariables ($Name, $Value) { Add-ParameterVariable $Name $Value } + + function Add-CommentedVariables ($Name, $Value) { Add-CommentedVariable $Name $Value } + + function Render-Value ($Value) { + if ($Value -isnot [string]) { return $Value } + + return ($Value -replace '\{\{([^{}]+)\}\}', { + param($match,$inner) + $placeholder = Parse-PlaceholderContent $inner + if ($placeholder.isDotenv) { return $match } + if ($placeholder.name -and $dotenvVariables.Contains($placeholder.name)) { + return "{{$dotenv $($placeholder.name)}}" + } + return $match + }) + } + + # ------------------------- + # Variabili da requestConfig + # ------------------------- + $configVariables = @() + if ($requestConfig.variables -is [System.Collections.IEnumerable]) { + $configVariables = $requestConfig.variables + } + foreach ($variable in $configVariables) { + if ($variable -and $variable.name) { + Add-Variable $variable.name $variable.value + } + } + + # ------------------------- + # URL + # ------------------------- + $url = $request.url ?? '' + if ($url) { + Add-ReferencedVariables $url + $url = ($url -replace ':([A-Za-z0-9_]+)', '{{$1}}') + $url = Render-Value $url + $url = $url.Split('?')[0] + } + + # ------------------------- + # Headers + # ------------------------- + $queryParams = @() + $headers = @() + + function Add-Header ($Name, $Value) { + if (-not $Name) { return } + Add-ReferencedVariables ($Value ?? '') + $headers += @{ + name = ([string]$Name).Trim() + value = Render-Value ($Value ?? '') + } + } + + foreach ($header in ($request.headers ?? @())) { + if ($header -and $header.name) { + Add-Header $header.name ($header.value ?? '') + } + } + + foreach ($header in ($requestConfig.headers ?? @())) { + if ($header -and $header.name) { + Add-Header $header.name ($header.value ?? '') + } + } + + # ------------------------- + # Params + # ------------------------- + foreach ($param in ($request.params ?? @())) { + $name = $param.name ?? '' + $value = $param.value ?? '' + $type = ([string]($param.type ?? 'query')).ToLower() + $disabled = ([string]$param.disabled).ToLower() -eq 'true' + + if ($disabled) { + Add-CommentedVariables $name $value + continue + } + + Add-ReferencedVariables $value + + if ($type -eq 'header') { + $headers += @{ name=$name; value=(Render-Value $value) } + } + else { + $queryParams += @{ name=$name; value="{{$name}}" } + Add-ParameterVariables $name $value + } + } + + # ------------------------- + # Auth + # ------------------------- + if ($requestConfig.auth) { + switch ($requestConfig.auth.type) { + 'bearer' { + Add-ReferencedVariables ($requestConfig.auth.token ?? '') + Add-Header 'Authorization' ("Bearer " + (Render-Value ($requestConfig.auth.token ?? ''))) + } + 'basic' { + $username = $requestConfig.auth.username ?? '' + $password = $requestConfig.auth.password ?? '' + Add-ReferencedVariables $username + Add-ReferencedVariables $password + Add-Header 'Authorization' ("Basic " + (Render-Value $username) + ":" + (Render-Value $password)) + } + default { + $headers += @{ + name = "UNKNOWN_$($requestConfig.auth.type)" + value = "Basic $($requestConfig.auth.token)" + } + } + } + } + + # ------------------------- + # Commented variables + # ------------------------- + if ($commentedVariableDefinitions.Count -gt 0) { + $lines += "# Other variables for $requestName" + foreach ($variable in $commentedVariableDefinitions) { + $lines += "# @$(Sanitize-VarName $variable.name)$VARIABLE_NAME_VALUE_SEPARATOR$(Format-VariableValue $variable.value @{ renderValue = { param($v) Render-Value $v } })" + } + } + + # ------------------------- + # Parameter variables + # ------------------------- + if ($parameterVariableDefinitions.Count -gt 0) { + $lines += "# Parameter variables for $requestName" + foreach ($variable in $parameterVariableDefinitions) { + $lines += "@$(Sanitize-VarName $variable.name)$VARIABLE_NAME_VALUE_SEPARATOR$(Format-VariableValue $variable.value @{ renderValue = { param($v) Render-Value $v } })" + } + } + + # ------------------------- + # Body + # ------------------------- + $requestBody = '' + + if ($request.body -and ($request.body -is [psobject] -or $request.body -is [hashtable])) { + $bodyType = ([string]($request.body.type ?? '')).ToLower() + + if ($bodyType -eq 'json') { + $jsonData = ConvertFrom-Json -Depth 20 -InputObject ($request.body.data) + + if ($jsonData -is [string]) { + $requestBody = Render-Value $jsonData + } + elseif ($jsonData -is [System.Collections.IEnumerable]) { + $requestBody = (ConvertTo-Json (RenderJsonValue $jsonData) -Depth 20) + } + elseif ($jsonData -is [psobject] -or $jsonData -is [hashtable]) { + $requestBody = (ConvertTo-Json (RenderJsonValue $jsonData) -Depth 20) + } + + Add-Header 'Content-Type' 'application/json' + } + elseif ($bodyType -eq 'form-urlencoded') { + $parts = @() + foreach ($entry in ($request.body.data ?? @())) { + if (-not $entry -or -not $entry.name) { continue } + Add-ReferencedVariables ($entry.value ?? '') + $parts += "$($entry.name)=$(Render-Value ($entry.value ?? ''))" + } + $requestBody = ($parts -join '&') + Add-Header 'Content-Type' 'application/x-www-form-urlencoded' + } + } + + # ------------------------- + # Variables + # ------------------------- + if ($variableDefinitions.Count -gt 0) { + $lines += "# Variables for $requestName" + foreach ($variable in $variableDefinitions) { + $lines += "@$(Sanitize-VarName $variable.name)$VARIABLE_NAME_VALUE_SEPARATOR$(Format-VariableValue $variable.value @{ renderValue = { param($v) Render-Value $v } })" + } + } + + # ------------------------- + # Final request line + # ------------------------- + $method = ([string]($request.method ?? 'GET')).ToUpper() + $requestUrl = $url + + foreach ($param in $queryParams) { + if (-not $param.name) { continue } + $separator = ($requestUrl.Contains('?') ? '&' : '?') + $requestUrl = "$requestUrl$separator$($param.name)=$($param.value)" + } + + $lines += '' + $lines += "$method $requestUrl" + + foreach ($header in $headers) { + $lines += "$($header.name): $($header.value)" + } + + if ($requestBody) { + $lines += '' + $lines += $requestBody + } + + return ($lines -join "`n") } function Ensure-Dir($path) { @@ -110,6 +651,12 @@ function Ensure-Dir($path) { } } +function Walk-YamlFiles($rootDir) { + Get-ChildItem -LiteralPath $rootDir -Recurse -File -Include *.yml, *.yaml | + Where-Object { $_.Name -notmatch '^\.|node_modules' } | + Select-Object -ExpandProperty FullName +} + function Clean-Folder($dir) { if (-not (Test-Path -LiteralPath $dir)) { return } @@ -128,239 +675,253 @@ function Clean-Folder($dir) { } } -function Parse-HttpBlock($text) { - $parsed = ConvertFrom-Yaml $text - $http = $parsed.http +function Get-DotenvVariablesForTargetDir ($TargetDir, $OutputRoot, $DotenvVariablesByTarget) { + $variables = New-Object System.Collections.Generic.HashSet[string] + $currentDir = $TargetDir - $headers = @() - foreach ($h in ($http.headers | Where-Object { $_ })) { - $headers += [ordered]@{ - name = $h.name - value = $h.value + while ($true) { + if ($DotenvVariablesByTarget.ContainsKey($currentDir)) { + foreach ($variable in $DotenvVariablesByTarget[$currentDir]) { + $variables.Add($variable) | Out-Null + } + } + + $parentDir = Split-Path -Parent $currentDir + if ($currentDir -eq $OutputRoot -or $parentDir -eq $currentDir) { + break + } + + $currentDir = $parentDir + } + + return $variables +} + +function Write-EnvironmentTemplates ($sourceDir, $outputRoot) { + $targets = @() + $dotenvVariablesByTarget = @{} # Hashtable: targetDir → HashSet + + function Visit ([string]$CurrentDir) { + $entries = Get-ChildItem -LiteralPath $CurrentDir -Force + $hasEnvironmentsDir = $entries | Where-Object { + $_.PSIsContainer -and $_.Name -eq 'environments' + } + + if ($hasEnvironmentsDir) { + $targets += $CurrentDir + } + + foreach ($entry in $entries) { + if (-not $entry.PSIsContainer) { continue } + if ($entry.Name.StartsWith('.')) { continue } + if ($entry.Name -eq 'node_modules') { continue } + + Visit (Join-Path $CurrentDir $entry.Name) } } - return @{ - method = $http.method - url = $http.url - params = $http.params - headers = $headers - body = $http.body - } -} + Visit $sourceDir -function Build-RequestContent($http, $name, $config, $dotenvVars) { - $lines = @() - $vars = @() - $paramsVars = @() - $commentVars = @() - $seen = New-Object System.Collections.Generic.HashSet[string] + foreach ($dir in $targets) { + $relativeDir = [System.IO.Path]::GetRelativePath($sourceDir, $dir) + if ($relativeDir -and $relativeDir -ne '.') { + $targetDir = Join-Path $outputRoot $relativeDir + } + else { + $targetDir = $outputRoot + } - 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 } - } + Ensure-Dir $targetDir - 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 + $environmentsDir = Join-Path $dir 'environments' + if (-not (Test-Path $environmentsDir)) { continue } - if ($type -eq 'header') { - $headers += @{ name = $name; value = $value } - } else { - $queryParams += @{ name = $name; value = "{{$name}}" } - Add-ParamVar $name $value - } - } + $envFiles = + Get-ChildItem -LiteralPath $environmentsDir -Force | + Where-Object { -not $_.PSIsContainer -and $_.Name -match '\.ya?ml$' } | + ForEach-Object { $_.FullName } - # Auth - if ($config.auth) { - switch ($config.auth.type) { - 'bearer' { - $headers += @{ name = 'Authorization'; value = "Bearer $($config.auth.token)" } + $variableNames = @() + $seenNames = New-Object System.Collections.Generic.HashSet[string] + + foreach ($envFile in $envFiles) { + $parsed = Parse-Yaml $envFile + if (-not $parsed) { continue } + + $variables = @() + if ($parsed.variables -is [System.Collections.IEnumerable]) { + $variables = $parsed.variables } - 'basic' { - $headers += @{ name = 'Authorization'; value = "Basic $($config.auth.username):$($config.auth.password)" } + + foreach ($variable in $variables) { + if (-not $variable -or -not $variable.name) { continue } + + $name = ([string]$variable.name).Trim() + if (-not $name) { continue } + if ($seenNames.Contains($name)) { continue } + + $seenNames.Add($name) + $variableNames += $name } } - } - # Commented vars - if ($commentVars.Count -gt 0) { - $lines += "# Other variables for $name" - foreach ($v in $commentVars) { - $lines += "# @$($v.name)=$($v.value)" + if ($variableNames.Count -gt 0) { + $templateContent = ($variableNames | ForEach-Object { "$_=$DEFAULT_ENV_VAR_VALUE" }) -join "`n" + $templateContent += "`n" } - } - - # Parameter vars - if ($paramsVars.Count -gt 0) { - $lines += "# Parameter variables for $name" - foreach ($v in $paramsVars) { - $lines += "@$($v.name)=$($v.value)" + else { + $templateContent = "" } + + $templatePath = Join-Path $targetDir '.env.template' + Set-Content -Path $templatePath -Value $templateContent -Encoding UTF8 + + $dotenvVariablesByTarget[$targetDir] = $seenNames } - # 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") + return $dotenvVariablesByTarget } -function Main { - $workspaceRoot = Find-WorkspaceRoot $StartDir - $workspace = Parse-Workspace (Join-Path $workspaceRoot 'workspace.yml') - $collections = $workspace.collections +function Write-JsFiles ($sourceDir, $outputRoot) { + $targets = @() - $outputBase = Join-Path $workspaceRoot 'autogen/httpyac_ps1' - Clean-Folder $outputBase + function Visit ($CurrentDir) { + $entries = Get-ChildItem -LiteralPath $CurrentDir -Force - foreach ($col in $collections) { - $sourceDir = Join-Path $workspaceRoot $col.path - if (-not (Test-Path -LiteralPath $sourceDir)) { - Write-Warning "Skipping missing collection path: $($col.path)" + $hasJsFiles = $entries | Where-Object { + -not $_.PSIsContainer -and $_.Name -match '\.js$' + } + + if ($hasJsFiles) { + $targets += $CurrentDir + } + + # Visita ricorsivamente le sottodirectory + foreach ($entry in $entries) { + if (-not $entry.PSIsContainer) { continue } + if ($entry.Name.StartsWith('.')) { continue } + if ($entry.Name -eq 'node_modules') { continue } + + Visit (Join-Path $CurrentDir $entry.Name) + } + } + + Visit $sourceDir + + foreach ($dir in $targets) { + $relativeDir = [System.IO.Path]::GetRelativePath($sourceDir, $dir) + if ($relativeDir -and $relativeDir -ne '.') { + $targetDir = Join-Path $outputRoot $relativeDir + } + else { + $targetDir = $outputRoot + } + + Ensure-Dir $targetDir + + if (-not (Test-Path $dir)) { continue } - $outputRoot = Join-Path $outputBase $col.name - Ensure-Dir $outputRoot - - # Copy JS - $jsCount = 0 - Get-ChildItem $sourceDir -Recurse -File -Include *.js | + $jsFiles = + Get-ChildItem -LiteralPath $dir -Force | + Where-Object { -not $_.PSIsContainer -and $_.Name -match '\.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 - } + @{ + Source = $_.FullName + Target = Join-Path $targetDir $_.Name } } - $template = ($vars | ForEach-Object { "$_=`"EDIT_VALUE_HERE`"" }) -join "`n" - Set-Content -LiteralPath (Join-Path $outputRoot '.env.template') -Value $template + foreach ($jsFile in $jsFiles) { + Copy-Item -LiteralPath $jsFile.Source -Destination $jsFile.Target -Force + } + } + + return $targets +} + +function Invoke-Main { + # Workspace root + $workspaceRoot = Find-WorkspaceRoot $PSScriptRoot + $workspaceFile = Join-Path $workspaceRoot 'workspace.yml' + $workspace = Parse-Workspace $workspaceFile + $collections = @() + if ($workspace.collections -is [System.Collections.IEnumerable]) { + $collections = $workspace.collections + } + + if ($collections.Count -eq 0) { + throw "No collections found in workspace.yml" + } + + $outputBaseRoot = Join-Path $workspaceRoot 'autogen/httpyac' + Clean-Folder $outputBaseRoot + + foreach ($collection in $collections) { + if (-not $collection -or -not $collection.name -or -not $collection.path) { + continue } - # YAML → .http + $sourceDir = Join-Path $workspaceRoot $collection.path + if (-not (Test-Path $sourceDir)) { + Write-Warning "Skipping missing collection path: $($collection.path)" + continue + } + + $outputRoot = Join-Path $outputBaseRoot $collection.name + Ensure-Dir $outputRoot + + # JS files + $writtenJsFiles = Write-JsFiles $sourceDir $outputRoot + + # dotenv templates + $dotenvVariablesByTarget = Write-EnvironmentTemplates $sourceDir $outputRoot + + # YAML files $yamlFiles = Walk-YamlFiles $sourceDir - $count = 0 + $processed = 0 - foreach ($file in $yamlFiles) { - $content = Get-Content -LiteralPath $file -Raw - $http = Parse-HttpBlock $content - if (-not $http.url) { continue } + foreach ($yamlFile in $yamlFiles) { - $rel = $file.Substring($sourceDir.Length).TrimStart('\') - $parsed = Split-Path $rel -LeafBase - $dir = Split-Path $rel -Parent + $content = Get-Content -LiteralPath $yamlFile -Raw + $requestName = Parse-RequestInfo $content + if (-not $requestName) { + $requestName = [System.IO.Path]::GetFileNameWithoutExtension($yamlFile) + } - $targetDir = Join-Path $outputRoot $dir + $httpBlock = Parse-HttpBlock $content + if (-not $httpBlock -or -not $httpBlock.url) { + continue + } + + $relativePath = [System.IO.Path]::GetRelativePath($sourceDir, $yamlFile) + $parsedPath = [System.IO.Path]::GetFileNameWithoutExtension($relativePath) + $parsedDir = Split-Path $relativePath -Parent + + $targetDir = Join-Path $outputRoot $parsedDir Ensure-Dir $targetDir - $requestName = $parsed - $config = @{ variables = @(); auth = $null } + $requestConfig = Get-RequestConfigForFile $yamlFile $sourceDir + $outputFile = Join-Path $targetDir ("$parsedPath.http") - $dotenv = $dotenvVars - $outFile = Join-Path $targetDir "$parsed.http" + $dotenvVariables = Get-DotenvVariablesForTargetDir $targetDir $outputRoot $dotenvVariablesByTarget - $reqContent = Build-RequestContent $http $requestName $config $dotenv - Set-Content -LiteralPath $outFile -Value $reqContent + $requestContent = Build-RequestContent $httpBlock $requestName $requestConfig $dotenvVariables - $count++ + Set-Content -Path $outputFile -Value ($requestContent + "`n") -Encoding UTF8 + + $processed++ } - Write-Host ("{0,-33} => generated {1,3} .http file(s) and {2,3} .js file(s)" -f $col.name, $count, $jsCount) + $namePadded = $collection.name.PadRight(33) + $httpCount = $processed.ToString().PadLeft(3) + $jsCount = $writtenJsFiles.Count.ToString().PadLeft(3) + + Write-Host "$namePadded => generated $httpCount .http file(s) and $jsCount .js file(s)" } } -Main +Invoke-Main