mioku-service-webui 4.0.7 → 4.0.8

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 +128 -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.8",
4
4
  "description": "Mioku WebUI 服务",
5
5
  "main": "index.ts",
6
6
  "type": "module",
package/system.ts CHANGED
@@ -32,6 +32,7 @@ import {
32
32
  WEBUI_DIST,
33
33
  writeJsonFile,
34
34
  } from "./utils";
35
+ import { spawn } from "node:child_process";
35
36
 
36
37
  async function fetchJson(url: string): Promise<any> {
37
38
  const res = await fetch(url, {
@@ -516,17 +517,7 @@ async function getGitOriginUrl(dir: string): Promise<string> {
516
517
  }
517
518
 
518
519
  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];
520
+ return path.join(process.cwd(), "..", "mioku-webui");
530
521
  }
531
522
 
532
523
  function readInstalledWebUIVersion(): string {
@@ -1010,11 +1001,17 @@ export async function installManagedPackage(
1010
1001
  ...Object.keys(afterPkg?.devDependencies || {}),
1011
1002
  ];
1012
1003
  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");
1004
+ afterDeps.find(
1005
+ (name) =>
1006
+ !beforeDeps.has(name) &&
1007
+ (name.startsWith("mioku-plugin-") || name.startsWith("mioku-service-")),
1008
+ ) || pkgName;
1009
+
1010
+ const packageJsonPath = path.join(
1011
+ NODE_MODULES_DIR,
1012
+ installedName,
1013
+ "package.json",
1014
+ );
1018
1015
  const packageJson = readPackageJson(packageJsonPath);
1019
1016
  const missingServices = checkDependentServices(packageJson);
1020
1017
 
@@ -1500,19 +1497,20 @@ async function fetchLatestWebUIUpdate(
1500
1497
  }
1501
1498
 
1502
1499
  const job = (async () => {
1503
- const projectDir = resolveWebUIProjectDir();
1504
1500
  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);
1501
+ // mioku-webui repo is at mioku-lab/mioku-webui, used for GitHub API and dist download
1502
+ const repo = {
1503
+ owner: "mioku-lab",
1504
+ repo: "mioku-webui",
1505
+ fullName: "mioku-lab/mioku-webui",
1506
+ };
1509
1507
 
1510
1508
  const fallback: WebUIUpdateCheckResult = {
1511
1509
  currentVersion,
1512
1510
  latestVersion: currentVersion,
1513
1511
  releaseTag: "",
1514
1512
  releaseUrl: "",
1515
- sourceRepo: repo?.fullName || "",
1513
+ sourceRepo: repo.fullName,
1516
1514
  hasUpdates: false,
1517
1515
  canUpdate: false,
1518
1516
  assetName: "",
@@ -1520,13 +1518,6 @@ async function fetchLatestWebUIUpdate(
1520
1518
  checkedAt: Date.now(),
1521
1519
  };
1522
1520
 
1523
- if (!repo) {
1524
- return {
1525
- ...fallback,
1526
- error: "WebUI 仓库不是 GitHub,暂不支持自动检查更新",
1527
- };
1528
- }
1529
-
1530
1521
  const res = await fetch(
1531
1522
  `https://api.github.com/repos/${repo.owner}/${repo.repo}/releases/latest`,
1532
1523
  {
@@ -1612,101 +1603,36 @@ async function fetchLatestMiokuUpdate(
1612
1603
  const cwd = process.cwd();
1613
1604
  const rootPkg = readRootPackageJson() || {};
1614
1605
  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
1606
 
1607
+ // Get latest version from npm registry
1686
1608
  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;
1609
+ try {
1610
+ const latestOutput = await runCommand(
1611
+ "bun",
1612
+ ["info", "mioku", "version"],
1613
+ cwd,
1614
+ );
1615
+ if (latestOutput.code === 0 && latestOutput.stdout.trim()) {
1616
+ latestVersion = normalizeVersionSpec(latestOutput.stdout.trim());
1698
1617
  }
1618
+ } catch {
1619
+ // ignore - keep currentVersion as fallback
1699
1620
  }
1700
1621
 
1622
+ const hasUpdates =
1623
+ latestVersion !== "unknown" &&
1624
+ currentVersion !== "unknown" &&
1625
+ isVersionNewer(latestVersion, currentVersion);
1626
+
1701
1627
  return {
1702
1628
  currentVersion,
1703
1629
  latestVersion,
1704
- sourceRepo: repo.fullName,
1705
- currentBranch,
1706
- targetRef,
1707
- hasUpdates: behind > 0,
1708
- behind,
1709
- changelog: changelogRes.stdout.trim().split("\n").filter(Boolean),
1630
+ sourceRepo: "mioku",
1631
+ currentBranch: "",
1632
+ targetRef: "",
1633
+ hasUpdates,
1634
+ behind: hasUpdates ? 1 : 0,
1635
+ changelog: [],
1710
1636
  checkedAt: Date.now(),
1711
1637
  error: "",
1712
1638
  };
@@ -1730,24 +1656,18 @@ export async function checkMiokuReleaseUpdate(
1730
1656
 
1731
1657
  export async function updateMiokuFromMain(): Promise<Record<string, any>> {
1732
1658
  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
1659
 
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);
1660
+ const beforePkg = readRootPackageJson();
1661
+ const beforeVersion = beforePkg?.version || "unknown";
1744
1662
 
1745
- if (pull.code !== 0) {
1746
- throw new Error(`git pull 失败: ${pull.stderr || pull.stdout}`);
1663
+ const update = await runCommand("bun", ["update", "mioku"], cwd);
1664
+ if (update.code !== 0) {
1665
+ throw new Error(`bun update mioku 失败: ${update.stderr || update.stdout}`);
1747
1666
  }
1748
1667
 
1749
- const after = await runCommand("git", ["show", "HEAD:package.json"], cwd);
1750
- const changed = packageJsonChanged(before.stdout, after.stdout);
1668
+ const afterPkg = readRootPackageJson();
1669
+ const afterVersion = afterPkg?.version || beforeVersion;
1670
+ const changed = beforeVersion !== afterVersion;
1751
1671
 
1752
1672
  let reinstallOutput = "";
1753
1673
  if (changed) {
@@ -1773,6 +1693,33 @@ export async function updateMiokuFromMain(): Promise<Record<string, any>> {
1773
1693
  };
1774
1694
  }
1775
1695
 
1696
+ async function notifyOwnerWebUIDownloaded(version: string): Promise<void> {
1697
+ const miokuConfig = getMiokuConfig();
1698
+ const owners = miokuConfig?.owners || [];
1699
+ if (owners.length === 0) {
1700
+ return;
1701
+ }
1702
+
1703
+ const bots = Array.from(connectedBots.values());
1704
+ if (bots.length === 0) {
1705
+ return;
1706
+ }
1707
+
1708
+ const bot = bots[0];
1709
+ const message = `🎉 WebUI 已自动更新到 v${version},记得重启哦~`;
1710
+
1711
+ for (const ownerId of owners) {
1712
+ try {
1713
+ await bot.api("send_private_msg", {
1714
+ user_id: Number(ownerId),
1715
+ message,
1716
+ });
1717
+ } catch (err: any) {
1718
+ logger.warn(`[webui] 通知主人 ${ownerId} 失败: ${err?.message || err}`);
1719
+ }
1720
+ }
1721
+ }
1722
+
1776
1723
  export async function updateWebUIDistFromRelease(): Promise<
1777
1724
  Record<string, any>
1778
1725
  > {
@@ -1800,7 +1747,7 @@ export async function updateWebUIDistFromRelease(): Promise<
1800
1747
 
1801
1748
  let lastLogTime = 0;
1802
1749
  const LOG_INTERVAL_MS = 2000;
1803
- const downloadUrl = new URL(check.assetUrl);
1750
+ const downloadUrl = check.assetUrl;
1804
1751
  const tempDir = path.join(
1805
1752
  os.tmpdir(),
1806
1753
  `mioku-webui-update-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
@@ -1813,53 +1760,65 @@ export async function updateWebUIDistFromRelease(): Promise<
1813
1760
 
1814
1761
  let backupPath = "";
1815
1762
  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
- }
1763
+ logger.info(`[webui] 正在下载 WebUI dist...`);
1764
+
1765
+ await new Promise<void>((resolve, reject) => {
1766
+ const curl = spawn(
1767
+ "curl",
1768
+ [
1769
+ "-L",
1770
+ "-o",
1771
+ zipPath,
1772
+ "-A",
1773
+ "mioku-webui-updater/1.0",
1774
+ "--silent",
1775
+ "--show-error",
1776
+ downloadUrl,
1777
+ ],
1778
+ {
1779
+ stdio: ["ignore", "pipe", "pipe"],
1780
+ },
1781
+ );
1832
1782
 
1833
- const chunks: Buffer[] = [];
1834
- let receivedBytes = 0;
1783
+ let stderr = "";
1784
+ curl.stderr.on("data", (chunk) => {
1785
+ stderr += String(chunk);
1786
+ });
1835
1787
 
1836
- while (true) {
1837
- const { done, value } = await reader.read();
1838
- if (done) break;
1788
+ curl.on("close", (code) => {
1789
+ if (code === 0) {
1790
+ resolve();
1791
+ } else {
1792
+ reject(new Error(`curl 下载失败 (code ${code}): ${stderr}`));
1793
+ }
1794
+ });
1839
1795
 
1840
- if (value) {
1841
- chunks.push(Buffer.from(value));
1842
- receivedBytes += value.length;
1796
+ curl.on("error", (err) => {
1797
+ reject(new Error(`curl 进程异常: ${err.message}`));
1798
+ });
1843
1799
 
1844
- if (contentLength > 0) {
1800
+ // Periodically log progress by checking file size
1801
+ const progressTimer = setInterval(() => {
1802
+ if (fs.existsSync(zipPath)) {
1803
+ const stat = fs.statSync(zipPath);
1845
1804
  const now = Date.now();
1846
1805
  if (now - lastLogTime >= LOG_INTERVAL_MS) {
1847
- const progress = Math.round((receivedBytes / contentLength) * 100);
1848
- logger.info(`[webui] 正在下载 WebUI dist... ${receivedBytes}/${contentLength} bytes (${progress}%)`);
1806
+ logger.info(
1807
+ `[webui] 正在下载 WebUI dist... ${(stat.size / 1024 / 1024).toFixed(1)} MB 已下载`,
1808
+ );
1849
1809
  lastLogTime = now;
1850
1810
  }
1851
1811
  }
1852
- }
1853
- }
1854
-
1855
- if (contentLength > 0 && receivedBytes !== contentLength) {
1856
- logger.warn(`[webui] 下载字节数不匹配: 预期 ${contentLength}, 实际 ${receivedBytes}`);
1857
- }
1812
+ }, 1000);
1858
1813
 
1859
- logger.info(`[webui] 下载完成,正在解压...`);
1814
+ // Clear timer when done
1815
+ curl.on("close", () => clearInterval(progressTimer));
1816
+ });
1860
1817
 
1861
- const buffer = Buffer.concat(chunks);
1862
- fs.writeFileSync(zipPath, buffer);
1818
+ const zipStat = fs.statSync(zipPath);
1819
+ logger.info(
1820
+ `[webui] 下载完成 (${(zipStat.size / 1024 / 1024).toFixed(1)} MB),正在解压...`,
1821
+ );
1863
1822
 
1864
1823
  const zip = new AdmZip(zipPath);
1865
1824
  zip.extractAllTo(unpackDir, true);
@@ -1911,6 +1870,8 @@ export async function updateWebUIDistFromRelease(): Promise<
1911
1870
  error: "",
1912
1871
  };
1913
1872
 
1873
+ await notifyOwnerWebUIDownloaded(check.latestVersion);
1874
+
1914
1875
  return {
1915
1876
  ok: true,
1916
1877
  updated: true,