1. 新增 pnpm start:dev / pnpm start:stop 命令 - scripts/dev.mjs: 跨平台启动脚本(后端+管理端+学生端) - scripts/stop.mjs: 端口清理停止脚本 - 根 package.json 定义 pnpm 脚本 2. 修复 Flutter Web 编译(Isar 3.x + flutter_secure_storage 不兼容) - isar_database: 条件导出,Web 用空 stub - isar_journal_repository: 条件导出,Web 用空 stub - sync_engine: 条件导出,Web 用内存队列(无 Isar 持久化) - 移除 flutter_secure_storage(v9 web 插件用 dart:html) - 新增 SecureTokenStore 接口 + shared_preferences 实现 - auth_repository 改用 SecureTokenStore 接口
112 lines
2.6 KiB
JavaScript
112 lines
2.6 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* 暖记开发环境停止脚本 — 清理所有服务进程
|
|
*
|
|
* 用法:
|
|
* pnpm start:stop
|
|
*/
|
|
|
|
import { execSync } from "node:child_process";
|
|
|
|
const IS_WIN = process.platform === "win32";
|
|
|
|
// ===== 配置 =====
|
|
const SERVICES = [
|
|
{ port: 3000, name: "后端 (Rust/Axum)" },
|
|
{ port: 5174, name: "管理端前端 (React/Vite)" },
|
|
{ port: 8080, name: "学生端前端 (Flutter Web)" },
|
|
];
|
|
|
|
// ===== 颜色输出 =====
|
|
const log = {
|
|
info: (msg) => console.log(`\x1b[34m[INFO]\x1b[0m ${msg}`),
|
|
ok: (msg) => console.log(`\x1b[32m[OK]\x1b[0m ${msg}`),
|
|
warn: (msg) => console.log(`\x1b[33m[WARN]\x1b[0m ${msg}`),
|
|
};
|
|
|
|
// ===== 工具函数 =====
|
|
|
|
function findPidsOnPort(port) {
|
|
try {
|
|
if (IS_WIN) {
|
|
const out = execSync(
|
|
`netstat -ano | findstr :${port} | findstr LISTENING`,
|
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
);
|
|
return [
|
|
...new Set(
|
|
out
|
|
.trim()
|
|
.split("\n")
|
|
.map((l) => l.trim().split(/\s+/).pop())
|
|
.filter((p) => p && p !== "0")
|
|
),
|
|
];
|
|
}
|
|
const out = execSync(`lsof -ti :${port}`, {
|
|
encoding: "utf-8",
|
|
stdio: ["pipe", "pipe", "pipe"],
|
|
});
|
|
return out.trim().split("\n").filter(Boolean);
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function killPid(pid) {
|
|
try {
|
|
if (IS_WIN) {
|
|
execSync(`taskkill /F /PID ${pid}`, { stdio: "pipe" });
|
|
} else {
|
|
execSync(`kill -9 ${pid}`, { stdio: "pipe" });
|
|
}
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function killPort(port, name) {
|
|
const pids = findPidsOnPort(port);
|
|
if (pids.length === 0) {
|
|
log.info(`${name} — 端口 ${port} 空闲`);
|
|
return;
|
|
}
|
|
for (const pid of pids) {
|
|
if (killPid(pid)) {
|
|
log.ok(`${name} — 已停止 (PID: ${pid}, 端口: ${port})`);
|
|
} else {
|
|
log.warn(`${name} — 无法停止 PID: ${pid}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ===== 主流程 =====
|
|
|
|
console.log();
|
|
console.log("\x1b[1m\x1b[36m ═══ 暖记 — 停止所有服务 ═══\x1b[0m");
|
|
console.log();
|
|
|
|
log.info("正在停止所有暖记服务...");
|
|
|
|
for (const { port, name } of SERVICES) {
|
|
killPort(port, name);
|
|
}
|
|
|
|
// Windows: 额外清理 erp-server.exe
|
|
if (IS_WIN) {
|
|
try {
|
|
execSync("taskkill /F /IM erp-server.exe", { stdio: "pipe" });
|
|
log.ok("已停止 erp-server.exe");
|
|
} catch {
|
|
// 没有运行中的进程,忽略
|
|
}
|
|
}
|
|
|
|
// Windows: 清理可能的残留 node 进程(仅清理 pnpm 启动的)
|
|
// 注意:不盲目杀所有 node 进程,只清理端口的即可
|
|
|
|
console.log();
|
|
log.ok("所有服务已停止");
|
|
console.log();
|