Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
103 lines
4.1 KiB
Bash
Executable File
103 lines
4.1 KiB
Bash
Executable File
#!/bin/bash
|
||
#
|
||
# rebrand: 把面向用户的品牌标识从 GPUStack 批量替换为 ZStack AIOS(或自定义品牌)。
|
||
#
|
||
# 设计目标:上游每次更新后,在新的 v<version>-lofyer 分支上跑一次即可重新应用品牌定制。
|
||
#
|
||
# 只替换「独立的品牌词」FROM,并刻意跳过所有功能性引用:
|
||
# - 小写 `gpustack` —— npm 包名 (@gpustack/core-ui)、URL、路径、k8s 命名空间、author(大小写敏感,天然不匹配)
|
||
# - 全大写 `GPUSTACK` —— 常量/环境变量/全局 (GPUSTACK_API_BASE_URL, __GPUSTACK_*__, GPUSTACK_UI_*)(大小写敏感,不匹配)
|
||
# - 代码标识符 —— getGPUStackPlugin / GPUStackVersionAtom / GPUStackPluginManager / GPUStackLogo 等
|
||
# (FROM 紧邻字母时视为标识符的一部分,跳过)
|
||
# - HTTP 头 X-GPUStack-* —— 后端契约 (如 X-GPUStack-Model),跳过
|
||
#
|
||
# 匹配规则:FROM 前后都不是字母(独立单词),且不是 `X-` 前缀的头名。
|
||
#
|
||
# 环境变量:
|
||
# FROM 源品牌词(默认 GPUStack)
|
||
# TO 目标品牌词(默认 ZStack AIOS)
|
||
# DRY_RUN 设为 1 时只预览将改动的行,不写文件
|
||
#
|
||
set -e
|
||
|
||
FROM="${FROM:-GPUStack}"
|
||
TO="${TO:-ZStack AIOS}"
|
||
DRY_RUN="${DRY_RUN:-0}"
|
||
|
||
log() { echo -e "\033[1;34m[rebrand]\033[0m $*"; }
|
||
|
||
# 大小写敏感、单词边界、排除 X- 头前缀的 Perl 正则。
|
||
# (?<![A-Za-z]) 前面不是字母 (?<!X-) 不是 X- 头 (?![A-Za-z]) 后面不是字母
|
||
# FROM 为纯字母品牌词(无正则元字符),故直接拼接,不用 \Q\E
|
||
# —— \Q\E 在「经变量插值进正则」时不会被求值,反而会破坏匹配。
|
||
PATTERN="(?<![A-Za-z])(?<!X-)${FROM}(?![A-Za-z])"
|
||
|
||
# 行级排除:某些 FROM 出现在「与后端契约绑定的字符串」里,改了会破坏运行时逻辑,
|
||
# 即使是独立单词也必须整行跳过。命中下列任一正则的行不替换。
|
||
# 已知例外:
|
||
# - llmodels/hooks/index.ts 用 startsWith() 比对后端返回的英文兼容性消息
|
||
# ("... does not exist on the GPUStack server ..."),后端仍发 GPUStack,前端不能改。
|
||
SKIP_LINE_PATTERNS=(
|
||
'does not exist on the .*server. It'"'"'s recommended'
|
||
)
|
||
skip_line() {
|
||
local line="$1" pat
|
||
for pat in "${SKIP_LINE_PATTERNS[@]}"; do
|
||
if echo "$line" | grep -qP "$pat"; then return 0; fi
|
||
done
|
||
return 1
|
||
}
|
||
|
||
# 待处理的已跟踪文本文件(排除 lockfile 与本脚本自身)。
|
||
mapfile -t files < <(
|
||
git ls-files -- \
|
||
'*.ts' '*.tsx' '*.js' '*.jsx' '*.json' '*.less' '*.html' '*.md' \
|
||
| grep -v 'pnpm-lock.yaml'
|
||
)
|
||
|
||
log "品牌替换: '${FROM}' -> '${TO}'"
|
||
log "候选文件: ${#files[@]}"
|
||
|
||
# 把行级排除合并成一个 perl 正则,供预览与替换共用(经环境变量传入,免去转义)。
|
||
SKIP_RE=""
|
||
for pat in "${SKIP_LINE_PATTERNS[@]}"; do
|
||
SKIP_RE="${SKIP_RE:+${SKIP_RE}|}(?:${pat})"
|
||
done
|
||
export REBRAND_SKIP_RE="${SKIP_RE}"
|
||
export REBRAND_PATTERN="${PATTERN}"
|
||
export REBRAND_TO="${TO}"
|
||
|
||
# 统计命中行(预览/确认用),已扣除被行级排除的行。
|
||
log "命中行预览(最多 40 行):"
|
||
grep -rnP "${PATTERN}" "${files[@]}" 2>/dev/null \
|
||
| { [[ -n "${SKIP_RE}" ]] && grep -vP "${SKIP_RE}" || cat; } \
|
||
| head -40 || true
|
||
total=$(grep -rnP "${PATTERN}" "${files[@]}" 2>/dev/null \
|
||
| { [[ -n "${SKIP_RE}" ]] && grep -vP "${SKIP_RE}" || cat; } | wc -l)
|
||
log "命中总行数: ${total}"
|
||
if [[ -n "${SKIP_RE}" ]]; then
|
||
skipped=$(grep -rnP "${PATTERN}" "${files[@]}" 2>/dev/null | grep -cP "${SKIP_RE}" || true)
|
||
log "行级排除(后端契约,保留 ${FROM}): ${skipped} 行"
|
||
fi
|
||
|
||
if [[ "${DRY_RUN}" == "1" ]]; then
|
||
log "DRY_RUN=1:未写入任何文件。"
|
||
exit 0
|
||
fi
|
||
|
||
# 实际替换(in-place)。命中行级排除的行整行跳过。
|
||
changed=0
|
||
for f in "${files[@]}"; do
|
||
if grep -qP "${PATTERN}" "$f" 2>/dev/null; then
|
||
perl -i -pe '
|
||
my $skip = $ENV{REBRAND_SKIP_RE};
|
||
next if length($skip) && /$skip/;
|
||
s/$ENV{REBRAND_PATTERN}/$ENV{REBRAND_TO}/g;
|
||
' "$f"
|
||
changed=$((changed + 1))
|
||
fi
|
||
done
|
||
|
||
log "已修改文件数: ${changed}"
|
||
log "完成。请用 'git diff' 复核改动。"
|