mioku-service-webui 4.0.7 → 4.0.9

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/system.ts +137 -167
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mioku-service-webui",
3
- "version": "4.0.7",
3
+ "version": "4.0.9",
4
4
  "description": "Mioku WebUI 服务",
5
5
  "main": "index.ts",
6
6
  "type": "module",
package/system.ts CHANGED
@@ -13,6 +13,7 @@ import type {
13
13
  WebUISettings,
14
14
  } from "./types";
15
15
  import {
16
+ AUTH_PATH,
16
17
  CHAT_CONFIG_DIR,
17
18
  defaultWebUISettings,
18
19
  ensureDir,
@@ -32,6 +33,7 @@ import {
32
33
  WEBUI_DIST,
33
34
  writeJsonFile,
34
35
  } from "./utils";
36
+ import { spawn } from "node:child_process";
35
37
 
36
38
  async function fetchJson(url: string): Promise<any> {
37
39
  const res = await fetch(url, {
@@ -516,17 +518,7 @@ async function getGitOriginUrl(dir: string): Promise<string> {
516
518
  }
517
519
 
518
520
  function resolveWebUIProjectDir(): string {
519
- const candidates = [
520
- path.join(process.cwd(), "mioku-webui"),
521
- path.join(process.cwd(), "webui"),
522
- ];
523
-
524
- for (const dir of candidates) {
525
- if (fs.existsSync(path.join(dir, "package.json"))) {
526
- return dir;
527
- }
528
- }
529
- return candidates[0];
521
+ return path.join(process.cwd(), "..", "mioku-webui");
530
522
  }
531
523
 
532
524
  function readInstalledWebUIVersion(): string {
@@ -1010,11 +1002,17 @@ export async function installManagedPackage(
1010
1002
  ...Object.keys(afterPkg?.devDependencies || {}),
1011
1003
  ];
1012
1004
  const installedName =
1013
- afterDeps.find((name) => !beforeDeps.has(name) &&
1014
- (name.startsWith("mioku-plugin-") || name.startsWith("mioku-service-"))) ||
1015
- pkgName;
1016
-
1017
- const packageJsonPath = path.join(NODE_MODULES_DIR, installedName, "package.json");
1005
+ afterDeps.find(
1006
+ (name) =>
1007
+ !beforeDeps.has(name) &&
1008
+ (name.startsWith("mioku-plugin-") || name.startsWith("mioku-service-")),
1009
+ ) || pkgName;
1010
+
1011
+ const packageJsonPath = path.join(
1012
+ NODE_MODULES_DIR,
1013
+ installedName,
1014
+ "package.json",
1015
+ );
1018
1016
  const packageJson = readPackageJson(packageJsonPath);
1019
1017
  const missingServices = checkDependentServices(packageJson);
1020
1018
 
@@ -1500,19 +1498,20 @@ async function fetchLatestWebUIUpdate(
1500
1498
  }
1501
1499
 
1502
1500
  const job = (async () => {
1503
- const projectDir = resolveWebUIProjectDir();
1504
1501
  const currentVersion = readInstalledWebUIVersion();
1505
- const pkg = readPackageJson(projectDir) || {};
1506
- const originUrl = (await getGitOriginUrl(projectDir)) || "";
1507
- const repoUrl = originUrl || getRepositoryFromPackage(pkg);
1508
- const repo = parseGitHubRepo(repoUrl);
1502
+ // mioku-webui repo is at mioku-lab/mioku-webui, used for GitHub API and dist download
1503
+ const repo = {
1504
+ owner: "mioku-lab",
1505
+ repo: "mioku-webui",
1506
+ fullName: "mioku-lab/mioku-webui",
1507
+ };
1509
1508
 
1510
1509
  const fallback: WebUIUpdateCheckResult = {
1511
1510
  currentVersion,
1512
1511
  latestVersion: currentVersion,
1513
1512
  releaseTag: "",
1514
1513
  releaseUrl: "",
1515
- sourceRepo: repo?.fullName || "",
1514
+ sourceRepo: repo.fullName,
1516
1515
  hasUpdates: false,
1517
1516
  canUpdate: false,
1518
1517
  assetName: "",
@@ -1520,13 +1519,6 @@ async function fetchLatestWebUIUpdate(
1520
1519
  checkedAt: Date.now(),
1521
1520
  };
1522
1521
 
1523
- if (!repo) {
1524
- return {
1525
- ...fallback,
1526
- error: "WebUI 仓库不是 GitHub,暂不支持自动检查更新",
1527
- };
1528
- }
1529
-
1530
1522
  const res = await fetch(
1531
1523
  `https://api.github.com/repos/${repo.owner}/${repo.repo}/releases/latest`,
1532
1524
  {
@@ -1612,101 +1604,36 @@ async function fetchLatestMiokuUpdate(
1612
1604
  const cwd = process.cwd();
1613
1605
  const rootPkg = readRootPackageJson() || {};
1614
1606
  const currentVersion = normalizeVersionSpec(rootPkg?.version || "unknown");
1615
- const originUrl = (await getGitOriginUrl(cwd)) || "";
1616
- const repoUrl = originUrl || getRepositoryFromPackage(rootPkg);
1617
- const repo = parseGitHubRepo(repoUrl);
1618
- const { currentBranch, targetRef, targetBranch } =
1619
- await resolveMiokuTargetRef(cwd);
1620
-
1621
- const fallback: MiokuUpdateCheckResult = {
1622
- currentVersion,
1623
- latestVersion: currentVersion,
1624
- sourceRepo: repo?.fullName || "",
1625
- currentBranch,
1626
- targetRef,
1627
- hasUpdates: false,
1628
- behind: 0,
1629
- changelog: [],
1630
- checkedAt: Date.now(),
1631
- };
1632
-
1633
- if (!fs.existsSync(path.join(cwd, ".git"))) {
1634
- return {
1635
- ...fallback,
1636
- error: isContainerRuntime()
1637
- ? "当前 Docker 容器未挂载 .git 到 /app/.git,无法检查 Mioku 更新"
1638
- : "当前目录不是 Git 仓库,无法检查 Mioku 更新",
1639
- };
1640
- }
1641
-
1642
- if (!repo) {
1643
- return {
1644
- ...fallback,
1645
- error: "Mioku 仓库不是 GitHub,暂不支持自动检查更新",
1646
- };
1647
- }
1648
-
1649
- const fetchRes = await runCommand(
1650
- "git",
1651
- ["fetch", "origin", targetBranch],
1652
- cwd,
1653
- );
1654
-
1655
- if (fetchRes.code !== 0) {
1656
- return {
1657
- ...fallback,
1658
- error: `git fetch 失败: ${fetchRes.stderr || fetchRes.stdout}`.trim(),
1659
- };
1660
- }
1661
-
1662
- const compare = await runCommand(
1663
- "git",
1664
- ["rev-list", "--left-right", "--count", `HEAD...${targetRef}`],
1665
- cwd,
1666
- );
1667
-
1668
- if (compare.code !== 0) {
1669
- return {
1670
- ...fallback,
1671
- error: `无法比较更新: ${compare.stderr || compare.stdout}`.trim(),
1672
- };
1673
- }
1674
-
1675
- const parts = compare.stdout
1676
- .trim()
1677
- .split(/\s+/)
1678
- .map((item) => Number(item));
1679
- const behind = Number.isFinite(parts[1]) ? parts[1] : 0;
1680
- const changelogRes = await runCommand(
1681
- "git",
1682
- ["log", "--oneline", `HEAD..${targetRef}`, "-n", "30"],
1683
- cwd,
1684
- );
1685
1607
 
1608
+ // Get latest version from npm registry
1686
1609
  let latestVersion = currentVersion;
1687
- const remotePkg = await runCommand(
1688
- "git",
1689
- ["show", `${targetRef}:package.json`],
1690
- cwd,
1691
- );
1692
- if (remotePkg.code === 0) {
1693
- try {
1694
- const parsed = JSON.parse(remotePkg.stdout);
1695
- latestVersion = normalizeVersionSpec(parsed?.version || currentVersion);
1696
- } catch {
1697
- latestVersion = currentVersion;
1610
+ try {
1611
+ const latestOutput = await runCommand(
1612
+ "bun",
1613
+ ["info", "mioku", "version"],
1614
+ cwd,
1615
+ );
1616
+ if (latestOutput.code === 0 && latestOutput.stdout.trim()) {
1617
+ latestVersion = normalizeVersionSpec(latestOutput.stdout.trim());
1698
1618
  }
1619
+ } catch {
1620
+ // ignore - keep currentVersion as fallback
1699
1621
  }
1700
1622
 
1623
+ const hasUpdates =
1624
+ latestVersion !== "unknown" &&
1625
+ currentVersion !== "unknown" &&
1626
+ isVersionNewer(latestVersion, currentVersion);
1627
+
1701
1628
  return {
1702
1629
  currentVersion,
1703
1630
  latestVersion,
1704
- sourceRepo: repo.fullName,
1705
- currentBranch,
1706
- targetRef,
1707
- hasUpdates: behind > 0,
1708
- behind,
1709
- changelog: changelogRes.stdout.trim().split("\n").filter(Boolean),
1631
+ sourceRepo: "mioku",
1632
+ currentBranch: "",
1633
+ targetRef: "",
1634
+ hasUpdates,
1635
+ behind: hasUpdates ? 1 : 0,
1636
+ changelog: [],
1710
1637
  checkedAt: Date.now(),
1711
1638
  error: "",
1712
1639
  };
@@ -1730,24 +1657,18 @@ export async function checkMiokuReleaseUpdate(
1730
1657
 
1731
1658
  export async function updateMiokuFromMain(): Promise<Record<string, any>> {
1732
1659
  const cwd = process.cwd();
1733
- if (!fs.existsSync(path.join(cwd, ".git"))) {
1734
- throw new Error(
1735
- isContainerRuntime()
1736
- ? "当前 Docker 容器未挂载 .git 到 /app/.git,无法更新 Mioku"
1737
- : "当前目录不是 Git 仓库,无法更新 Mioku",
1738
- );
1739
- }
1740
1660
 
1741
- const { targetBranch } = await resolveMiokuTargetRef(cwd);
1742
- const before = await runCommand("git", ["show", "HEAD:package.json"], cwd);
1743
- const pull = await runCommand("git", ["pull", "origin", targetBranch], cwd);
1661
+ const beforePkg = readRootPackageJson();
1662
+ const beforeVersion = beforePkg?.version || "unknown";
1744
1663
 
1745
- if (pull.code !== 0) {
1746
- throw new Error(`git pull 失败: ${pull.stderr || pull.stdout}`);
1664
+ const update = await runCommand("bun", ["update", "mioku"], cwd);
1665
+ if (update.code !== 0) {
1666
+ throw new Error(`bun update mioku 失败: ${update.stderr || update.stdout}`);
1747
1667
  }
1748
1668
 
1749
- const after = await runCommand("git", ["show", "HEAD:package.json"], cwd);
1750
- const changed = packageJsonChanged(before.stdout, after.stdout);
1669
+ const afterPkg = readRootPackageJson();
1670
+ const afterVersion = afterPkg?.version || beforeVersion;
1671
+ const changed = beforeVersion !== afterVersion;
1751
1672
 
1752
1673
  let reinstallOutput = "";
1753
1674
  if (changed) {
@@ -1773,6 +1694,41 @@ export async function updateMiokuFromMain(): Promise<Record<string, any>> {
1773
1694
  };
1774
1695
  }
1775
1696
 
1697
+ async function notifyOwnerWebUIDownloaded(version: string): Promise<void> {
1698
+ const miokuConfig = getMiokuConfig();
1699
+ const owners = miokuConfig?.owners || [];
1700
+ if (owners.length === 0) {
1701
+ return;
1702
+ }
1703
+
1704
+ const bots = Array.from(connectedBots.values());
1705
+ if (bots.length === 0) {
1706
+ return;
1707
+ }
1708
+
1709
+ const bot = bots[0];
1710
+ const settings = getWebUISettings();
1711
+ const auth = readJsonFile<{ token?: string }>(AUTH_PATH, {});
1712
+ const url = `http://${settings.host}:${settings.port}`;
1713
+ const token = auth?.token || "未设置";
1714
+ const message = [
1715
+ `🎉 WebUI v${version} 已就绪`,
1716
+ `地址: ${url}`,
1717
+ `鉴权密钥: ${token}`,
1718
+ ].join("\n");
1719
+
1720
+ for (const ownerId of owners) {
1721
+ try {
1722
+ await bot.api("send_private_msg", {
1723
+ user_id: Number(ownerId),
1724
+ message,
1725
+ });
1726
+ } catch (err: any) {
1727
+ logger.warn(`[webui] 通知主人 ${ownerId} 失败: ${err?.message || err}`);
1728
+ }
1729
+ }
1730
+ }
1731
+
1776
1732
  export async function updateWebUIDistFromRelease(): Promise<
1777
1733
  Record<string, any>
1778
1734
  > {
@@ -1800,7 +1756,7 @@ export async function updateWebUIDistFromRelease(): Promise<
1800
1756
 
1801
1757
  let lastLogTime = 0;
1802
1758
  const LOG_INTERVAL_MS = 2000;
1803
- const downloadUrl = new URL(check.assetUrl);
1759
+ const downloadUrl = check.assetUrl;
1804
1760
  const tempDir = path.join(
1805
1761
  os.tmpdir(),
1806
1762
  `mioku-webui-update-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
@@ -1813,53 +1769,65 @@ export async function updateWebUIDistFromRelease(): Promise<
1813
1769
 
1814
1770
  let backupPath = "";
1815
1771
  try {
1816
- const downloadRes = await fetch(downloadUrl, {
1817
- headers: {
1818
- Accept: "application/octet-stream",
1819
- "User-Agent": "mioku-webui-updater",
1820
- },
1821
- });
1822
-
1823
- if (!downloadRes.ok) {
1824
- throw new Error(`下载 dist 压缩包失败: HTTP_${downloadRes.status}`);
1825
- }
1826
-
1827
- const contentLength = Number(downloadRes.headers.get("content-length") || 0);
1828
- const reader = downloadRes.body?.getReader();
1829
- if (!reader) {
1830
- throw new Error("无法读取下载流");
1831
- }
1772
+ logger.info(`[webui] 正在下载 WebUI dist...`);
1773
+
1774
+ await new Promise<void>((resolve, reject) => {
1775
+ const curl = spawn(
1776
+ "curl",
1777
+ [
1778
+ "-L",
1779
+ "-o",
1780
+ zipPath,
1781
+ "-A",
1782
+ "mioku-webui-updater/1.0",
1783
+ "--silent",
1784
+ "--show-error",
1785
+ downloadUrl,
1786
+ ],
1787
+ {
1788
+ stdio: ["ignore", "pipe", "pipe"],
1789
+ },
1790
+ );
1832
1791
 
1833
- const chunks: Buffer[] = [];
1834
- let receivedBytes = 0;
1792
+ let stderr = "";
1793
+ curl.stderr.on("data", (chunk) => {
1794
+ stderr += String(chunk);
1795
+ });
1835
1796
 
1836
- while (true) {
1837
- const { done, value } = await reader.read();
1838
- if (done) break;
1797
+ curl.on("close", (code) => {
1798
+ if (code === 0) {
1799
+ resolve();
1800
+ } else {
1801
+ reject(new Error(`curl 下载失败 (code ${code}): ${stderr}`));
1802
+ }
1803
+ });
1839
1804
 
1840
- if (value) {
1841
- chunks.push(Buffer.from(value));
1842
- receivedBytes += value.length;
1805
+ curl.on("error", (err) => {
1806
+ reject(new Error(`curl 进程异常: ${err.message}`));
1807
+ });
1843
1808
 
1844
- if (contentLength > 0) {
1809
+ // Periodically log progress by checking file size
1810
+ const progressTimer = setInterval(() => {
1811
+ if (fs.existsSync(zipPath)) {
1812
+ const stat = fs.statSync(zipPath);
1845
1813
  const now = Date.now();
1846
1814
  if (now - lastLogTime >= LOG_INTERVAL_MS) {
1847
- const progress = Math.round((receivedBytes / contentLength) * 100);
1848
- logger.info(`[webui] 正在下载 WebUI dist... ${receivedBytes}/${contentLength} bytes (${progress}%)`);
1815
+ logger.info(
1816
+ `[webui] 正在下载 WebUI dist... ${(stat.size / 1024 / 1024).toFixed(1)} MB 已下载`,
1817
+ );
1849
1818
  lastLogTime = now;
1850
1819
  }
1851
1820
  }
1852
- }
1853
- }
1854
-
1855
- if (contentLength > 0 && receivedBytes !== contentLength) {
1856
- logger.warn(`[webui] 下载字节数不匹配: 预期 ${contentLength}, 实际 ${receivedBytes}`);
1857
- }
1821
+ }, 1000);
1858
1822
 
1859
- logger.info(`[webui] 下载完成,正在解压...`);
1823
+ // Clear timer when done
1824
+ curl.on("close", () => clearInterval(progressTimer));
1825
+ });
1860
1826
 
1861
- const buffer = Buffer.concat(chunks);
1862
- fs.writeFileSync(zipPath, buffer);
1827
+ const zipStat = fs.statSync(zipPath);
1828
+ logger.info(
1829
+ `[webui] 下载完成 (${(zipStat.size / 1024 / 1024).toFixed(1)} MB),正在解压...`,
1830
+ );
1863
1831
 
1864
1832
  const zip = new AdmZip(zipPath);
1865
1833
  zip.extractAllTo(unpackDir, true);
@@ -1911,6 +1879,8 @@ export async function updateWebUIDistFromRelease(): Promise<
1911
1879
  error: "",
1912
1880
  };
1913
1881
 
1882
+ await notifyOwnerWebUIDownloaded(check.latestVersion);
1883
+
1914
1884
  return {
1915
1885
  ok: true,
1916
1886
  updated: true,