Ctrl K
ComunidadGuías…

scripts/scan-project-safety.ps1

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
[CmdletBinding()]
param(
  [Parameter(Mandatory)][string]$Workspace,
  [switch]$Json
)

$ErrorActionPreference = 'Stop'
$results = [System.Collections.Generic.List[object]]::new()

function Add-Result {
  param(
    [ValidateSet('PASS','WARN','FAIL')][string]$Level,
    [string]$Check,
    [string]$Detail
  )
  $results.Add([pscustomobject]@{
    level = $Level
    check = $Check
    detail = $Detail
  })
}

function Invoke-GitRead {
  param([string]$Directory, [string[]]$Arguments)
  $output = (& git -C $Directory @Arguments 2>&1) -join "`n"
  return [pscustomobject]@{ ExitCode = $LASTEXITCODE; Output = $output.Trim() }
}

function Get-SafeTree {
  param([string]$Root)
  $pending = [System.Collections.Generic.Stack[string]]::new()
  $pending.Push($Root)
  while ($pending.Count -gt 0) {
    $directory = $pending.Pop()
    foreach ($entry in Get-ChildItem -LiteralPath $directory -Force) {
      $entry
      if ($entry.PSIsContainer -and
          -not ($entry.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -and
          $entry.Name -ne '.git') {
        $pending.Push($entry.FullName)
      }
    }
  }
}

$workspacePath = [System.IO.Path]::GetFullPath($Workspace)
if (-not (Test-Path -LiteralPath $workspacePath -PathType Container)) {
  Add-Result FAIL workspace "Workspace does not exist: $workspacePath"
} else {
  Add-Result PASS workspace $workspacePath
}

$attachment = Join-Path $workspacePath '.ferryman'
$communications = Join-Path $attachment 'ferryman'
$mainGit = Join-Path $workspacePath '.git'

if (Test-Path -LiteralPath $mainGit) {
  $mainRemote = Invoke-GitRead $workspacePath @('config','--get','remote.origin.url')
  Add-Result PASS main_remote $(if ($mainRemote.Output) { $mainRemote.Output } else { '(none)' })
  # Probe a child path so a directory-only rule such as /.ferryman/ is
  # recognized even before the attachment directory exists.
  $ignored = Invoke-GitRead $workspacePath @('check-ignore','--no-index','-q','--','.ferryman/probe')
  if ($ignored.ExitCode -eq 0) {
    Add-Result PASS main_ignore '/.ferryman/ is ignored by the main project'
  } else {
    Add-Result FAIL main_ignore '/.ferryman/ is not ignored by the main project'
  }
} else {
  Add-Result WARN main_git 'Workspace is not a Git checkout'
}

if (-not (Test-Path -LiteralPath $attachment -PathType Container)) {
  Add-Result WARN attachment 'No .ferryman attachment exists'
  $safeEntries = @()
} else {
  Add-Result PASS attachment $attachment
  $safeEntries = @(Get-SafeTree $attachment)
  $reparsePoints = @($safeEntries |
    Where-Object { $_.Attributes -band [System.IO.FileAttributes]::ReparsePoint })
  if ($reparsePoints.Count -eq 0) {
    Add-Result PASS reparse_points 'No reparse points or symlinks under .ferryman'
  } else {
    Add-Result FAIL reparse_points (($reparsePoints.FullName | Sort-Object) -join '; ')
  }
}

if (Test-Path -LiteralPath (Join-Path $attachment 'token') -PathType Leaf) {
  Add-Result PASS outer_token 'Outer token exists; contents were not read'
} else {
  Add-Result WARN outer_token 'Outer token is absent; hub registration may be deferred'
}

if (-not (Test-Path -LiteralPath (Join-Path $communications '.git'))) {
  Add-Result WARN inner_git 'Portable communications Git checkout is absent'
} else {
  $innerRemote = Invoke-GitRead $communications @('config','--get','remote.origin.url')
  $scanOwner = $env:FERRYMAN_CHANNEL_GIT_OWNER
  $scanSuffix = if ($env:FERRYMAN_CHANNEL_GIT_SUFFIX) { $env:FERRYMAN_CHANNEL_GIT_SUFFIX } else { '-ferryman' }
  $expectedPattern = '^https://github\.com/' + [regex]::Escape($scanOwner) + '/[A-Za-z0-9._-]+' + [regex]::Escape($scanSuffix) + '(?:\.git)?$'
  if (-not $innerRemote.Output) {
    # No upstream at all is the Syncthing-only channel, not a failure.
    Add-Result PASS inner_remote '(none; Syncthing-only)'
  } elseif (-not $scanOwner) {
    Add-Result FAIL inner_remote "$($innerRemote.Output) (set FERRYMAN_CHANNEL_GIT_OWNER to pin the expected owner)"
  } elseif ($innerRemote.ExitCode -eq 0 -and $innerRemote.Output -match $expectedPattern) {
    Add-Result PASS inner_remote $innerRemote.Output
  } else {
    Add-Result FAIL inner_remote $innerRemote.Output
  }
  $innerStatus = Invoke-GitRead $communications @('status','--porcelain','--untracked-files=all')
  if ($innerStatus.ExitCode -ne 0) {
    Add-Result FAIL inner_status $innerStatus.Output
  } elseif ($innerStatus.Output) {
    Add-Result WARN inner_status $innerStatus.Output
  } else {
    Add-Result PASS inner_status 'Portable repository is clean'
  }
}

foreach ($forbidden in @('token','runtime')) {
  $path = Join-Path $communications $forbidden
  if (Test-Path -LiteralPath $path) {
    Add-Result FAIL "inner_$forbidden" "Forbidden portable path exists: $path"
  } else {
    Add-Result PASS "inner_$forbidden" "No portable $forbidden path"
  }
}

if (Test-Path -LiteralPath $communications -PathType Container) {
  $suspicious = @($safeEntries |
    Where-Object {
      -not $_.PSIsContainer -and
      $_.FullName.StartsWith($communications, [System.StringComparison]::OrdinalIgnoreCase) -and
      $_.Name -match '(^\.env(?:\.|$)|token|secret|credential|password|\.sqlite3?$|\.db$|\.lock$)'
    })
  if ($suspicious.Count -eq 0) {
    Add-Result PASS portable_names 'No suspicious portable filenames'
  } else {
    Add-Result FAIL portable_names (($suspicious.FullName | Sort-Object) -join '; ')
  }
}

$standard = Join-Path $communications 'STANDARD.toml'
if (Test-Path -LiteralPath $standard -PathType Leaf) {
  $revisionLine = Get-Content -LiteralPath $standard |
    Where-Object { $_ -match '^revision\s*=\s*\d+\s*$' } |
    Select-Object -First 1
  $revision = if ($revisionLine -match '(\d+)') { [int]$Matches[1] } else { 0 }
  if ($revision -eq 2) {
    Add-Result PASS standard_revision 'Ferryman project standard revision 2'
  } elseif ($revision -gt 2) {
    Add-Result FAIL standard_revision "Project revision $revision is newer than this Ferryman checkout"
  } else {
    Add-Result WARN standard_revision "Project revision $revision needs update to revision 2"
  }
} else {
  Add-Result WARN standard_revision 'STANDARD.toml is missing; project needs a standard update'
}

if ($Json) {
  $results | ConvertTo-Json -Depth 4
} else {
  $results | Format-Table -AutoSize
}

if ($results.Where({ $_.level -eq 'FAIL' }).Count -gt 0) {
  exit 2
}