Files
nj/scripts/stop.mjs
iven 11d0971a67
Some checks failed
Main Merge / backend (push) Has been cancelled
Main Merge / frontend (push) Has been cancelled
feat(app): pnpm 一键启动 + Flutter Web 编译修复
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 接口
2026-06-03 09:50:19 +08:00

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();