feat: add comprehensive build diagnostics for UIExtension debugging
- Disco assembly version reporting at build start - Reflection-based feature verification: - Discovers UIExtensionFeature<T> implementations in compiled DLL - Validates [PluginFeature] attribute presence on each feature - Confirms ExecuteAction and Initialize are overridden - Checks model types implement BaseUIModel interface - Reports generic type definition FullName for manifest comparison - ManifestGenerator output capture (stdout + stderr with labels) - Manifest validation after generation: - Verifies Features array is present and non-empty - Char-by-char CategoryTypeName comparison with hex dump on mismatch - Dumps raw manifest.json for manual inspection - Package verification (zip content listing) - Final summary with actionable next-step checklist when issues found
This commit is contained in:
+308
-13
@@ -7,7 +7,7 @@
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$DiscoBinPath,
|
||||
|
||||
|
||||
[string]$Configuration = "Release"
|
||||
)
|
||||
|
||||
@@ -34,6 +34,18 @@ foreach ($dll in $requiredDlls) {
|
||||
}
|
||||
Write-Host "All required Disco assemblies found" -ForegroundColor Green
|
||||
|
||||
# --- Show Disco Assembly Versions ---
|
||||
Write-Host "`n--- Disco Assembly Versions ---" -ForegroundColor DarkGray
|
||||
foreach ($dll in $requiredDlls) {
|
||||
$dllPath = Join-Path $DiscoBinPath $dll
|
||||
try {
|
||||
$asmName = [System.Reflection.AssemblyName]::GetAssemblyName($dllPath)
|
||||
Write-Host " $dll : v$($asmName.Version)" -ForegroundColor Gray
|
||||
} catch {
|
||||
Write-Warning " $dll : could not read version - $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
# --- Find MSBuild ---
|
||||
$msbuild = $null
|
||||
$vsWhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
|
||||
@@ -78,9 +90,7 @@ if (-not (Test-Path $pluginDll)) {
|
||||
}
|
||||
Write-Host "Build successful: $pluginDll" -ForegroundColor Green
|
||||
|
||||
# --- Generate Manifest ---
|
||||
Write-Host "`nGenerating manifest..." -ForegroundColor Yellow
|
||||
|
||||
# --- Copy Dependencies for Reflection and ManifestGenerator ---
|
||||
foreach ($dll in $requiredDlls) {
|
||||
Copy-Item (Join-Path $DiscoBinPath $dll) $buildOutput -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
@@ -90,28 +100,167 @@ foreach ($dep in $extraDlls) {
|
||||
if (Test-Path $depPath) { Copy-Item $depPath $buildOutput -Force -ErrorAction SilentlyContinue }
|
||||
}
|
||||
|
||||
# --- Reflection-Based Feature Diagnostics ---
|
||||
Write-Host "`n--- Plugin Assembly Diagnostics ---" -ForegroundColor Yellow
|
||||
|
||||
$reflectionOk = $true
|
||||
try {
|
||||
$pluginAsmBytes = [System.IO.File]::ReadAllBytes($pluginDll)
|
||||
$pluginAsm = [System.Reflection.Assembly]::Load($pluginAsmBytes)
|
||||
|
||||
Write-Host " Plugin assembly loaded: $($pluginAsm.FullName)" -ForegroundColor Gray
|
||||
|
||||
# Find all types that extend UIExtensionFeature<T>
|
||||
$uiExtTypes = @()
|
||||
foreach ($t in $pluginAsm.GetExportedTypes()) {
|
||||
$baseType = $t.BaseType
|
||||
if ($baseType -ne $null -and $baseType.IsGenericType) {
|
||||
$genDef = $baseType.GetGenericTypeDefinition()
|
||||
if ($genDef.FullName -eq 'Disco.Services.Plugins.Features.UIExtension.UIExtensionFeature`1') {
|
||||
$uiExtTypes += $t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($uiExtTypes.Count -eq 0) {
|
||||
Write-Warning " NO UIExtensionFeature<T> implementations found in assembly!"
|
||||
Write-Warning " ExecuteAction will never be called without UIExtension features."
|
||||
$reflectionOk = $false
|
||||
} else {
|
||||
Write-Host " Found $($uiExtTypes.Count) UIExtension feature(s):" -ForegroundColor Green
|
||||
}
|
||||
|
||||
foreach ($t in $uiExtTypes) {
|
||||
$baseType = $t.BaseType
|
||||
$genArgs = $baseType.GetGenericArguments()
|
||||
$modelType = $genArgs[0]
|
||||
$genDefFullName = $baseType.GetGenericTypeDefinition().FullName
|
||||
|
||||
Write-Host "`n Feature: $($t.FullName)" -ForegroundColor Cyan
|
||||
Write-Host " Base type: $($baseType.FullName)" -ForegroundColor Gray
|
||||
Write-Host " Generic definition: $genDefFullName" -ForegroundColor Gray
|
||||
Write-Host " Model type: $($modelType.FullName)" -ForegroundColor Gray
|
||||
Write-Host " Model assembly: $($modelType.Assembly.GetName().Name) v$($modelType.Assembly.GetName().Version)" -ForegroundColor Gray
|
||||
|
||||
# Verify [PluginFeature] attribute
|
||||
$attrs = $t.GetCustomAttributes($true) | Where-Object { $_.GetType().Name -eq 'PluginFeatureAttribute' }
|
||||
if ($attrs) {
|
||||
foreach ($attr in $attrs) {
|
||||
Write-Host " [PluginFeature] Id: $($attr.Id)" -ForegroundColor Gray
|
||||
Write-Host " [PluginFeature] Name: $($attr.Name)" -ForegroundColor Gray
|
||||
}
|
||||
} else {
|
||||
Write-Warning " MISSING [PluginFeature] attribute! Disco cannot discover this feature."
|
||||
$reflectionOk = $false
|
||||
}
|
||||
|
||||
# Verify ExecuteAction is overridden (not just the abstract base)
|
||||
$execMethod = $t.GetMethod('ExecuteAction', [System.Reflection.BindingFlags]'Public,Instance')
|
||||
if ($execMethod -and $execMethod.DeclaringType -eq $t) {
|
||||
Write-Host " ExecuteAction: overridden (OK)" -ForegroundColor Green
|
||||
} elseif ($execMethod) {
|
||||
Write-Warning " ExecuteAction: NOT overridden (declared in $($execMethod.DeclaringType.Name))"
|
||||
$reflectionOk = $false
|
||||
} else {
|
||||
Write-Warning " ExecuteAction: NOT FOUND"
|
||||
$reflectionOk = $false
|
||||
}
|
||||
|
||||
# Verify Initialize is overridden and calls Register()
|
||||
$initMethod = $t.GetMethod('Initialize', [System.Reflection.BindingFlags]'Public,Instance', $null, @([Type]'Disco.Data.Repository.DiscoDataContext'), $null)
|
||||
if ($initMethod -and $initMethod.DeclaringType -eq $t) {
|
||||
Write-Host " Initialize: overridden (OK)" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Warning " Initialize: NOT overridden - Register() will never be called!"
|
||||
$reflectionOk = $false
|
||||
}
|
||||
|
||||
# Check that the model type implements BaseUIModel
|
||||
$baseUIModel = $modelType.GetInterface('BaseUIModel')
|
||||
if ($baseUIModel) {
|
||||
Write-Host " BaseUIModel: $($modelType.Name) implements BaseUIModel (OK)" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Warning " BaseUIModel: $($modelType.Name) does NOT implement BaseUIModel!"
|
||||
$reflectionOk = $false
|
||||
}
|
||||
}
|
||||
|
||||
# Check the main plugin class
|
||||
$pluginClass = $pluginAsm.GetExportedTypes() | Where-Object {
|
||||
$_.BaseType -ne $null -and $_.BaseType.FullName -eq 'Disco.Services.Plugins.Plugin'
|
||||
}
|
||||
if ($pluginClass) {
|
||||
Write-Host "`n Plugin class: $($pluginClass.FullName)" -ForegroundColor Cyan
|
||||
$pluginAttrs = $pluginClass.GetCustomAttributes($true) | Where-Object { $_.GetType().Name -eq 'PluginAttribute' }
|
||||
if ($pluginAttrs) {
|
||||
foreach ($attr in $pluginAttrs) {
|
||||
Write-Host " [Plugin] Id: $($attr.Id)" -ForegroundColor Gray
|
||||
Write-Host " [Plugin] Name: $($attr.Name)" -ForegroundColor Gray
|
||||
}
|
||||
} else {
|
||||
Write-Warning " MISSING [Plugin] attribute!"
|
||||
}
|
||||
}
|
||||
|
||||
} catch {
|
||||
Write-Warning " Reflection diagnostics failed: $($_.Exception.Message)"
|
||||
Write-Warning " This may indicate assembly load issues (missing dependencies, version mismatches)."
|
||||
$reflectionOk = $false
|
||||
}
|
||||
|
||||
if ($reflectionOk) {
|
||||
Write-Host "`n All reflection checks PASSED" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "`n Some reflection checks FAILED - see warnings above" -ForegroundColor Red
|
||||
}
|
||||
|
||||
# --- Generate Manifest ---
|
||||
Write-Host "`n--- Manifest Generation ---" -ForegroundColor Yellow
|
||||
|
||||
$useManifestGen = $false
|
||||
$manifestGenExe = Join-Path $DiscoBinPath "Disco.Services.Plugins.ManifestGenerator.exe"
|
||||
if (-not (Test-Path $manifestGenExe)) {
|
||||
$manifestGenExe = Join-Path (Split-Path $DiscoBinPath -Parent) "Disco.Services.Plugins.ManifestGenerator.exe"
|
||||
}
|
||||
if (Test-Path $manifestGenExe) {
|
||||
Write-Host "Found ManifestGenerator, using it for proper feature discovery..." -ForegroundColor Gray
|
||||
Write-Host "Found ManifestGenerator: $manifestGenExe" -ForegroundColor Gray
|
||||
$useManifestGen = $true
|
||||
& $manifestGenExe $pluginDll
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Warning "ManifestGenerator failed, creating manifest manually..."
|
||||
|
||||
# Capture stdout and stderr separately for diagnostics
|
||||
$manifestGenOutput = & $manifestGenExe $pluginDll 2>&1
|
||||
$manifestGenExit = $LASTEXITCODE
|
||||
|
||||
if ($manifestGenOutput) {
|
||||
Write-Host " ManifestGenerator output:" -ForegroundColor DarkGray
|
||||
$manifestGenOutput | ForEach-Object {
|
||||
$line = $_.ToString()
|
||||
if ($_ -is [System.Management.Automation.ErrorRecord]) {
|
||||
Write-Host " [STDERR] $line" -ForegroundColor Red
|
||||
} else {
|
||||
Write-Host " $line" -ForegroundColor DarkGray
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($manifestGenExit -ne 0) {
|
||||
Write-Warning "ManifestGenerator exited with code $manifestGenExit, falling back to manual manifest..."
|
||||
$useManifestGen = $false
|
||||
} else {
|
||||
Write-Host "ManifestGenerator completed successfully" -ForegroundColor Green
|
||||
}
|
||||
} else {
|
||||
Write-Host "ManifestGenerator not found at:" -ForegroundColor DarkGray
|
||||
Write-Host " $DiscoBinPath" -ForegroundColor DarkGray
|
||||
Write-Host " $(Split-Path $DiscoBinPath -Parent)" -ForegroundColor DarkGray
|
||||
Write-Host "Falling back to manual manifest generation..." -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
if (-not $useManifestGen) {
|
||||
$version = [System.Reflection.AssemblyName]::GetAssemblyName($pluginDll).Version
|
||||
|
||||
# CategoryTypeName must match what ManifestGenerator produces:
|
||||
# For UIExtensionFeature<T>, the generic type definition FullName is UIExtensionFeature`1
|
||||
# CategoryTypeName must match the .NET generic type definition FullName.
|
||||
# For UIExtensionFeature<T>, this is UIExtensionFeature`1 (one backtick, then 1).
|
||||
# In PowerShell double-quoted strings, `` (two backticks) produces one literal backtick.
|
||||
$uiExtCategoryTypeName = "Disco.Services.Plugins.Features.UIExtension.UIExtensionFeature``1"
|
||||
|
||||
$manifest = [ordered]@{
|
||||
@@ -141,8 +290,104 @@ if (-not $useManifestGen) {
|
||||
}
|
||||
)
|
||||
}
|
||||
$manifest | ConvertTo-Json -Depth 5 | Set-Content (Join-Path $buildOutput "manifest.json") -Encoding UTF8
|
||||
Write-Host "Manual manifest.json created with UIExtension features" -ForegroundColor Green
|
||||
$manifestJson = $manifest | ConvertTo-Json -Depth 5
|
||||
$manifestJson | Set-Content (Join-Path $buildOutput "manifest.json") -Encoding UTF8
|
||||
Write-Host "Manual manifest.json created" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# --- Validate Manifest ---
|
||||
Write-Host "`n--- Manifest Validation ---" -ForegroundColor Yellow
|
||||
|
||||
$manifestPath = Join-Path $buildOutput "manifest.json"
|
||||
if (-not (Test-Path $manifestPath)) {
|
||||
Write-Error "manifest.json NOT FOUND in build output! Plugin cannot be loaded by Disco."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$manifestContent = Get-Content $manifestPath -Raw
|
||||
$manifestObj = $manifestContent | ConvertFrom-Json
|
||||
|
||||
# Basic structure checks
|
||||
$manifestErrors = @()
|
||||
$manifestWarnings = @()
|
||||
|
||||
if (-not $manifestObj.Id) { $manifestErrors += "Missing 'Id'" }
|
||||
if (-not $manifestObj.TypeName) { $manifestErrors += "Missing 'TypeName'" }
|
||||
if (-not $manifestObj.AssemblyPath) { $manifestErrors += "Missing 'AssemblyPath'" }
|
||||
if (-not $manifestObj.Version) { $manifestWarnings += "Missing 'Version'" }
|
||||
|
||||
Write-Host " Id: $($manifestObj.Id)" -ForegroundColor Gray
|
||||
Write-Host " Version: $($manifestObj.Version)" -ForegroundColor Gray
|
||||
Write-Host " TypeName: $($manifestObj.TypeName)" -ForegroundColor Gray
|
||||
|
||||
if (-not $manifestObj.Features -or $manifestObj.Features.Count -eq 0) {
|
||||
$manifestErrors += "Features array is EMPTY or MISSING - no UIExtensions will be loaded!"
|
||||
} else {
|
||||
Write-Host " Features: $($manifestObj.Features.Count) found" -ForegroundColor Green
|
||||
|
||||
# Expected CategoryTypeName (with literal backtick)
|
||||
$expectedCategory = 'Disco.Services.Plugins.Features.UIExtension.UIExtensionFeature`1'
|
||||
|
||||
foreach ($feature in $manifestObj.Features) {
|
||||
Write-Host "`n Feature: $($feature.Id)" -ForegroundColor Cyan
|
||||
Write-Host " Name: $($feature.Name)" -ForegroundColor Gray
|
||||
Write-Host " TypeName: $($feature.TypeName)" -ForegroundColor Gray
|
||||
Write-Host " CategoryTypeName: $($feature.CategoryTypeName)" -ForegroundColor Gray
|
||||
Write-Host " PrimaryFeature: $($feature.PrimaryFeature)" -ForegroundColor Gray
|
||||
|
||||
if (-not $feature.TypeName) {
|
||||
$manifestErrors += "Feature '$($feature.Id)' is missing TypeName"
|
||||
}
|
||||
|
||||
if (-not $feature.CategoryTypeName) {
|
||||
$manifestErrors += "Feature '$($feature.Id)' is missing CategoryTypeName"
|
||||
} elseif ($feature.CategoryTypeName -ne $expectedCategory) {
|
||||
$manifestErrors += "Feature '$($feature.Id)' CategoryTypeName MISMATCH!"
|
||||
Write-Host " Expected: $expectedCategory" -ForegroundColor Red
|
||||
Write-Host " Got: $($feature.CategoryTypeName)" -ForegroundColor Red
|
||||
|
||||
# Char-by-char comparison to identify the exact difference
|
||||
$expChars = $expectedCategory.ToCharArray()
|
||||
$actChars = $feature.CategoryTypeName.ToCharArray()
|
||||
for ($i = 0; $i -lt [Math]::Max($expChars.Length, $actChars.Length); $i++) {
|
||||
$ec = if ($i -lt $expChars.Length) { $expChars[$i] } else { $null }
|
||||
$ac = if ($i -lt $actChars.Length) { $actChars[$i] } else { $null }
|
||||
if ($ec -ne $ac) {
|
||||
$ecDisplay = if ($ec) { "'$ec' (0x{0:X2})" -f [int][char]$ec } else { '[END]' }
|
||||
$acDisplay = if ($ac) { "'$ac' (0x{0:X2})" -f [int][char]$ac } else { '[END]' }
|
||||
Write-Host " First difference at position ${i}: expected $ecDisplay vs got $acDisplay" -ForegroundColor Red
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Write-Host " CategoryTypeName: VALID" -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Show raw manifest for manual inspection
|
||||
Write-Host "`n --- Raw manifest.json ---" -ForegroundColor DarkGray
|
||||
$manifestContent -split "`n" | ForEach-Object {
|
||||
Write-Host " $_" -ForegroundColor DarkGray
|
||||
}
|
||||
Write-Host " --- End manifest.json ---" -ForegroundColor DarkGray
|
||||
|
||||
if ($manifestErrors.Count -gt 0) {
|
||||
Write-Host "`n MANIFEST ERRORS:" -ForegroundColor Red
|
||||
foreach ($err in $manifestErrors) {
|
||||
Write-Host " [ERROR] $err" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
if ($manifestWarnings.Count -gt 0) {
|
||||
foreach ($warn in $manifestWarnings) {
|
||||
Write-Host " [WARN] $warn" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
if ($manifestErrors.Count -eq 0) {
|
||||
Write-Host "`n All manifest checks PASSED" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "`n Manifest has errors - UIExtensions will NOT render!" -ForegroundColor Red
|
||||
Write-Host " Fix the errors above before installing the plugin." -ForegroundColor Red
|
||||
}
|
||||
|
||||
# --- Package ---
|
||||
@@ -162,16 +407,24 @@ Get-ChildItem $PluginDir -Filter "*.zip" | Where-Object { $_.Name -like "$Plugin
|
||||
|
||||
$tempPkg = Join-Path $env:TEMP "discoplugin_$([guid]::NewGuid().ToString('N'))"
|
||||
New-Item -ItemType Directory -Path $tempPkg -Force | Out-Null
|
||||
$missingFiles = @()
|
||||
foreach ($f in $includeFiles) {
|
||||
$src = Join-Path $buildOutput $f
|
||||
if (Test-Path $src) {
|
||||
Copy-Item $src $tempPkg -Force
|
||||
} else {
|
||||
$missingFiles += $f
|
||||
}
|
||||
}
|
||||
|
||||
if ($missingFiles.Count -gt 0) {
|
||||
Write-Warning "Missing files not included in package: $($missingFiles -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "Files in package:" -ForegroundColor Gray
|
||||
Get-ChildItem $tempPkg -File | ForEach-Object {
|
||||
Write-Host " $($_.Name)" -ForegroundColor Gray
|
||||
$size = [math]::Round($_.Length / 1KB, 1)
|
||||
Write-Host " $($_.Name) ($size KB)" -ForegroundColor Gray
|
||||
}
|
||||
|
||||
$zipPath = Join-Path $PluginDir "$PluginName-$($version.ToString()).zip"
|
||||
@@ -179,6 +432,48 @@ Compress-Archive -Path "$tempPkg\*" -DestinationPath $zipPath -Force
|
||||
Rename-Item $zipPath $packageName -Force
|
||||
Remove-Item $tempPkg -Recurse -Force
|
||||
|
||||
# --- Final Package Verification ---
|
||||
Write-Host "`n--- Package Verification ---" -ForegroundColor Yellow
|
||||
try {
|
||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||
$zip = [System.IO.Compression.ZipFile]::OpenRead($packagePath)
|
||||
$entries = $zip.Entries | Select-Object -ExpandProperty Name
|
||||
$zip.Dispose()
|
||||
|
||||
$requiredEntries = @("$PluginName.dll", "manifest.json")
|
||||
foreach ($req in $requiredEntries) {
|
||||
if ($entries -contains $req) {
|
||||
Write-Host " [OK] $req" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " [MISSING] $req" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
# Show any extras
|
||||
foreach ($entry in $entries) {
|
||||
if ($entry -notin $requiredEntries) {
|
||||
Write-Host " [+] $entry" -ForegroundColor Gray
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Write-Warning " Could not verify package contents: $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
$packageSize = [math]::Round((Get-Item $packagePath).Length / 1KB, 1)
|
||||
Write-Host "`n=== Package created: $packagePath ($($packageSize) KB) ===" -ForegroundColor Green
|
||||
Write-Host "Import into Disco ICT via: Configuration > Plugins > Install Plugin" -ForegroundColor Cyan
|
||||
|
||||
# --- Summary ---
|
||||
$hasIssues = (-not $reflectionOk) -or ($manifestErrors.Count -gt 0)
|
||||
if ($hasIssues) {
|
||||
Write-Host "`n=== BUILD COMPLETED WITH WARNINGS ===" -ForegroundColor Yellow
|
||||
Write-Host "Review the diagnostics above before installing." -ForegroundColor Yellow
|
||||
Write-Host "If UIExtensions still don't render after install:" -ForegroundColor Yellow
|
||||
Write-Host " 1. Check Disco logs for feature initialization messages" -ForegroundColor Yellow
|
||||
Write-Host " 2. View page source and search for 'layout_uiExtensions'" -ForegroundColor Yellow
|
||||
Write-Host " - Present but empty = ExecuteAction returned Nothing()/null" -ForegroundColor Yellow
|
||||
Write-Host " - Missing entirely = Feature not registered (Register() not called)" -ForegroundColor Yellow
|
||||
Write-Host " 3. Check the installed manifest at:" -ForegroundColor Yellow
|
||||
Write-Host " App_Data\Plugins\$PluginName\manifest.json" -ForegroundColor Yellow
|
||||
} else {
|
||||
Write-Host "`n=== BUILD COMPLETED SUCCESSFULLY ===" -ForegroundColor Green
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user