Regression / regression (push) Canceled after 0s
## 品牌标识(本次会话) 起因:品牌此前没有任何图形标识 —— 唯一 favicon 是内联 data-URI 里的字母「A」, 那是 v2.0.0「Aurora Admin → Kole UI」改名漏掉的一处(PC 顶栏也是「A」, 移动端站已是「K」;移动端文档站则完全没有 favicon)。 - 几何:24 网格三个互不接触的笔画(竖 + 两斜),圆头描边; 描边 2.25 → 16px 标签页尺寸下正好 1.5px = 规范原文「描边1.5px」 - 取色分两套(刻意):favicon 硬编码品牌蓝/白(渲染在浏览器标签栏,不继承 kole-dark); 顶栏标记走 currentColor(实测暗色下自动转 rgb(20,22,28)) - 新增 theme-color 双条(light #FFFFFF / dark #1C1F26,取 --kole-color-card-bg) - 修 site/app.js hero 标语 KOLE ADMIN → KOLE UI(改名变形残留) - 移动端 7 个模板补 favicon(此前计数 0) 验收:门禁 9 条全 OK(site-routing/site-routes/mobile-docs/mobile-site/isolation/ theme/nav/i18n/icons);PC 回归 1464/1464 · 移动端 807/807,各连跑 8 次一致; 两端 favicon 405 字节逐字节一致;PC 站控制台错误 1→0。 ## 并行会话成果(本次一并入库) - 图标系统:2576 图标(TDesign/Element Plus,MIT)+ 11 端注入 + 5 个构建门禁工具 + IconPreview 预览页 + ICON-SPEC.md 冻结规格 - 移动端平台:47 组件 × 6 端 + 文档站 53 页 + 隔离门禁 - PC 组件:103 个大后台组件 / 组件11 批次 - uni-app:PC 端试点 + 移动端端实现 + 真实编译验证 ## 工程 - .gitignore 补 .scratch/ 与 .zcode-preexisting-*.txt(会话中间产物,实测 9.1MB,不入库) - CHANGELOG 补品牌标识条目 - ROADMAP 登 S8-P4(品牌标识任务包 + og:image/apple-touch-icon 未做部分)
493 lines
21 KiB
PowerShell
493 lines
21 KiB
PowerShell
# 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/kole-ui"
|
|
|
|
$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','layout','container','typography','icon','link')
|
|
$catMap['navigation']= @('tabs','steps','breadcrumb','sidemenu','topmenu','mixednavigation','quicknav','anchornav','enhancedtabnav','dropdown','pageheader')
|
|
$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','radio','checkbox','inputnumber','switch','timepicker','datepicker','datetimepicker','form')
|
|
$catMap['display'] = @('table','treetable','expandabletable','mergedcelltable','summaryrowtable','fixedcolumntable','cardlist','timelinelist','steplist','chartpanel','dashboardcard','calendar','carousel','imagepreview','qrcode','countdown','tree','collapse','watermark','employeecard','pagination','badge','avatar','descriptions','divider','infinitescroll')
|
|
$catMap['feedback'] = @('modal','confirmmodal','alertmodal','formmodal','fullscreenmodal','messagepro','notificationpro','progressvariants','skeletonpro','emptypro','loadingoverlay','resultvariants','exceptionvariants','popconfirm','bottomsheet','alert','tooltip','popover','drawer')
|
|
$catMap['system'] = @('themeswitcher','layoutswitcher','densityswitcher','shortcutpanel','backtotop','pagetransition')
|
|
$slugCat = @{}
|
|
foreach ($ck in $catMap.Keys) { foreach ($s in $catMap[$ck]) { $slugCat[$s] = $ck } }
|
|
|
|
# ---------------- component families (variant parameterization) ----------------
|
|
# families.json is generated by tools/gen-families.mjs from tools/lib/family-model.mjs.
|
|
# A family expresses several same-origin components as ONE base component plus
|
|
# parameter values. Component identity (slug) never changes: data.json still lists
|
|
# all 79 components; the family layer is additive metadata on top of them.
|
|
#
|
|
# PS 5.1 notes (both are real traps, do not "simplify"):
|
|
# 1. ConvertFrom-Json yields PSCustomObject, which Json-Value below does NOT
|
|
# understand -- it would fall through to Json-Escape and emit "@{...}".
|
|
# 2. An empty array returned from a function gets unrolled to $null. So arrays are
|
|
# built and assigned to hashtable slots INSIDE the helper, never returned.
|
|
$famDoc = $null
|
|
$famRaw = ''
|
|
$famOf = @{}
|
|
$famPath = "$lib/families.json"
|
|
if (Test-Path $famPath) {
|
|
$famRaw = (Get-Content $famPath -Raw -Encoding UTF8).Trim()
|
|
$famDoc = $famRaw | ConvertFrom-Json
|
|
foreach ($f in $famDoc.families) {
|
|
foreach ($m in $f.members) {
|
|
$pv = [ordered]@{}
|
|
if ($m.params) {
|
|
foreach ($p in $m.params.PSObject.Properties) {
|
|
$val = $p.Value
|
|
if ($null -eq $val) { $pv[$p.Name] = $null }
|
|
elseif ($val -is [System.Object[]] -or $val -is [System.Collections.ArrayList]) {
|
|
$arr = @()
|
|
foreach ($x in $val) { $arr += $x }
|
|
$pv[$p.Name] = $arr
|
|
}
|
|
else { $pv[$p.Name] = $val }
|
|
}
|
|
}
|
|
$famOf[[string]$m.slug] = [ordered]@{
|
|
family = [string]$f.id
|
|
role = [string]$m.role
|
|
params = $pv
|
|
}
|
|
}
|
|
}
|
|
Write-Output ("families: {0} families, {1} member components" -f $famDoc.totalFamilies, $famDoc.totalMembers)
|
|
} else {
|
|
Write-Warning "families.json not found; the family layer will be omitted from data.json"
|
|
}
|
|
|
|
# ---------------- 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 } }
|
|
$notes = @(); if ($c.clarificationNotes) {
|
|
foreach ($n in $c.clarificationNotes) {
|
|
$notes += [ordered]@{
|
|
item = [string]$n.item
|
|
kind = [string]$n.kind
|
|
meaning = [string]$n.meaning
|
|
why = [string]$n.why
|
|
decision = [string]$n.decision
|
|
}
|
|
}
|
|
}
|
|
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
|
|
clarificationNotes = $notes
|
|
}
|
|
}
|
|
|
|
# ---------------- design tokens from colors_and_type.css ----------------
|
|
$tokenRe = [regex]'--(kole-[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
|
|
family = $(if ($famOf.ContainsKey([string]$c.slug)) { $famOf[[string]$c.slug].family } else { $null })
|
|
familyRole = $(if ($famOf.ContainsKey([string]$c.slug)) { $famOf[[string]$c.slug].role } else { $null })
|
|
familyParams= $(if ($famOf.ContainsKey([string]$c.slug)) { $famOf[[string]$c.slug].params } else { $null })
|
|
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 = "kole-ui"
|
|
total = $out.Count
|
|
core = $coreCount
|
|
extension = ($out.Count - $coreCount)
|
|
brand = "#2F54EB"
|
|
version = $siteVersion
|
|
families = $(if ($famDoc) { [int]$famDoc.totalFamilies } else { 0 })
|
|
familyMembers = $(if ($famDoc) { [int]$famDoc.totalMembers } else { 0 })
|
|
note = "generated by build-site.ps1 from components/index.json + frameworks/ + specs + contracts + tokens + families"
|
|
}
|
|
|
|
$data = [ordered]@{
|
|
meta = $meta
|
|
categories = $catList
|
|
tokens = $tokens
|
|
families = '__AA_FAMILIES_RAW__'
|
|
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
|
|
# Splice the family layer in as raw JSON: families.json is already valid JSON, and
|
|
# round-tripping it through PS objects would risk both of the traps noted above.
|
|
if ($famRaw) {
|
|
$json = $json.Replace('"__AA_FAMILIES_RAW__"', $famRaw)
|
|
} else {
|
|
$json = $json.Replace('"__AA_FAMILIES_RAW__"', 'null')
|
|
}
|
|
New-Item -ItemType Directory -Force -Path site | Out-Null
|
|
$js = "/* AUTO-GENERATED by build-site.ps1 - do not edit by hand */`r`nwindow.KOLE_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 = 'Kole UI'; 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('/* Kole UI 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) ----------------
|
|
# Set SITE_URL_BASE in the environment for deployed absolute URLs.
|
|
$defaultSiteUrlBase = "https://YOUR-ACCOUNT.github.io/kole-ui/"
|
|
if ([string]::IsNullOrWhiteSpace($env:SITE_URL_BASE)) {
|
|
Write-Warning "SITE_URL_BASE is not set; using placeholder URL $defaultSiteUrlBase"
|
|
$SiteUrlBase = $defaultSiteUrlBase
|
|
} else {
|
|
$SiteUrlBase = $env:SITE_URL_BASE.Trim()
|
|
}
|
|
$shellDir = "site/components"
|
|
$siteBase = $SiteUrlBase.TrimEnd('/')
|
|
$platforms = @('h5', 'react', 'vue2', 'vue3')
|
|
$platformLabels = @{ h5 = 'H5'; react = 'React'; vue2 = 'Vue 2'; vue3 = 'Vue 3' }
|
|
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('"','"')
|
|
}
|
|
$siteBaseHtml = Html-Escape $siteBase
|
|
foreach ($c in $out) {
|
|
$title = Html-Escape ([string]$c.name)
|
|
$desc = Html-Escape ([string]$c.specSummary)
|
|
$slug = [string]$c.slug
|
|
if ($slug -notmatch '^[a-z0-9]+(?:-[a-z0-9]+)*$') {
|
|
throw "Invalid component slug '$slug'; expected lowercase letters, digits, and single hyphens only."
|
|
}
|
|
# Route targets are History-API paths (no "#"): app.js renders /site/component/<slug>
|
|
# and the web server falls back to /site/index.html. Shell depth decides the "../" prefix.
|
|
$target = "../component/$slug"
|
|
$targetJson = Json-Escape $target
|
|
$shell = @"
|
|
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>$title - Kole UI</title>
|
|
<meta name="description" content="$desc">
|
|
<link rel="canonical" href="$siteBaseHtml/site/components/$slug.html">
|
|
<meta http-equiv="refresh" content="0;url=$target">
|
|
</head>
|
|
<body>
|
|
<script>location.replace($targetJson);</script>
|
|
<noscript><p><a href="$target">Open $title</a></p></noscript>
|
|
</body>
|
|
</html>
|
|
"@
|
|
[System.IO.File]::WriteAllText((Join-Path (Get-Location) "$shellDir/$slug.html"), $shell, $utf8NoBom)
|
|
|
|
foreach ($platform in $platforms) {
|
|
$platformLabel = $platformLabels[$platform]
|
|
$platformDir = Join-Path (Get-Location) "$shellDir/$slug"
|
|
New-Item -ItemType Directory -Force -Path $platformDir | Out-Null
|
|
$target = "../../component/$slug/$platform"
|
|
$targetJson = Json-Escape $target
|
|
$platformShell = @"
|
|
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>$title - $platformLabel - Kole UI</title>
|
|
<meta name="description" content="$desc">
|
|
<link rel="canonical" href="$siteBaseHtml/site/components/$slug/$platform.html">
|
|
<meta http-equiv="refresh" content="0;url=$target">
|
|
</head>
|
|
<body>
|
|
<script>location.replace($targetJson);</script>
|
|
<noscript><p><a href="$target">Open $title - $platformLabel</a></p></noscript>
|
|
</body>
|
|
</html>
|
|
"@
|
|
[System.IO.File]::WriteAllText((Join-Path $platformDir "$platform.html"), $platformShell, $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 = @($siteBase + "/site/index.html")
|
|
foreach ($c in $out) {
|
|
$urls += $siteBase + "/site/components/$($c.slug).html"
|
|
foreach ($platform in $platforms) {
|
|
$urls += $siteBase + "/site/components/$($c.slug)/$platform.html"
|
|
}
|
|
}
|
|
# Template page library (S4-P11): typical admin pages under site/scenario/
|
|
$scenarioPages = @('login', 'dashboard', 'order-list', 'settings', 'user-management')
|
|
foreach ($p in $scenarioPages) {
|
|
$urls += $siteBase + "/site/scenario/$p.html"
|
|
}
|
|
foreach ($u in $urls) {
|
|
[void]$sb.AppendLine(' <url><loc>' + $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}, platform shells: {1}, sitemap.xml: {2} urls" -f $out.Count, ($out.Count * $platforms.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 ($famDoc) {
|
|
$famCount = @($out | Where-Object { $_.family }).Count
|
|
Write-Output ("family layer: {0} families, {1}/{2} components carry family metadata" -f $famDoc.totalFamilies, $famCount, $out.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)" }
|