配置: 初始化 ISOS Agent Teams 软件研发模板
CI / lint (push) Successful in 6s

This commit is contained in:
2026-04-19 21:47:08 +08:00
parent 3ab6fe6504
commit 34346be862
202 changed files with 23544 additions and 0 deletions
+539
View File
@@ -0,0 +1,539 @@
#!/bin/bash
# ==============================================================================
# DevContainer 公共函数库
# ==============================================================================
# 禁止直接执行,必须被 source
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
echo "错误: 此文件应该被 source,不能直接执行"
exit 1
fi
# ============================================================================
# 颜色代码定义
# ============================================================================
readonly COLOR_RED='\033[0;31m'
readonly COLOR_GREEN='\033[0;32m'
readonly COLOR_YELLOW='\033[0;33m'
readonly COLOR_BLUE='\033[0;34m'
readonly COLOR_PURPLE='\033[0;35m'
readonly COLOR_CYAN='\033[0;36m'
readonly COLOR_GRAY='\033[0;90m'
readonly COLOR_BOLD='\033[1m'
readonly COLOR_RESET='\033[0m'
# ============================================================================
# 状态符号函数
# ============================================================================
status_ok() {
echo -e "${COLOR_GREEN}${COLOR_RESET}"
}
status_warn() {
echo -e "${COLOR_YELLOW}⚠️${COLOR_RESET}"
}
status_miss() {
echo -e "${COLOR_RED}${COLOR_RESET}"
}
status_info() {
echo -e "${COLOR_BLUE}${COLOR_RESET}"
}
status_fail() {
echo -e "${COLOR_RED}🔴${COLOR_RESET}"
}
# ============================================================================
# 带颜色的文本函数
# ============================================================================
text_green() {
echo -e "${COLOR_GREEN}${1}${COLOR_RESET}"
}
text_yellow() {
echo -e "${COLOR_YELLOW}${1}${COLOR_RESET}"
}
text_red() {
echo -e "${COLOR_RED}${1}${COLOR_RESET}"
}
text_blue() {
echo -e "${COLOR_BLUE}${1}${COLOR_RESET}"
}
text_cyan() {
echo -e "${COLOR_CYAN}${1}${COLOR_RESET}"
}
text_gray() {
echo -e "${COLOR_GRAY}${1}${COLOR_RESET}"
}
text_bold() {
echo -e "${COLOR_BOLD}${1}${COLOR_RESET}"
}
text_purple() {
echo -e "${COLOR_PURPLE}${1}${COLOR_RESET}"
}
text_reset() {
echo -e "${COLOR_RESET}"
}
# ============================================================================
# 输出格式函数
# ============================================================================
print_header() {
local title="$1"
echo ""
echo "$(text_bold '═══════════════════════════════════════════════════════════')"
echo "$(text_bold "${title}")"
echo "$(text_bold '═══════════════════════════════════════════════════════════')"
echo ""
}
print_section() {
local title="$1"
echo ""
echo "$(text_bold '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')"
echo "$(text_bold "${title}")"
echo "$(text_bold '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')"
echo ""
}
print_step() {
local step="$1"
local desc="$2"
echo "[${step}] ${desc}"
}
print_status() {
local status="$1"
local message="$2"
printf " %s %s\n" "${status}" "${message}"
}
# ============================================================================
# 辅助函数
# ============================================================================
# 追加配置到文件(如果不存在标记)
append_config() {
local file="$1"
local marker="$2"
local content="$3"
grep -q "$marker" "$file" 2>/dev/null || echo "$content" >> "$file"
}
# 检查工具是否安装
check_tool() {
local name="$1"
local cmd="$2"
local version_cmd="${3:-$cmd --version}"
if command -v "$cmd" &>/dev/null; then
echo " $(status_ok) ${name}: $($version_cmd 2>/dev/null | head -n1)"
else
echo " $(status_miss) ${name}"
fi
}
# 检查配置文件
check_config() {
local file="$1"
local pattern="$2"
local desc="$3"
if [ -f "$file" ] && grep -q "$pattern" "$file" 2>/dev/null; then
echo " $(status_ok) ${desc}"
else
local status="配置异常"
[ ! -f "$file" ] && status="文件不存在"
echo " $(status_warn) ${desc} ($(text_red "$status"))"
fi
}
# 测试 URL 可用性
test_url() {
local name="$1"
local url="$2"
local status="$(status_fail)"
wget -q --spider --connect-timeout=3 --timeout=5 "$url" 2>/dev/null && status="$(status_ok)"
echo " ${name}: ${status}"
}
# 计算字符串显示宽度(中文字符算2个宽度)
calc_width() {
local str=$1
local width=0
local char
while read -r -n1 char; do
local code
code=$(printf '%d' "'${char}" 2>/dev/null || echo 0)
((width += (code > 127 ? 2 : 1)))
done <<< "$str"
echo "$width"
}
# ============================================================================
# 文件验证函数
# ============================================================================
# 检查文件是否存在且非空
file_exists() {
[ -f "$1" ] && [ -s "$1" ]
}
# 检查通配符文件是否存在
glob_exists() {
local file
file=$(ls $1 2>/dev/null | head -n1)
[ -n "${file}" ] && [ -f "${file}" ] && [ -s "${file}" ]
}
# 检查目录是否有效(非空)
dir_valid() {
[ -d "$1" ] && [ "$(find "$1" -maxdepth 1 -mindepth 1 -not -name ".git" 2>/dev/null | wc -l)" -gt 0 ]
}
# ============================================================================
# 下载函数(带重试和校验)
# ============================================================================
# 计算 SHA256 校验和
compute_sha256() {
local file=$1
if [ -f "${file}" ]; then
sha256sum "${file}" 2>/dev/null | cut -d' ' -f1
else
echo ""
fi
}
# 从 GitHub releases 获取预期校验和
get_github_release_checksum() {
local repo=$1
local version=$2
local filename=$3
local checksums_url="https://github.com/${repo}/releases/download/${version}/checksums.txt"
local temp_checksums
temp_checksums=$(mktemp)
if wget -q --timeout=30 -O "${temp_checksums}" "${checksums_url}" 2>/dev/null; then
local checksum
checksum=$(grep "${filename}" "${temp_checksums}" 2>/dev/null | awk '{print $1}' | head -n1)
rm -f "${temp_checksums}"
echo "${checksum}"
else
rm -f "${temp_checksums}"
echo ""
fi
}
# 从 SHASUMS256.txt 文件获取校验和(用于 Node.js)
get_shasums256_checksum() {
local shasums_url=$1
local filename=$2
local temp_shasums
temp_shasums=$(mktemp)
if wget -q --timeout=30 -O "${temp_shasums}" "${shasums_url}" 2>/dev/null; then
local checksum
checksum=$(grep "${filename}" "${temp_shasums}" 2>/dev/null | awk '{print $1}' | head -n1)
rm -f "${temp_shasums}"
echo "${checksum}"
else
rm -f "${temp_shasums}"
echo ""
fi
}
# 验证文件完整性
verify_file_integrity() {
local file=$1
local expected_checksum=$2
local min_size=${3:-1024}
if [ ! -f "${file}" ] || [ ! -s "${file}" ]; then
printf " %s 文件不存在或为空: %s\n" "$(status_fail)" "$(basename "${file}")"
return 1
fi
local file_size
file_size=$(stat -c%s "${file}" 2>/dev/null || echo 0)
if [ -n "${min_size}" ] && [ "${file_size}" -lt "${min_size}" ]; then
printf " %s 文件过小: %s 字节 (最小: %s)\n" "$(status_fail)" "${file_size}" "${min_size}"
return 1
fi
if [ -n "${expected_checksum}" ]; then
local actual_checksum
actual_checksum=$(compute_sha256 "${file}")
if [ "${actual_checksum}" != "${expected_checksum}" ]; then
printf " %s 校验和不匹配\n" "$(status_fail)"
printf " 预期: %s\n" "${expected_checksum}"
printf " 实际: %s\n" "${actual_checksum}"
return 1
fi
printf " %s 校验和验证通过\n" "$(status_ok)"
fi
return 0
}
# 下载文件(带重试)
download_file() {
local url=$1
local output_file=$2
local desc=$3
local expected_checksum=$4
local min_size=${5:-1024}
local max_retries=3
local retry_count=0
if file_exists "${output_file}"; then
if [ -n "${expected_checksum}" ]; then
if verify_file_integrity "${output_file}" "${expected_checksum}" "${min_size}"; then
print_status_skip "$(basename "${output_file}")" "已验证"
return 0
else
printf " %s 文件损坏,重新下载...\n" "$(status_warn)"
rm -f "${output_file}"
fi
else
if verify_file_integrity "${output_file}" "" "${min_size}"; then
print_status_skip "$(basename "${output_file}")"
return 0
fi
fi
fi
while [ $retry_count -lt $max_retries ]; do
print_status_get "${desc}"
printf " %s 下载地址: %s\n" "$(status_info)" "${url}"
local wget_exit_code=0
if [ -f "${output_file}" ] && [ -s "${output_file}" ]; then
# 文件存在但校验失败,使用断点续传
wget -c --tries=3 --waitretry=2 --show-progress \
--connect-timeout=10 --timeout=300 \
-O "${output_file}" "${url}" 2>&1 || wget_exit_code=$?
else
wget --tries=3 --waitretry=2 --show-progress \
--connect-timeout=10 --timeout=300 \
-O "${output_file}" "${url}" 2>&1 || wget_exit_code=$?
fi
if [ ${wget_exit_code} -eq 0 ]; then
if verify_file_integrity "${output_file}" "${expected_checksum}" "${min_size}"; then
print_status_ok "${desc}"
return 0
else
((retry_count++))
if [ $retry_count -lt $max_retries ]; then
print_status_retry "下载" "${retry_count}"
sleep 2
rm -f "${output_file}"
fi
fi
else
((retry_count++))
if [ $retry_count -lt $max_retries ]; then
print_status_retry "下载失败 (wget=${wget_exit_code})" "${retry_count}"
sleep 2
fi
fi
done
print_status_fail "${desc}" "已重试 ${max_retries}"
rm -f "${output_file}"
return 1
}
# 克隆仓库
clone_repo() {
local url=$1
local dest_dir=$2
local branch=$3
local desc=$4
if dir_valid "${dest_dir}"; then
print_status_skip "${desc}"
return 0
fi
rm -rf "${dest_dir}" 2>/dev/null
printf " %s %s\n" "$(text_cyan "⟳ ")" "${desc}"
local clone_args=("--depth=1")
[ -n "${branch}" ] && clone_args+=("--branch" "${branch}")
if git clone "${clone_args[@]}" "${url}" "${dest_dir}" && dir_valid "${dest_dir}"; then
rm -rf "${dest_dir}/.git"
print_status_ok "${desc}"
return 0
fi
print_status_fail "${desc}"
rm -rf "${dest_dir}"
return 1
}
# ============================================================================
# VSCode 扩展函数
# ============================================================================
# 从 .vsix 文件中提取扩展版本
get_vscode_extension_version() {
local vsix_file=$1
if [ -f "${vsix_file}" ]; then
unzip -p "${vsix_file}" "extension/package.json" 2>/dev/null | \
grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' | \
cut -d'"' -f4 | head -n1
else
echo ""
fi
}
# 从 Marketplace API 获取扩展的最新版本
get_vscode_latest_version() {
local extension_id=$1
local publisher
local extension_name
publisher=${extension_id%%.*}
extension_name=${extension_id#*.}
local api_url="https://marketplace.visualstudio.com/items?itemName=${extension_id}"
# 从 Marketplace 页面提取版本号
wget -q --timeout=30 -U "Mozilla/5.0" -O - "${api_url}" 2>/dev/null | \
grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' | \
cut -d'"' -f4 | head -n1
}
# ============================================================================
# NPM 包版本函数
# ============================================================================
# 从 .tgz 文件中提取 npm 包版本
get_npm_package_version() {
local tgz_file=$1
if [ -f "${tgz_file}" ]; then
# npm pack 生成的文件结构为 package/package.json
tar -xzf "${tgz_file}" -O package/package.json 2>/dev/null | \
grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' | \
cut -d'"' -f4 | head -n1
else
echo ""
fi
}
# 从 npm registry 获取包的最新版本
get_npm_latest_version() {
local package_name=$1
local registry=${2:-"https://registry.npmmirror.com"}
# 使用 npm view 命令获取版本
npm view "${package_name}" version --registry "${registry}" 2>/dev/null | head -n1
}
# ============================================================================
# 下载状态输出函数
# ============================================================================
# 统一的下载状态输出
print_status_check() {
local item=$1
printf " %s %s\n" "$(status_info)" "${item}"
}
print_status_get() {
local item=$1
printf " %s %s\n" "$(text_cyan "↓ ")" "${item}"
}
print_status_skip() {
local item=$1
local version=${2:-""}
if [ -n "${version}" ]; then
printf " %s %s (版本: %s)\n" "$(status_ok)" "${item}" "${version}"
else
printf " %s %s\n" "$(status_ok)" "${item}"
fi
}
print_status_update() {
local item=$1
local old_version=$2
local new_version=$3
printf " %s %s: %s → %s\n" "$(text_yellow "↻ ")" "${item}" "${old_version}" "${new_version}"
}
print_status_ok() {
local item=$1
printf " %s %s\n" "$(status_ok)" "${item}"
}
print_status_fail() {
local item=$1
local reason=${2:-""}
if [ -n "${reason}" ]; then
printf " %s %s (%s)\n" "$(status_fail)" "${item}" "${reason}"
else
printf " %s %s\n" "$(status_fail)" "${item}"
fi
}
print_status_warn() {
local item=$1
local message=${2:-""}
if [ -n "${message}" ]; then
printf " %s %s: %s\n" "$(status_warn)" "${item}" "${message}"
else
printf " %s %s\n" "$(status_warn)" "${item}"
fi
}
print_status_retry() {
local item=$1
local count=$2
printf " %s %s - 第 %s 次重试...\n" "$(status_warn)" "${item}" "${count}"
}
print_status_info() {
local message=$1
printf " %s %s\n" "$(status_info)" "${message}"
}
print_step_extra() {
local label=${1:-"+1"}
printf "%s " "$(text_yellow "[${label}]")"
}
# ============================================================================
# 导出所有函数
# ============================================================================
export -f status_ok status_warn status_miss status_info status_fail
export -f text_green text_yellow text_red text_blue text_cyan text_gray text_bold text_purple text_reset
export -f print_header print_section print_step print_status
export -f append_config check_tool check_config test_url calc_width
export -f file_exists glob_exists dir_valid
export -f compute_sha256 get_github_release_checksum get_shasums256_checksum verify_file_integrity download_file clone_repo
export -f get_vscode_extension_version get_vscode_latest_version
export -f get_npm_package_version get_npm_latest_version
export -f print_status_check print_status_get print_status_skip print_status_update print_status_ok print_status_fail print_status_warn print_status_retry print_step_extra