Files
wangjia 4df8dc6f87 feat(web): 官网 ui_kits/website → Astro 纯静态 SSG 迁移 (tsk_acMYQ-Z-EIF_)
新建 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>
2026-06-13 14:26:40 +08:00

42 lines
1.3 KiB
JavaScript

#!/usr/bin/env node
/**
* dist-hash.mjs — 计算构建产物的确定性指纹。
*
* 用于「主站 + 镜像内容一致性校验」(验收:两边构建产物 hash 相同)。
* 因为 Astro 资源名是内容哈希、构建确定性,相同源码 → 相同 dist → 相同指纹。
* CI 在主站与镜像两路构建后比对本指纹即可断言一致。
*
* 算法:对 dist 下所有文件按「相对路径\0sha256」排序后再求总 sha256。
*/
import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs';
import { createHash } from 'node:crypto';
import { join, resolve, relative } from 'node:path';
const DIST = resolve(process.argv[2] ?? 'dist');
if (!existsSync(DIST)) {
console.error(`[dist-hash] 找不到 ${DIST}`);
process.exit(2);
}
function walk(dir) {
const out = [];
for (const name of readdirSync(dir)) {
const p = join(dir, name);
const st = statSync(p);
if (st.isDirectory()) out.push(...walk(p));
else out.push(p);
}
return out;
}
const entries = walk(DIST)
.map((p) => {
const rel = relative(DIST, p).split('\\').join('/');
const h = createHash('sha256').update(readFileSync(p)).digest('hex');
return `${rel}\0${h}`;
})
.sort();
const fingerprint = createHash('sha256').update(entries.join('\n'), 'utf8').digest('hex');
console.log(fingerprint);