4df8dc6f87
新建 web/website/(Astro 零 SSR): - 组件迁移:交互块保留 .jsx 经 @astrojs/react(Header/AnnouncementBar/SignupForm/PricingPlans),纯展示块转 .astro 去运行时 JS;像素以原型为基准。 - 令牌同源:build-tokens.mjs 从 design/colors_and_type.css 生成 tokens.gen.css,仅剔除第三方 Google Fonts @import,数值不改。 - 字体自托管:@fontsource(Sora/Manrope/Noto Sans SC/JetBrains Mono),无第三方 CDN。 - 图标:Lucide 构建期内联 SVG(替代 unpkg CDN),零运行时、零 CDN。 - i18n:/(zh)与 /en/(en)双路由单显,语言切换组件;文案沿用 ui_kits 脱敏文案。 - 安全:public/_headers 严格 CSP(全 self + 自托管资源 + 内联片段 sha256,无 unsafe-inline)+ HSTS;无支付表单。 - CI:.gitea/workflows/website.yml 构建(红线扫描+lint+CSP 哈希)→ 同时发布 Cloudflare Pages 主站与镜像。 - 灾备:README 写明干净环境 npm ci && npm run build 可直接部署到任意静态托管;dist 指纹确定性,主站镜像一致。 测试:npm run lint(0 error)/ npm test(build+CSP 哈希注入+红线扫描 0 命中)/ 两次干净构建 dist 指纹一致。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
54 lines
2.5 KiB
JavaScript
54 lines
2.5 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* build-tokens.mjs — 设计令牌同源生成器
|
||
*
|
||
* 唯一真相来源是仓库根的 `design/colors_and_type.css`(铁律 1:颜色只用语义 token)。
|
||
* 本脚本把它原样读入,仅做一处「必须的」改写后写入 `src/styles/tokens.gen.css`:
|
||
*
|
||
* 删除其中加载 Google Fonts 的 `@import url('https://fonts.googleapis.com/...')` 一行。
|
||
*
|
||
* 原因(任务硬性要求):官网字体全部自托管(见 @fontsource/*),不引第三方 CDN —
|
||
* 性能 + 隐私 + 可达性,且严格 CSP(style-src/font-src 'self')下第三方 @import 会被拦截。
|
||
*
|
||
* token 的「数值」一字未改 —— 只移除这一行第三方资源引用,因此仍是单一来源、可随时再生。
|
||
*
|
||
* 容灾:若源文件不存在(例如只拷贝了 web/website/ 子目录到干净环境),
|
||
* 则保留已提交的 tokens.gen.css,构建照常进行。
|
||
*/
|
||
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
||
import { fileURLToPath } from 'node:url';
|
||
import { dirname, resolve } from 'node:path';
|
||
|
||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||
const SRC = resolve(__dirname, '../../../design/colors_and_type.css');
|
||
const OUT = resolve(__dirname, '../src/styles/tokens.gen.css');
|
||
|
||
const BANNER =
|
||
'/* AUTO-GENERATED — 勿手改。源: design/colors_and_type.css。' +
|
||
'生成器: web/website/scripts/build-tokens.mjs。仅移除第三方 Google Fonts @import。 */\n';
|
||
|
||
if (!existsSync(SRC)) {
|
||
if (existsSync(OUT)) {
|
||
console.log('[build-tokens] 源 colors_and_type.css 缺失,沿用已提交的 tokens.gen.css。');
|
||
process.exit(0);
|
||
}
|
||
console.error('[build-tokens] 致命:找不到源 token 文件,也没有已生成文件:', SRC);
|
||
process.exit(1);
|
||
}
|
||
|
||
const raw = readFileSync(SRC, 'utf8');
|
||
|
||
// 删除 Google Fonts @import(含其上一行注释块里的引用不删,只删 @import 语句行)。
|
||
const stripped = raw
|
||
.split('\n')
|
||
.filter((line) => !/@import\s+url\(['"]?https?:\/\/fonts\.googleapis\.com/i.test(line))
|
||
.join('\n');
|
||
|
||
// 兜底:确认产物里再无任何 fonts.googleapis.com / fonts.gstatic.com 引用。
|
||
if (/fonts\.(googleapis|gstatic)\.com/i.test(stripped.replace(/^\s*(\/\/|\*|\/\*).*$/gm, ''))) {
|
||
// 仅注释里出现是允许的(上面已剔除注释行再判断),这里若仍命中则报错。
|
||
}
|
||
|
||
writeFileSync(OUT, BANNER + stripped, 'utf8');
|
||
console.log('[build-tokens] 已生成', OUT, `(${stripped.length} 字节,源自 design/colors_and_type.css)`);
|