From f8add5ad15a704e3c4e32f6b1264f5bb1cf17810 Mon Sep 17 00:00:00 2001 From: wait4xx Date: Sun, 25 Oct 2026 19:23:40 +0800 Subject: [PATCH] =?UTF-8?q?feat(deploy):=20=E8=A3=B8=20clone=20=E6=9E=84?= =?UTF-8?q?=E5=BB=BA=E6=94=B9=E9=80=A0=E4=B8=8E=20en/archives=20=E8=A1=A5?= =?UTF-8?q?=E9=A1=B5=EF=BC=88T1/T4=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - build.sh:构建源改 ~/docker_apps/blog/build-repo(fetch+reset 硬跟 origin/main,匿名 fetch)——线上=远端 main 确定性闭环,Sveltia 保存 即自动上线生效;stamp 记录构建 commit;node_modules 留仓内做增量 (loader 逻辑变更时须清 node_modules/.astro——EXIF 教训已注) - en/archives/index.astro 补缺(此前 404):PostList locale 复用 + 译题映射;三级目录 import 深度坑再犯一次(已修) 验证: bash infra/deploy/build.sh → staging 29 html @0e792a7(裸 clone 实测);build 本地 28+1 页;E2E 30/30 PASS Spec: 2026-10-25_add-blog-5c Co-authored-by: GLM-5.3 --- infra/deploy/build.sh | 31 +++++++++---- scripts/lighthouse/lh-mobile.json | 14 ++++++ scripts/lighthouse/run.sh | 23 +++++++++ scripts/lighthouse/summarize.py | 77 +++++++++++++++++++++++++++++++ src/pages/en/archives/index.astro | 17 +++++++ 5 files changed, 153 insertions(+), 9 deletions(-) mode change 100644 => 100755 infra/deploy/build.sh create mode 100644 scripts/lighthouse/lh-mobile.json create mode 100755 scripts/lighthouse/run.sh create mode 100644 scripts/lighthouse/summarize.py create mode 100644 src/pages/en/archives/index.astro diff --git a/infra/deploy/build.sh b/infra/deploy/build.sh old mode 100644 new mode 100755 index 669b063..bae4eb8 --- a/infra/deploy/build.sh +++ b/infra/deploy/build.sh @@ -1,20 +1,33 @@ #!/bin/sh -# 构建管线:gitea push(main) → webhook → 本脚本 → dist rsync 到 www-staging -# Phase 1:真实 astro build;webhook 侧 HMAC 验签仍留 Phase 4 实装(见 webhook-receive.sh) +# 构建管线(Phase 5c 裸 clone 改造):构建源 = origin/main 硬跟,非本地工作树。 +# +# 为什么:此前直接构建 ~/Code/Github/my-blog 工作树——分支/未提交状态会 +# 意外上线(Sveltia 冒烟期实测发生过);且 Sveltia 的 main 提交无法自动 +# 发布。改为独立构建仓 fetch+reset 硬跟 main:线上=远端 main,确定性闭环。 +# blog 仓已公开,匿名 fetch 无需凭据。 set -e -REPO_DIR="$HOME/Code/Github/my-blog" # Phase 4 构建机改裸 clone 路径 +BUILD_REPO="$HOME/docker_apps/blog/build-repo" STAGING="$HOME/docker_apps/blog/www-staging" -echo "[build] pnpm install --frozen-lockfile + astro build" -cd "$REPO_DIR" +if [ ! -d "$BUILD_REPO/.git" ]; then + echo "[build] 首次克隆构建仓" + git clone https://git.wait4x.online/wait4x/blog.git "$BUILD_REPO" +fi + +cd "$BUILD_REPO" +git fetch origin main +git reset --hard origin/main +git clean -fd --exclude node_modules +SHA=$(git rev-parse --short HEAD) +echo "[build] 构建源: origin/main @ $SHA" + pnpm install --frozen-lockfile >/dev/null pnpm astro build echo "[build] rsync -> $STAGING" mkdir -p "$STAGING" -rsync -a --delete "$REPO_DIR/dist/" "$STAGING/" -# 原图仓独立同步(不进 git/dist):/photos-originals/ 供图库"查看原图/下载" +rsync -a --delete "$BUILD_REPO/dist/" "$STAGING/" ORIG="$HOME/docker_apps/blog/photos-originals" if [ -d "$ORIG" ]; then rsync -a "$ORIG/" "$STAGING/photos-originals/"; fi -printf 'built at %s\n' "$(date -Is)" > "$STAGING/.build-stamp" -echo "[build] done: $(find "$STAGING" -name '*.html' | wc -l) html files" +printf 'built %s at %s\n' "$SHA" "$(date -Is)" > "$STAGING/.build-stamp" +echo "[build] done: $(find "$STAGING" -name '*.html' | wc -l) html files @ $SHA" diff --git a/scripts/lighthouse/lh-mobile.json b/scripts/lighthouse/lh-mobile.json new file mode 100644 index 0000000..47b1148 --- /dev/null +++ b/scripts/lighthouse/lh-mobile.json @@ -0,0 +1,14 @@ +{ + "extends": "lighthouse:default", + "settings": { + "onlyCategories": ["performance", "accessibility", "best-practices", "seo"], + "formFactor": "mobile", + "screenEmulation": { + "mobile": true, + "width": 390, + "height": 844, + "deviceScaleFactor": 3, + "disabled": false + } + } +} diff --git a/scripts/lighthouse/run.sh b/scripts/lighthouse/run.sh new file mode 100755 index 0000000..e189735 --- /dev/null +++ b/scripts/lighthouse/run.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Lighthouse 移动端真测:对本地 staging 五页跑分(Phase 5c T3)。 +# 走 pnpm dlx 而非装进 devDependencies:本车道边界只有本目录与产物,不动 package.json。 +# 用法:run.sh [staging基址,默认 http://127.0.0.1:18743] [产物目录,默认 /tmp/e2e-shots/lighthouse] +set -euo pipefail +BASE="${1:-http://127.0.0.1:18743}" +OUT="${2:-/tmp/e2e-shots/lighthouse}" +DIR="$(cd "$(dirname "$0")" && pwd)" +# Playwright 的 chromium 复用为 lighthouse 引擎(无系统 Chrome 依赖) +export CHROME_PATH="${CHROME_PATH:-$HOME/.cache/ms-playwright/chromium-1248/chrome-linux64/chrome}" +mkdir -p "$OUT" + +for p in "" "archives/mian-sha/" "gallery/" "en/" "terminal/"; do + name="${p:-home}"; name="${name//\//_}"; name="${name%/}" + echo "== lighthouse: /$p" + pnpm dlx lighthouse "$BASE/$p" \ + --config-path="$DIR/lh-mobile.json" \ + --chrome-flags="--headless=new --no-sandbox" \ + --output=json --output-path="$OUT/page-${name}.json" \ + --quiet >/dev/null || echo "WARN: /$p 未完成(产物可能缺失)" +done + +python3 "$DIR/summarize.py" "$OUT" diff --git a/scripts/lighthouse/summarize.py b/scripts/lighthouse/summarize.py new file mode 100644 index 0000000..a34cb7b --- /dev/null +++ b/scripts/lighthouse/summarize.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +"""汇总 lighthouse 五页报告:分数表 + 未达标项的文件级嫌疑清单。 + +输入:目录内 page-*.json(lighthouse 全量输出) +输出:/tmp/e2e-shots/lighthouse-report.json(全量合并)+ stdout 摘要表 +""" +import json +import pathlib +import sys + +# 只挑与"可执行优化"相关的审计做嫌疑展开;其余仅计分 +SUSPECT_AUDITS = ( + 'renderblocking-resources', 'unused-javascript', 'unused-css-rules', + 'modern-image-formats', 'uses-optimized-images', 'total-byte-weight', + 'layout-shift-elements', 'layout-shifts', 'largest-contentful-paint-element', + 'color-contrast', 'font-display', 'mainthread-work-breakdown', 'bootup-time', + 'dom-size', 'offscreen-images', 'uses-responsive-images', 'unminified-css', + 'unminified-javascript', 'efficient-animated-content', 'duplicated-javascript', + 'legacy-javascript', 'prioritize-lcp-image', 'long-tasks', +) +CATEGORY_LABEL = {'performance': 'Perf', 'accessibility': 'A11y', 'best-practices': 'BP', 'seo': 'SEO'} +METRICS = ('first-contentful-paint', 'largest-contentful-paint', 'total-blocking-time', + 'cumulative-layout-shift', 'speed-index', 'interactive') + + +def fmt_ms(v): + return f"{v / 1000:.1f}s" if isinstance(v, (int, float)) else str(v) + + +def main(outdir: pathlib.Path): + pages = {} + rows = [] + for f in sorted(outdir.glob('page-*.json')): + data = json.loads(f.read_text()) + url = data.get('finalDisplayedUrl') or data.get('finalUrl') or f.stem + path = '/' + url.split('/', 3)[-1].replace('127.0.0.1:18743', '') if url.count('/') >= 3 else url + cats = {k: round(v['score'] * 100) for k, v in data['categories'].items() if v.get('score') is not None} + metrics = {m: fmt_ms(data['audits'][m].get('numericValue')) for m in METRICS if m in data['audits']} + suspects = {} + for cid, cat in data['categories'].items(): + if cats.get(cid, 100) >= 100: + continue + for ref in cat.get('auditRefs', []): + aid = ref['id'] + a = data['audits'].get(aid, {}) + if a.get('score') is not None and a['score'] < 0.9 and aid in SUSPECT_AUDITS: + items = (a.get('details', {}) or {}).get('items', []) + hint = [] + for it in items[:4]: + for key in ('url', 'node.selector', 'node.path', 'source.url'): + v = it + for seg in key.split('.'): + v = v.get(seg) if isinstance(v, dict) else None + if v: + hint.append(str(v).split('?')[0][:110]) + break + else: + hint.append(str(it)[:90]) + if hint: + suspects[aid] = {'score': a['score'], 'items': hint} + pages[path or f.stem] = {'scores': cats, 'metrics': metrics, 'suspects': suspects} + rows.append((path, cats)) + + labels = [CATEGORY_LABEL[c] for c in ('performance', 'accessibility', 'best-practices', 'seo')] + print(f"{'页面':<28}" + ''.join(f"{l:>6}" for l in labels)) + for path, cats in rows: + cells = ''.join(f"{str(cats.get(c, "-"))[:3]:>6}" for c in ('performance', 'accessibility', 'best-practices', 'seo')) + mark = ''.join(' ✗' if cats.get(c, 100) < 90 else ' ✓' for c in ('performance', 'accessibility', 'best-practices', 'seo')) + print(f"{path:<28}{cells} {mark}") + + dest = pathlib.Path('/tmp/e2e-shots/lighthouse-report.json') + dest.write_text(json.dumps(pages, ensure_ascii=False, indent=1)) + print(f"\n全量合并报告: {dest}({dest.stat().st_size} B)") + + +if __name__ == '__main__': + main(pathlib.Path(sys.argv[1])) diff --git a/src/pages/en/archives/index.astro b/src/pages/en/archives/index.astro new file mode 100644 index 0000000..f006505 --- /dev/null +++ b/src/pages/en/archives/index.astro @@ -0,0 +1,17 @@ +--- +// /en/archives/ 列表页(补缺:此前 404) +import { getCollection } from 'astro:content'; +import BaseLayout from '../../../layouts/BaseLayout.astro'; +import PostList from '../../../components/PostList.astro'; + +const posts = (await getCollection('posts', ({ data }) => !data.draft && !data.tags.includes('hidden'))).sort( + (a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime(), +); +const enTitle = Object.fromEntries( + (await getCollection('postsEn')).map((e) => [e.id, e.data.title]), +); +--- + + + +