dsh-mobile-plugin 0.1.62 → 0.1.64
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/LICENSE +21 -0
- package/README.md +21 -1
- package/lib/http/routing.js +8 -1
- package/lib/impl.js +12 -15
- package/lib/mobile/activity.cjs +28 -0
- package/lib/panel/index.html +8 -8
- package/lib/relay/lan-network.cjs +97 -7
- package/lib/routes/control-plane.js +46 -1
- package/lib/update-install.cjs +56 -23
- package/package.json +2 -3
|
@@ -48,10 +48,14 @@ function interfaceRank(name) {
|
|
|
48
48
|
return 3
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
/**
|
|
52
|
+
* 扫一遍网卡,把 `accept` 认的 IPv4 地址收成候选(剔除虚拟/VPN 网卡、按物理网卡优先级
|
|
53
|
+
* 排序、去重、最多 16 条)。
|
|
54
|
+
*
|
|
55
|
+
* `lanInterfaces` 与 `publicInterfaces` 共用这一段:两者的差别**只在 accept**——一个挑私网、
|
|
56
|
+
* 一个挑公网。网卡黑名单与排序规则必须一模一样,否则同一台机器上两份候选会给出互相矛盾的顺序。
|
|
57
|
+
*/
|
|
58
|
+
function collectEndpoints(interfaces, accept) {
|
|
55
59
|
const found = []
|
|
56
60
|
let order = 0
|
|
57
61
|
for (const [name, entries] of Object.entries(interfaces || {})) {
|
|
@@ -60,7 +64,7 @@ function lanInterfaces(port, interfaces = os.networkInterfaces()) {
|
|
|
60
64
|
for (const entry of entries || []) {
|
|
61
65
|
if (!entry || entry.internal) continue
|
|
62
66
|
if (entry.family !== 'IPv4' && entry.family !== 4) continue
|
|
63
|
-
if (!
|
|
67
|
+
if (!accept(entry.address)) continue
|
|
64
68
|
found.push({ name, address: entry.address, order: current })
|
|
65
69
|
}
|
|
66
70
|
}
|
|
@@ -73,7 +77,17 @@ function lanInterfaces(port, interfaces = os.networkInterfaces()) {
|
|
|
73
77
|
return true
|
|
74
78
|
})
|
|
75
79
|
.slice(0, 16)
|
|
76
|
-
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function toCandidates(list, port) {
|
|
83
|
+
return list.map((item) => ({ url: `http://${item.address}:${port}`, interface: item.name, family: 'IPv4', address: item.address }))
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Structured view of the usable LAN endpoints. The relay's `normalizeLanMetadata`
|
|
87
|
+
// and the app's `candidateUrls()` both accept this shape, so callers that want
|
|
88
|
+
// interface metadata (for diagnostics or smarter ordering) should prefer it.
|
|
89
|
+
function lanInterfaces(port, interfaces = os.networkInterfaces()) {
|
|
90
|
+
return toCandidates(collectEndpoints(interfaces, isPrivateLanAddress), port)
|
|
77
91
|
}
|
|
78
92
|
function lanUrls(port, interfaces = os.networkInterfaces()) {
|
|
79
93
|
return lanInterfaces(port, interfaces).map((candidate) => candidate.url)
|
|
@@ -82,4 +96,80 @@ function lanCandidates(port, interfaces = os.networkInterfaces()) {
|
|
|
82
96
|
return lanInterfaces(port, interfaces)
|
|
83
97
|
}
|
|
84
98
|
|
|
85
|
-
|
|
99
|
+
/**
|
|
100
|
+
* 公网地址:同一遍扫描,挑出**真能从外网拨过来**的那些。
|
|
101
|
+
*
|
|
102
|
+
* 判据是「不在任何特殊网段里」,而不是「不是 RFC1918」——后者会把 169.254/16(链路本地)、
|
|
103
|
+
* 100.64/10(CGNAT)、198.18/15(基准测试)这些同样拨不通的地址当成公网地址发出去。
|
|
104
|
+
* 实测踩过:macOS 上一块多出来的网卡带着 `169.254.87.131`,被当成「公网地址」摆到面板上。
|
|
105
|
+
* 这份特殊网段清单和本文件开头那句「intentionally excludes VPN/CGNAT, link-local and
|
|
106
|
+
* benchmarking/test ranges」是同一条口径。
|
|
107
|
+
*
|
|
108
|
+
* 为什么不并进 `lanInterfaces`:那份列表的语义是「手机和这台机器在同一个 Wi-Fi 里能直连的
|
|
109
|
+
* 地址」,混进公网地址会改掉 App 的探测顺序与中转上报的内容(`test/lan-network.test.cjs`
|
|
110
|
+
* 既钉着「WAF 容器的 192.168.0.1 被当成候选」那次事故,也钉着「只有公网地址的机器候选为空」)。
|
|
111
|
+
*
|
|
112
|
+
* 需要公网地址的地方只有两处,都在取回来之后再决定怎么用:面板显示「手机该填哪个地址」,
|
|
113
|
+
* 以及一台只有公网 IP 的机器上给不出任何私网候选时的兜底(见 `../impl.js` 的
|
|
114
|
+
* `safeLanCandidates`)。
|
|
115
|
+
*/
|
|
116
|
+
const NOT_PUBLIC_RANGES = [
|
|
117
|
+
[0x00000000, 0x00ffffff], // 0/8 “本网络”
|
|
118
|
+
[0x64400000, 0x647fffff], // 100.64/10 CGNAT(运营商级 NAT,外网拨不进来)
|
|
119
|
+
[0x7f000000, 0x7fffffff], // 127/8 回环
|
|
120
|
+
[0xa9fe0000, 0xa9feffff], // 169.254/16 链路本地
|
|
121
|
+
[0xc6120000, 0xc613ffff], // 198.18/15 基准测试
|
|
122
|
+
]
|
|
123
|
+
function isPublicAddress(address) {
|
|
124
|
+
const value = ipv4Number(address)
|
|
125
|
+
if (value === null) return false
|
|
126
|
+
if (isPrivateLanAddress(address)) return false
|
|
127
|
+
// 224/4 起是组播与保留(含 255.255.255.255)。
|
|
128
|
+
if (value >= 0xe0000000) return false
|
|
129
|
+
return !NOT_PUBLIC_RANGES.some(([start, end]) => inRange(value, start, end))
|
|
130
|
+
}
|
|
131
|
+
function publicInterfaces(port, interfaces = os.networkInterfaces()) {
|
|
132
|
+
return toCandidates(collectEndpoints(interfaces, isPublicAddress), port)
|
|
133
|
+
}
|
|
134
|
+
function publicUrls(port, interfaces = os.networkInterfaces()) {
|
|
135
|
+
return publicInterfaces(port, interfaces).map((candidate) => candidate.url)
|
|
136
|
+
}
|
|
137
|
+
function publicCandidates(port, interfaces = os.networkInterfaces()) {
|
|
138
|
+
return publicInterfaces(port, interfaces)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* 对外公布的候选地址:**先私网,一个都没有时才退到公网**。
|
|
143
|
+
*
|
|
144
|
+
* 为什么要有这一条:`lanInterfaces` 只认 RFC1918,于是「DSH 装在一台只有公网 IP 的服务器上」
|
|
145
|
+
* 时那份列表**恒为空** —— 二维码里一个可拨的地址都没有,「手机直连」这条路从根上走不通
|
|
146
|
+
* (2026-10-11 用户报的现场)。只在为空时才退:有私网地址的机器,候选列表一个字都不变。
|
|
147
|
+
*
|
|
148
|
+
* 取不到网卡(异常)时回空清单、绝不抛:它喂的是二维码与移动端的连接信息,
|
|
149
|
+
* 让它把整条控制面拖下水不值得。
|
|
150
|
+
*/
|
|
151
|
+
function advertisedCandidates(port, interfaces = os.networkInterfaces()) {
|
|
152
|
+
try {
|
|
153
|
+
const lan = lanCandidates(port, interfaces)
|
|
154
|
+
if (lan.length > 0) return lan
|
|
155
|
+
return publicCandidates(port, interfaces)
|
|
156
|
+
} catch {
|
|
157
|
+
return []
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = {
|
|
162
|
+
ipv4Number,
|
|
163
|
+
isPrivateLanAddress,
|
|
164
|
+
isPublicAddress,
|
|
165
|
+
isVpnInterface,
|
|
166
|
+
isVirtualInterface,
|
|
167
|
+
interfaceRank,
|
|
168
|
+
lanInterfaces,
|
|
169
|
+
lanUrls,
|
|
170
|
+
lanCandidates,
|
|
171
|
+
publicInterfaces,
|
|
172
|
+
publicUrls,
|
|
173
|
+
publicCandidates,
|
|
174
|
+
advertisedCandidates,
|
|
175
|
+
}
|
|
@@ -22,6 +22,12 @@ const { randomBytes } = require('node:crypto');
|
|
|
22
22
|
|
|
23
23
|
const core = require('../relay/core.js');
|
|
24
24
|
const pairingApproval = require('../relay/pairing-policy.js');
|
|
25
|
+
// 端口自检用的是局域网代理**同一把探针**(见 `../relay/port-allocator.cjs` 的文件头):
|
|
26
|
+
// 这里说「可用」,真正 listen 时就不会 EADDRINUSE,两边不会各说各话。
|
|
27
|
+
const { canListen } = require('../relay/port-allocator.cjs');
|
|
28
|
+
// 面板的「手机该填哪个地址」要两份:私网(lanCandidates)与公网(publicCandidates)。
|
|
29
|
+
// 两者刻意分开,理由写在 lan-network.cjs 里那两个函数头上。
|
|
30
|
+
const { lanCandidates, publicCandidates } = require('../relay/lan-network.cjs');
|
|
25
31
|
const desktopView = require('../desktop-view.cjs');
|
|
26
32
|
const readReceipt = require('../read-receipt.cjs');
|
|
27
33
|
// 客户端半边的活体台账:两条绿点同步通道都是「单向轮询 + 无回执」,不记账就永远分不开
|
|
@@ -173,6 +179,10 @@ function registerControlPlaneRoutes(deps) {
|
|
|
173
179
|
getLanPortAdjusted,
|
|
174
180
|
pairingPayload,
|
|
175
181
|
restartSidecar,
|
|
182
|
+
// 「手机上最近来过没有」。**只往本机这条状态路由上补**(和 panelUrl 一样),
|
|
183
|
+
// 不动 createStatusView——那份 JSON 手机端也在读,给它加字段是另一个决定。
|
|
184
|
+
// 缺省回 0:没有记账的宿主(老版本 / 测试里单独挂 impl)如实报「从来没来过」。
|
|
185
|
+
mobileActivity = () => ({ lastMobileAt: 0 }),
|
|
176
186
|
// 插件 ctx。只给「桌面端说它读过了」那条路由用:它要清掉宿主侧的完成提醒。
|
|
177
187
|
// 缺省 null 时那条路由降级为「只回 200、什么都不清」。
|
|
178
188
|
ctx = null,
|
|
@@ -203,7 +213,16 @@ function registerControlPlaneRoutes(deps) {
|
|
|
203
213
|
// 控制面状态里额外带上面板页的绝对地址:客户端半边(lib/client.js)那一项现在只是
|
|
204
214
|
// 一个「打开配置页面」的入口,链接必须是绝对的(原因见 panelUrlOf)。地址只在这里
|
|
205
215
|
// 补,不动 createStatusView —— 那份 JSON 手机端也在读,127.0.0.1 的地址对它没意义。
|
|
206
|
-
|
|
216
|
+
// 面板的「手机该填哪个地址」:把检测到的网卡地址按**局域网 / 公网**分成两份报出来,
|
|
217
|
+
// 用户不用自己去 `ipconfig`,界面也能一眼看出该填哪个(引导第三、四页用它)。
|
|
218
|
+
//
|
|
219
|
+
// 只给面板看:移动端那份候选契约(二维码里带的 `lanUrls`)**一个字都没动**,那边的
|
|
220
|
+
// 私网/公网取舍见 `../relay/lan-network.cjs` 的 `advertisedCandidates`。
|
|
221
|
+
registerRoute({ kind: 'exact', path: '/api/relay/addresses', handler: localOnly('GET', async (_req, res) => {
|
|
222
|
+
const port = lanPortInUse()
|
|
223
|
+
sendJson(res, 200, { ok: true, port, lan: lanCandidates(port), public: publicCandidates(port) })
|
|
224
|
+
}) })
|
|
225
|
+
registerRoute({ kind: 'exact', path: '/api/relay/status', handler: localOnly('GET', async (req, res) => sendJson(res, 200, { ...status(), panelUrl: panelUrlOf(req), ...mobileActivity() })) })
|
|
207
226
|
// 面板页本体:设置界面从客户端半边搬到这里,浏览器(或桌面端里的「打开配置页面」)
|
|
208
227
|
// 直接打开它。三条地址吐的是同一份 HTML,分流在 React 侧(见 React/src/Root.tsx):
|
|
209
228
|
// /api/relay/panel 看宿主记的「配没配过」,自己决定先走引导还是直接进主页
|
|
@@ -376,6 +395,32 @@ function registerControlPlaneRoutes(deps) {
|
|
|
376
395
|
await syncSidecar().catch(() => undefined)
|
|
377
396
|
sendJson(res, 200, status())
|
|
378
397
|
}) })
|
|
398
|
+
// 端口自检:面板上那颗「检查端口」用的。真实地试绑一次,回答「本机现在能不能监听它」,
|
|
399
|
+
// 探针就是局域网代理挑端口用的那一把,所以它给的答案和真正 listen 的结果是同一件事。
|
|
400
|
+
//
|
|
401
|
+
// 只读,不写任何配置:用户点着玩的动作不该改落盘状态(要改端口走上面的 `/config`)。
|
|
402
|
+
// 一个例外必须认出来:同一个端口如果是这台宿主**自己**正在监听的直连端口,
|
|
403
|
+
// `canListen` 当然会失败,但那不是「被别的程序占用」,要单独回 `ours`。
|
|
404
|
+
registerRoute({ kind: 'exact', path: '/api/relay/check-port', handler: localOnly('POST', async (req, res) => {
|
|
405
|
+
const body = await readJson(req)
|
|
406
|
+
const port = Number(body?.port)
|
|
407
|
+
if (!Number.isInteger(port) || port < 1024 || port > 65535) {
|
|
408
|
+
return sendJson(res, 200, { ok: true, port: null, available: false, reason: 'out-of-range' })
|
|
409
|
+
}
|
|
410
|
+
const snapshot = status()
|
|
411
|
+
const listening = snapshot.lan.state === 'listening'
|
|
412
|
+
const currentPort = snapshot.lan.port
|
|
413
|
+
// 宿主自己的网页端口不能当直连端口——那等于把桌面 GUI 挂到手机上(预览那条路由的
|
|
414
|
+
// `protectedPorts` 也排它)。**这一条光靠试绑发现不了**:实测 macOS 上别的进程占着
|
|
415
|
+
// `127.0.0.1:P` 时,`0.0.0.0:P` 照样绑得成功,于是「可用」会是个假答案。
|
|
416
|
+
if (snapshot.localPort === port) {
|
|
417
|
+
return sendJson(res, 200, { ok: true, port, available: false, ours: false, listening, currentPort, reason: 'host-web-port' })
|
|
418
|
+
}
|
|
419
|
+
if (listening && currentPort === port) {
|
|
420
|
+
return sendJson(res, 200, { ok: true, port, available: true, ours: true, listening, currentPort })
|
|
421
|
+
}
|
|
422
|
+
sendJson(res, 200, { ok: true, port, available: await canListen(port, '0.0.0.0'), ours: false, listening, currentPort })
|
|
423
|
+
}) })
|
|
379
424
|
registerRoute({ kind: 'exact', path: '/api/relay/reconnect', handler: localOnly('POST', async (_req, res) => {
|
|
380
425
|
await syncSidecar()
|
|
381
426
|
sendJson(res, 200, status())
|
package/lib/update-install.cjs
CHANGED
|
@@ -32,16 +32,22 @@
|
|
|
32
32
|
* (`cordis`、`dsh-tools`、`dsh-client-runtime`…),它们是 **DSH 运行时注入的、npm 上
|
|
33
33
|
* 根本没有**。pnpm 8 起 `auto-install-peers` 默认开,会去 npm 上拉这些 peer 然后 404。
|
|
34
34
|
* profile 里通常已经关掉了,但那是市场帮它写的 —— 换台机器不一定有,所以这里显式带上。
|
|
35
|
-
* · `--config.minimum-release-age=0
|
|
36
|
-
*
|
|
35
|
+
* · `--config.minimum-release-age=0`:**撞上 pnpm 的「新发布先晾一会儿」策略时才带**。
|
|
36
|
+
* 强制更新(`force`)一开始就带;平时不带,但**失败信息确实是这类策略时会带开关重试一次**
|
|
37
|
+
* —— 那道策略查的是**整份 lockfile**,别的插件刚发不久也会把整份锁文件判成「含未过期
|
|
38
|
+
* 条目」,于是我们自己的更新整整一天都做不了,而原因跟我们这个包毫无关系(2026-10-11
|
|
39
|
+
* 真机撞上的就是 `billion-context@0.1.192`)。市场那边对这种拒绝同样是带开关重试一次。
|
|
37
40
|
* · `CI=1`:没有终端可交互。包管理器在无人值守的子进程里问问题是没法回答的。
|
|
38
41
|
*
|
|
39
|
-
* ## 装之前先看有没有 agent
|
|
42
|
+
* ## 装之前先看有没有 agent 在跑(但「有没有新版」排在它前面)
|
|
40
43
|
*
|
|
41
44
|
* 装是**就地替换插件文件**,正在干活的对话可能读到半个包。市场那条路有一道这样的闸
|
|
42
45
|
* (`AGENTS_RUNNING`),这里也补上了 —— 判到有人跑就**不动盘**,如实报成「可再试」。
|
|
43
46
|
* `force`(面板上的「强制更新」)是用户明说「我知道,装吧」的那一挡,会跳过它。
|
|
44
47
|
*
|
|
48
|
+
* 顺序上**先答「有没有新版」**:没东西可装时拿一句「有对话在跑」挡在前面是误导,用户会以为
|
|
49
|
+
* 是那个原因,而真正的答案是「已经是最新」。
|
|
50
|
+
*
|
|
45
51
|
* ## 边界(照实说)
|
|
46
52
|
*
|
|
47
53
|
* · **只动 profile 的依赖树**(`pnpm add` 会顺手把 profile 的 `package.json` 里的依赖声明
|
|
@@ -317,17 +323,39 @@ async function installLatest({
|
|
|
317
323
|
}
|
|
318
324
|
|
|
319
325
|
const spec = `${name}@${version}`;
|
|
326
|
+
const releaseAgeFlag = '--config.minimum-release-age=0';
|
|
320
327
|
const args = picked.manager === 'pnpm'
|
|
321
|
-
? ['add', spec, '--config.auto-install-peers=false', ...(force ? [
|
|
328
|
+
? ['add', spec, '--config.auto-install-peers=false', ...(force ? [releaseAgeFlag] : [])]
|
|
322
329
|
: ['install', spec, '--no-audit', '--no-fund'];
|
|
323
|
-
|
|
330
|
+
let command = [picked.manager, ...args].join(' ');
|
|
324
331
|
onEvent({ phase: 'install-started', command, manager: picked.kind });
|
|
325
332
|
|
|
326
|
-
const
|
|
333
|
+
const spawnAdd = extra => run(picked.command, [...picked.prefix, ...args, ...extra], {
|
|
327
334
|
cwd: profileDir,
|
|
328
335
|
env: { ...childEnv, ...picked.env },
|
|
329
336
|
timeoutMs,
|
|
330
337
|
});
|
|
338
|
+
let result = await spawnAdd([]);
|
|
339
|
+
let output = result.output;
|
|
340
|
+
/**
|
|
341
|
+
* 撞上 pnpm 的「新发布先晾一会儿」策略:**带上开关重试一次**。
|
|
342
|
+
*
|
|
343
|
+
* 为什么非重试不可:那道策略查的是**整份 lockfile**,不是我们要装的这个包。这台机器上
|
|
344
|
+
* 实测过一次 —— 另一个插件(`billion-context@0.1.192`)刚发不久,整份 lockfile 就被判
|
|
345
|
+
* 「含未过期的条目」而拒绝,于是我们自己的更新整整一天都做不了,而原因跟我们这个包毫无
|
|
346
|
+
* 关系。市场那边对这种拒绝也是带开关重试一次(`RELEASE_AGE_OVERRIDE`)。
|
|
347
|
+
*
|
|
348
|
+
* 只在**第一次没带开关**、且失败信息确实是这类策略时才重试:别的失败(网络、权限、盘满)
|
|
349
|
+
* 重试一次只会把同一条错误再说一遍,还多花一次等待。
|
|
350
|
+
*/
|
|
351
|
+
const releaseAgeLike = /MINIMUM_RELEASE_AGE|minimumReleaseAge|minimum-release-age|release age/i;
|
|
352
|
+
const retried = !result.ok && picked.manager === 'pnpm' && !force && releaseAgeLike.test(output);
|
|
353
|
+
if (retried) {
|
|
354
|
+
onEvent({ phase: 'release-age-retry' });
|
|
355
|
+
result = await spawnAdd([releaseAgeFlag]);
|
|
356
|
+
command = [picked.manager, ...args, releaseAgeFlag].join(' ');
|
|
357
|
+
output = `${output}\n--- 撞上 pnpm 的发布时长策略,带上 ${releaseAgeFlag} 重试一次 ---\n${result.output}`;
|
|
358
|
+
}
|
|
331
359
|
const after = installedVersion(profileNodeModules, name);
|
|
332
360
|
if (!result.ok) {
|
|
333
361
|
return {
|
|
@@ -338,7 +366,8 @@ async function installLatest({
|
|
|
338
366
|
source: declaredSource(profileDir, name),
|
|
339
367
|
command,
|
|
340
368
|
manager: picked.kind,
|
|
341
|
-
|
|
369
|
+
retried,
|
|
370
|
+
output,
|
|
342
371
|
// 超时与「真的失败了」分开说:前者可以再试,后者多半要看输出。
|
|
343
372
|
failure: {
|
|
344
373
|
code: result.code === null ? 'INSTALL_ABORTED' : 'INSTALL_FAILED',
|
|
@@ -356,7 +385,8 @@ async function installLatest({
|
|
|
356
385
|
source: declaredSource(profileDir, name),
|
|
357
386
|
command,
|
|
358
387
|
manager: picked.kind,
|
|
359
|
-
|
|
388
|
+
retried,
|
|
389
|
+
output,
|
|
360
390
|
failure: changed
|
|
361
391
|
? null
|
|
362
392
|
: { code: 'VERSION_UNCHANGED', message: `${command} 跑完了,但盘上还是 ${after ?? '读不到版本'},这一次不算数` },
|
|
@@ -382,6 +412,22 @@ async function installLatest({
|
|
|
382
412
|
async function installLatestIfNeeded(options = {}) {
|
|
383
413
|
const { profileNodeModules, force = false, name = DEFAULT_NAME, agents = null } = options;
|
|
384
414
|
const beforeVersion = installedVersion(profileNodeModules, name);
|
|
415
|
+
const check = await checkForUpdate({ installedVersion: beforeVersion, source: declaredSource(path.dirname(profileNodeModules), name) });
|
|
416
|
+
// 「有没有新版」排在「有没有人在跑」**前面**:没东西可装的时候,拿一句「有对话在跑」挡住
|
|
417
|
+
// 真正的答案(已经是最新)是误导 —— 2026-10-11 真机上就是这么被绊了一下。
|
|
418
|
+
if (!force && !check.updateAvailable) {
|
|
419
|
+
return {
|
|
420
|
+
installed: false,
|
|
421
|
+
skipped: true,
|
|
422
|
+
beforeVersion,
|
|
423
|
+
installedVersion: beforeVersion,
|
|
424
|
+
source: check.source,
|
|
425
|
+
command: null,
|
|
426
|
+
output: '',
|
|
427
|
+
failure: null,
|
|
428
|
+
check,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
385
431
|
if (!force) {
|
|
386
432
|
const running = runningAgentIds(agents);
|
|
387
433
|
if (running.length > 0) {
|
|
@@ -390,11 +436,12 @@ async function installLatestIfNeeded(options = {}) {
|
|
|
390
436
|
skipped: false,
|
|
391
437
|
beforeVersion,
|
|
392
438
|
installedVersion: beforeVersion,
|
|
393
|
-
source:
|
|
439
|
+
source: check.source,
|
|
394
440
|
command: null,
|
|
395
441
|
output: '',
|
|
396
442
|
runningAgents: running,
|
|
397
443
|
retryable: true,
|
|
444
|
+
check,
|
|
398
445
|
failure: {
|
|
399
446
|
code: 'AGENTS_RUNNING',
|
|
400
447
|
message:
|
|
@@ -405,20 +452,6 @@ async function installLatestIfNeeded(options = {}) {
|
|
|
405
452
|
};
|
|
406
453
|
}
|
|
407
454
|
}
|
|
408
|
-
const check = await checkForUpdate({ installedVersion: beforeVersion, source: declaredSource(path.dirname(profileNodeModules), name) });
|
|
409
|
-
if (!force && !check.updateAvailable) {
|
|
410
|
-
return {
|
|
411
|
-
installed: false,
|
|
412
|
-
skipped: true,
|
|
413
|
-
beforeVersion,
|
|
414
|
-
installedVersion: beforeVersion,
|
|
415
|
-
source: check.source,
|
|
416
|
-
command: null,
|
|
417
|
-
output: '',
|
|
418
|
-
failure: null,
|
|
419
|
-
check,
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
455
|
const result = await installLatest({ ...options, version: check.latestVersion });
|
|
423
456
|
return { ...result, check };
|
|
424
457
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-mobile-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.64",
|
|
4
4
|
"description": "Mobile API (/api/mobile/v1) and relay client for the DSH mobile app — desktop profile build (0.2.0-rc.2 compatible, peers resolved by the host)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -15,8 +15,7 @@
|
|
|
15
15
|
},
|
|
16
16
|
"repository": {
|
|
17
17
|
"type": "git",
|
|
18
|
-
"url": "git+https://github.com/WNLUO/
|
|
19
|
-
"directory": "dsh-mobile-plugin"
|
|
18
|
+
"url": "git+https://github.com/WNLUO/dsh-mobile-plugin.git"
|
|
20
19
|
},
|
|
21
20
|
"keywords": [
|
|
22
21
|
"dsh",
|