Aurora Admin v1.2.0: 79 components x 5 ends, doc site, contracts batch 1, playground, regression, deploy ready

This commit is contained in:
aurora-admin
2026-09-10 19:21:56 +08:00
commit 51ce3a28f7
654 changed files with 49082 additions and 0 deletions
+292
View File
@@ -0,0 +1,292 @@
# 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)
# ---------------- 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('&','&amp;').Replace('<','&lt;').Replace('>','&gt;').Replace('"','&quot;')
}
foreach ($c in $out) {
$title = Html-Escape ([string]$c.name)
$desc = Html-Escape ([string]$c.specSummary)
$slug = [string]$c.slug
$shell = @"
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>$title - Aurora Admin</title>
<meta name="description" content="$desc">
<link rel="canonical" href="$SiteUrlBase"site/components/$slug.html">
<meta http-equiv="refresh" content="0;url=../index.html#/component/$slug">
</head>
<body>
<script>location.replace('../index.html#/component/$slug');</script>
<noscript><p><a href="../index.html#/component/$slug">Open $title</a></p></noscript>
</body>
</html>
"@
[System.IO.File]::WriteAllText((Join-Path (Get-Location) "$shellDir/$slug.html"), $shell, $utf8NoBom)
}
$sb = New-Object System.Text.StringBuilder
[void]$sb.AppendLine('<?xml version="1.0" encoding="UTF-8"?>')
[void]$sb.AppendLine('<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">')
$urls = @("site/index.html") + ($out | ForEach-Object { "site/components/$($_.slug).html" })
foreach ($u in $urls) {
[void]$sb.AppendLine(' <url><loc>' + $SiteUrlBase + $u + '</loc></url>')
}
[void]$sb.AppendLine('</urlset>')
[System.IO.File]::WriteAllText((Join-Path (Get-Location) "sitemap.xml"), $sb.ToString(), $utf8NoBom)
Write-Output ("thin shells: {0}, sitemap.xml: {1} urls" -f $out.Count, $urls.Count)
$sizeKb = (Get-Item site/data.js).Length / 1KB
Write-Output ("site/data.js written: {0:N0} KB, {1} components, {2} tokens" -f $sizeKb, $out.Count, $tokens.Count)
if ($missing.Count -gt 0) { Write-Warning ("missing framework files: " + ($missing -join ', ')) }
if ($noCat.Count -gt 0) { Write-Warning ("components without category: " + ($noCat -join ', ')) }
if ($missing.Count -eq 0 -and $noCat.Count -eq 0) { Write-Output "all components complete (5-form files + category)" }