Added to Git
This commit is contained in:
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,178 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$PackagePath,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$TargetRoot,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Normalize-RelativePath {
|
||||
param([string]$Path)
|
||||
$value = $Path -replace "\\", "/"
|
||||
$value = $value.Trim()
|
||||
while ($value.StartsWith("./")) {
|
||||
$value = $value.Substring(2)
|
||||
}
|
||||
$value = $value.TrimStart("/")
|
||||
return $value.TrimEnd("/")
|
||||
}
|
||||
|
||||
function Test-PathMatchesPrefix {
|
||||
param(
|
||||
[string]$RelativePath,
|
||||
[string]$Prefix
|
||||
)
|
||||
|
||||
$pathValue = Normalize-RelativePath -Path $RelativePath
|
||||
$prefixValue = Normalize-RelativePath -Path $Prefix
|
||||
if ([string]::IsNullOrWhiteSpace($prefixValue)) { return $false }
|
||||
return $pathValue -eq $prefixValue -or $pathValue.StartsWith("$prefixValue/")
|
||||
}
|
||||
|
||||
function Test-IsAllowed {
|
||||
param(
|
||||
[string]$RelativePath,
|
||||
[string[]]$AllowedPaths
|
||||
)
|
||||
|
||||
foreach ($allowed in $AllowedPaths) {
|
||||
if (Test-PathMatchesPrefix -RelativePath $RelativePath -Prefix $allowed) {
|
||||
return $true
|
||||
}
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
function Test-IsBlocked {
|
||||
param(
|
||||
[string]$RelativePath,
|
||||
[string[]]$BlockedPaths
|
||||
)
|
||||
|
||||
foreach ($blocked in $BlockedPaths) {
|
||||
if (Test-PathMatchesPrefix -RelativePath $RelativePath -Prefix $blocked) {
|
||||
return $true
|
||||
}
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
function New-Directory {
|
||||
param([string]$Path)
|
||||
if (-not (Test-Path -LiteralPath $Path)) {
|
||||
New-Item -ItemType Directory -Force -Path $Path | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
function Get-TargetFileHash {
|
||||
param([string]$Path)
|
||||
if (-not (Test-Path -LiteralPath $Path)) { return "" }
|
||||
return (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
}
|
||||
|
||||
$resolvedPackage = Resolve-Path -LiteralPath $PackagePath
|
||||
$packageItem = Get-Item -LiteralPath $resolvedPackage.Path
|
||||
$packageRoot = if ($packageItem.PSIsContainer) { $packageItem.FullName } else { Split-Path -Parent $packageItem.FullName }
|
||||
$packageManifestPath = if ($packageItem.PSIsContainer) { Join-Path $packageRoot "package-manifest.json" } else { $packageItem.FullName }
|
||||
|
||||
if (-not (Test-Path -LiteralPath $packageManifestPath)) {
|
||||
throw "Package manifest not found: $packageManifestPath"
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $TargetRoot)) {
|
||||
throw "Target root not found: $TargetRoot"
|
||||
}
|
||||
|
||||
$targetRootPath = (Resolve-Path -LiteralPath $TargetRoot).Path
|
||||
$packageManifest = Get-Content -LiteralPath $packageManifestPath -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||
$allowedPaths = @($packageManifest.system_paths | ForEach-Object { [string]$_ })
|
||||
$blockedPaths = @($packageManifest.excluded_paths + $packageManifest.preserve_paths | ForEach-Object { [string]$_ })
|
||||
|
||||
$operations = New-Object System.Collections.Generic.List[object]
|
||||
|
||||
foreach ($file in @($packageManifest.files)) {
|
||||
$relative = Normalize-RelativePath -Path ([string]$file.path)
|
||||
if (-not (Test-IsAllowed -RelativePath $relative -AllowedPaths $allowedPaths)) {
|
||||
throw "Package file is outside system allowlist: $relative"
|
||||
}
|
||||
if (Test-IsBlocked -RelativePath $relative -BlockedPaths $blockedPaths) {
|
||||
throw "Package file targets a preserved/excluded path: $relative"
|
||||
}
|
||||
|
||||
$source = Join-Path $packageRoot ($relative -replace "/", "\")
|
||||
if (-not (Test-Path -LiteralPath $source)) {
|
||||
throw "Package file is missing: $relative"
|
||||
}
|
||||
|
||||
$sourceHash = (Get-FileHash -LiteralPath $source -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
if ($sourceHash -ne ([string]$file.sha256).ToLowerInvariant()) {
|
||||
throw "Checksum mismatch for package file: $relative"
|
||||
}
|
||||
|
||||
$target = Join-Path $targetRootPath ($relative -replace "/", "\")
|
||||
$targetHash = Get-TargetFileHash -Path $target
|
||||
if ($targetHash -eq $sourceHash) {
|
||||
continue
|
||||
}
|
||||
|
||||
$action = if (Test-Path -LiteralPath $target) { "update" } else { "create" }
|
||||
$operations.Add([ordered]@{
|
||||
action = $action
|
||||
path = $relative
|
||||
source = $source
|
||||
target = $target
|
||||
sha256 = $sourceHash
|
||||
})
|
||||
}
|
||||
|
||||
if ($DryRun) {
|
||||
Write-Output "Dry run: $($operations.Count) file operation(s) would be applied."
|
||||
foreach ($operation in $operations) {
|
||||
Write-Output "$($operation.action): $($operation.path)"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
$timestamp = (Get-Date).ToUniversalTime().ToString("yyyyMMdd-HHmmss-fff")
|
||||
$hotUpdateDir = Join-Path $targetRootPath "workspace/.hot-update"
|
||||
$backupRoot = Join-Path $hotUpdateDir "backups/$timestamp"
|
||||
New-Directory -Path $backupRoot
|
||||
|
||||
$backupEntries = New-Object System.Collections.Generic.List[object]
|
||||
foreach ($operation in $operations) {
|
||||
if ($operation.action -eq "update") {
|
||||
$backupPath = Join-Path $backupRoot ($operation.path -replace "/", "\")
|
||||
New-Directory -Path (Split-Path -Parent $backupPath)
|
||||
Copy-Item -LiteralPath $operation.target -Destination $backupPath -Force
|
||||
$backupEntries.Add([ordered]@{
|
||||
path = $operation.path
|
||||
backup_path = ("workspace/.hot-update/backups/$timestamp/" + $operation.path)
|
||||
original_sha256 = (Get-FileHash -LiteralPath $backupPath -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
})
|
||||
}
|
||||
|
||||
New-Directory -Path (Split-Path -Parent $operation.target)
|
||||
Copy-Item -LiteralPath $operation.source -Destination $operation.target -Force
|
||||
}
|
||||
|
||||
$applyLog = [ordered]@{
|
||||
applied_at = (Get-Date).ToUniversalTime().ToString("o")
|
||||
package_name = [string]$packageManifest.package_name
|
||||
version = [string]$packageManifest.version
|
||||
dry_run = $false
|
||||
operations = $operations
|
||||
backups = $backupEntries
|
||||
}
|
||||
|
||||
$backupManifestPath = Join-Path $backupRoot "backup-manifest.json"
|
||||
$applyLog | ConvertTo-Json -Depth 20 | Set-Content -LiteralPath $backupManifestPath -Encoding UTF8
|
||||
|
||||
$applyLogPath = Join-Path $hotUpdateDir "last-apply.json"
|
||||
$applyLog | ConvertTo-Json -Depth 20 | Set-Content -LiteralPath $applyLogPath -Encoding UTF8
|
||||
|
||||
Write-Output "Applied $($operations.Count) file operation(s)."
|
||||
Write-Output "Backup: $backupRoot"
|
||||
@@ -0,0 +1,146 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$OutputDir = "dist/wireframe-system",
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$ManifestPath = "wireframe-system.manifest.json"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Get-RepoRoot {
|
||||
return (Split-Path -Parent (Split-Path -Parent $PSScriptRoot))
|
||||
}
|
||||
|
||||
function Normalize-RelativePath {
|
||||
param([string]$Path)
|
||||
$value = $Path -replace "\\", "/"
|
||||
$value = $value.Trim()
|
||||
while ($value.StartsWith("./")) {
|
||||
$value = $value.Substring(2)
|
||||
}
|
||||
$value = $value.TrimStart("/")
|
||||
return $value.TrimEnd("/")
|
||||
}
|
||||
|
||||
function Test-PathMatchesPrefix {
|
||||
param(
|
||||
[string]$RelativePath,
|
||||
[string]$Prefix
|
||||
)
|
||||
|
||||
$pathValue = Normalize-RelativePath -Path $RelativePath
|
||||
$prefixValue = Normalize-RelativePath -Path $Prefix
|
||||
if ([string]::IsNullOrWhiteSpace($prefixValue)) { return $false }
|
||||
return $pathValue -eq $prefixValue -or $pathValue.StartsWith("$prefixValue/")
|
||||
}
|
||||
|
||||
function Test-IsExcluded {
|
||||
param(
|
||||
[string]$RelativePath,
|
||||
[string[]]$ExcludedPaths
|
||||
)
|
||||
|
||||
foreach ($excluded in $ExcludedPaths) {
|
||||
if (Test-PathMatchesPrefix -RelativePath $RelativePath -Prefix $excluded) {
|
||||
return $true
|
||||
}
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
function Get-RelativePath {
|
||||
param(
|
||||
[string]$Root,
|
||||
[string]$Path
|
||||
)
|
||||
|
||||
$rootPath = (Resolve-Path -LiteralPath $Root).Path
|
||||
if (-not $rootPath.EndsWith([System.IO.Path]::DirectorySeparatorChar)) {
|
||||
$rootPath += [System.IO.Path]::DirectorySeparatorChar
|
||||
}
|
||||
|
||||
$pathValue = (Resolve-Path -LiteralPath $Path).Path
|
||||
$rootUri = New-Object System.Uri($rootPath)
|
||||
$pathUri = New-Object System.Uri($pathValue)
|
||||
$relativeUri = $rootUri.MakeRelativeUri($pathUri)
|
||||
return [System.Uri]::UnescapeDataString($relativeUri.ToString()).Replace("/", "\")
|
||||
}
|
||||
|
||||
function New-Directory {
|
||||
param([string]$Path)
|
||||
if (-not (Test-Path -LiteralPath $Path)) {
|
||||
New-Item -ItemType Directory -Force -Path $Path | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
$root = Get-RepoRoot
|
||||
$manifestFullPath = Join-Path $root $ManifestPath
|
||||
if (-not (Test-Path -LiteralPath $manifestFullPath)) {
|
||||
throw "System manifest not found: $manifestFullPath"
|
||||
}
|
||||
|
||||
$manifest = Get-Content -LiteralPath $manifestFullPath -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||
$packageName = [string]$manifest.package_name
|
||||
$version = [string]$manifest.version
|
||||
if ([string]::IsNullOrWhiteSpace($packageName) -or [string]::IsNullOrWhiteSpace($version)) {
|
||||
throw "System manifest must include package_name and version."
|
||||
}
|
||||
|
||||
$excludedPaths = @($manifest.excluded_paths | ForEach-Object { [string]$_ })
|
||||
$outputRoot = if ([System.IO.Path]::IsPathRooted($OutputDir)) { $OutputDir } else { Join-Path $root $OutputDir }
|
||||
New-Directory -Path $outputRoot
|
||||
|
||||
$timestamp = (Get-Date).ToUniversalTime().ToString("yyyyMMdd-HHmmss-fff")
|
||||
$packageRoot = Join-Path $outputRoot "$packageName-$version-$timestamp"
|
||||
New-Directory -Path $packageRoot
|
||||
|
||||
foreach ($relativePath in @($manifest.system_paths)) {
|
||||
$relative = Normalize-RelativePath -Path ([string]$relativePath)
|
||||
if (Test-IsExcluded -RelativePath $relative -ExcludedPaths $excludedPaths) {
|
||||
throw "System path is excluded and cannot be packaged: $relative"
|
||||
}
|
||||
|
||||
$source = Join-Path $root $relative
|
||||
if (-not (Test-Path -LiteralPath $source)) {
|
||||
throw "System path listed in manifest does not exist: $relative"
|
||||
}
|
||||
|
||||
$destination = Join-Path $packageRoot $relative
|
||||
$destinationParent = Split-Path -Parent $destination
|
||||
if (-not [string]::IsNullOrWhiteSpace($destinationParent)) {
|
||||
New-Directory -Path $destinationParent
|
||||
}
|
||||
Copy-Item -LiteralPath $source -Destination $destination -Recurse -Force
|
||||
}
|
||||
|
||||
$files = New-Object System.Collections.Generic.List[object]
|
||||
Get-ChildItem -LiteralPath $packageRoot -Recurse -File -Force |
|
||||
Sort-Object FullName |
|
||||
ForEach-Object {
|
||||
$relative = (Get-RelativePath -Root $packageRoot -Path $_.FullName) -replace "\\", "/"
|
||||
$hash = Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256
|
||||
$files.Add([ordered]@{
|
||||
path = $relative
|
||||
sha256 = $hash.Hash.ToLowerInvariant()
|
||||
length = $_.Length
|
||||
})
|
||||
}
|
||||
|
||||
$packageManifest = [ordered]@{
|
||||
package_name = $packageName
|
||||
version = $version
|
||||
exported_at = (Get-Date).ToUniversalTime().ToString("o")
|
||||
source_manifest = $ManifestPath
|
||||
default_artifact_dir = [string]$manifest.default_artifact_dir
|
||||
system_paths = @($manifest.system_paths)
|
||||
preserve_paths = @($manifest.preserve_paths)
|
||||
excluded_paths = @($manifest.excluded_paths)
|
||||
files = $files
|
||||
}
|
||||
|
||||
$packageManifestPath = Join-Path $packageRoot "package-manifest.json"
|
||||
$packageManifest | ConvertTo-Json -Depth 20 | Set-Content -LiteralPath $packageManifestPath -Encoding UTF8
|
||||
|
||||
Write-Output "Exported $($files.Count) system file(s)."
|
||||
Write-Output $packageRoot
|
||||
@@ -0,0 +1,70 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$WorkspaceDir = "workspace",
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$ArtifactDir = "workspace/artifacts/wireframe-gen",
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$LegacyArtifactDir = "artifacts/wireframe-gen"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Get-RepoRoot {
|
||||
return (Split-Path -Parent (Split-Path -Parent $PSScriptRoot))
|
||||
}
|
||||
|
||||
function Resolve-RepoPath {
|
||||
param(
|
||||
[string]$Root,
|
||||
[string]$Path
|
||||
)
|
||||
if ([System.IO.Path]::IsPathRooted($Path)) { return $Path }
|
||||
return Join-Path $Root $Path
|
||||
}
|
||||
|
||||
function New-Directory {
|
||||
param([string]$Path)
|
||||
if (-not (Test-Path -LiteralPath $Path)) {
|
||||
New-Item -ItemType Directory -Force -Path $Path | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
function Test-DirectoryHasFiles {
|
||||
param([string]$Path)
|
||||
if (-not (Test-Path -LiteralPath $Path)) { return $false }
|
||||
return $null -ne (Get-ChildItem -LiteralPath $Path -Force -Recurse -File | Select-Object -First 1)
|
||||
}
|
||||
|
||||
$root = Get-RepoRoot
|
||||
$workspacePath = Resolve-RepoPath -Root $root -Path $WorkspaceDir
|
||||
$artifactPath = Resolve-RepoPath -Root $root -Path $ArtifactDir
|
||||
$legacyArtifactPath = Resolve-RepoPath -Root $root -Path $LegacyArtifactDir
|
||||
|
||||
New-Directory -Path $workspacePath
|
||||
New-Directory -Path $artifactPath
|
||||
New-Directory -Path (Join-Path $workspacePath "system-feedback/bug-reports")
|
||||
New-Directory -Path (Join-Path $workspacePath ".hot-update/backups")
|
||||
|
||||
$targetHadFiles = Test-DirectoryHasFiles -Path $artifactPath
|
||||
$legacyExists = Test-Path -LiteralPath $legacyArtifactPath
|
||||
$copiedLegacy = $false
|
||||
|
||||
if ($legacyExists -and -not $targetHadFiles) {
|
||||
Get-ChildItem -LiteralPath $legacyArtifactPath -Force | ForEach-Object {
|
||||
Copy-Item -LiteralPath $_.FullName -Destination $artifactPath -Recurse -Force
|
||||
}
|
||||
$copiedLegacy = $true
|
||||
}
|
||||
|
||||
if (-not (Test-DirectoryHasFiles -Path $artifactPath)) {
|
||||
$initArtifactsScript = Join-Path $root "scripts/wireframe/init-artifacts.ps1"
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File $initArtifactsScript -ArtifactDir $artifactPath | Out-Null
|
||||
}
|
||||
|
||||
Write-Output "Workspace initialized: $workspacePath"
|
||||
Write-Output "Artifact dir: $artifactPath"
|
||||
if ($copiedLegacy) {
|
||||
Write-Output "Copied legacy artifacts from $legacyArtifactPath. Legacy artifacts were not deleted."
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Title,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Area,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Expected,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Actual,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[ValidateSet("low", "medium", "high", "critical")]
|
||||
[string]$Severity = "medium",
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$TriggerContext = "Not specified",
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string[]]$ReproSteps = @(),
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$Impact = "",
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$Workaround = "",
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$DepersonalizationNotes = "No client names, copied client documents, Figma content, credentials, or private identifiers are included.",
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$PrivacyConfirmed,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$WorkspaceDir = "workspace",
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$SystemManifestPath = "wireframe-system.manifest.json"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Get-RepoRoot {
|
||||
return (Split-Path -Parent (Split-Path -Parent $PSScriptRoot))
|
||||
}
|
||||
|
||||
function Resolve-RepoPath {
|
||||
param(
|
||||
[string]$Root,
|
||||
[string]$Path
|
||||
)
|
||||
if ([System.IO.Path]::IsPathRooted($Path)) { return $Path }
|
||||
return Join-Path $Root $Path
|
||||
}
|
||||
|
||||
function New-Slug {
|
||||
param([string]$Value)
|
||||
$slug = $Value.ToLowerInvariant() -replace "[^a-z0-9]+", "-"
|
||||
$slug = $slug.Trim("-")
|
||||
if ([string]::IsNullOrWhiteSpace($slug)) { return "bug-report" }
|
||||
if ($slug.Length -gt 48) { return $slug.Substring(0, 48).Trim("-") }
|
||||
return $slug
|
||||
}
|
||||
|
||||
function New-Directory {
|
||||
param([string]$Path)
|
||||
if (-not (Test-Path -LiteralPath $Path)) {
|
||||
New-Item -ItemType Directory -Force -Path $Path | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
$root = Get-RepoRoot
|
||||
$workspacePath = Resolve-RepoPath -Root $root -Path $WorkspaceDir
|
||||
$reportRoot = Join-Path $workspacePath "system-feedback/bug-reports"
|
||||
New-Directory -Path $reportRoot
|
||||
|
||||
$systemVersion = "unknown"
|
||||
$manifestPath = Resolve-RepoPath -Root $root -Path $SystemManifestPath
|
||||
if (Test-Path -LiteralPath $manifestPath) {
|
||||
$manifest = Get-Content -LiteralPath $manifestPath -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||
if ($manifest.PSObject.Properties.Name -contains "version") {
|
||||
$systemVersion = [string]$manifest.version
|
||||
}
|
||||
}
|
||||
|
||||
$createdAt = (Get-Date).ToUniversalTime()
|
||||
$id = "BUG-" + $createdAt.ToString("yyyyMMdd-HHmmss-fff")
|
||||
$reportDir = Join-Path $reportRoot ($id + "-" + (New-Slug -Value $Title))
|
||||
New-Directory -Path $reportDir
|
||||
New-Directory -Path (Join-Path $reportDir "sanitized-snippets")
|
||||
|
||||
$report = [ordered]@{
|
||||
id = $id
|
||||
title = $Title
|
||||
created_at = $createdAt.ToString("o")
|
||||
system_version = $systemVersion
|
||||
area = $Area
|
||||
severity = $Severity
|
||||
trigger_context = $TriggerContext
|
||||
actual_behavior = $Actual
|
||||
expected_behavior = $Expected
|
||||
repro_steps = @($ReproSteps)
|
||||
impact = $Impact
|
||||
workaround = $Workaround
|
||||
depersonalization_notes = $DepersonalizationNotes
|
||||
privacy_confirmed = [bool]$PrivacyConfirmed
|
||||
}
|
||||
|
||||
$jsonPath = Join-Path $reportDir "report.json"
|
||||
$report | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath $jsonPath -Encoding UTF8
|
||||
|
||||
$stepsText = if (@($ReproSteps).Count -gt 0) {
|
||||
(@($ReproSteps) | ForEach-Object { "- $_" }) -join "`n"
|
||||
}
|
||||
else {
|
||||
"- Not provided"
|
||||
}
|
||||
|
||||
$markdown = @"
|
||||
# $id - $Title
|
||||
|
||||
- Created at: $($report.created_at)
|
||||
- System version: $systemVersion
|
||||
- Area: $Area
|
||||
- Severity: $Severity
|
||||
- Privacy confirmed: $([bool]$PrivacyConfirmed)
|
||||
|
||||
## Trigger Context
|
||||
$TriggerContext
|
||||
|
||||
## Actual Behavior
|
||||
$Actual
|
||||
|
||||
## Expected Behavior
|
||||
$Expected
|
||||
|
||||
## Reproduction Steps
|
||||
$stepsText
|
||||
|
||||
## Impact
|
||||
$Impact
|
||||
|
||||
## Workaround
|
||||
$Workaround
|
||||
|
||||
## Depersonalization Notes
|
||||
$DepersonalizationNotes
|
||||
|
||||
## Sanitized Attachments
|
||||
Place only manually cleaned snippets in sanitized-snippets/. Do not attach source client documents, copied Figma content, credentials, private names, or identifiers.
|
||||
"@
|
||||
|
||||
$markdownPath = Join-Path $reportDir "report.md"
|
||||
Set-Content -LiteralPath $markdownPath -Value $markdown -Encoding UTF8
|
||||
|
||||
Set-Content -LiteralPath (Join-Path $reportDir "sanitized-snippets/.gitkeep") -Value "" -Encoding UTF8
|
||||
|
||||
Write-Output "Bug report created: $reportDir"
|
||||
Write-Output $jsonPath
|
||||
Write-Output $markdownPath
|
||||
@@ -0,0 +1,154 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$InputPath = ".",
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$OutputDir = "workspace/artifacts/wireframe-gen/extracted"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function New-Slug {
|
||||
param([string]$Value)
|
||||
$slug = [System.IO.Path]::GetFileNameWithoutExtension($Value).ToLowerInvariant()
|
||||
$slug = $slug -replace "[^a-z0-9]+", "-"
|
||||
$slug = $slug.Trim("-")
|
||||
if ([string]::IsNullOrWhiteSpace($slug)) { return "source" }
|
||||
return $slug
|
||||
}
|
||||
|
||||
function Get-TextFileContent {
|
||||
param([string]$Path)
|
||||
return Get-Content -LiteralPath $Path -Raw -Encoding UTF8
|
||||
}
|
||||
|
||||
function Get-DocxText {
|
||||
param([string]$Path)
|
||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||
$zip = [System.IO.Compression.ZipFile]::OpenRead((Resolve-Path -LiteralPath $Path))
|
||||
try {
|
||||
$parts = $zip.Entries | Where-Object {
|
||||
$_.FullName -eq "word/document.xml" -or
|
||||
$_.FullName -like "word/header*.xml" -or
|
||||
$_.FullName -like "word/footer*.xml"
|
||||
}
|
||||
$texts = New-Object System.Collections.Generic.List[string]
|
||||
foreach ($part in $parts) {
|
||||
$reader = New-Object System.IO.StreamReader($part.Open())
|
||||
try {
|
||||
$xml = $reader.ReadToEnd()
|
||||
$xml = $xml -replace "</w:p>", "`n"
|
||||
$matches = [regex]::Matches($xml, "<w:t[^>]*>(.*?)</w:t>")
|
||||
foreach ($match in $matches) {
|
||||
$texts.Add([System.Net.WebUtility]::HtmlDecode($match.Groups[1].Value))
|
||||
}
|
||||
$texts.Add("`n")
|
||||
}
|
||||
finally {
|
||||
$reader.Dispose()
|
||||
}
|
||||
}
|
||||
return (($texts -join "") -replace "`r", "" -replace "`n{3,}", "`n`n").Trim()
|
||||
}
|
||||
finally {
|
||||
$zip.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
function Get-PdfText {
|
||||
param([string]$Path)
|
||||
$tool = Get-Command pdftotext -ErrorAction SilentlyContinue
|
||||
if ($null -eq $tool) {
|
||||
return @{
|
||||
Text = ""
|
||||
Note = "PDF extraction requires pdftotext in PATH. No OCR fallback is used in v1."
|
||||
Status = "needs_external_extractor"
|
||||
}
|
||||
}
|
||||
|
||||
$tempFile = Join-Path ([System.IO.Path]::GetTempPath()) ("wireframe-pdf-" + [guid]::NewGuid().ToString() + ".txt")
|
||||
& $tool.Source "-layout" $Path $tempFile | Out-Null
|
||||
$text = Get-Content -LiteralPath $tempFile -Raw -Encoding UTF8
|
||||
Remove-Item -LiteralPath $tempFile -Force
|
||||
return @{
|
||||
Text = $text
|
||||
Note = "Extracted with pdftotext."
|
||||
Status = "ok"
|
||||
}
|
||||
}
|
||||
|
||||
function Get-SourceFiles {
|
||||
param([string]$Path)
|
||||
$resolved = Resolve-Path -LiteralPath $Path
|
||||
$item = Get-Item -LiteralPath $resolved
|
||||
$extensions = @(".md", ".markdown", ".txt", ".pdf", ".docx")
|
||||
if ($item.PSIsContainer) {
|
||||
return Get-ChildItem -LiteralPath $item.FullName -Recurse -File |
|
||||
Where-Object { $extensions -contains $_.Extension.ToLowerInvariant() } |
|
||||
Sort-Object FullName
|
||||
}
|
||||
if ($extensions -contains $item.Extension.ToLowerInvariant()) {
|
||||
return @($item)
|
||||
}
|
||||
throw "Unsupported input file type: $($item.Extension)"
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $OutputDir | Out-Null
|
||||
$files = Get-SourceFiles -Path $InputPath
|
||||
$sources = New-Object System.Collections.Generic.List[object]
|
||||
$index = 1
|
||||
|
||||
foreach ($file in $files) {
|
||||
$sourceId = "SRC-{0:D3}" -f $index
|
||||
$extension = $file.Extension.ToLowerInvariant()
|
||||
$status = "ok"
|
||||
$notes = ""
|
||||
$text = ""
|
||||
|
||||
try {
|
||||
switch ($extension) {
|
||||
".md" { $text = Get-TextFileContent -Path $file.FullName }
|
||||
".markdown" { $text = Get-TextFileContent -Path $file.FullName }
|
||||
".txt" { $text = Get-TextFileContent -Path $file.FullName }
|
||||
".docx" { $text = Get-DocxText -Path $file.FullName }
|
||||
".pdf" {
|
||||
$pdf = Get-PdfText -Path $file.FullName
|
||||
$text = $pdf.Text
|
||||
$notes = $pdf.Note
|
||||
$status = $pdf.Status
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$status = "error"
|
||||
$notes = $_.Exception.Message
|
||||
}
|
||||
|
||||
$slug = New-Slug -Value $file.Name
|
||||
$outName = "$sourceId-$slug.txt"
|
||||
$outPath = Join-Path $OutputDir $outName
|
||||
Set-Content -LiteralPath $outPath -Value $text -Encoding UTF8
|
||||
|
||||
$sources.Add([ordered]@{
|
||||
source_id = $sourceId
|
||||
path = $file.FullName
|
||||
type = $extension.TrimStart(".")
|
||||
status = $status
|
||||
extracted_text_path = $outPath
|
||||
character_count = $text.Length
|
||||
notes = $notes
|
||||
})
|
||||
$index += 1
|
||||
}
|
||||
|
||||
$inventory = [ordered]@{
|
||||
generated_at = (Get-Date).ToUniversalTime().ToString("o")
|
||||
input_path = (Resolve-Path -LiteralPath $InputPath).Path
|
||||
output_dir = (Resolve-Path -LiteralPath $OutputDir).Path
|
||||
sources = $sources
|
||||
}
|
||||
|
||||
$inventoryPath = Join-Path $OutputDir "source_inventory.json"
|
||||
$inventory | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath $inventoryPath -Encoding UTF8
|
||||
Write-Output "Extracted $($sources.Count) source(s) to $OutputDir"
|
||||
Write-Output $inventoryPath
|
||||
@@ -0,0 +1,77 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$ArtifactDir = "workspace/artifacts/wireframe-gen"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
New-Item -ItemType Directory -Force -Path $ArtifactDir | Out-Null
|
||||
|
||||
function Write-IfMissing {
|
||||
param(
|
||||
[string]$Path,
|
||||
[string]$Value
|
||||
)
|
||||
if (-not (Test-Path -LiteralPath $Path)) {
|
||||
Set-Content -LiteralPath $Path -Value $Value -Encoding UTF8
|
||||
}
|
||||
}
|
||||
|
||||
Write-IfMissing (Join-Path $ArtifactDir "source_inventory.json") @'
|
||||
{
|
||||
"generated_at": "",
|
||||
"input_path": "",
|
||||
"output_dir": "",
|
||||
"sources": []
|
||||
}
|
||||
'@
|
||||
|
||||
Write-IfMissing (Join-Path $ArtifactDir "normalized_project.json") @'
|
||||
{
|
||||
"project": {},
|
||||
"audiences": [],
|
||||
"goals": [],
|
||||
"actors": [],
|
||||
"functional_modules": [],
|
||||
"entities": [],
|
||||
"rules": [],
|
||||
"constraints": [],
|
||||
"risks": [],
|
||||
"open_questions": [],
|
||||
"source_trace": []
|
||||
}
|
||||
'@
|
||||
|
||||
Write-IfMissing (Join-Path $ArtifactDir "ux_spec.json") @'
|
||||
{
|
||||
"information_architecture": [],
|
||||
"user_flows": [],
|
||||
"screen_inventory": [],
|
||||
"screen_purposes": [],
|
||||
"ux_decisions": [],
|
||||
"research_citations": [],
|
||||
"acceptance_criteria": []
|
||||
}
|
||||
'@
|
||||
|
||||
Write-IfMissing (Join-Path $ArtifactDir "screen_blueprints.json") "[]"
|
||||
|
||||
Write-IfMissing (Join-Path $ArtifactDir "figma_build_manifest.json") @'
|
||||
{
|
||||
"file_key": "",
|
||||
"page": "",
|
||||
"screen_ids": [],
|
||||
"created_node_ids": [],
|
||||
"mutated_node_ids": [],
|
||||
"annotation_node_ids": [],
|
||||
"screenshots": [],
|
||||
"validation_notes": [],
|
||||
"known_issues": []
|
||||
}
|
||||
'@
|
||||
|
||||
Write-IfMissing (Join-Path $ArtifactDir "normalized_project.summary.md") "# Normalized Project Summary`n"
|
||||
Write-IfMissing (Join-Path $ArtifactDir "ux_spec.summary.md") "# UX Spec Summary`n"
|
||||
Write-IfMissing (Join-Path $ArtifactDir "figma_validation.md") "# Figma Validation`n"
|
||||
Write-IfMissing (Join-Path $ArtifactDir "decision_log.md") "# Decision Log`n"
|
||||
|
||||
Write-Output "Initialized artifacts in $ArtifactDir"
|
||||
@@ -0,0 +1,53 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$RegistryPath = ".agents/skills/_shared/references/ux-research-registry.json",
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Id,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Title,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Url,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$Publisher = "",
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string[]]$Domains = @(),
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string[]]$Claims = @()
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
if (-not (Test-Path -LiteralPath $RegistryPath)) {
|
||||
throw "Registry not found: $RegistryPath"
|
||||
}
|
||||
|
||||
$registry = Get-Content -LiteralPath $RegistryPath -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||
$sources = @($registry.sources)
|
||||
$existing = $sources | Where-Object { $_.id -eq $Id } | Select-Object -First 1
|
||||
|
||||
$entry = [ordered]@{
|
||||
id = $Id
|
||||
title = $Title
|
||||
url = $Url
|
||||
publisher = $Publisher
|
||||
domains = @($Domains)
|
||||
claims = @($Claims)
|
||||
last_checked = (Get-Date).ToString("yyyy-MM-dd")
|
||||
}
|
||||
|
||||
if ($null -ne $existing) {
|
||||
$sources = $sources | Where-Object { $_.id -ne $Id }
|
||||
}
|
||||
|
||||
$sources += [pscustomobject]$entry
|
||||
$registry.last_updated = (Get-Date).ToString("yyyy-MM-dd")
|
||||
$registry.sources = @($sources | Sort-Object id)
|
||||
|
||||
$registry | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath $RegistryPath -Encoding UTF8
|
||||
Write-Output "Updated registry entry: $Id"
|
||||
@@ -0,0 +1,291 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$ArtifactDir = "workspace/artifacts/wireframe-gen",
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$Strict,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[ValidateSet("schema", "pre-ux", "pre-figma")]
|
||||
[string]$Stage = "schema"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$errors = New-Object System.Collections.Generic.List[string]
|
||||
|
||||
function Read-Json {
|
||||
param([string]$Path)
|
||||
try {
|
||||
return Get-Content -LiteralPath $Path -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||
}
|
||||
catch {
|
||||
$script:errors.Add("Invalid JSON: $Path - $($_.Exception.Message)")
|
||||
return $null
|
||||
}
|
||||
}
|
||||
|
||||
function Has-Property {
|
||||
param(
|
||||
[object]$Object,
|
||||
[string]$Name
|
||||
)
|
||||
if ($null -eq $Object) { return $false }
|
||||
return $null -ne ($Object.PSObject.Properties | Where-Object { $_.Name -eq $Name })
|
||||
}
|
||||
|
||||
function Test-RequiredFields {
|
||||
param(
|
||||
[object]$Object,
|
||||
[string[]]$Fields,
|
||||
[string]$Label
|
||||
)
|
||||
foreach ($field in $Fields) {
|
||||
if (-not (Has-Property -Object $Object -Name $field)) {
|
||||
$script:errors.Add("$Label missing required field: $field")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Test-IsNumeric {
|
||||
param([object]$Value)
|
||||
return (
|
||||
$Value -is [byte] -or
|
||||
$Value -is [sbyte] -or
|
||||
$Value -is [int16] -or
|
||||
$Value -is [uint16] -or
|
||||
$Value -is [int32] -or
|
||||
$Value -is [uint32] -or
|
||||
$Value -is [int64] -or
|
||||
$Value -is [uint64] -or
|
||||
$Value -is [single] -or
|
||||
$Value -is [double] -or
|
||||
$Value -is [decimal]
|
||||
)
|
||||
}
|
||||
|
||||
function Test-GridNumber {
|
||||
param(
|
||||
[object]$Value,
|
||||
[string]$Label,
|
||||
[switch]$Positive
|
||||
)
|
||||
|
||||
if (-not (Test-IsNumeric -Value $Value)) {
|
||||
$script:errors.Add("$Label must be numeric")
|
||||
return
|
||||
}
|
||||
|
||||
$number = [double]$Value
|
||||
if ($Positive -and $number -lt 1) {
|
||||
$script:errors.Add("$Label must be at least 1")
|
||||
}
|
||||
|
||||
$rounded = [math]::Round($number)
|
||||
if ([math]::Abs($number - $rounded) -gt 0.000001) {
|
||||
$script:errors.Add("$Label must be a whole number")
|
||||
return
|
||||
}
|
||||
|
||||
if (([int64]$rounded % 4) -ne 0) {
|
||||
$script:errors.Add("$Label must be a multiple of 4")
|
||||
}
|
||||
}
|
||||
|
||||
function Test-LayoutNumbers {
|
||||
param(
|
||||
[object]$Value,
|
||||
[string]$Path = "screen_blueprints"
|
||||
)
|
||||
|
||||
if ($null -eq $Value) { return }
|
||||
|
||||
if ($Value -is [System.Array]) {
|
||||
for ($i = 0; $i -lt $Value.Count; $i++) {
|
||||
Test-LayoutNumbers -Value $Value[$i] -Path "$Path[$i]"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (Test-IsNumeric -Value $Value) { return }
|
||||
if ($Value -is [string]) { return }
|
||||
|
||||
$layoutNamePattern = '^(x|y|width|height|minWidth|maxWidth|minHeight|maxHeight|min_width|max_width|min_height|max_height|top|right|bottom|left|padding|paddingTop|paddingRight|paddingBottom|paddingLeft|padding_top|padding_right|padding_bottom|padding_left|margin|marginTop|marginRight|marginBottom|marginLeft|margin_top|margin_right|margin_bottom|margin_left|gap|rowGap|columnGap|row_gap|column_gap|spacing|radius|borderRadius|cornerRadius|border_radius|corner_radius|inset|offset)$'
|
||||
|
||||
foreach ($prop in $Value.PSObject.Properties) {
|
||||
$propPath = "$Path.$($prop.Name)"
|
||||
if ((Test-IsNumeric -Value $prop.Value) -and ($prop.Name -match $layoutNamePattern)) {
|
||||
Test-GridNumber -Value $prop.Value -Label $propPath
|
||||
}
|
||||
elseif ($prop.Value -is [System.Array] -or ($null -ne $prop.Value -and -not ($prop.Value -is [string]) -and -not (Test-IsNumeric -Value $prop.Value) -and $prop.Value.PSObject.Properties.Count -gt 0)) {
|
||||
Test-LayoutNumbers -Value $prop.Value -Path $propPath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Get-QuestionStatus {
|
||||
param([object]$Question)
|
||||
if (Has-Property -Object $Question -Name "status" -and -not [string]::IsNullOrWhiteSpace([string]$Question.status)) {
|
||||
return ([string]$Question.status).ToLowerInvariant()
|
||||
}
|
||||
return "unresolved"
|
||||
}
|
||||
|
||||
function Test-QuestionIsResolved {
|
||||
param([object]$Question)
|
||||
$status = Get-QuestionStatus -Question $Question
|
||||
return @("resolved", "answered", "closed") -contains $status
|
||||
}
|
||||
|
||||
function Get-QuestionBlocks {
|
||||
param([object]$Question)
|
||||
if (-not (Has-Property -Object $Question -Name "blocks") -or $null -eq $Question.blocks) {
|
||||
return @()
|
||||
}
|
||||
return @($Question.blocks | ForEach-Object { ([string]$_).ToLowerInvariant() })
|
||||
}
|
||||
|
||||
function Test-QuestionBlocksStage {
|
||||
param(
|
||||
[object]$Question,
|
||||
[string]$StageName
|
||||
)
|
||||
|
||||
$blocks = Get-QuestionBlocks -Question $Question
|
||||
if ($blocks.Count -eq 0) { return $false }
|
||||
|
||||
$common = @("all", "pipeline", "project", "workflow")
|
||||
$preUx = @("ux", "pre-ux", "ux-construction", "ux-constructor", "ux-spec", "screen-blueprints", "screen_blueprints")
|
||||
$preFigma = @("figma", "pre-figma", "figma-build", "figma-generation", "figma-target", "roles", "role", "screens", "screen", "critical-states", "states", "screen-blueprints", "screen_blueprints")
|
||||
|
||||
foreach ($block in $blocks) {
|
||||
if ($common -contains $block) { return $true }
|
||||
if ($StageName -eq "pre-ux" -and $preUx -contains $block) { return $true }
|
||||
if ($StageName -eq "pre-figma" -and $preFigma -contains $block) { return $true }
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
|
||||
function Test-OpenQuestionGate {
|
||||
param(
|
||||
[object]$NormalizedProject,
|
||||
[string]$StageName
|
||||
)
|
||||
|
||||
if ($StageName -eq "schema" -or $null -eq $NormalizedProject -or -not (Has-Property -Object $NormalizedProject -Name "open_questions")) {
|
||||
return
|
||||
}
|
||||
|
||||
foreach ($question in @($NormalizedProject.open_questions)) {
|
||||
if (-not (Test-QuestionBlocksStage -Question $question -StageName $StageName)) {
|
||||
continue
|
||||
}
|
||||
|
||||
$id = if (Has-Property -Object $question -Name "id") { [string]$question.id } else { "(missing id)" }
|
||||
$text = if (Has-Property -Object $question -Name "question") { [string]$question.question } else { "(missing question text)" }
|
||||
|
||||
if (-not (Test-QuestionIsResolved -Question $question)) {
|
||||
$errors.Add("Open question $id blocks ${StageName}: $text")
|
||||
continue
|
||||
}
|
||||
|
||||
if (-not (Has-Property -Object $question -Name "answer") -or [string]::IsNullOrWhiteSpace([string]$question.answer)) {
|
||||
$errors.Add("Open question $id is resolved for ${StageName} but missing answer")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$contracts = @{
|
||||
"source_inventory.json" = @("generated_at", "input_path", "output_dir", "sources")
|
||||
"normalized_project.json" = @("project", "audiences", "goals", "actors", "functional_modules", "entities", "rules", "constraints", "risks", "open_questions", "source_trace")
|
||||
"ux_spec.json" = @("information_architecture", "user_flows", "screen_inventory", "screen_purposes", "ux_decisions", "research_citations", "acceptance_criteria")
|
||||
"figma_build_manifest.json" = @("file_key", "page", "screen_ids", "created_node_ids", "mutated_node_ids", "screenshots", "validation_notes", "known_issues")
|
||||
}
|
||||
|
||||
$artifactJsons = @{}
|
||||
foreach ($fileName in $contracts.Keys) {
|
||||
$path = Join-Path $ArtifactDir $fileName
|
||||
if (-not (Test-Path -LiteralPath $path)) {
|
||||
if ($Strict) { $errors.Add("Missing artifact: $path") }
|
||||
continue
|
||||
}
|
||||
$json = Read-Json -Path $path
|
||||
$artifactJsons[$fileName] = $json
|
||||
Test-RequiredFields -Object $json -Fields $contracts[$fileName] -Label $fileName
|
||||
}
|
||||
|
||||
if ($artifactJsons.ContainsKey("normalized_project.json")) {
|
||||
Test-OpenQuestionGate -NormalizedProject $artifactJsons["normalized_project.json"] -StageName $Stage
|
||||
}
|
||||
|
||||
$blueprintsPath = Join-Path $ArtifactDir "screen_blueprints.json"
|
||||
if (Test-Path -LiteralPath $blueprintsPath) {
|
||||
$blueprints = Read-Json -Path $blueprintsPath
|
||||
if ($null -ne $blueprints) {
|
||||
$items = @($blueprints)
|
||||
$screenRequired = @("content_type", "screen_id", "viewport", "purpose", "sections", "components", "states", "content_requirements", "interactions", "empty_error_loading_states")
|
||||
$elementRequired = @("content_type", "element_id", "parent_screen_id", "bounds")
|
||||
for ($i = 0; $i -lt $items.Count; $i++) {
|
||||
$item = $items[$i]
|
||||
$label = "screen_blueprints[$i]"
|
||||
Test-RequiredFields -Object $item -Fields @("content_type") -Label $label
|
||||
|
||||
if (-not (Has-Property -Object $item -Name "content_type")) {
|
||||
continue
|
||||
}
|
||||
|
||||
switch ($item.content_type) {
|
||||
"screen" {
|
||||
Test-RequiredFields -Object $item -Fields $screenRequired -Label $label
|
||||
if (Has-Property -Object $item -Name "viewport") {
|
||||
Test-RequiredFields -Object $item.viewport -Fields @("width", "height") -Label "$label.viewport"
|
||||
if (Has-Property -Object $item.viewport -Name "width") {
|
||||
Test-GridNumber -Value $item.viewport.width -Label "$label.viewport.width" -Positive
|
||||
if ([double]$item.viewport.width -ne 1440) {
|
||||
$errors.Add("$label.viewport.width must equal 1440 for content_type screen")
|
||||
}
|
||||
}
|
||||
if (Has-Property -Object $item.viewport -Name "height") {
|
||||
Test-GridNumber -Value $item.viewport.height -Label "$label.viewport.height" -Positive
|
||||
if ([double]$item.viewport.height -lt 800) {
|
||||
$errors.Add("$label.viewport.height must be at least 800 for content_type screen")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"element" {
|
||||
Test-RequiredFields -Object $item -Fields $elementRequired -Label $label
|
||||
if (Has-Property -Object $item -Name "bounds") {
|
||||
Test-RequiredFields -Object $item.bounds -Fields @("width", "height") -Label "$label.bounds"
|
||||
foreach ($field in @("x", "y", "width", "height")) {
|
||||
if (Has-Property -Object $item.bounds -Name $field) {
|
||||
$positive = $field -eq "width" -or $field -eq "height"
|
||||
if ($positive) {
|
||||
Test-GridNumber -Value $item.bounds.$field -Label "$label.bounds.$field" -Positive
|
||||
}
|
||||
else {
|
||||
Test-GridNumber -Value $item.bounds.$field -Label "$label.bounds.$field"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
default {
|
||||
$errors.Add("$label.content_type must be either screen or element")
|
||||
}
|
||||
}
|
||||
|
||||
Test-LayoutNumbers -Value $item -Path $label
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ($Strict) {
|
||||
$errors.Add("Missing artifact: $blueprintsPath")
|
||||
}
|
||||
|
||||
if ($errors.Count -gt 0) {
|
||||
$errors | ForEach-Object { Write-Error $_ }
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Output "Artifact validation passed for $ArtifactDir"
|
||||
Reference in New Issue
Block a user