@co0ontty/wand 1.43.7 → 1.43.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.
@@ -5,11 +5,13 @@
5
5
  * 命令本身不直接写 stdout / stderr —— TUI 模式下 stderr 已经被 log-bus 劫持。
6
6
  */
7
7
  import { spawn, spawnSync } from "node:child_process";
8
+ import { compareSemver } from "../version-utils.js";
8
9
  import { existsSync, mkdirSync, writeFileSync, unlinkSync } from "node:fs";
9
10
  import os from "node:os";
10
11
  import path from "node:path";
11
12
  import process from "node:process";
12
13
  import { installPackageGloballySync, resolveGlobalWandCli } from "../npm-update-utils.js";
14
+ import { whichSync } from "../path-repair.js";
13
15
  import { computeRelaunch } from "../relaunch.js";
14
16
  const PACKAGE_NAME = "@co0ontty/wand";
15
17
  // ─── 重启 ────────────────────────────────────────────────────────────────
@@ -440,9 +442,9 @@ function resolveWandBin(ctx) {
440
442
  const argv1 = process.argv[1];
441
443
  if (argv1 && existsSync(argv1))
442
444
  return argv1;
443
- const which = spawnSync("which", ["wand"], { encoding: "utf8" });
444
- if (which.status === 0 && which.stdout)
445
- return which.stdout.trim();
445
+ const found = whichSync("wand");
446
+ if (found)
447
+ return found;
446
448
  return "wand";
447
449
  }
448
450
  /**
@@ -676,16 +678,4 @@ function uninstallLaunchdService(scope) {
676
678
  function errMsg(err) {
677
679
  return err instanceof Error ? err.message : String(err);
678
680
  }
679
- /** 简易语义化版本比较;返回正数 = a > b。 */
680
- function compareSemver(a, b) {
681
- const pa = a.replace(/^v/, "").split(/[.\-+]/).map((s) => Number.parseInt(s, 10));
682
- const pb = b.replace(/^v/, "").split(/[.\-+]/).map((s) => Number.parseInt(s, 10));
683
- const len = Math.max(pa.length, pb.length);
684
- for (let i = 0; i < len; i++) {
685
- const x = Number.isFinite(pa[i]) ? pa[i] : 0;
686
- const y = Number.isFinite(pb[i]) ? pb[i] : 0;
687
- if (x !== y)
688
- return x - y;
689
- }
690
- return 0;
691
- }
681
+ // compareSemver 已统一到 ../version-utils.ts
@@ -0,0 +1,16 @@
1
+ /**
2
+ * 语义化版本工具:单一真源,供 server / tui / path-repair / models 共用,
3
+ * 避免各处各写一份比较/提取逻辑导致 debug.MMDDHHMM 后缀排序不一致。
4
+ */
5
+ /** 从任意文本中提取 X.Y.Z[-/+后缀] 形式的版本号(带捕获组)。 */
6
+ export declare const SEMVER_PATTERN: RegExp;
7
+ /** 提取文本中的第一个语义化版本号,没有则返回 null。 */
8
+ export declare function extractSemver(text: string): string | null;
9
+ /**
10
+ * 比较两个语义化版本号,返回正数 = a > b,负数 = a < b,0 = 相等。
11
+ * - 容忍前导 `v`(如 nvm/fnm 的 v18.0.0 目录名)。
12
+ * - 主版本逐段数值比较;相等时按 semver 规则:无 prerelease > 有 prerelease。
13
+ * - 两者都有 prerelease 时按 `.` 分段比较(数字段数值比、非数字段字典序),
14
+ * 贴近标准 semver,避免 debug.MMDDHHMM 后缀因纯字典序而跨月/跨年排反。
15
+ */
16
+ export declare function compareSemver(a: string, b: string): number;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * 语义化版本工具:单一真源,供 server / tui / path-repair / models 共用,
3
+ * 避免各处各写一份比较/提取逻辑导致 debug.MMDDHHMM 后缀排序不一致。
4
+ */
5
+ /** 从任意文本中提取 X.Y.Z[-/+后缀] 形式的版本号(带捕获组)。 */
6
+ export const SEMVER_PATTERN = /(\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?)/;
7
+ /** 提取文本中的第一个语义化版本号,没有则返回 null。 */
8
+ export function extractSemver(text) {
9
+ const match = text.match(SEMVER_PATTERN);
10
+ return match ? match[1] : null;
11
+ }
12
+ /**
13
+ * 比较两个语义化版本号,返回正数 = a > b,负数 = a < b,0 = 相等。
14
+ * - 容忍前导 `v`(如 nvm/fnm 的 v18.0.0 目录名)。
15
+ * - 主版本逐段数值比较;相等时按 semver 规则:无 prerelease > 有 prerelease。
16
+ * - 两者都有 prerelease 时按 `.` 分段比较(数字段数值比、非数字段字典序),
17
+ * 贴近标准 semver,避免 debug.MMDDHHMM 后缀因纯字典序而跨月/跨年排反。
18
+ */
19
+ export function compareSemver(a, b) {
20
+ const parse = (v) => {
21
+ const [main, ...rest] = v.replace(/^v/, "").split("-");
22
+ const pre = rest.join("-");
23
+ const mainParts = main.split(".").map((n) => Number(n) || 0);
24
+ return { mainParts, pre };
25
+ };
26
+ const pa = parse(a);
27
+ const pb = parse(b);
28
+ for (let i = 0; i < 3; i++) {
29
+ const diff = (pa.mainParts[i] || 0) - (pb.mainParts[i] || 0);
30
+ if (diff !== 0)
31
+ return diff;
32
+ }
33
+ // Main version equal — apply semver prerelease rule: no prerelease > with prerelease.
34
+ if (!pa.pre && pb.pre)
35
+ return 1;
36
+ if (pa.pre && !pb.pre)
37
+ return -1;
38
+ if (!pa.pre && !pb.pre)
39
+ return 0;
40
+ const segA = pa.pre.split(".");
41
+ const segB = pb.pre.split(".");
42
+ const segLen = Math.max(segA.length, segB.length);
43
+ for (let i = 0; i < segLen; i++) {
44
+ const sa = segA[i];
45
+ const sb = segB[i];
46
+ if (sa === undefined)
47
+ return -1; // 段少者更小
48
+ if (sb === undefined)
49
+ return 1;
50
+ const na = Number(sa);
51
+ const nb = Number(sb);
52
+ const aIsNum = sa !== "" && !Number.isNaN(na);
53
+ const bIsNum = sb !== "" && !Number.isNaN(nb);
54
+ if (aIsNum && bIsNum) {
55
+ if (na !== nb)
56
+ return na < nb ? -1 : 1;
57
+ }
58
+ else if (aIsNum !== bIsNum) {
59
+ return aIsNum ? -1 : 1; // 数字段 < 非数字段
60
+ }
61
+ else if (sa !== sb) {
62
+ return sa < sb ? -1 : 1;
63
+ }
64
+ }
65
+ return 0;
66
+ }