Build the frontend once per pipeline and reuse it in the dev backend image [CI]
ci/woodpecker/push/web-build Pipeline was successful
ci/woodpecker/push/verify Pipeline was canceled
ci/woodpecker/push/dev-image Pipeline was canceled

This commit is contained in:
justme committed 2026-10-21 15:17:37 +08:00
1 parent 3654d37da5
commit d1ece67a6a
5 files changed
+82 -6

No files matched your search

+4 -4
View File
@@ -56,11 +56,11 @@ trap cleanup EXIT
export MOSHIRO_COMMIT="${MOSHIRO_COMMIT:-${CI_COMMIT_SHA:0:10}}"
export MOSHIRO_BUILT_AT="${MOSHIRO_BUILT_AT:-$(date -u +%Y-%m-%dT%H:%M:%SZ)}"
# 前端先构建:Backend.Api 在 web/*/dist 存在时把它们内嵌进程序(展示端 /,管理端 /admin/),这样 passkey 能绑定在面板自己的域名上
# 发版流水线里有单独的 web-build 步骤先构建好了(并行的两个架构不能同时 npm ci);main 的 [CI] 构建只有一个 Backend 步骤,这里自己构建
# 前端先就位:Backend.Api 在 web/*/dist 存在时把它们内嵌进程序(展示端 /,管理端 /admin/),这样 passkey 能绑定在面板自己的域名上
# 发版流水线里有单独的 web-build 步骤先构建好了;main 的 [CI] 构建用同一条流水线里 web-build workflow 的产物(带这次的版本号),
# 等不到(它没跑或失败了)才自己构建兜底,依赖共用 node_modules 缓存
if [[ ! -f "$root/web/display/dist/index.html" || ! -f "$root/web/admin/dist/index.html" ]]; then
# 这里的前端带着这次构建的版本号(apply_version 已经改了 package.json),不能用 web-build workflow 的产物;但依赖共用它的 node_modules 缓存(flock 防止同时读写),不再 npm ci
WEB_SKIP_LINT=1 bash "$root/ci/web-build.sh"
bash "$root/ci/shared-web.sh" fetch || WEB_SKIP_LINT=1 bash "$root/ci/web-build.sh"
fi
[[ -f "$root/web/display/dist/index.html" && -f "$root/web/admin/dist/index.html" ]] || { echo "the web build did not produce both apps" >&2; exit 1; }
+44
View File
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# 同一条流水线里,前端只构建一次:web-build workflow 构建完把 dist 放到流水线的共享目录,开发镜像的 Backend 步骤直接拿来用。
# 用法:
# shared-web.sh publish 把 web/display/dist 和 web/admin/dist 放进共享目录(先写临时目录,最后用一次 rename 加上 ready 标记)
# shared-web.sh fetch 等 ready 标记出现(最多 MOSHIRO_SHARED_WEB_WAIT 秒,默认 600),再复制回工作区;
# 退出码 0 = 已复制,1 = 没等到或流水线已经失败(调用方自己构建兜底)
# 共享目录和 ci/guard-step.py 用的是同一个:${MOSHIRO_CI_STATE}/${CI_REPO_ID}-${CI_PIPELINE_NUMBER}。
set -euo pipefail
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
state="${MOSHIRO_CI_STATE:-/home/justme/woodpecker/cache/pipeline-state}/${CI_REPO_ID:-0}-${CI_PIPELINE_NUMBER:-0}"
shared="$state/web-dist"
apps=(display admin)
case "${1:-}" in
publish)
mkdir -p "$state"
tmp="$state/web-dist.tmp.$$"
rm -rf "$tmp" "$shared"
for app in "${apps[@]}"; do
[[ -f "$root/web/$app/dist/index.html" ]] || { echo "web/$app/dist is missing" >&2; exit 1; }
mkdir -p "$tmp/$app"
cp -a "$root/web/$app/dist/." "$tmp/$app/"
done
mv "$tmp" "$shared"
;;
fetch)
deadline=$((SECONDS + ${MOSHIRO_SHARED_WEB_WAIT:-600}))
until [[ -f "$shared/display/index.html" && -f "$shared/admin/index.html" ]]; do
if [[ -e "$state/failed" ]]; then echo "the pipeline already failed; no shared web build" >&2; exit 1; fi
if ((SECONDS >= deadline)); then echo "the shared web build did not appear in time" >&2; exit 1; fi
sleep 2
done
for app in "${apps[@]}"; do
rm -rf "$root/web/$app/dist"
mkdir -p "$root/web/$app/dist"
cp -a "$shared/$app/." "$root/web/$app/dist/"
done
;;
*)
echo "usage: shared-web.sh publish|fetch" >&2
exit 2
;;
esac
+4 -1
View File
@@ -11,7 +11,8 @@ dirs=(node_modules display/node_modules admin/node_modules)
mkdir -p "$cache"
exec 9> "$cache/.lock"
key="$(cat "$web/package-lock.json" "$web/package.json" "$web/display/package.json" "$web/admin/package.json" | sha256sum | cut -c1-16)"
# 开发构建会把版本号写进 package.json;缓存键里去掉 version 行,否则每个提交都是新的键,缓存永远命中不了
key="$(cat "$web/package-lock.json" "$web/package.json" "$web/display/package.json" "$web/admin/package.json" | grep -v '^ "version":' | sha256sum | cut -c1-16)"
restore() {
[[ -d "$cache/$key/node_modules" ]] || return 1
@@ -57,3 +58,5 @@ failed=0
for pid in "${pids[@]}"; do wait "$pid" || failed=1; done
[[ "$failed" == 0 ]] || { echo "web build failed" >&2; exit 1; }
[[ -n "${WEB_SKIP_LINT:-}" ]] || npm run lint:css --prefix "$web"
# 开发构建(main 上带 [CI] 的提交):前端是这条流水线的唯一一份,放进共享目录,开发镜像的 Backend 直接用(见 ci/shared-web.sh)
[[ -z "${WEB_PUBLISH_SHARED:-}" ]] || bash "$root/ci/shared-web.sh" publish