#!/usr/bin/env node /** * build-dist.mjs — 产出可分发产物(npm 包 / CDN) * * 设计约束: * - 零运行时依赖:只用 Node 内置能力(fs / path) * - 不依赖 build-site.ps1(那是 Windows 专用的文档站构建) * - 产物自包含:dist/ 里的东西拷走就能用 * * 输入: * - .design_library/aurora-admin/colors_and_type.css 设计令牌 * - .design_library/aurora-admin/components.css 6 核心组件样式聚合 * - .design_library/aurora-admin/css.json 结构化令牌 * - frameworks/*.css 79 个组件样式 * - frameworks/*.html 79 个演示页 * * 输出: * - dist/tokens/tokens.css 设计令牌(可直接 @import) * - dist/tokens/tokens.json W3C DTCG 格式 * - dist/tokens/figma.json Figma Tokens 格式 * - dist/components/.css 单个组件样式 * - dist/components/index.css 全部组件样式聚合 * - dist/components/.html 静态演示页(可独立打开) * - dist/manifest.json 组件清单 + 版本 + 校验和 * - dist/README.md 用法速查 */ import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, statSync } from 'fs'; import { createHash } from 'crypto'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const ROOT = join(__dirname, '..'); const LIB = join(ROOT, '.design_library', 'aurora-admin'); const SRC = join(ROOT, 'frameworks'); const DIST = join(ROOT, 'dist'); /* ---------- 工具 ---------- */ function ensure(dir) { if (!existsSync(dir)) mkdirSync(dir, { recursive: true }); } function read(p) { return readFileSync(p, 'utf8'); } function write(p, content) { ensure(dirname(p)); // 统一 LF,避免跨平台差异 writeFileSync(p, content.replace(/\r\n/g, '\n')); } function sha256(content) { return createHash('sha256').update(content).digest('hex').slice(0, 16); } /* ---------- 1. 读组件清单 ---------- */ const idxPath = join(LIB, 'components', 'index.json'); if (!existsSync(idxPath)) { console.error('[FATAL] 找不到 components/index.json'); process.exit(1); } const idx = JSON.parse(read(idxPath)); const components = idx.components; const pkg = JSON.parse(read(join(ROOT, 'package.json'))); const version = pkg.version; console.log(`[build-dist] 版本 ${version} / ${components.length} 个组件`); /* ---------- 2. 令牌 ---------- */ const tokenCss = read(join(LIB, 'colors_and_type.css')); // 从令牌文件解析出 name/value/comment(与 build-site.ps1 同一套正则思路) const tokens = []; const TOKEN_RE = /--(au-[a-z0-9-]+)\s*:\s*([^;]+);(?:\s*\/\*\s*(.*?)\s*\*\/)?/g; let m; while ((m = TOKEN_RE.exec(tokenCss)) !== null) { tokens.push({ name: m[1], value: m[2].trim(), comment: (m[3] || '').trim() }); } write(join(DIST, 'tokens', 'tokens.css'), tokenCss); // W3C DTCG function groupOf(name) { if (/color|brand|text|bg|border|success|warning|error|info|mask/.test(name)) return 'color'; if (/font|type/.test(name)) return 'typography'; if (/radius/.test(name)) return 'radius'; if (/space|gap|padding/.test(name)) return 'spacing'; if (/shadow|elevation/.test(name)) return 'shadow'; if (/duration|ease|motion/.test(name)) return 'motion'; if (/size|height|width|icon/.test(name)) return 'size'; return 'misc'; } const dtcg = {}; for (const t of tokens) { const g = groupOf(t.name); dtcg[g] = dtcg[g] || {}; dtcg[g][t.name] = { $value: t.value, $type: g === 'color' ? 'color' : 'dimension', $description: t.comment || undefined }; } write( join(DIST, 'tokens', 'tokens.json'), JSON.stringify( { $schema: 'https://schemas.design-tokens.org/draft/dtcg.json', $metadata: { name: 'Aurora Admin', version }, tokens: dtcg }, null, 2 ) ); // Figma Tokens const figma = {}; for (const t of tokens) { const g = groupOf(t.name); figma[t.name] = { value: t.value, type: g === 'color' ? 'color' : g === 'size' ? 'sizing' : g === 'spacing' ? 'spacing' : g === 'radius' ? 'borderRadius' : 'other', description: t.comment || '', }; } write(join(DIST, 'tokens', 'figma.json'), JSON.stringify({ global: figma, $themes: [], $metadata: { tokenSetOrder: ['global'] } }, null, 2)); console.log(`[build-dist] 令牌 ${tokens.length} 个 → dist/tokens/`); /* ---------- 3. 组件样式 ---------- */ const manifest = { name: pkg.name, version, generated: new Date().toISOString().slice(0, 19).replace('T', ' '), tokens: tokens.length, components: [], }; const aggregate = [ '/* Aurora Admin — 全部组件样式聚合', ` * 版本 ${version} · ${components.length} 个组件`, ' * 用法:在令牌之后引入本文件即可获得全部组件样式', ' */', '', /* 本文件位于 dist/components/,令牌在 dist/tokens/ —— 故必须是 ../tokens/ */ `@import url('../tokens/tokens.css');`, '', ].join('\n'); const parts = [aggregate]; for (const c of components) { const slug = c.slug; const prefix = c.frameworksPrefix; // 组件 CSS const cssPath = join(SRC, `${prefix}.css`); if (existsSync(cssPath)) { const css = read(cssPath); write(join(DIST, 'components', `${slug}.css`), `/* Aurora Admin · ${c.name} */\n${css}`); parts.push(`/* ---- ${c.name} (${slug}) ---- */`, css, ''); } // 演示页(重写引用,使其在 dist 下能独立打开) const htmlPath = join(SRC, `${prefix}.html`); let hasDemo = false; if (existsSync(htmlPath)) { let html = read(htmlPath); /* dist 的目录结构: dist/components/.html dist/components/.css dist/tokens/tokens.css 因此所有引用都要重写。漏掉文件名是最容易犯的错 —— 会产出「路径对但文件不存在」的坏产物。 */ html = html.replace(/\.\.\/\.design_library\/aurora-admin\/colors_and_type\.css/g, '../tokens/tokens.css'); html = html.replace(/\.\.\/\.design_library\/aurora-admin\/components\.css/g, './index.css'); html = html.replace(/\.\.\/\.design_library\/aurora-admin\//g, '../tokens/'); // 组件自身样式:./.css → ./.css html = html.replace(new RegExp(`(\\./)?${prefix}\\.css`, 'g'), `./${slug}.css`); write(join(DIST, 'components', `${slug}.html`), html); hasDemo = true; } manifest.components.push({ slug, name: c.name, category: c.category, tier: c.tier, css: existsSync(cssPath) ? `components/${slug}.css` : null, demo: hasDemo ? `components/${slug}.html` : null, cssHash: existsSync(cssPath) ? sha256(read(cssPath)) : null, }); } write(join(DIST, 'components', 'index.css'), parts.join('\n')); write(join(DIST, 'manifest.json'), JSON.stringify(manifest, null, 2)); const cssCount = readdirSync(join(DIST, 'components')).filter((f) => f.endsWith('.css')).length; const htmlCount = readdirSync(join(DIST, 'components')).filter((f) => f.endsWith('.html')).length; console.log(`[build-dist] 组件 ${components.length} 个 → dist/components/(${cssCount} css / ${htmlCount} html)`); /* ---------- 4. 用法速查 ---------- */ write( join(DIST, 'README.md'), `# Aurora Admin Design System — 分发包 版本 ${version} · ${components.length} 个组件 · ${tokens.length} 个设计令牌 · 零运行时依赖 ## 最快用法 \`\`\`html \`\`\` ## 按需引入 \`\`\`html \`\`\` ## 改主题 所有色值都引用 \`--au-*\` 令牌,覆盖即可全局换色: \`\`\`css :root { --au-color-brand: #722ED1; } \`\`\` ## 目录 | 路径 | 内容 | |---|---| | \`tokens/tokens.css\` | 设计令牌(CSS 变量) | | \`tokens/tokens.json\` | 同上(W3C DTCG 格式,供工具消费) | | \`tokens/figma.json\` | Figma Tokens(Tokens Studio 可导入) | | \`components/index.css\` | 全部组件样式聚合 | | \`components/.css\` | 单个组件样式 | | \`components/.html\` | 该组件的静态演示页(可直接打开) | | \`manifest.json\` | 组件清单 + 校验和 | ## 组件清单 ${manifest.components.map((c) => `- \`${c.slug}\` — ${c.name}`).join('\n')} ` ); console.log('[build-dist] 完成 → dist/'); console.log(` components/manifest.json · README.md`);