@xcanwin/manyoyo 5.8.5 → 5.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/bin/manyoyo.js +145 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -128,6 +128,7 @@ manyoyo serve 127.0.0.1:3000
|
|
|
128
128
|
manyoyo serve 127.0.0.1:3000 -U admin -P 123456
|
|
129
129
|
manyoyo serve 127.0.0.1:3000 -U admin -P 123456 -d
|
|
130
130
|
manyoyo serve 127.0.0.1:3000 -d # 未设置密码时会打印本次随机密码
|
|
131
|
+
manyoyo serve 127.0.0.1:3000 --stop # 停止指定后台服务
|
|
131
132
|
|
|
132
133
|
# 查看配置与命令拼装
|
|
133
134
|
manyoyo config show
|
package/bin/manyoyo.js
CHANGED
|
@@ -1216,6 +1216,7 @@ Notes:
|
|
|
1216
1216
|
const serveCommand = program.command('serve [listen]').description('启动网页交互服务 (默认 127.0.0.1:3000)');
|
|
1217
1217
|
applyRunStyleOptions(serveCommand, { includeRmOnExit: false, includeWebAuthOptions: true });
|
|
1218
1218
|
serveCommand.option('-d, --detach', '后台启动网页服务并立即返回');
|
|
1219
|
+
serveCommand.option('--stop', '停止后台网页服务;必须显式传入 listen');
|
|
1219
1220
|
serveCommand.action((listen, options) => {
|
|
1220
1221
|
selectAction('serve', {
|
|
1221
1222
|
...options,
|
|
@@ -1317,8 +1318,12 @@ Notes:
|
|
|
1317
1318
|
const isShowConfigMode = selectedAction === 'config-show';
|
|
1318
1319
|
const isShowCommandMode = selectedAction === 'config-command';
|
|
1319
1320
|
const isServerMode = options.server !== undefined;
|
|
1321
|
+
const isServerStopMode = Boolean(selectedAction === 'serve' && options.stop);
|
|
1320
1322
|
|
|
1321
1323
|
const noDockerActions = new Set(['init', 'update', 'install', 'config-show', 'plugin']);
|
|
1324
|
+
if (isServerStopMode) {
|
|
1325
|
+
noDockerActions.add('serve');
|
|
1326
|
+
}
|
|
1322
1327
|
if (!noDockerActions.has(selectedAction)) {
|
|
1323
1328
|
ensureDocker();
|
|
1324
1329
|
}
|
|
@@ -1498,7 +1503,7 @@ Notes:
|
|
|
1498
1503
|
SERVER_AUTH_PASS_AUTO = false;
|
|
1499
1504
|
}
|
|
1500
1505
|
|
|
1501
|
-
if (isServerMode) {
|
|
1506
|
+
if (isServerMode && !isServerStopMode) {
|
|
1502
1507
|
ensureWebServerAuthCredentials();
|
|
1503
1508
|
}
|
|
1504
1509
|
|
|
@@ -1560,7 +1565,9 @@ Notes:
|
|
|
1560
1565
|
isRemoveMode,
|
|
1561
1566
|
isShowCommandMode,
|
|
1562
1567
|
isServerMode,
|
|
1568
|
+
isServerStop: isServerStopMode,
|
|
1563
1569
|
isServerDetach: Boolean(selectedAction === 'serve' && options.detach),
|
|
1570
|
+
isServerListenSpecified: Boolean(isServerMode && options.server !== true),
|
|
1564
1571
|
isPluginMode: false
|
|
1565
1572
|
};
|
|
1566
1573
|
}
|
|
@@ -1588,7 +1595,9 @@ function createRuntimeContext(modeState = {}) {
|
|
|
1588
1595
|
showCommand: Boolean(modeState.isShowCommandMode),
|
|
1589
1596
|
rmOnExit: RM_ON_EXIT,
|
|
1590
1597
|
serverMode: Boolean(modeState.isServerMode),
|
|
1598
|
+
serverStop: Boolean(modeState.isServerStop),
|
|
1591
1599
|
serverDetach: Boolean(modeState.isServerDetach),
|
|
1600
|
+
serverListenSpecified: Boolean(modeState.isServerListenSpecified),
|
|
1592
1601
|
serverHost: SERVER_HOST,
|
|
1593
1602
|
serverPort: SERVER_PORT,
|
|
1594
1603
|
serverAuthUser: SERVER_AUTH_USER,
|
|
@@ -1657,10 +1666,138 @@ function buildDetachedServeEnv(runtime) {
|
|
|
1657
1666
|
return env;
|
|
1658
1667
|
}
|
|
1659
1668
|
|
|
1669
|
+
function formatServeListenHost(host) {
|
|
1670
|
+
const text = String(host || '').trim() || '127.0.0.1';
|
|
1671
|
+
if (text.includes(':') && !text.startsWith('[')) {
|
|
1672
|
+
return `[${text}]`;
|
|
1673
|
+
}
|
|
1674
|
+
return text;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
function buildServeListenLabel(host, port) {
|
|
1678
|
+
return `${formatServeListenHost(host)}:${port}`;
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
function buildServePidFile(host, port, homeDir = os.homedir()) {
|
|
1682
|
+
const dir = path.join(homeDir, '.manyoyo', 'run', 'serve');
|
|
1683
|
+
const listen = buildServeListenLabel(host, port);
|
|
1684
|
+
const safeName = listen.replace(/[^A-Za-z0-9_.-]+/g, '_');
|
|
1685
|
+
return {
|
|
1686
|
+
dir,
|
|
1687
|
+
listen,
|
|
1688
|
+
path: path.join(dir, `${safeName}.pid`)
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
function removeServePidFile(filePath) {
|
|
1693
|
+
if (!filePath) return;
|
|
1694
|
+
try {
|
|
1695
|
+
fs.rmSync(filePath, { force: true });
|
|
1696
|
+
} catch (e) {
|
|
1697
|
+
// ignore cleanup failures
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
function isProcessRunning(pid) {
|
|
1702
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
1703
|
+
return false;
|
|
1704
|
+
}
|
|
1705
|
+
try {
|
|
1706
|
+
process.kill(pid, 0);
|
|
1707
|
+
return true;
|
|
1708
|
+
} catch (e) {
|
|
1709
|
+
return e && e.code !== 'ESRCH';
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
function readServePidFile(filePath) {
|
|
1714
|
+
try {
|
|
1715
|
+
const text = fs.readFileSync(filePath, 'utf-8').trim();
|
|
1716
|
+
if (!/^\d+$/.test(text)) {
|
|
1717
|
+
return 0;
|
|
1718
|
+
}
|
|
1719
|
+
return Number(text);
|
|
1720
|
+
} catch (e) {
|
|
1721
|
+
return 0;
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
function getServePidTarget(host, port, homeDir = os.homedir()) {
|
|
1726
|
+
const pidFile = buildServePidFile(host, port, homeDir);
|
|
1727
|
+
const pid = readServePidFile(pidFile.path);
|
|
1728
|
+
if (!Number.isInteger(pid) || pid <= 0 || !isProcessRunning(pid)) {
|
|
1729
|
+
removeServePidFile(pidFile.path);
|
|
1730
|
+
return null;
|
|
1731
|
+
}
|
|
1732
|
+
return {
|
|
1733
|
+
pid,
|
|
1734
|
+
listen: pidFile.listen,
|
|
1735
|
+
path: pidFile.path
|
|
1736
|
+
};
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
function installServePidCleanup(pidFilePath, logger) {
|
|
1740
|
+
if (!pidFilePath || global.__manyoyoServePidCleanupInstalled) {
|
|
1741
|
+
return;
|
|
1742
|
+
}
|
|
1743
|
+
global.__manyoyoServePidCleanupInstalled = true;
|
|
1744
|
+
process.on('exit', () => {
|
|
1745
|
+
removeServePidFile(pidFilePath);
|
|
1746
|
+
if (logger && typeof logger.info === 'function') {
|
|
1747
|
+
logger.info('serve pid file removed', { pidFilePath });
|
|
1748
|
+
}
|
|
1749
|
+
});
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
function writeServePidFile(runtime, serverHandle) {
|
|
1753
|
+
const pidFile = buildServePidFile(serverHandle.host, serverHandle.port);
|
|
1754
|
+
fs.mkdirSync(pidFile.dir, { recursive: true });
|
|
1755
|
+
fs.writeFileSync(pidFile.path, `${process.pid}\n`);
|
|
1756
|
+
installServePidCleanup(pidFile.path, runtime && runtime.logger);
|
|
1757
|
+
return pidFile.path;
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
async function stopServeProcess(runtime) {
|
|
1761
|
+
if (!runtime || !runtime.serverListenSpecified) {
|
|
1762
|
+
throw new Error('serve --stop 必须显式传入 listen,例如 manyoyo serve 127.0.0.1:3000 --stop');
|
|
1763
|
+
}
|
|
1764
|
+
const target = getServePidTarget(runtime.serverHost, runtime.serverPort);
|
|
1765
|
+
if (!target) {
|
|
1766
|
+
const label = buildServeListenLabel(runtime.serverHost, runtime.serverPort);
|
|
1767
|
+
console.log(`${YELLOW}⚠️ 未发现运行中的 serve 实例: ${label}${NC}`);
|
|
1768
|
+
return;
|
|
1769
|
+
}
|
|
1770
|
+
try {
|
|
1771
|
+
process.kill(target.pid, 'SIGTERM');
|
|
1772
|
+
} catch (e) {
|
|
1773
|
+
if (!e || e.code !== 'ESRCH') {
|
|
1774
|
+
throw e;
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
await sleep(200);
|
|
1778
|
+
if (isProcessRunning(target.pid)) {
|
|
1779
|
+
try {
|
|
1780
|
+
process.kill(target.pid, 'SIGKILL');
|
|
1781
|
+
} catch (e) {
|
|
1782
|
+
if (!e || e.code !== 'ESRCH') {
|
|
1783
|
+
throw e;
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
removeServePidFile(target.path);
|
|
1788
|
+
console.log(`${GREEN}✅ 已停止 serve: ${target.listen} (pid: ${target.pid})${NC}`);
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1660
1791
|
function relaunchServeDetached(runtime) {
|
|
1661
1792
|
const serveLog = buildManyoyoLogPath('serve');
|
|
1662
1793
|
fs.mkdirSync(serveLog.dir, { recursive: true });
|
|
1663
1794
|
|
|
1795
|
+
const existing = getServePidTarget(runtime.serverHost, runtime.serverPort);
|
|
1796
|
+
if (existing) {
|
|
1797
|
+
console.log(`${YELLOW}⚠️ serve 已在后台运行: ${existing.listen} (pid: ${existing.pid})${NC}`);
|
|
1798
|
+
return;
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1664
1801
|
const child = spawn(process.argv[0], buildDetachedServeArgv(process.argv.slice(1)), {
|
|
1665
1802
|
detached: true,
|
|
1666
1803
|
stdio: 'ignore',
|
|
@@ -1954,7 +2091,7 @@ async function runWebServerMode(runtime) {
|
|
|
1954
2091
|
runtime.serverAuthPassAuto = SERVER_AUTH_PASS_AUTO;
|
|
1955
2092
|
}
|
|
1956
2093
|
|
|
1957
|
-
await startWebServer({
|
|
2094
|
+
const serverHandle = await startWebServer({
|
|
1958
2095
|
serverHost: runtime.serverHost,
|
|
1959
2096
|
serverPort: runtime.serverPort,
|
|
1960
2097
|
authUser: runtime.serverAuthUser,
|
|
@@ -1993,6 +2130,8 @@ async function runWebServerMode(runtime) {
|
|
|
1993
2130
|
},
|
|
1994
2131
|
logger: runtime.logger
|
|
1995
2132
|
});
|
|
2133
|
+
writeServePidFile(runtime, serverHandle);
|
|
2134
|
+
return serverHandle;
|
|
1996
2135
|
}
|
|
1997
2136
|
|
|
1998
2137
|
async function main() {
|
|
@@ -2015,6 +2154,10 @@ async function main() {
|
|
|
2015
2154
|
|
|
2016
2155
|
// 2. Start web server mode
|
|
2017
2156
|
if (runtime.serverMode) {
|
|
2157
|
+
if (runtime.serverStop) {
|
|
2158
|
+
await stopServeProcess(runtime);
|
|
2159
|
+
return;
|
|
2160
|
+
}
|
|
2018
2161
|
if (runtime.serverDetach) {
|
|
2019
2162
|
relaunchServeDetached(runtime);
|
|
2020
2163
|
return;
|