Untitled
unknown
plain_text
10 months ago
33 kB
14
Indexable
# UEPluginManager.ps1 - Multi-functional UE Plugin Packager & Bootstrap Manager
# Commands:
# package | install | add | init | list | clean
[CmdletBinding()] param(
[Parameter(Position=0)]
[ValidateSet("package", "install", "add", "init", "list", "clean")]
[string]$Command = ""
)
$ErrorActionPreference = "Stop"
Add-Type -AssemblyName System.Windows.Forms -ErrorAction SilentlyContinue | Out-Null
# ========== UTILITIES ==========
function Pick-File($title,$filter="All (*.*)|*.*"){
Write-Host "Opening file dialog: $title" -ForegroundColor Gray
try {
$dlg=New-Object System.Windows.Forms.OpenFileDialog
$dlg.Title=$title; $dlg.Filter=$filter; $dlg.Multiselect=$false
$dlg.CheckFileExists=$true; $dlg.CheckPathExists=$true
$res = $dlg.ShowDialog()
if($res -eq [System.Windows.Forms.DialogResult]::OK){
Write-Host "Selected: $($dlg.FileName)" -ForegroundColor Green
return $dlg.FileName
} else {
Write-Host "Cancelled." -ForegroundColor Yellow
return $null
}
} catch {
Write-Host "Dialog failed: $($_.Exception.Message)" -ForegroundColor Red
$path = Read-Host "Enter full file path"
if(Test-Path $path){ return $path } else { return $null }
}
}
function Pick-Folder($desc){
Write-Host "Opening folder dialog: $desc" -ForegroundColor Gray
try {
$dlg=New-Object System.Windows.Forms.FolderBrowserDialog
$dlg.Description=$desc; $dlg.ShowNewFolderButton=$true
$res = $dlg.ShowDialog()
if($res -eq [System.Windows.Forms.DialogResult]::OK){
Write-Host "Selected: $($dlg.SelectedPath)" -ForegroundColor Green
return $dlg.SelectedPath
} else {
Write-Host "Cancelled." -ForegroundColor Yellow
return $null
}
} catch {
Write-Host "Dialog failed: $($_.Exception.Message)" -ForegroundColor Red
$path = Read-Host "Enter full folder path"
if(Test-Path $path){ return $path } else { return $null }
}
}
function Ask($prompt,$def=""){
if([string]::IsNullOrWhiteSpace($def)){ Read-Host $prompt } else {
$v = Read-Host "$prompt [$def]"; if([string]::IsNullOrWhiteSpace($v)){ $def } else { $v }
}
}
function Ask-YesNo($prompt, $default=$true){
$suffix = if($default){"(Y/n)"}else{"(y/N)"}; $v = Read-Host "$prompt $suffix"
if([string]::IsNullOrWhiteSpace($v)){ return $default }
return ($v -match '^(?i)y|yes$')
}
function Ensure-Dir($p){ if(-not(Test-Path -LiteralPath $p)){ New-Item -ItemType Directory -Force -Path $p | Out-Null } }
function Read-Json($p){
if(-not(Test-Path $p)){ return $null }
try { (Get-Content -Raw -LiteralPath $p) | ConvertFrom-Json } catch { Write-Warning "Bad JSON: $p"; $null }
}
function Write-Json($obj,$p){ ($obj|ConvertTo-Json -Depth 20) | Out-File -LiteralPath $p -Encoding UTF8 }
function Parse-Platforms($s){ if([string]::IsNullOrWhiteSpace($s)){ @('Win64') } else { ($s -split '[,\s]+' | Where-Object { $_.Trim() } | ForEach-Object { $_.Trim() }) } }
# Coerce Hashtable -> PSCustomObject with string keys (PS 5.1-safe for ConvertTo-Json)
function New-JsonObject {
param([Hashtable]$Map)
$o = [pscustomobject]@{}
foreach($k in $Map.Keys){
$name = [string]$k
$o | Add-Member -NotePropertyName $name -NotePropertyValue $Map[$k] -Force
}
return $o
}
# ========== SEMVER HELPERS ==========
function Parse-SemVer($version){
if($version -match '^(\d+)\.(\d+)\.(\d+)(-.*)?$'){
return @{ Major=[int]$matches[1]; Minor=[int]$matches[2]; Patch=[int]$matches[3]; PreRelease=$matches[4]; Original=$version }
}
$null
}
function Compare-SemVer($v1,$v2){
$a=Parse-SemVer $v1; $b=Parse-SemVer $v2
if(-not $a -or -not $b){ return [string]::Compare($v1,$v2) }
if($a.Major -ne $b.Major){ return $a.Major - $b.Major }
if($a.Minor -ne $b.Minor){ return $a.Minor - $b.Minor }
if($a.Patch -ne $b.Patch){ return $a.Patch - $b.Patch }
0
}
function Test-SemVerRange($version,$range){
if([string]::IsNullOrWhiteSpace($range)){ return $true }
$ver=Parse-SemVer $version; if(-not $ver){ return $false }
if($range -match '^\^(\d+\.\d+\.\d+)$'){
$t=Parse-SemVer $matches[1]
return ($ver.Major -eq $t.Major -and ( ($ver.Minor -gt $t.Minor) -or ($ver.Minor -eq $t.Minor -and $ver.Patch -ge $t.Patch) ))
}
if($range -match '^~(\d+\.\d+\.\d+)$'){
$t=Parse-SemVer $matches[1]
return ($ver.Major -eq $t.Major -and $ver.Minor -eq $t.Minor -and $ver.Patch -ge $t.Patch)
}
($version -eq $range)
}
# ========== UE DISCOVERY ==========
function Find-UE-Installations{
$c=@(); $roots=@("$env:ProgramFiles\Epic Games","${env:ProgramFiles(x86)}\Epic Games","$env:ProgramW6432\Epic Games")
foreach($r in $roots){ if(Test-Path $r){
Get-ChildItem -Path $r -Directory | Where-Object { $_.Name -match '^UE_\d+\.\d+' } | ForEach-Object {
$uat=Join-Path $_.FullName "Engine\Build\BatchFiles\RunUAT.bat"
if(Test-Path $uat){ $c += @{ Path=$_.FullName; Version=($_.Name -replace '^UE_',''); Display="$($_.Name) - $($_.FullName)" } }
}
} }
$c | Sort-Object Version -Descending
}
function Prompt-UE-Selection{
Write-Host ""
Write-Host "=== Unreal Engine Selection ===" -ForegroundColor Cyan
$inst = Find-UE-Installations
if($inst.Count -gt 0){
Write-Host "Found UE installations:" -ForegroundColor Yellow
for($i=0;$i -lt $inst.Count;$i++){ Write-Host " [$($i+1)] $($inst[$i].Display)" -ForegroundColor Green }
Write-Host " [0] Browse..." -ForegroundColor Gray
do{ $s = Read-Host "Select UE (1-$($inst.Count)) or 0"; $n = $s -as [int] } while($n -lt 0 -or $n -gt $inst.Count)
if($n -eq 0){ Pick-Folder "Select UE root (e.g. C:\Program Files\Epic Games\UE_5.5)" } else { $inst[$n-1].Path }
} else {
Write-Host "No UE installations found automatically." -ForegroundColor Yellow
Pick-Folder "Select UE root (e.g. C:\Program Files\Epic Games\UE_5.5)"
}
}
# ========== PACKAGING HELPERS ==========
function Prompt-Plugin-Selection{
Write-Host ""
Write-Host "=== Main Plugin Selection ===" -ForegroundColor Cyan
Write-Host "Select the .uplugin file you want to package..." -ForegroundColor Yellow
$f = Pick-File "Select the main .uplugin" "Unreal Plugin (*.uplugin)|*.uplugin|All (*.*)|*.*"
if(-not $f){ Write-Host "No plugin selected." -ForegroundColor Red; return $null }
Write-Host "Selected Plugin: $f" -ForegroundColor Green
$f
}
function Find-Dependencies-OneLevel-Up($mainPluginPath,$depNames){
Write-Host ""
Write-Host "Looking for dependencies one level up..." -ForegroundColor Yellow
$mainDir = Split-Path -Parent $mainPluginPath
$rootDir = Split-Path -Parent $mainDir
$found=@()
foreach($dep in $depNames){
Write-Host " Looking for: $dep" -ForegroundColor Cyan
$depU = Join-Path (Join-Path $rootDir $dep) ($dep + ".uplugin")
if(Test-Path $depU){ Write-Host " Found: $depU" -ForegroundColor Green; $found += $depU } else {
Write-Host " Not found at: $depU" -ForegroundColor Red
$sub = Get-ChildItem -Path $rootDir -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -eq $dep } | Select-Object -First 1
if($sub){
$u = Join-Path $sub.FullName ($dep + ".uplugin")
if(Test-Path $u){ Write-Host " Found in subdir: $u" -ForegroundColor Green; $found += $u }
}
}
}
$found
}
function Copy-PluginFolder($pluginUPluginPath,$destPluginsRoot){
$src = Split-Path -Parent $pluginUPluginPath
$name = [IO.Path]::GetFileNameWithoutExtension($pluginUPluginPath)
$dst = Join-Path $destPluginsRoot $name
if(Test-Path $dst){ Remove-Item -Recurse -Force -LiteralPath $dst }
Write-Host " Copying $name -> $dst" -ForegroundColor Green
$null = & robocopy "$src" "$dst" /E /NFL /NDL /NJH /NJS /NP 2>$null
if($LASTEXITCODE -ge 8){ Copy-Item -Path $src -Destination $dst -Recurse -Force }
$dst
}
# >>> THIS is the critical part you were missing: Method 3 with Plugins[] in the .uproject
function Try-BuildPlugin-Methods($runUAT,$pluginPath,$depPluginPaths,$packageDir,$platforms,$workDir){
$ok=$false; $method=""
Write-Host ""
Write-Host "==> Trying Method 1: Direct BuildPlugin (no HostProject)" -ForegroundColor Yellow
if($depPluginPaths.Count -eq 0){
$args=@('BuildPlugin',("-Plugin=""$pluginPath"""),("-Package=""$packageDir"""),("-TargetPlatforms={0}" -f ($platforms -join '+')),'-Rocket','-NoP4')
Write-Host "UAT: $($args -join ' ')" -ForegroundColor Gray
try{ & $runUAT @args; if($LASTEXITCODE -eq 0){ $ok=$true; $method="Direct BuildPlugin (no dependencies)" } } catch { }
} else {
Write-Host "Skipping: plugin declares dependencies." -ForegroundColor Gray
}
if(-not $ok){
Write-Host ""
Write-Host "==> Trying Method 2: Minimal HostProject" -ForegroundColor Yellow
$hostDir = Join-Path $workDir "HostProject"
$hostProj = Join-Path $hostDir "HostProject.uproject"
Ensure-Dir $hostDir
try { $pj = Read-Json $pluginPath; $eng = if($pj.EngineVersion){ $pj.EngineVersion } else { "5.5" } } catch { $eng="5.5" }
@"
{
"FileVersion": 3,
"EngineAssociation": "$eng",
"Category": "",
"Description": "Minimal host project for BuildPlugin"
}
"@ | Out-File -LiteralPath $hostProj -Encoding UTF8
$args=@('BuildPlugin',("-Plugin=""$pluginPath"""),("-Package=""$packageDir"""),("-TargetPlatforms={0}" -f ($platforms -join '+')),("-HostProject=""$hostProj"""),'-Rocket','-NoP4')
Write-Host "UAT: $($args -join ' ')" -ForegroundColor Gray
try{ & $runUAT @args; if($LASTEXITCODE -eq 0){ $ok=$true; $method="BuildPlugin with minimal HostProject" } } catch { }
}
if(-not $ok){
Write-Host ""
Write-Host "==> Trying Method 3: HostProject with dependencies (copied + enabled)" -ForegroundColor Yellow
$hostDir = Join-Path $workDir "HostProjectWithDeps"
$hostProj = Join-Path $hostDir "HostProject.uproject"
$hostPlugins = Join-Path $hostDir "Plugins"
if(Test-Path $hostDir){ Remove-Item -Recurse -Force $hostDir }
Ensure-Dir $hostPlugins
# Copy deps
$copied=@()
foreach($d in $depPluginPaths){ $copied += (Copy-PluginFolder -pluginUPluginPath $d -destPluginsRoot $hostPlugins) }
# Copy main
$mainCopiedDir = Copy-PluginFolder -pluginUPluginPath $pluginPath -destPluginsRoot $hostPlugins
$mainName = [IO.Path]::GetFileNameWithoutExtension($pluginPath)
$copiedMainUplugin = Join-Path $hostPlugins "$mainName\$mainName.uplugin"
# Create .uproject with Plugins[] enabled
try { $pj = Read-Json $pluginPath; $eng = if($pj.EngineVersion){ $pj.EngineVersion } else { "5.5" } } catch { $eng="5.5" }
$pluginRefs = @()
foreach($p in $depPluginPaths){
$dn = [IO.Path]::GetFileNameWithoutExtension($p)
$pluginRefs += @{ "Name" = $dn; "Enabled" = $true }
}
$pluginRefs += @{ "Name" = $mainName; "Enabled" = $true }
$uprojectContent = @{
"FileVersion" = 3
"EngineAssociation" = $eng
"Category" = ""
"Description" = "Host project with dependencies for BuildPlugin"
"Plugins" = $pluginRefs
}
Write-Json $uprojectContent $hostProj
$args=@('BuildPlugin',("-Plugin=""$copiedMainUplugin"""),("-Package=""$packageDir"""),("-TargetPlatforms={0}" -f ($platforms -join '+')),("-HostProject=""$hostProj"""),'-Rocket','-NoP4')
Write-Host "UAT: $($args -join ' ')" -ForegroundColor Gray
try{ & $runUAT @args; if($LASTEXITCODE -eq 0){ $ok=$true; $method="BuildPlugin with HostProject and dependencies" } } catch { }
}
@{ Success=$ok; Method=$method }
}
function Get-PackagedPluginPath($packageDir,$pluginBase){
$expect = Join-Path $packageDir $pluginBase
if(Test-Path (Join-Path $expect ($pluginBase + ".uplugin"))){ return $expect }
$found = Get-ChildItem -Path $packageDir -Filter ($pluginBase + ".uplugin") -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1
if($found){ return $found.Directory.FullName }
$null
}
# ========== BOOTSTRAP "NEXUS" (LOCAL REPO) ==========
function Initialize-PluginProject{
$root = Get-Location
Write-Host "Initializing plugin project in: $root" -ForegroundColor Cyan
$pluginsDir = Join-Path $root "Plugins"
Ensure-Dir $pluginsDir
$req = Join-Path $pluginsDir "plugins.requirements.json"
if(-not(Test-Path $req)){
$engine = Ask "Engine version" "5.5"
$data = [ordered]@{ engine=$engine; plugins=@{} }
Write-Json $data $req
Write-Host "Created: $req" -ForegroundColor Green
} else { Write-Host "Requirements exists: $req" -ForegroundColor Yellow }
Write-Host "Initialized." -ForegroundColor Green
}
function Add-Plugin-Requirement($pluginName,$versionRange){
$pluginsDir = Join-Path (Get-Location) "Plugins"
$req = Join-Path $pluginsDir "plugins.requirements.json"
if(-not(Test-Path $req)){ Write-Host "Run 'init' first." -ForegroundColor Red; return }
$j = Read-Json $req; if(-not $j){ Write-Host "Bad requirements." -ForegroundColor Red; return }
if(-not $j.plugins){ $j.plugins=@{} }
$j.plugins.$pluginName = $versionRange
Write-Json $j $req
Write-Host "Added $pluginName@$versionRange" -ForegroundColor Green
}
function Get-Local-Nexus-Path{
$nx = Join-Path (Get-Location) "Nexus"
if(-not(Test-Path $nx)){
Write-Host "No local Nexus at: $nx" -ForegroundColor Yellow
if(Ask-YesNo "Create local Nexus directory?" $true){ Ensure-Dir $nx; Write-Host "Created: $nx" -ForegroundColor Green } else { return $null }
}
$nx
}
function Scan-Local-Nexus($nexusPath){
Write-Host "Scanning local Nexus: $nexusPath" -ForegroundColor Yellow
$manifests=@{}; $zips = Get-ChildItem -Path $nexusPath -Filter "*.zip" -ErrorAction SilentlyContinue
foreach($z in $zips){
$m = $z.FullName -replace '\.zip$','.manifest.json'
if(Test-Path $m){
$doc = Read-Json $m
if($doc -and $doc.name){
if(-not $manifests[$doc.name]){ $manifests[$doc.name]=@() }
$doc | Add-Member -NotePropertyName 'zipPath' -NotePropertyValue $z.FullName -Force
$manifests[$doc.name] += $doc
}
}
}
foreach($k in $manifests.Keys){
$manifests[$k] = $manifests[$k] | Sort-Object { $v=Parse-SemVer $_.version; if($v){ "{0}.{1}.{2}" -f $v.Major,$v.Minor,$v.Patch } else { $_.version } } -Descending
}
return $manifests
}
function Resolve-Plugin-Dependencies($requirements,$available,$targetEngine){
Write-Host ""
Write-Host "=== Resolving Dependencies ===" -ForegroundColor Cyan
$resolved=@{}; $queue=@()
foreach($k in $requirements.plugins.Keys){ $queue += @{ Name=$k; Range=$requirements.plugins.$k; IsRoot=$true } }
while($queue.Count -gt 0){
$cur=$queue[0]; if($queue.Count -gt 1){ $queue=$queue[1..($queue.Count-1)] } else { $queue=@() }
$name=$cur.Name; $range=$cur.Range
Write-Host "Processing: $name@$range" -ForegroundColor Gray
if($resolved.ContainsKey($name)){ Write-Host " Already resolved." -ForegroundColor Gray; continue }
if(-not $available.ContainsKey($name)){ throw "Plugin not found: $name" }
$cands = $available[$name] | Where-Object { $_.engine -eq $targetEngine -and (Test-SemVerRange $_.version $range) }
if($cands.Count -eq 0){ throw "No compatible version: $name@$range (engine $targetEngine)" }
$sel = $cands[0]; $resolved[$name]=$sel
Write-Host " Selected: $name@$($sel.version)" -ForegroundColor Green
if($sel.dependencies){
foreach($d in $sel.dependencies){
if($d.name -and -not $resolved.ContainsKey($d.name)){
if(-not ($queue | Where-Object { $_.Name -eq $d.name })){
$queue += @{ Name=$d.name; Range=$d.range; IsRoot=$false }
Write-Host " Added dep: $($d.name)@$($d.range)" -ForegroundColor Cyan
}
}
}
}
}
$resolved
}
function Install-Resolved-Plugins($resolved,$pluginsDir){
Write-Host ""
Write-Host "=== Installing Plugins ===" -ForegroundColor Cyan
Ensure-Dir $pluginsDir
foreach($name in $resolved.Keys){
$m = $resolved[$name]; $zip=$m.zipPath; $dst=Join-Path $pluginsDir $name
Write-Host "Installing: $name@$($m.version)" -ForegroundColor Yellow
Write-Host " From: $zip" -ForegroundColor Gray
Write-Host " To: $dst" -ForegroundColor Gray
if(Test-Path $dst){ Write-Host " Removing existing..." -ForegroundColor Gray; Remove-Item -Recurse -Force $dst }
$tmp = Join-Path $env:TEMP "uepm_extract_$(Get-Random)"; Ensure-Dir $tmp
try{
Expand-Archive -Path $zip -DestinationPath $tmp -Force
$plug = Get-ChildItem -Path $tmp -Directory | Where-Object { Test-Path (Join-Path $_.FullName ($_.Name + ".uplugin")) } | Select-Object -First 1
if($plug){ Move-Item -Path $plug.FullName -Destination $dst; Write-Host " Installed" -ForegroundColor Green } else { Write-Host " Could not find plugin folder in ZIP" -ForegroundColor Red }
} finally { if(Test-Path $tmp){ Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue } }
}
}
function Create-Lockfile($resolved,$requirements,$lockFilePath){
$lock=[ordered]@{ engine=$requirements.engine; resolved=@() }
foreach($k in ($resolved.Keys | Sort-Object)){
$m=$resolved[$k]
$sha = ""
if($m.sha256 -and ($m.sha256 | Get-Member -Name Win64 -MemberType NoteProperty -ErrorAction SilentlyContinue)){
$sha = $m.sha256.Win64
}
$lock.resolved += [ordered]@{ name=$m.name; version=$m.version; platform="Win64"; url=[IO.Path]::GetFileName($m.zipPath); sha256=$sha }
}
Write-Json $lock $lockFilePath
Write-Host "Created lockfile: $lockFilePath" -ForegroundColor Green
}
# ========== COMMAND HANDLERS ==========
function Handle-Package-Command{
Write-Host "=== UE Plugin Packager ===" -ForegroundColor Cyan
# Select UE
$ueRoot = Prompt-UE-Selection
if(-not $ueRoot) { throw "No UE installation selected" }
$runUAT = Join-Path $ueRoot "Engine\Build\BatchFiles\RunUAT.bat"
if(-not (Test-Path $runUAT)) { throw "RunUAT not found at: $runUAT" }
Write-Host ""
Write-Host "Selected UE: $ueRoot" -ForegroundColor Green
# Select plugin
$main = Prompt-Plugin-Selection
if(-not $main) { throw "No plugin selected" }
# Plugin info
try {
$pj = Read-Json $main
$pluginBase = [IO.Path]::GetFileNameWithoutExtension($main)
$pluginName = if($pj.FriendlyName) { $pj.FriendlyName } else { $pluginBase }
$pluginVer = if($pj.VersionName) { $pj.VersionName } elseif($pj.Version) { $pj.Version.ToString() } else { "1.0.0" }
} catch {
throw "Could not read plugin file: $main"
}
# Build config
Write-Host ""
Write-Host "=== Build Configuration ===" -ForegroundColor Cyan
$engineVer = Ask "Engine version" "5.5"
$platforms = Parse-Platforms (Ask "Target platforms (comma-separated)" "Win64")
# Output dir
Write-Host ""
$artifactsDir = Pick-Folder "Select output directory for build artifacts"
if(-not $artifactsDir) { throw "No output directory selected" }
Write-Host ""
Write-Host "=== Plugin Information ===" -ForegroundColor Cyan
Write-Host " Name: $pluginName" -ForegroundColor Green
Write-Host " Base Name: $pluginBase" -ForegroundColor Green
Write-Host " Version: $pluginVer" -ForegroundColor Green
Write-Host " Engine: $engineVer" -ForegroundColor Green
Write-Host " Platforms: $($platforms -join ', ')" -ForegroundColor Green
Write-Host " Output: $artifactsDir" -ForegroundColor Green
# Dependencies
$depNames=@()
if($pj.Plugins){ foreach($dep in $pj.Plugins){ if($dep.Name){ $depNames += $dep.Name } } }
$depNames = $depNames | Select-Object -Unique
$depPluginPaths=@()
if($depNames.Count -gt 0) {
Write-Host ""
Write-Host "=== Dependency Resolution ===" -ForegroundColor Cyan
Write-Host "Dependencies required: $($depNames -join ', ')" -ForegroundColor Yellow
$foundDeps = Find-Dependencies-OneLevel-Up -mainPluginPath $main -depNames $depNames
foreach($depName in $depNames) {
$found = $foundDeps | Where-Object { [IO.Path]::GetFileNameWithoutExtension($_) -eq $depName } | Select-Object -First 1
if($found) {
Write-Host "Found dependency: $depName at $found" -ForegroundColor Green
if(Ask-YesNo "Use this dependency?" $true){ $depPluginPaths += $found } else {
$manual = Pick-File "Locate $depName.uplugin manually" "Unreal Plugin (*.uplugin)|*.uplugin"
if($manual){ $depPluginPaths += $manual } else { Write-Warning "Dependency $depName not provided - build may fail" }
}
} else {
Write-Host "Could not find dependency: $depName" -ForegroundColor Red
$manual = Pick-File "Locate $depName.uplugin manually" "Unreal Plugin (*.uplugin)|*.uplugin"
if($manual){ $depPluginPaths += $manual } else { Write-Warning "Dependency $depName not provided - build may fail" }
}
}
} else {
Write-Host ""
Write-Host "No dependencies declared in plugin." -ForegroundColor Green
}
# Work dirs
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$buildStamp = "${pluginBase}_${pluginVer}_UE${engineVer}_$($platforms -join '+')_${timestamp}"
$workDir = Join-Path $artifactsDir "_Work_$buildStamp"
$packageDir = Join-Path $artifactsDir "Package_$buildStamp"
if(Test-Path $workDir){ Remove-Item -Recurse -Force $workDir }
if(Test-Path $packageDir){ Remove-Item -Recurse -Force $packageDir }
Ensure-Dir $workDir
Ensure-Dir $packageDir
Write-Host ""
Write-Host "=== Starting Build Process ===" -ForegroundColor Cyan
Write-Host "Work directory: $workDir" -ForegroundColor Gray
Write-Host "Package directory: $packageDir" -ForegroundColor Gray
try {
$buildResult = Try-BuildPlugin-Methods -runUAT $runUAT -pluginPath $main -depPluginPaths $depPluginPaths -packageDir $packageDir -platforms $platforms -workDir $workDir
if(-not $buildResult.Success) {
Write-Host ""
Write-Host "=== BUILD FAILED ===" -ForegroundColor Red
Write-Host "All build methods failed. Check UAT output above." -ForegroundColor Red
throw "Plugin build failed with all attempted methods"
}
Write-Host ""
Write-Host "=== BUILD SUCCESSFUL ===" -ForegroundColor Green
Write-Host "Method used: $($buildResult.Method)" -ForegroundColor Yellow
} finally {
if(Test-Path $workDir) {
try { Remove-Item -Recurse -Force $workDir } catch { Write-Warning "Could not clean up work directory" }
}
}
# Locate packaged plugin dir
$packagedDir = Get-PackagedPluginPath -packageDir $packageDir -pluginBase $pluginBase
if(-not $packagedDir) { throw "Could not locate packaged plugin in: $packageDir" }
# Create ZIP that includes plugin folder + manifest.json (sibling)
$zipPath = Join-Path $artifactsDir "${buildStamp}.zip"
if(Test-Path $zipPath) { Remove-Item $zipPath -Force }
Write-Host ""
Write-Host "Creating ZIP package..." -ForegroundColor Yellow
Write-Host " Source: $packagedDir" -ForegroundColor Gray
Write-Host " ZIP: $zipPath" -ForegroundColor Gray
$tempPackageDir = Join-Path $env:TEMP "uepm_package_$(Get-Random)"
Ensure-Dir $tempPackageDir
try {
# Copy plugin folder into temp root
$tempPluginDir = Join-Path $tempPackageDir $pluginBase
Copy-Item -Path $packagedDir -Destination $tempPluginDir -Recurse -Force
# Manifest inside zip (no sha256 self reference)
$zipFileName = [IO.Path]::GetFileName($zipPath)
$artifactsMap = @{}
$shaMap = @{}
foreach($p in $platforms){ $artifactsMap[$p] = $zipFileName }
$manifestInside = [ordered]@{
name = [string]$pluginName
id = [string]$pluginBase
version = [string]$pluginVer
engine = [string]$engineVer
platforms = @($platforms | ForEach-Object { [string]$_ })
artifacts = (New-JsonObject $artifactsMap)
url = [string]$zipFileName
sha256 = (New-JsonObject $shaMap) # empty in-zip manifest
buildMethod = [string]$buildResult.Method
timestamp = (Get-Date).ToString("o")
}
if($depNames.Count -gt 0){
$manifestInside.dependencies = @($depNames | ForEach-Object { [pscustomobject]@{ name=[string]$_; range=$null } })
}
$packageManifestPath = Join-Path $tempPackageDir "manifest.json"
Write-Json ([pscustomobject]$manifestInside) $packageManifestPath
Compress-Archive -Path "$tempPackageDir\*" -DestinationPath $zipPath -Force
$hash = (Get-FileHash -Algorithm SHA256 -LiteralPath $zipPath).Hash.ToLower()
Write-Host " SHA256: $hash" -ForegroundColor Gray
# External manifest with sha256
$shaFilled=@{}; foreach($p in $platforms){ $shaFilled[$p] = $hash }
$manifestOutside = $manifestInside.PSObject.Copy()
$manifestOutside.sha256 = (New-JsonObject $shaFilled)
$manifestPath = Join-Path $artifactsDir "${buildStamp}.manifest.json"
Write-Json ([pscustomobject]$manifestOutside) $manifestPath
Write-Host ""
Write-Host "=== PACKAGING COMPLETE ===" -ForegroundColor Green
Write-Host "Build Method: $($buildResult.Method)" -ForegroundColor Cyan
Write-Host "Package ZIP : $zipPath" -ForegroundColor Cyan
Write-Host "SHA256 : $hash" -ForegroundColor Cyan
Write-Host "Manifest : $manifestPath" -ForegroundColor Cyan
Write-Host ""
Write-Host "ZIP contains: /$pluginBase and /manifest.json" -ForegroundColor Yellow
} finally {
if(Test-Path $tempPackageDir) {
Remove-Item -Recurse -Force $tempPackageDir -ErrorAction SilentlyContinue
}
}
}
function Handle-Init-Command{ Initialize-PluginProject }
function Handle-Add-Command($pluginSpec){
if([string]::IsNullOrWhiteSpace($pluginSpec)){ $pluginSpec = Read-Host "Enter plugin spec (name@version)" }
if($pluginSpec -match '^(.+)@(.+)$'){ Add-Plugin-Requirement -pluginName $matches[1] -versionRange $matches[2] } else {
Write-Host "Invalid spec. Example: MyPlugin@^1.0.0" -ForegroundColor Red
}
}
function Handle-Install-Command{
Write-Host "=== UE Plugin Installer ===" -ForegroundColor Cyan
$pluginsDir = Join-Path (Get-Location) "Plugins"
$reqFile = Join-Path $pluginsDir "plugins.requirements.json"
$lockFile = Join-Path $pluginsDir ".ueplugins.lock.json"
if(-not(Test-Path $reqFile)){ Write-Host "No requirements file. Run 'init' first." -ForegroundColor Red; return }
$requirements = Read-Json $reqFile; if(-not $requirements){ Write-Host "Could not read requirements." -ForegroundColor Red; return }
if(-not $requirements.plugins -or $requirements.plugins.PSObject.Properties.Count -eq 0){ Write-Host "No plugins specified. Use 'add'." -ForegroundColor Yellow; return }
$nexus = Get-Local-Nexus-Path; if(-not $nexus){ return }
$available = Scan-Local-Nexus $nexus
Write-Host "Found $($available.Keys.Count) plugins in local Nexus" -ForegroundColor Green
try {
$resolved = Resolve-Plugin-Dependencies -requirements $requirements -available $available -targetEngine $requirements.engine
Write-Host ""
Write-Host "=== Resolution Summary ===" -ForegroundColor Cyan
foreach($k in ($resolved.Keys | Sort-Object)){ $m=$resolved[$k]; Write-Host " $k@$($m.version)" -ForegroundColor Green }
Install-Resolved-Plugins -resolved $resolved -pluginsDir $pluginsDir
Create-Lockfile -resolved $resolved -requirements $requirements -lockFilePath $lockFile
Write-Host ""
Write-Host "=== INSTALLATION COMPLETE ===" -ForegroundColor Green
Write-Host "Installed $($resolved.Keys.Count) plugins" -ForegroundColor Yellow
} catch {
Write-Host ""
Write-Host "=== RESOLUTION FAILED ===" -ForegroundColor Red
Write-Host $_.Exception.Message -ForegroundColor Red
}
}
function Handle-List-Command{
Write-Host "=== Plugin Status ===" -ForegroundColor Cyan
$pluginsDir = Join-Path (Get-Location) "Plugins"
$reqFile = Join-Path $pluginsDir "plugins.requirements.json"
$lockFile = Join-Path $pluginsDir ".ueplugins.lock.json"
if(Test-Path $reqFile){
$req = Read-Json $reqFile
Write-Host ""
Write-Host "Requirements (plugins.requirements.json):" -ForegroundColor Yellow
Write-Host " Engine: $($req.engine)" -ForegroundColor Gray
if($req.plugins){
foreach($k in $req.plugins.Keys){ Write-Host " $k@$($req.plugins.$k)" -ForegroundColor Green }
}
} else { Write-Host "No requirements file found." -ForegroundColor Red }
if(Test-Path $lockFile){
$lock = Read-Json $lockFile
Write-Host ""
Write-Host "Locked versions (.ueplugins.lock.json):" -ForegroundColor Yellow
if($lock.resolved){ foreach($p in $lock.resolved){ Write-Host " $($p.name)@$($p.version)" -ForegroundColor Green } }
} else { Write-Host "No lockfile found." -ForegroundColor Red }
if(Test-Path $pluginsDir){
$installed = Get-ChildItem -Path $pluginsDir -Directory | Where-Object { Test-Path (Join-Path $_.FullName ($_.Name + ".uplugin")) }
Write-Host ""
Write-Host "Installed plugins:" -ForegroundColor Yellow
if($installed.Count -gt 0){
foreach($d in $installed){
$up = Join-Path $d.FullName ($d.Name + ".uplugin"); $j=Read-Json $up
$ver = if($j -and $j.VersionName){ $j.VersionName } else { "unknown" }
Write-Host " $($d.Name)@$ver" -ForegroundColor Green
}
} else { Write-Host " (none)" -ForegroundColor Gray }
}
$nexus = Join-Path (Get-Location) "Nexus"
if(Test-Path $nexus){
$avail = Scan-Local-Nexus $nexus
Write-Host ""
Write-Host "Local Nexus ($nexus):" -ForegroundColor Yellow
if($avail.Keys.Count -gt 0){
foreach($n in ($avail.Keys | Sort-Object)){
$vers = $avail[$n] | ForEach-Object { $_.version }
Write-Host (" {0}: {1}" -f $n, ($vers -join ', ')) # formatting avoids the $n: pitfall
}
} else { Write-Host " (empty)" -ForegroundColor Gray }
}
}
function Handle-Clean-Command{
Write-Host "=== Clean Plugin Installation ===" -ForegroundColor Cyan
$pluginsDir = Join-Path (Get-Location) "Plugins"
$lockFile = Join-Path $pluginsDir ".ueplugins.lock.json"
if(-not(Test-Path $pluginsDir)){ Write-Host "No Plugins directory found" -ForegroundColor Yellow; return }
$installed = Get-ChildItem -Path $pluginsDir -Directory | Where-Object { Test-Path (Join-Path $_.FullName ($_.Name + ".uplugin")) }
if($installed.Count -eq 0){ Write-Host "No installed plugins found" -ForegroundColor Yellow; return }
Write-Host ""
Write-Host "Found $($installed.Count) installed plugins:" -ForegroundColor Yellow
foreach($p in $installed){ Write-Host " $($p.Name)" -ForegroundColor Gray }
if(Ask-YesNo "Remove all installed plugins?" $false){
foreach($p in $installed){ Write-Host "Removing: $($p.Name)" -ForegroundColor Red; Remove-Item -Recurse -Force $p.FullName }
if(Test-Path $lockFile){ Write-Host "Removing lockfile: $lockFile" -ForegroundColor Red; Remove-Item $lockFile }
Write-Host ""
Write-Host "Clean complete" -ForegroundColor Green
} else {
Write-Host "Clean cancelled" -ForegroundColor Yellow
}
}
function Show-Help{
Write-Host ""
Write-Host "=== UE Plugin Manager ===" -ForegroundColor Cyan
Write-Host "package - Package plugin (ZIP contains plugin folder + manifest.json); external manifest has SHA256" -ForegroundColor Green
Write-Host "init - Initialize Plugins/plugins.requirements.json" -ForegroundColor Green
Write-Host "add - Add requirement: add MyPlugin@^1.0.0" -ForegroundColor Green
Write-Host "install - Resolve + install from local 'Nexus' (zip + .manifest.json pairs)" -ForegroundColor Green
Write-Host "list - Show requirements, lockfile, installed, local Nexus" -ForegroundColor Green
Write-Host "clean - Remove installed plugins + lockfile" -ForegroundColor Green
}
# ========== MAIN ==========
if([string]::IsNullOrWhiteSpace($Command)){ Show-Help; return }
try{
switch($Command.ToLower()){
"package" { Handle-Package-Command }
"init" { Handle-Init-Command }
"add" { $spec = if($args.Count -gt 0){ $args[0] } else { $null }; Handle-Add-Command $spec }
"install" { Handle-Install-Command }
"list" { Handle-List-Command }
"clean" { Handle-Clean-Command }
default { Write-Host "Unknown command: $Command" -ForegroundColor Red; Show-Help }
}
} catch {
Write-Host ""
Write-Host "=== ERROR ===" -ForegroundColor Red
Write-Host $_.Exception.Message -ForegroundColor Red
Write-Host ""
}
Editor is loading...
Leave a Comment