Phase 1: 共用类型定义 - apps/server/src/common/import-result.types.ts — ImportRowError + ImportResult<T> - apps/admin/src/types/import.ts — 前端对应类型 + ImportDisplayConfig Phase 2: 后端增强 - students.service.ts — batchImport/matchImport 改为 ImportResult 格式 · 每行跳过时收集 ImportRowError(code + reason) · reason 支持 markdown 标记(**字段名** / `值`) - archive.service.ts — batchImportArchive 统一用 ImportResult 格式 · 各 Sheet 错误使用标准 error code - archive.controller.ts — 日志使用新的 success 字段 Phase 3: 前端基础 - 安装 react-markdown - ImportResultModal 组件(共用导入结果弹窗) · Statistic 成功统计 + Table 错误明细 · 原因列支持 react-markdown 渲染(粗体字段名、等宽代码值) · 错误类型用 Tag 颜色区分 - api/index.ts — 403 错误从 console.warn 改为 message.warning 用户可见 Phase 4: 前端集成 - Students/index.tsx — 替换硬编码导入结果弹窗 · 删除'后端只返回统计汇总'注释和跳过原因 hardcode · 新格式自动使用 ImportResultModal,旧格式兼容 - StudentProfileContent/index.tsx — 替换内联导入 Modal 为 ImportResultModal
121 lines
3.2 KiB
Bash
Executable File
121 lines
3.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Stop the brainstorm server and clean up
|
|
# Usage: stop-server.sh <session_dir>
|
|
#
|
|
# Kills the server process. Only deletes session directory if it's
|
|
# under /tmp (ephemeral). Persistent directories (.superpowers/) are
|
|
# kept so mockups can be reviewed later.
|
|
|
|
SESSION_DIR="$1"
|
|
|
|
if [[ -z "$SESSION_DIR" ]]; then
|
|
echo '{"error": "Usage: stop-server.sh <session_dir>"}'
|
|
exit 1
|
|
fi
|
|
|
|
STATE_DIR="${SESSION_DIR}/state"
|
|
PID_FILE="${STATE_DIR}/server.pid"
|
|
SERVER_ID_FILE="${STATE_DIR}/server-instance-id"
|
|
|
|
mark_stopped() {
|
|
local reason="$1"
|
|
rm -f "${STATE_DIR}/server-info"
|
|
printf '{"reason":"%s","timestamp":%s}\n' "$reason" "$(date +%s)" > "${STATE_DIR}/server-stopped"
|
|
}
|
|
|
|
read_expected_server_id() {
|
|
[[ -f "$SERVER_ID_FILE" ]] || return 1
|
|
local id
|
|
id="$(tr -d '\r\n' < "$SERVER_ID_FILE" 2>/dev/null || true)"
|
|
[[ "$id" =~ ^[A-Za-z0-9_-]{32,64}$ ]] || return 1
|
|
printf '%s\n' "$id"
|
|
}
|
|
|
|
command_line_for_pid() {
|
|
local pid="$1"
|
|
if [[ -r "/proc/$pid/cmdline" ]]; then
|
|
tr '\0' '\n' < "/proc/$pid/cmdline" 2>/dev/null || true
|
|
return 0
|
|
fi
|
|
ps -ww -p "$pid" -o command= 2>/dev/null || ps -f -p "$pid" 2>/dev/null | sed '1d' || true
|
|
}
|
|
|
|
command_has_server_id() {
|
|
local pid="$1"
|
|
local expected="$2"
|
|
local expected_arg="--brainstorm-server-id=$expected"
|
|
if [[ -r "/proc/$pid/cmdline" ]]; then
|
|
local arg
|
|
while IFS= read -r -d '' arg || [[ -n "$arg" ]]; do
|
|
[[ "$arg" == "$expected_arg" ]] && return 0
|
|
done < "/proc/$pid/cmdline"
|
|
return 1
|
|
fi
|
|
local command_line
|
|
command_line="$(command_line_for_pid "$pid")"
|
|
[[ -n "$command_line" ]] || return 1
|
|
case " $command_line " in
|
|
*" $expected_arg "*) return 0 ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
# Confirm a PID has this session's per-start instance id, not just a familiar
|
|
# process name. Ambiguous or legacy metadata fails closed as stale_pid.
|
|
is_brainstorm_server() {
|
|
kill -0 "$1" 2>/dev/null || return 1
|
|
local expected_id
|
|
expected_id="$(read_expected_server_id)" || return 1
|
|
command_has_server_id "$1" "$expected_id" || return 1
|
|
return 0
|
|
}
|
|
|
|
if [[ -f "$PID_FILE" ]]; then
|
|
pid=$(cat "$PID_FILE")
|
|
|
|
# Refuse to signal a PID we can't prove is our server. A stale pid file may
|
|
# point at an unrelated process after a reboot/PID wraparound.
|
|
if ! is_brainstorm_server "$pid"; then
|
|
rm -f "$PID_FILE" "$SERVER_ID_FILE"
|
|
mark_stopped "stale_pid"
|
|
echo '{"status": "stale_pid"}'
|
|
exit 0
|
|
fi
|
|
|
|
# Try to stop gracefully, fallback to force if still alive
|
|
kill "$pid" 2>/dev/null || true
|
|
|
|
# Wait for graceful shutdown (up to ~2s)
|
|
for _ in {1..20}; do
|
|
if ! kill -0 "$pid" 2>/dev/null; then
|
|
break
|
|
fi
|
|
sleep 0.1
|
|
done
|
|
|
|
# If still running, escalate to SIGKILL
|
|
if kill -0 "$pid" 2>/dev/null; then
|
|
kill -9 "$pid" 2>/dev/null || true
|
|
|
|
# Give SIGKILL a moment to take effect
|
|
sleep 0.1
|
|
fi
|
|
|
|
if kill -0 "$pid" 2>/dev/null; then
|
|
echo '{"status": "failed", "error": "process still running"}'
|
|
exit 1
|
|
fi
|
|
|
|
rm -f "$PID_FILE" "$SERVER_ID_FILE" "${STATE_DIR}/server.log"
|
|
mark_stopped "stop-server.sh"
|
|
|
|
# Only delete ephemeral /tmp directories
|
|
if [[ "$SESSION_DIR" == /tmp/* ]]; then
|
|
rm -rf "$SESSION_DIR"
|
|
fi
|
|
|
|
echo '{"status": "stopped"}'
|
|
else
|
|
echo '{"status": "not_running"}'
|
|
fi
|