# build-site.ps1 v2 - generate site/data.js for the docs-style showcase # Adds: category classification, per-component spec section extraction, # design-contract parsing (core 6), design-token extraction. # Usage: run from repo root: & .\build-site.ps1 # NOTE: keep this script ASCII-only (Windows PowerShell parses BOM-less .ps1 in ANSI codepage). $ErrorActionPreference = 'Stop' $lib = ".design_library/aurora-admin" $idx = Get-Content "$lib/components/index.json" -Raw -Encoding UTF8 | ConvertFrom-Json $fw = Get-ChildItem frameworks -File $fwMap = @{} foreach ($f in $fw) { $fwMap[$f.Name.ToLowerInvariant()] = $f.Name } function Find-FwFile([string]$name) { $key = $name.ToLowerInvariant() if ($fwMap.ContainsKey($key)) { $fwMap[$key] } else { $null } } # ---------------- category classification ---------------- $catMap = @{} $catMap['general'] = @('button','buttongroup','tag','card') $catMap['navigation']= @('tabs','steps','breadcrumb','sidemenu','topmenu','mixednavigation','quicknav','anchornav','enhancedtabnav','dropdown') $catMap['input'] = @('input','select','cascader','colorpicker','rangequickpicker','numberrangeinput','switchgroup','dragupload','signaturepad','codeinput','passwordinput','phoneinput','bankcardinput','idcardinput','plateinput','autocomplete','mention','transfer','rate','slider','listpicker','radiocard','checkboxcard','segmented') $catMap['display'] = @('table','treetable','expandabletable','mergedcelltable','summaryrowtable','fixedcolumntable','cardlist','timelinelist','steplist','chartpanel','dashboardcard','calendar','carousel','imagepreview','qrcode','countdown','tree','collapse','watermark','employeecard') $catMap['feedback'] = @('modal','confirmmodal','alertmodal','formmodal','fullscreenmodal','messagepro','notificationpro','progressvariants','skeletonpro','emptypro','loadingoverlay','resultvariants','exceptionvariants','popconfirm','bottomsheet') $catMap['system'] = @('themeswitcher','layoutswitcher','densityswitcher','shortcutpanel','backtotop','pagetransition') $slugCat = @{} foreach ($ck in $catMap.Keys) { foreach ($s in $catMap[$ck]) { $slugCat[$s] = $ck } } # ---------------- spec section extraction ---------------- function Extract-SpecSection([string]$specPath, [string]$enToken) { if (-not (Test-Path $specPath)) { return $null } $lines = Get-Content $specPath -Encoding UTF8 $hit = -1 $re = [regex]('^(#{2,4})\s+(.*)$') for ($n = 0; $n -lt $lines.Count; $n++) { $m = $re.Match($lines[$n]) if ($m.Success -and ($m.Groups[2].Value -match ('\b' + [regex]::Escape($enToken) + '\b'))) { $hit = $n break } } if ($hit -lt 0) { return $null } $body = New-Object System.Collections.Generic.List[string] for ($n = $hit + 1; $n -lt $lines.Count; $n++) { if ($lines[$n] -match '^#{1,4}\s') { break } [void]$body.Add($lines[$n]) } while ($body.Count -gt 0 -and $body[0].Trim() -eq '') { $body.RemoveAt(0) } while ($body.Count -gt 0 -and $body[$body.Count-1].Trim() -eq '') { $body.RemoveAt($body.Count-1) } if ($body.Count -eq 0) { return $null } $summary = '' foreach ($L in $body) { if ($L.Trim() -ne '') { $summary = $L.Trim(); break } } return @{ lines = $body; summary = $summary } } # ---------------- design contract parsing (core 6) ---------------- function Parse-Contract([string]$path) { if (-not (Test-Path $path)) { return $null } $c = Get-Content $path -Raw -Encoding UTF8 | ConvertFrom-Json $dims = @() foreach ($d in $c.variantDimensions) { $dims += ,@{ name = $d.name; values = @($d.values) } } $variants = @() foreach ($v in $c.representativeVariants) { $variants += [string]$v.label } $usage = @(); foreach ($u in $c.usageHints) { $usage += [string]$u } $doNot = @(); if ($c.doNotInvent) { foreach ($x in $c.doNotInvent) { $doNot += [string]$x } } $unknowns = @(); if ($c.unknowns) { foreach ($x in $c.unknowns) { $unknowns += [string]$x } } function PsObjToMap($obj) { $m = [ordered]@{} if ($obj) { foreach ($p in $obj.PSObject.Properties) { $m[$p.Name] = [string]$p.Value } } return $m } return @{ dims = $dims variants = $variants usage = $usage structure = PsObjToMap $c.structurePatterns anatomy = PsObjToMap $c.anatomy doNot = $doNot unknowns = $unknowns } } # ---------------- design tokens from colors_and_type.css ---------------- $tokenRe = [regex]'--(au-[a-z0-9-]+)\s*:\s*([^;]+);(?:\s*/\*\s*(.*?)\s*\*/)?' $tokens = @() foreach ($m in $tokenRe.Matches((Get-Content "$lib/colors_and_type.css" -Raw -Encoding UTF8))) { $tokens += ,@{ name = $m.Groups[1].Value; value = $m.Groups[2].Value.Trim(); comment = $m.Groups[3].Value.Trim() } } # ---------------- assemble components ---------------- $kinds = @('html','css','jsx','vue2','vue3') $out = @() $missing = @() $noCat = @() $i = 0 foreach ($c in $idx.components) { $i++ $p = $c.frameworksPrefix $filesMap = [ordered]@{} $sizesMap = [ordered]@{} $sourcesMap = [ordered]@{} foreach ($k in $kinds) { $ext = switch ($k) { 'html' { ".html" } 'css' { ".css" } 'jsx' { ".jsx" } 'vue2' { ".vue2.vue" } 'vue3' { ".vue3.vue" } } $fname = Find-FwFile ("{0}{1}" -f $p, $ext) if ($fname) { $filesMap[$k] = "../frameworks/$fname" $fInfo = Get-Item ("frameworks/{0}" -f $fname) $sizesMap[$k] = [int]$fInfo.Length $sourcesMap[$k] = Get-Content $fInfo.FullName -Raw -Encoding UTF8 } else { $missing += ("{0}:{1}" -f $c.slug, $k) } } $cat = $null if ($slugCat.ContainsKey($c.slug)) { $cat = $slugCat[$c.slug] } else { $noCat += $c.slug } $nameStr = [string]$c.name $enToken = $nameStr.Split(' ')[-1] $spec = Extract-SpecSection (Join-Path "." $c.specFile) $enToken $contract = $null if ($c.contract) { $contract = Parse-Contract (Join-Path $lib $c.contract) } $entry = [ordered]@{ slug = $c.slug name = $nameStr tier = $c.tier confidence = $c.confidence specBatch = [int]$c.specBatch specFile = $c.specFile contractRef = $c.contract category = $cat specLines = $(if ($spec) { @($spec.lines) } else { @() }) specSummary = $(if ($spec) { $spec.summary } else { '' }) contract = $contract files = $filesMap sizes = $sizesMap sources = $sourcesMap } $out += $entry if (($i % 20) -eq 0) { Write-Host (" processed {0}/{1}" -f $i, $idx.components.Count) } } # ---------------- changelog extraction (site /changelog page + meta.version sync) ---------------- $changelog = @() if (Test-Path "CHANGELOG.md") { $clLines = Get-Content "CHANGELOG.md" -Encoding UTF8 $cur = $null foreach ($L in $clLines) { if ($L -match '^##\s+\[([0-9]+\.[0-9]+\.[0-9]+)\]\s*-\s*(\S+)') { if ($cur) { $changelog += $cur } $cur = @{ version = $Matches[1]; date = $Matches[2]; sections = @() } } elseif ($L -match '^###\s+(.+)$' -and $cur) { $cur.sections += ,@{ title = $Matches[1]; items = @() } } elseif ($L -match '^-\s+(.+)$' -and $cur -and $cur.sections.Count -gt 0) { $lastSec = $cur.sections[$cur.sections.Count - 1] $lastSec.items += $Matches[1] } } if ($cur) { $changelog += $cur } } $siteVersion = "1.0.0" if ($changelog.Count -gt 0) { $siteVersion = [string]$changelog[0].version } $catList = @() foreach ($ck in @('general','navigation','input','display','feedback','system')) { $catList += ,@{ key = $ck; count = @($slugCat.GetEnumerator() | Where-Object { $_.Value -eq $ck }).Count } } $coreCount = @($out | Where-Object { $_.tier -eq 'core' }).Count $meta = [ordered]@{ generated = Get-Date -Format "yyyy-MM-dd HH:mm" lib = "aurora-admin" total = $out.Count core = $coreCount extension = ($out.Count - $coreCount) brand = "#2F54EB" version = $siteVersion note = "generated by build-site.ps1 from components/index.json + frameworks/ + specs + contracts + tokens" } $data = [ordered]@{ meta = $meta categories = $catList tokens = $tokens changelog = $changelog components = $out } # ---------------- serialize (fast on both PS5.1 and PS7) ---------------- function Json-Escape([string]$s) { if ($null -eq $s) { return '""' } $s = $s.Replace('\', '\\').Replace('"', '\"') $s = $s.Replace("`r`n", "\n").Replace("`r", "\n").Replace("`n", "\n") $s = $s.Replace("`t", "\t").Replace("`b", "\b").Replace("`f", "\f") $s = [regex]::Replace($s, "[\x00-\x08\x0B\x0C\x0E-\x1F]", { param($m) ('\u{0:x4}' -f [int][char]$m.Value) }) return ('"' + $s + '"') } function Json-Value($v) { if ($null -eq $v) { return 'null' } if ($v -is [bool]) { if ($v) { return 'true' } else { return 'false' } } if ($v -is [int] -or $v -is [long] -or $v -is [double]) { return [string]$v } if ($v -is [string]) { return Json-Escape $v } if ($v -is [System.Collections.IDictionary]) { $sb = New-Object System.Text.StringBuilder [void]$sb.Append('{') $first = $true foreach ($key in $v.Keys) { if (-not $first) { [void]$sb.Append(',') } $first = $false [void]$sb.Append((Json-Escape ([string]$key))) [void]$sb.Append(':') [void]$sb.Append((Json-Value $v[$key])) } [void]$sb.Append('}') return $sb.ToString() } if ($v -is [System.Collections.IEnumerable]) { $sb = New-Object System.Text.StringBuilder [void]$sb.Append('[') $first = $true foreach ($item in $v) { if (-not $first) { [void]$sb.Append(',') } $first = $false [void]$sb.Append((Json-Value $item)) } [void]$sb.Append(']') return $sb.ToString() } return (Json-Escape ([string]$v)) } $json = Json-Value $data New-Item -ItemType Directory -Force -Path site | Out-Null $js = "/* AUTO-GENERATED by build-site.ps1 - do not edit by hand */`r`nwindow.AA_DATA = $json;`r`n" # UTF-8 WITHOUT BOM: BOM breaks strict JSON parsers (node require, agent toolchains) $utf8NoBom = New-Object System.Text.UTF8Encoding($false) [System.IO.File]::WriteAllText((Join-Path (Get-Location) "site/data.js"), $js, $utf8NoBom) [System.IO.File]::WriteAllText((Join-Path (Get-Location) "site/data.json"), $json, $utf8NoBom) # ---------------- design token export (W3C DTCG + Figma Tokens) ---------------- # Two standard interchange formats so the tokens can be consumed outside this repo # without hand-copying values. Both are generated here, never edited by hand. function Get-TokenGroup([string]$name) { if ($name -match 'color|brand|text|bg|border|success|warning|error|info|mask') { return 'color' } if ($name -match 'font|type') { return 'typography' } if ($name -match 'radius') { return 'radius' } if ($name -match 'space|gap|padding') { return 'spacing' } if ($name -match 'shadow|elevation') { return 'shadow' } if ($name -match 'duration|ease|motion') { return 'motion' } if ($name -match 'size|height|width|icon') { return 'size' } return 'misc' } # W3C Design Tokens Community Group format: nested groups, $value / $type. $dtcg = [ordered]@{} foreach ($t in $tokens) { $g = Get-TokenGroup $t.name if (-not $dtcg.Contains($g)) { $dtcg[$g] = [ordered]@{} } $type = switch ($g) { 'color' { 'color' } 'radius' { 'dimension' } 'spacing' { 'dimension' } 'size' { 'dimension' } 'shadow' { 'shadow' } 'typography' { 'fontFamily' } default { 'other' } } $node = [ordered]@{ '$value' = $t.value; '$type' = $type } if ($t.comment) { $node['$description'] = $t.comment } $dtcg[$g][$t.name] = $node } $dtcgDoc = [ordered]@{ '$schema' = 'https://schemas.design-tokens.org/draft/dtcg.json' '$metadata' = [ordered]@{ name = 'Aurora Admin'; version = $siteVersion; brand = '#2F54EB'; generated = (Get-Date -Format 'yyyy-MM-dd HH:mm') } 'tokens' = $dtcg } New-Item -ItemType Directory -Force -Path "site/tokens" | Out-Null [System.IO.File]::WriteAllText((Join-Path (Get-Location) "site/tokens/dtcg.json"), (Json-Value $dtcgDoc), $utf8NoBom) # Figma Tokens (Tokens Studio) format: flat "global" set, value + type + description. $figmaGlobal = [ordered]@{} foreach ($t in $tokens) { $g = Get-TokenGroup $t.name $ft = switch ($g) { 'color' { 'color' } 'size' { 'sizing' } 'spacing' { 'spacing' } 'radius' { 'borderRadius' } 'shadow' { 'boxShadow' } default { 'other' } } $figmaGlobal[$t.name] = [ordered]@{ value = $t.value; type = $ft; description = $t.comment } } $figmaDoc = [ordered]@{ global = $figmaGlobal '$themes' = @() '$metadata' = [ordered]@{ tokenSetOrder = @('global') } } [System.IO.File]::WriteAllText((Join-Path (Get-Location) "site/tokens/figma.json"), (Json-Value $figmaDoc), $utf8NoBom) # Plain CSS custom properties — drop-in for any project that just wants the variables. $cssLines = New-Object System.Text.StringBuilder [void]$cssLines.AppendLine('/* Aurora Admin design tokens - generated by build-site.ps1 */') [void]$cssLines.AppendLine(':root {') foreach ($t in $tokens) { $note = if ($t.comment) { ' /* ' + $t.comment + ' */' } else { '' } [void]$cssLines.AppendLine((' --{0}: {1};{2}' -f $t.name, $t.value, $note)) } [void]$cssLines.AppendLine('}') [System.IO.File]::WriteAllText((Join-Path (Get-Location) "site/tokens/tokens.css"), $cssLines.ToString(), $utf8NoBom) Write-Output ("token export: site/tokens/ (dtcg.json + figma.json + tokens.css), {0} tokens" -f $tokens.Count) # ---------------- static thin shells + sitemap (SEO / crawler entry points) ---------------- # UPDATE $SiteUrlBase after repository deployment (GitHub Pages URL), then rebuild once. $SiteUrlBase = "https://YOUR-ACCOUNT.github.io/aurora-admin/" $shellDir = "site/components" New-Item -ItemType Directory -Force -Path $shellDir | Out-Null function Html-Escape([string]$s) { if ($null -eq $s) { return '' } return $s.Replace('&','&').Replace('<','<').Replace('>','>').Replace('"','"') } foreach ($c in $out) { $title = Html-Escape ([string]$c.name) $desc = Html-Escape ([string]$c.specSummary) $slug = [string]$c.slug $shell = @"