mioku-service-webui 4.5.0 → 4.5.1

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/database.ts CHANGED
@@ -300,7 +300,9 @@ export function listMemeTree(): Record<string, Record<string, string[]>> {
300
300
  tree[character][emotion] = fs
301
301
  .readdirSync(emotionDir)
302
302
  .filter((name) => /\.(png|jpg|jpeg|gif|webp)$/i.test(name))
303
- .map((name) => path.join("data", "chat", "meme", character, emotion, name));
303
+ .map((name) =>
304
+ path.posix.join("data", "chat", "meme", character, emotion, name),
305
+ );
304
306
  }
305
307
  }
306
308
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mioku-service-webui",
3
- "version": "4.5.0",
3
+ "version": "4.5.1",
4
4
  "description": "Mioku WebUI 服务",
5
5
  "main": "index.ts",
6
6
  "type": "module",
package/port-utils.ts CHANGED
@@ -41,9 +41,33 @@ function parsePids(raw: string): number[] {
41
41
  return Array.from(pids);
42
42
  }
43
43
 
44
+ function parseNetstatPids(raw: string, port: number): number[] {
45
+ const pids = new Set<number>();
46
+ for (const line of raw.split(/\r?\n/)) {
47
+ const cols = line.trim().split(/\s+/);
48
+ if (cols.length < 5 || cols[0].toUpperCase() !== "TCP") continue;
49
+ if (!/^LISTENING$/i.test(cols[3])) continue;
50
+ // Local address is host:port — IPv6 forms look like [::]:3339.
51
+ const localPort = cols[1].slice(cols[1].lastIndexOf(":") + 1);
52
+ if (Number(localPort) !== port) continue;
53
+ const pid = Number(cols[4]);
54
+ if (Number.isInteger(pid) && pid > 0) pids.add(pid);
55
+ }
56
+ return Array.from(pids);
57
+ }
58
+
44
59
  async function listListeningPids(
45
60
  port: number,
46
61
  ): Promise<{ pids: number[]; lsofAvailable: boolean }> {
62
+ if (process.platform === "win32") {
63
+ try {
64
+ const { stdout } = await execFileAsync("netstat", ["-ano", "-p", "TCP"]);
65
+ return { pids: parseNetstatPids(stdout, port), lsofAvailable: true };
66
+ } catch (err: any) {
67
+ return { pids: [], lsofAvailable: err?.code !== "ENOENT" };
68
+ }
69
+ }
70
+
47
71
  try {
48
72
  const { stdout } = await execFileAsync("lsof", [
49
73
  "-nP",
@@ -84,8 +108,9 @@ export async function freePort(
84
108
  const initial = await listListeningPids(port);
85
109
 
86
110
  if (!initial.lsofAvailable) {
111
+ const missing = process.platform === "win32" ? "netstat" : "lsof";
87
112
  log.warn(
88
- `未检测到 lsof 命令,无法定位端口 ${port} 的占用进程,建议安装 lsof`,
113
+ `未检测到 ${missing} 命令,无法定位端口 ${port} 的占用进程,建议安装 ${missing}`,
89
114
  );
90
115
  return {
91
116
  port,
package/routes/db.ts CHANGED
@@ -231,7 +231,10 @@ export function createMemeRoutes() {
231
231
  const buffer = Buffer.from(await file.arrayBuffer());
232
232
  fs.writeFileSync(filePath, buffer);
233
233
 
234
- return c.json({ ok: true, path: path.relative(process.cwd(), filePath) });
234
+ return c.json({
235
+ ok: true,
236
+ path: path.relative(process.cwd(), filePath).split(path.sep).join("/"),
237
+ });
235
238
  });
236
239
 
237
240
  app.delete("/", async (c) => {
package/system.ts CHANGED
@@ -33,7 +33,7 @@ import {
33
33
  WEBUI_DIST,
34
34
  writeJsonFile,
35
35
  } from "./utils";
36
- import { spawn } from "node:child_process";
36
+ import * as fsp from "node:fs/promises";
37
37
 
38
38
  async function fetchJson(url: string): Promise<any> {
39
39
  const res = await fetch(url, {
@@ -553,44 +553,6 @@ function getRepositoryFromPackage(pkg: any): string {
553
553
  return "";
554
554
  }
555
555
 
556
- async function commandExists(cmd: string): Promise<boolean> {
557
- const result = await runCommand("which", [cmd], process.cwd());
558
- return result.code === 0;
559
- }
560
-
561
- async function ensureUnzipCommand(): Promise<void> {
562
- if (await commandExists("unzip")) {
563
- return;
564
- }
565
-
566
- const platform = os.platform();
567
- if (platform === "darwin") {
568
- const installRes = await runCommand(
569
- "brew",
570
- ["install", "unzip"],
571
- process.cwd(),
572
- );
573
- if (installRes.code !== 0) {
574
- throw new Error(
575
- `未找到 unzip 且自动安装失败: ${installRes.stderr || installRes.stdout}。\n` +
576
- `请手动运行: brew install unzip`,
577
- );
578
- }
579
- } else {
580
- const installRes = await runCommand(
581
- "sudo",
582
- ["apt", "install", "-y", "unzip"],
583
- process.cwd(),
584
- );
585
- if (installRes.code !== 0) {
586
- throw new Error(
587
- `未找到 unzip 且自动安装失败: ${installRes.stderr || installRes.stdout}。\n` +
588
- `请手动运行: sudo apt install unzip`,
589
- );
590
- }
591
- }
592
- }
593
-
594
556
  async function getGitOriginUrl(dir: string): Promise<string> {
595
557
  const res = await runCommand("git", ["remote", "get-url", "origin"], dir);
596
558
  if (res.code !== 0) return "";
@@ -1912,58 +1874,31 @@ export async function updateWebUIDistFromRelease(): Promise<
1912
1874
  try {
1913
1875
  logger.info(`[webui] 正在下载 WebUI dist...`);
1914
1876
 
1915
- await new Promise<void>((resolve, reject) => {
1916
- const curl = spawn(
1917
- "curl",
1918
- [
1919
- "-L",
1920
- "-o",
1921
- zipPath,
1922
- "-A",
1923
- "mioku-webui-updater/1.0",
1924
- "--silent",
1925
- "--show-error",
1926
- downloadUrl,
1927
- ],
1928
- {
1929
- stdio: ["ignore", "pipe", "pipe"],
1930
- },
1931
- );
1932
-
1933
- let stderr = "";
1934
- curl.stderr.on("data", (chunk) => {
1935
- stderr += String(chunk);
1936
- });
1877
+ const res = await fetch(downloadUrl, {
1878
+ headers: { "User-Agent": "mioku-webui-updater/1.0" },
1879
+ redirect: "follow",
1880
+ });
1881
+ if (!res.ok || !res.body) {
1882
+ throw new Error(`下载失败: HTTP ${res.status}`);
1883
+ }
1937
1884
 
1938
- curl.on("close", (code) => {
1939
- if (code === 0) {
1940
- resolve();
1941
- } else {
1942
- reject(new Error(`curl 下载失败 (code ${code}): ${stderr}`));
1943
- }
1944
- });
1945
-
1946
- curl.on("error", (err) => {
1947
- reject(new Error(`curl 进程异常: ${err.message}`));
1948
- });
1949
-
1950
- // Periodically log progress by checking file size
1951
- const progressTimer = setInterval(() => {
1952
- if (fs.existsSync(zipPath)) {
1953
- const stat = fs.statSync(zipPath);
1954
- const now = Date.now();
1955
- if (now - lastLogTime >= LOG_INTERVAL_MS) {
1956
- logger.info(
1957
- `[webui] 正在下载 WebUI dist... ${(stat.size / 1024 / 1024).toFixed(1)} MB 已下载`,
1958
- );
1959
- lastLogTime = now;
1960
- }
1885
+ let downloaded = 0;
1886
+ const handle = await fsp.open(zipPath, "w");
1887
+ try {
1888
+ for await (const chunk of res.body as unknown as AsyncIterable<Uint8Array>) {
1889
+ await handle.write(chunk);
1890
+ downloaded += chunk.byteLength;
1891
+ const now = Date.now();
1892
+ if (now - lastLogTime >= LOG_INTERVAL_MS) {
1893
+ logger.info(
1894
+ `[webui] 正在下载 WebUI dist... ${(downloaded / 1024 / 1024).toFixed(1)} MB 已下载`,
1895
+ );
1896
+ lastLogTime = now;
1961
1897
  }
1962
- }, 1000);
1963
-
1964
- // Clear timer when done
1965
- curl.on("close", () => clearInterval(progressTimer));
1966
- });
1898
+ }
1899
+ } finally {
1900
+ await handle.close();
1901
+ }
1967
1902
 
1968
1903
  const zipStat = fs.statSync(zipPath);
1969
1904
  logger.info(
package/utils.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
- import { spawn } from "node:child_process";
5
4
  import type { PackageManager, WebUISettings } from "./types";
6
5
  import {
7
6
  getServiceConfigDir,
8
7
  getServiceDataDir,
9
8
  getConfigDir,
10
9
  getDataDir,
10
+ runCommand as runMiokuCommand,
11
11
  } from "mioku";
12
12
 
13
13
  const __filename = fileURLToPath(import.meta.url);
@@ -86,34 +86,7 @@ export async function runCommand(
86
86
  args: string[],
87
87
  cwd: string,
88
88
  ): Promise<{ stdout: string; stderr: string; code: number }> {
89
- return new Promise((resolve) => {
90
- const child = spawn(command, args, {
91
- cwd,
92
- stdio: ["ignore", "pipe", "pipe"],
93
- });
94
- let stdout = "";
95
- let stderr = "";
96
-
97
- child.stdout.on("data", (chunk) => {
98
- stdout += String(chunk);
99
- });
100
-
101
- child.stderr.on("data", (chunk) => {
102
- stderr += String(chunk);
103
- });
104
-
105
- child.on("close", (code) => {
106
- resolve({ stdout, stderr, code: code ?? 1 });
107
- });
108
-
109
- child.on("error", (error) => {
110
- resolve({
111
- stdout,
112
- stderr: `${stderr}\n${error.message}`.trim(),
113
- code: 1,
114
- });
115
- });
116
- });
89
+ return runMiokuCommand(command, args, { cwd });
117
90
  }
118
91
 
119
92
  export function getInstallCommand(): { cmd: string; args: string[] } {