594 lines
18 KiB
PowerShell
594 lines
18 KiB
PowerShell
# -*- coding: utf-8 -*-
|
||||
|
|
<#
|
|||
|
|
说明:
|
|||
|
|
- 本脚本用于在本仓库内整理与汇总各处的 env 文件。
|
|||
|
|
- 默认不会在控制台打印任何 env 的内容,避免泄露敏感信息。
|
|||
|
|
|
|||
|
|
用法示例:
|
|||
|
|
pwsh -NoProfile -ExecutionPolicy Bypass -File scripts/env_tools.ps1 -CleanupSupabase -Merge -OutFile .env.all
|
|||
|
|
#>
|
|||
|
|
|
|||
|
|
[CmdletBinding()]
|
|||
|
|
param(
|
|||
|
|
[switch]$CleanupSupabase,
|
|||
|
|
# 兼容旧参数:-Merge 等价于 -MergeRaw
|
|||
|
|
[switch]$Merge,
|
|||
|
|
[switch]$MergeRaw,
|
|||
|
|
[switch]$MergeUnique,
|
|||
|
|
[switch]$Report,
|
|||
|
|
[switch]$ListApplied,
|
|||
|
|
# 合并优先级:默认生产优先;如需本地开发优先,传 -DevFirst
|
|||
|
|
[switch]$DevFirst,
|
|||
|
|
[switch]$AppliedOnly,
|
|||
|
|
[switch]$IncludeLegacy,
|
|||
|
|
[string]$RawOutFile = ".env.all.raw",
|
|||
|
|
[string]$OutFile = ".env.all",
|
|||
|
|
[string]$ConflictReportFile = "env.all.conflicts.md",
|
|||
|
|
[string]$AppliedReportFile = "env.applied.md"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
$ErrorActionPreference = "Stop"
|
|||
|
|
|
|||
|
|
function New-Utf8NoBomEncoding {
|
|||
|
|
return New-Object System.Text.UTF8Encoding($false)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function Read-TextUtf8 {
|
|||
|
|
param([Parameter(Mandatory = $true)][string]$Path)
|
|||
|
|
return [System.IO.File]::ReadAllText($Path, [System.Text.Encoding]::UTF8)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function Write-TextUtf8NoBom {
|
|||
|
|
param(
|
|||
|
|
[Parameter(Mandatory = $true)][string]$Path,
|
|||
|
|
[Parameter(Mandatory = $true)][string]$Text
|
|||
|
|
)
|
|||
|
|
$encoding = New-Utf8NoBomEncoding
|
|||
|
|
[System.IO.File]::WriteAllText($Path, $Text, $encoding)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function Get-RepoRoot {
|
|||
|
|
return (Resolve-Path -LiteralPath (Join-Path $PSScriptRoot "..")).Path
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function Get-EnvFiles {
|
|||
|
|
param([Parameter(Mandatory = $true)][string]$Root)
|
|||
|
|
|
|||
|
|
$excludePattern = "\\(node_modules|\.git|dist|build|\.next|out|coverage|pnpm-store|artifacts)\\"
|
|||
|
|
$envFiles =
|
|||
|
|
Get-ChildItem -Path $Root -Recurse -Force -File |
|
|||
|
|
Where-Object {
|
|||
|
|
$n = $_.Name
|
|||
|
|
($n -eq ".env" -or $n -like ".env.*" -or $n -like "*.env" -or $n -like "*.env.*" -or $n -like "*.env.example" -or $n -eq ".env.example" -or $n -like ".env.*.example") -and
|
|||
|
|
($_.FullName -notmatch $excludePattern)
|
|||
|
|
} |
|
|||
|
|
Sort-Object FullName
|
|||
|
|
|
|||
|
|
return $envFiles
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function Get-EnvFileKind {
|
|||
|
|
param(
|
|||
|
|
[Parameter(Mandatory = $true)][string]$Root,
|
|||
|
|
[Parameter(Mandatory = $true)][string]$FullName
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
$rel = $FullName.Substring($Root.Length).TrimStart("\","/")
|
|||
|
|
$name = [System.IO.Path]::GetFileName($FullName)
|
|||
|
|
$lowerName = $name.ToLowerInvariant()
|
|||
|
|
|
|||
|
|
if ($rel -match "^(?i)cankao[/\\\\]") { return "legacy" }
|
|||
|
|
if ($rel -match "(?i)[/\\\\]test[/\\\\]") { return "legacy" }
|
|||
|
|
|
|||
|
|
if ($lowerName.EndsWith(".example")) { return "template" }
|
|||
|
|
if ($lowerName.EndsWith(".sample")) { return "template" }
|
|||
|
|
if ($lowerName.EndsWith(".mock")) { return "template" }
|
|||
|
|
if ($lowerName.EndsWith(".library")) { return "template" }
|
|||
|
|
if ($lowerName -eq "test.env") { return "template" }
|
|||
|
|
|
|||
|
|
return "runtime"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function Get-AppliedEnvFiles {
|
|||
|
|
param([Parameter(Mandatory = $true)][string]$Root)
|
|||
|
|
|
|||
|
|
# 说明:这里的[已应用]指默认启动链路/配置文件明确加载的 env 文件。
|
|||
|
|
$candidates = @(
|
|||
|
|
# 统一入口:仓库根目录的 .env.all(生产优先,且要求 Key 唯一)
|
|||
|
|
(Join-Path $Root ".env.all")
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
$applied = New-Object System.Collections.Generic.List[string]
|
|||
|
|
foreach ($p in ($candidates | Where-Object { Test-Path -LiteralPath $_ })) {
|
|||
|
|
$applied.Add((Resolve-Path -LiteralPath $p).Path) | Out-Null
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Docker(运行中)推断:从容器的 compose working_dir 与 compose 文件解析 env_file
|
|||
|
|
$dockerApplied = Get-AppliedEnvFilesFromDocker -Root $Root
|
|||
|
|
foreach ($p in $dockerApplied) {
|
|||
|
|
if (Test-Path -LiteralPath $p) {
|
|||
|
|
$applied.Add((Resolve-Path -LiteralPath $p).Path) | Out-Null
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $applied | Select-Object -Unique
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function Get-AppliedEnvFilesFromDocker {
|
|||
|
|
param([Parameter(Mandatory = $true)][string]$Root)
|
|||
|
|
|
|||
|
|
$result = New-Object System.Collections.Generic.List[string]
|
|||
|
|
$dockerOk = $false
|
|||
|
|
try {
|
|||
|
|
$null = & docker ps --format "{{.ID}}" 2>$null
|
|||
|
|
$dockerOk = $true
|
|||
|
|
} catch {
|
|||
|
|
$dockerOk = $false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (-not $dockerOk) {
|
|||
|
|
return @()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$ids = @()
|
|||
|
|
try {
|
|||
|
|
$ids = & docker ps --format "{{.ID}}" 2>$null
|
|||
|
|
} catch {
|
|||
|
|
return @()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$composeFiles = New-Object System.Collections.Generic.HashSet[string]
|
|||
|
|
$workingDirs = New-Object System.Collections.Generic.HashSet[string]
|
|||
|
|
|
|||
|
|
foreach ($id in $ids) {
|
|||
|
|
$workdir = ""
|
|||
|
|
$cfg = ""
|
|||
|
|
try { $workdir = (& docker inspect -f '{{ index .Config.Labels "com.docker.compose.project.working_dir" }}' $id 2>$null) } catch {}
|
|||
|
|
try { $cfg = (& docker inspect -f '{{ index .Config.Labels "com.docker.compose.project.config_files" }}' $id 2>$null) } catch {}
|
|||
|
|
|
|||
|
|
if ($workdir -and $workdir -ne "<no value>" -and ($workdir -like "$Root*")) {
|
|||
|
|
$workingDirs.Add($workdir) | Out-Null
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($cfg -and $cfg -ne "<no value>") {
|
|||
|
|
foreach ($p in ($cfg -split "[;,]" | ForEach-Object { $_.Trim() } | Where-Object { $_ })) {
|
|||
|
|
if ($p -like "$Root*") {
|
|||
|
|
$composeFiles.Add($p) | Out-Null
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach ($wd in $workingDirs) {
|
|||
|
|
$p = Join-Path $wd ".env"
|
|||
|
|
if (Test-Path -LiteralPath $p) {
|
|||
|
|
$result.Add($p) | Out-Null
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach ($cf in $composeFiles) {
|
|||
|
|
foreach ($p in (Get-EnvFilesFromCompose -ComposeFile $cf)) {
|
|||
|
|
if ($p -like "$Root*") {
|
|||
|
|
$result.Add($p) | Out-Null
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $result | Select-Object -Unique
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function Get-EnvFilesFromCompose {
|
|||
|
|
param([Parameter(Mandatory = $true)][string]$ComposeFile)
|
|||
|
|
|
|||
|
|
if (-not (Test-Path -LiteralPath $ComposeFile)) {
|
|||
|
|
return @()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$dir = Split-Path -Parent $ComposeFile
|
|||
|
|
$lines = Get-Content -LiteralPath $ComposeFile
|
|||
|
|
|
|||
|
|
$out = New-Object System.Collections.Generic.List[string]
|
|||
|
|
$inEnvFile = $false
|
|||
|
|
$envIndent = 0
|
|||
|
|
|
|||
|
|
foreach ($line in $lines) {
|
|||
|
|
if ($line -match "^(\\s*)env_file\\s*:\\s*$") {
|
|||
|
|
$inEnvFile = $true
|
|||
|
|
$envIndent = $matches[1].Length
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($inEnvFile) {
|
|||
|
|
# 退出条件:遇到更浅的缩进且不是空行/注释
|
|||
|
|
if ($line -match "^(\\s*)(\\S.*)$") {
|
|||
|
|
$indent = $matches[1].Length
|
|||
|
|
$payload = $matches[2]
|
|||
|
|
if ($indent -le $envIndent -and $payload -notmatch "^-") {
|
|||
|
|
$inEnvFile = $false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (-not $inEnvFile) { continue }
|
|||
|
|
|
|||
|
|
if ($line -match "^\\s*-\\s*(.+?)\\s*$") {
|
|||
|
|
$raw = $matches[1].Trim()
|
|||
|
|
$raw = $raw.Trim('"').Trim("'")
|
|||
|
|
if (-not $raw) { continue }
|
|||
|
|
|
|||
|
|
$resolved = if ([System.IO.Path]::IsPathRooted($raw)) { $raw } else { Join-Path $dir $raw }
|
|||
|
|
try {
|
|||
|
|
# 归一化 .. 等相对段,避免 Test-Path / -LiteralPath 因路径包含 .. 而误判
|
|||
|
|
$resolved = [System.IO.Path]::GetFullPath($resolved)
|
|||
|
|
} catch {}
|
|||
|
|
|
|||
|
|
$out.Add($resolved) | Out-Null
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $out | Select-Object -Unique
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function Remove-LinesContaining {
|
|||
|
|
param(
|
|||
|
|
[Parameter(Mandatory = $true)][string]$Path,
|
|||
|
|
[Parameter(Mandatory = $true)][string]$Pattern
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
$text = Read-TextUtf8 -Path $Path
|
|||
|
|
$lines = $text -split "(\r?\n)", 0
|
|||
|
|
|
|||
|
|
# 上面的 split 会保留分隔符(换行)作为独立元素;便于最大限度保留原始换行风格。
|
|||
|
|
$out = New-Object System.Text.StringBuilder
|
|||
|
|
for ($i = 0; $i -lt $lines.Count; $i++) {
|
|||
|
|
$chunk = $lines[$i]
|
|||
|
|
if ($chunk -match "^\r?\n$") {
|
|||
|
|
[void]$out.Append($chunk)
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
if ($chunk -match $Pattern) {
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
[void]$out.Append($chunk)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Write-TextUtf8NoBom -Path $Path -Text $out.ToString()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function Get-MergeOrderScore {
|
|||
|
|
param(
|
|||
|
|
[Parameter(Mandatory = $true)][string]$Root,
|
|||
|
|
[Parameter(Mandatory = $true)][string]$FullName
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
$rel = $FullName.Substring($Root.Length).TrimStart("\","/")
|
|||
|
|
$name = [System.IO.Path]::GetFileName($FullName)
|
|||
|
|
|
|||
|
|
# 目录组优先级:越大越[后覆盖](优先级更高)
|
|||
|
|
$group = 0
|
|||
|
|
if ($rel -match "^(?i)wolai-backend[/\\\\]") { $group = 60 }
|
|||
|
|
elseif ($rel -match "^(?i)services[/\\\\]") { $group = 30 }
|
|||
|
|
elseif ($rel -match "^(?i)cankao[/\\\\]") { $group = -50 }
|
|||
|
|
elseif ($rel -match "(?i)[/\\\\]test[/\\\\]") { $group = -40 }
|
|||
|
|
else { $group = 0 }
|
|||
|
|
|
|||
|
|
# 文件名优先级:越大越[后覆盖]
|
|||
|
|
$fileScore = 0
|
|||
|
|
if ($DevFirst) {
|
|||
|
|
# 开发优先:.env.local 覆盖 .env;production 文件仍保持最高
|
|||
|
|
if ($name -match "(?i)\\.production\\.local$") { $fileScore = 50 }
|
|||
|
|
elseif ($name -match "(?i)\\.production$") { $fileScore = 40 }
|
|||
|
|
elseif ($name -match "(?i)\\.local$") { $fileScore = 30 }
|
|||
|
|
elseif ($name -eq ".env") { $fileScore = 20 }
|
|||
|
|
else { $fileScore = 0 }
|
|||
|
|
} else {
|
|||
|
|
# 生产优先(默认):.env 覆盖 .env.local;production 文件最高
|
|||
|
|
if ($name -match "(?i)\\.production\\.local$") { $fileScore = 50 }
|
|||
|
|
elseif ($name -match "(?i)\\.production$") { $fileScore = 40 }
|
|||
|
|
elseif ($name -eq ".env") { $fileScore = 30 }
|
|||
|
|
elseif ($name -match "(?i)\\.local$") { $fileScore = 20 }
|
|||
|
|
else { $fileScore = 0 }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# template/legacy 默认更弱(更靠前),避免覆盖运行时真实值
|
|||
|
|
$kind = Get-EnvFileKind -Root $Root -FullName $FullName
|
|||
|
|
$kindScore = 0
|
|||
|
|
if ($kind -eq "template") { $kindScore = -1000 }
|
|||
|
|
elseif ($kind -eq "legacy") { $kindScore = -2000 }
|
|||
|
|
|
|||
|
|
return ($kindScore + ($group * 100) + $fileScore)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function Merge-EnvFilesRaw {
|
|||
|
|
param(
|
|||
|
|
[Parameter(Mandatory = $true)][string]$Root,
|
|||
|
|
[Parameter(Mandatory = $true)][string]$OutPath,
|
|||
|
|
[Parameter(Mandatory = $true)][System.IO.FileInfo[]]$Files
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
$sb = New-Object System.Text.StringBuilder
|
|||
|
|
$now = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
|||
|
|
[void]$sb.AppendLine("# 自动生成(UTF-8)。此文件为[原样拼接版],可能包含重复 Key。")
|
|||
|
|
[void]$sb.AppendLine("# 生成时间:$now")
|
|||
|
|
[void]$sb.AppendLine("# 重新生成:pwsh -NoProfile -ExecutionPolicy Bypass -File scripts/env_tools.ps1 -MergeRaw -RawOutFile `"$($OutPath | Split-Path -Leaf)`"")
|
|||
|
|
|
|||
|
|
foreach ($f in $files) {
|
|||
|
|
$rel = $f.FullName.Substring($Root.Length).TrimStart("\","/")
|
|||
|
|
[void]$sb.AppendLine("")
|
|||
|
|
[void]$sb.AppendLine("# ===== $rel =====")
|
|||
|
|
try {
|
|||
|
|
$content = Read-TextUtf8 -Path $f.FullName
|
|||
|
|
} catch {
|
|||
|
|
# 若遇到非 UTF-8 文件,仍尽量以系统默认读取(但写回统一为 UTF-8)。
|
|||
|
|
$content = [System.IO.File]::ReadAllText($f.FullName)
|
|||
|
|
}
|
|||
|
|
$content = $content.TrimEnd("`r", "`n")
|
|||
|
|
if ($content.Length -gt 0) {
|
|||
|
|
[void]$sb.AppendLine($content)
|
|||
|
|
}
|
|||
|
|
[void]$sb.AppendLine("")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Write-TextUtf8NoBom -Path $OutPath -Text $sb.ToString()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function Parse-EnvAssignments {
|
|||
|
|
param(
|
|||
|
|
[Parameter(Mandatory = $true)][string]$Root,
|
|||
|
|
[Parameter(Mandatory = $true)][string]$FullName
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
$content = Read-TextUtf8 -Path $FullName
|
|||
|
|
} catch {
|
|||
|
|
$content = [System.IO.File]::ReadAllText($FullName)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$rel = $FullName.Substring($Root.Length).TrimStart("\","/")
|
|||
|
|
$lines = $content -split "\r?\n"
|
|||
|
|
$out = @()
|
|||
|
|
|
|||
|
|
for ($i = 0; $i -lt $lines.Count; $i++) {
|
|||
|
|
$line = $lines[$i]
|
|||
|
|
if ($null -eq $line) { continue }
|
|||
|
|
if ($line -match "^\s*#") { continue }
|
|||
|
|
if ($line -match "^\s*$") { continue }
|
|||
|
|
|
|||
|
|
if ($line -match "^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)\s*$") {
|
|||
|
|
$key = $matches[1]
|
|||
|
|
$rhs = $matches[2]
|
|||
|
|
$out += [pscustomobject]@{
|
|||
|
|
Key = $key
|
|||
|
|
Rhs = $rhs
|
|||
|
|
Source = $rel
|
|||
|
|
Line = ($i + 1)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $out
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function Merge-EnvUnique {
|
|||
|
|
param(
|
|||
|
|
[Parameter(Mandatory = $true)][string]$Root,
|
|||
|
|
[Parameter(Mandatory = $true)][string]$OutPath,
|
|||
|
|
[Parameter(Mandatory = $true)][System.IO.FileInfo[]]$Files,
|
|||
|
|
[Parameter(Mandatory = $true)][string]$ConflictReportPath
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 排序:分数越大,越靠后覆盖
|
|||
|
|
$ordered = $Files | Sort-Object { Get-MergeOrderScore -Root $Root -FullName $_.FullName }, FullName
|
|||
|
|
|
|||
|
|
$final = @{}
|
|||
|
|
$conflicts = @{}
|
|||
|
|
|
|||
|
|
foreach ($f in $ordered) {
|
|||
|
|
$assignments = Parse-EnvAssignments -Root $Root -FullName $f.FullName
|
|||
|
|
foreach ($a in $assignments) {
|
|||
|
|
if ($final.ContainsKey($a.Key)) {
|
|||
|
|
if (-not $conflicts.ContainsKey($a.Key)) {
|
|||
|
|
$conflicts[$a.Key] = @($final[$a.Key].Source)
|
|||
|
|
}
|
|||
|
|
$conflicts[$a.Key] += $a.Source
|
|||
|
|
}
|
|||
|
|
$final[$a.Key] = $a
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$now = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
|||
|
|
$sb = New-Object System.Text.StringBuilder
|
|||
|
|
[void]$sb.AppendLine("# 自动生成(UTF-8)。此文件为[去重版]:每个 Key 只保留一个定义。")
|
|||
|
|
[void]$sb.AppendLine("# 生成时间:$now")
|
|||
|
|
[void]$sb.AppendLine("# 合并规则:按目录/文件优先级排序,越靠后越覆盖(template/legacy 默认不会覆盖 runtime)。")
|
|||
|
|
[void]$sb.AppendLine("# 冲突报告:$([System.IO.Path]::GetFileName($ConflictReportPath))(不包含任何值)")
|
|||
|
|
[void]$sb.AppendLine("")
|
|||
|
|
|
|||
|
|
foreach ($k in ($final.Keys | Sort-Object)) {
|
|||
|
|
$a = $final[$k]
|
|||
|
|
[void]$sb.AppendLine("# from: $($a.Source)")
|
|||
|
|
[void]$sb.AppendLine("$($a.Key)=$($a.Rhs)")
|
|||
|
|
[void]$sb.AppendLine("")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Write-TextUtf8NoBom -Path $OutPath -Text $sb.ToString()
|
|||
|
|
|
|||
|
|
# 写冲突报告(不输出值)
|
|||
|
|
$rep = New-Object System.Text.StringBuilder
|
|||
|
|
[void]$rep.AppendLine("# env key 冲突报告(不含任何值)")
|
|||
|
|
[void]$rep.AppendLine("# 生成时间:$now")
|
|||
|
|
[void]$rep.AppendLine("")
|
|||
|
|
if ($conflicts.Keys.Count -eq 0) {
|
|||
|
|
[void]$rep.AppendLine("未发现重复 Key。")
|
|||
|
|
} else {
|
|||
|
|
foreach ($k in ($conflicts.Keys | Sort-Object)) {
|
|||
|
|
$chosen = $final[$k].Source
|
|||
|
|
$allSources = ($conflicts[$k] + @($chosen)) | Select-Object -Unique
|
|||
|
|
$others = $allSources | Where-Object { $_ -ne $chosen }
|
|||
|
|
[void]$rep.AppendLine("- $k")
|
|||
|
|
[void]$rep.AppendLine(" - 采用:$chosen")
|
|||
|
|
if ($others.Count -gt 0) {
|
|||
|
|
[void]$rep.AppendLine(" - 其它:$($others -join ', ')")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
Write-TextUtf8NoBom -Path $ConflictReportPath -Text $rep.ToString()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$root = Get-RepoRoot
|
|||
|
|
|
|||
|
|
if ($Merge -and -not $MergeRaw) {
|
|||
|
|
$MergeRaw = $true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$appliedPaths = Get-AppliedEnvFiles -Root $root
|
|||
|
|
if ($ListApplied) {
|
|||
|
|
foreach ($p in ($appliedPaths | Sort-Object)) {
|
|||
|
|
$rel = $p.Substring($root.Length).TrimStart("\","/")
|
|||
|
|
Write-Output $rel
|
|||
|
|
}
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$appliedOnlyDefault = $true
|
|||
|
|
if (-not $PSBoundParameters.ContainsKey("AppliedOnly")) {
|
|||
|
|
$AppliedOnly = $appliedOnlyDefault
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$allEnvFiles = Get-EnvFiles -Root $root
|
|||
|
|
|
|||
|
|
$appliedSet = @{}
|
|||
|
|
foreach ($p in $appliedPaths) { $appliedSet[$p.ToLowerInvariant()] = $true }
|
|||
|
|
|
|||
|
|
$selected =
|
|||
|
|
if ($AppliedOnly) {
|
|||
|
|
$allEnvFiles | Where-Object { $appliedSet.ContainsKey($_.FullName.ToLowerInvariant()) }
|
|||
|
|
} else {
|
|||
|
|
$allEnvFiles
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (-not $IncludeLegacy) {
|
|||
|
|
$selected =
|
|||
|
|
$selected | Where-Object {
|
|||
|
|
(Get-EnvFileKind -Root $root -FullName $_.FullName) -ne "legacy"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$rawOutFull = Join-Path $root $RawOutFile
|
|||
|
|
$outFull = Join-Path $root $OutFile
|
|||
|
|
$conflictFull = Join-Path $root $ConflictReportFile
|
|||
|
|
$appliedReportFull = Join-Path $root $AppliedReportFile
|
|||
|
|
|
|||
|
|
$root = Get-RepoRoot
|
|||
|
|
|
|||
|
|
if ($CleanupSupabase) {
|
|||
|
|
$filesWithSupabase =
|
|||
|
|
$allEnvFiles |
|
|||
|
|
Where-Object {
|
|||
|
|
try {
|
|||
|
|
(Read-TextUtf8 -Path $_.FullName) -match "(?i)supabase"
|
|||
|
|
} catch {
|
|||
|
|
([System.IO.File]::ReadAllText($_.FullName)) -match "(?i)supabase"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach ($f in $filesWithSupabase) {
|
|||
|
|
Remove-LinesContaining -Path $f.FullName -Pattern "(?i)supabase"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($MergeRaw) {
|
|||
|
|
$files = $selected | Where-Object { $_.FullName -ne $rawOutFull -and $_.FullName -ne $outFull }
|
|||
|
|
Merge-EnvFilesRaw -Root $root -OutPath $rawOutFull -Files $files
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($MergeUnique) {
|
|||
|
|
$files = $selected | Where-Object { $_.FullName -ne $rawOutFull -and $_.FullName -ne $outFull }
|
|||
|
|
Merge-EnvUnique -Root $root -OutPath $outFull -Files $files -ConflictReportPath $conflictFull
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($Report -or $MergeRaw -or $MergeUnique) {
|
|||
|
|
# 写[已应用/未应用]报告(不包含值)
|
|||
|
|
$now = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
|||
|
|
$sb2 = New-Object System.Text.StringBuilder
|
|||
|
|
[void]$sb2.AppendLine("# env 应用情况报告(不含任何值)")
|
|||
|
|
[void]$sb2.AppendLine("# 生成时间:$now")
|
|||
|
|
[void]$sb2.AppendLine("")
|
|||
|
|
[void]$sb2.AppendLine("## 默认链路明确加载的 env 文件(Applied)")
|
|||
|
|
if ($appliedPaths.Count -eq 0) {
|
|||
|
|
[void]$sb2.AppendLine("- (未发现)")
|
|||
|
|
} else {
|
|||
|
|
foreach ($p in $appliedPaths) {
|
|||
|
|
$rel = $p.Substring($root.Length).TrimStart("\","/")
|
|||
|
|
[void]$sb2.AppendLine("- $rel")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
[void]$sb2.AppendLine("")
|
|||
|
|
[void]$sb2.AppendLine("## 扫描到的 env 文件(Found)")
|
|||
|
|
$generatedEnvFull = @(
|
|||
|
|
$outFull.ToLowerInvariant(),
|
|||
|
|
$rawOutFull.ToLowerInvariant()
|
|||
|
|
)
|
|||
|
|
foreach ($f in $allEnvFiles) {
|
|||
|
|
$rel = $f.FullName.Substring($root.Length).TrimStart("\","/")
|
|||
|
|
$kind = Get-EnvFileKind -Root $root -FullName $f.FullName
|
|||
|
|
$tag = ""
|
|||
|
|
if ($generatedEnvFull -contains $f.FullName.ToLowerInvariant()) {
|
|||
|
|
$tag = "generated"
|
|||
|
|
}
|
|||
|
|
if ($tag -ne "") {
|
|||
|
|
[void]$sb2.AppendLine("- $rel ($kind, $tag)")
|
|||
|
|
} else {
|
|||
|
|
[void]$sb2.AppendLine("- $rel ($kind)")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
[void]$sb2.AppendLine("")
|
|||
|
|
[void]$sb2.AppendLine("## 默认链路未使用的文件(按类型分组)")
|
|||
|
|
|
|||
|
|
$notApplied = $allEnvFiles | Where-Object {
|
|||
|
|
(-not $appliedSet.ContainsKey($_.FullName.ToLowerInvariant())) -and
|
|||
|
|
(-not ($generatedEnvFull -contains $_.FullName.ToLowerInvariant()))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$notAppliedRuntime = $notApplied | Where-Object { (Get-EnvFileKind -Root $root -FullName $_.FullName) -eq "runtime" }
|
|||
|
|
$notAppliedTemplate = $notApplied | Where-Object { (Get-EnvFileKind -Root $root -FullName $_.FullName) -eq "template" }
|
|||
|
|
$notAppliedLegacy = $notApplied | Where-Object { (Get-EnvFileKind -Root $root -FullName $_.FullName) -eq "legacy" }
|
|||
|
|
|
|||
|
|
[void]$sb2.AppendLine("")
|
|||
|
|
[void]$sb2.AppendLine("### runtime(可能是历史遗留/需确认是否仍在用)")
|
|||
|
|
if ($notAppliedRuntime.Count -eq 0) {
|
|||
|
|
[void]$sb2.AppendLine("- (无)")
|
|||
|
|
} else {
|
|||
|
|
foreach ($f in $notAppliedRuntime) {
|
|||
|
|
$rel = $f.FullName.Substring($root.Length).TrimStart("\","/")
|
|||
|
|
[void]$sb2.AppendLine("- $rel")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
[void]$sb2.AppendLine("")
|
|||
|
|
[void]$sb2.AppendLine("### template(示例/样例文件)")
|
|||
|
|
if ($notAppliedTemplate.Count -eq 0) {
|
|||
|
|
[void]$sb2.AppendLine("- (无)")
|
|||
|
|
} else {
|
|||
|
|
foreach ($f in $notAppliedTemplate) {
|
|||
|
|
$rel = $f.FullName.Substring($root.Length).TrimStart("\","/")
|
|||
|
|
[void]$sb2.AppendLine("- $rel")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
[void]$sb2.AppendLine("")
|
|||
|
|
[void]$sb2.AppendLine("### legacy(参考资料/测试夹带)")
|
|||
|
|
if ($notAppliedLegacy.Count -eq 0) {
|
|||
|
|
[void]$sb2.AppendLine("- (无)")
|
|||
|
|
} else {
|
|||
|
|
foreach ($f in $notAppliedLegacy) {
|
|||
|
|
$rel = $f.FullName.Substring($root.Length).TrimStart("\","/")
|
|||
|
|
[void]$sb2.AppendLine("- $rel")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Write-TextUtf8NoBom -Path $appliedReportFull -Text $sb2.ToString()
|
|||
|
|
}
|