dsh-mobile-plugin 0.1.60 → 0.1.61
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/lib/index.js +35 -30
- package/lib/panel/index.html +1 -1
- package/lib/routes/control-plane.js +112 -15
- package/lib/shell/updater.js +68 -8
- package/lib/update-install.cjs +311 -0
- package/lib/update-registry.cjs +142 -0
- package/package.json +1 -1
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 「有没有新版」由插件自己回答:直接问 npm 注册表(`<registry>/<name>/latest`)。
|
|
5
|
+
*
|
|
6
|
+
* ## 为什么不再借道 DSH 插件市场
|
|
7
|
+
*
|
|
8
|
+
* 更新其实分两件事:**查**(线上最新是几、这台机器上是几)和**装**(在 profile 里跑依赖
|
|
9
|
+
* 安装、再把运行中的实现换掉)。装只能交给市场 —— 它握着 profile 的依赖树、版本核验与
|
|
10
|
+
* 回滚点,插件自己再 shell 出去装一遍等于把那些抄一遍,而抄漏一处就是「装了个不是最新
|
|
11
|
+
* 的版本」。但**查**不需要市场:这是个公开的 npm 包,一个 GET 就够了。
|
|
12
|
+
*
|
|
13
|
+
* 绕市场的代价很具体:市场(另一个插件)没装、太老或者坏了,「检查更新」就整个答不出来
|
|
14
|
+
* —— 一个插件得另一个插件在场,才能告诉你自己有没有新版。所以这一格拿回来自己做。
|
|
15
|
+
*
|
|
16
|
+
* ## 边界(别把这条读成「更新彻底不依赖市场了」)
|
|
17
|
+
*
|
|
18
|
+
* · 这里**只查不装**:从头到尾不碰 profile、不碰市场、不写盘。装仍然是
|
|
19
|
+
* `/api/relay/update/apply` 那条路(外壳问市场 → 市场装 → 外壳就地换代)。
|
|
20
|
+
* · `source`(npm / 本地 link / `file:` tgz)只有市场知道 —— 它读的是 profile 里的依赖
|
|
21
|
+
* 声明。所以那一格传进来的是外壳**上次**问市场的结果;市场不在时如实为空,本模块
|
|
22
|
+
* **不会**为了这一格去敲市场。
|
|
23
|
+
* · 判据是「线上版本严格大于盘上版本」。线上比盘上旧(回滚、本地构建跑在发布前面)一律
|
|
24
|
+
* 报「没有新版」——「这次更新会降级」必须由版本比较判定,不能由「两个号不相等」判定。
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** 默认注册表:官方源。私有镜像 / 测试用 `DSHM_NPM_REGISTRY` 覆盖。 */
|
|
28
|
+
const DEFAULT_REGISTRY = 'https://registry.npmjs.org';
|
|
29
|
+
|
|
30
|
+
/** 本插件的 npm 包名(`package.json` 的 name)。 */
|
|
31
|
+
const DEFAULT_NAME = 'dsh-mobile-plugin';
|
|
32
|
+
|
|
33
|
+
/** 一次查询的上限。查更新是背景动作,宁可如实报「连不上」也不要挂住面板。 */
|
|
34
|
+
const REQUEST_TIMEOUT_MS = 8000;
|
|
35
|
+
|
|
36
|
+
/** 注册表地址(去掉末尾斜杠;空串当没设)。 */
|
|
37
|
+
function registryBase(env = process.env) {
|
|
38
|
+
const value = env && typeof env.DSHM_NPM_REGISTRY === 'string' ? env.DSHM_NPM_REGISTRY.trim() : '';
|
|
39
|
+
return value ? value.replace(/\/+$/, '') : DEFAULT_REGISTRY;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 版本号比较:只认 `x.y.z` 的三段数字,返回正数表示 `a` 比 `b` 新。
|
|
44
|
+
*
|
|
45
|
+
* 与外壳那份(`lib/shell/manager.js` 的 `compareVersion`)**刻意各留一份**:代不许 require
|
|
46
|
+
* 外壳文件(外壳在进程启动时被缓存,代去读它会拿到另一份模块实例,而且换代清 CJS 缓存时
|
|
47
|
+
* 会被连着清掉)。两份都是十几行、语义必须一致:预发布后缀(`-rc.1`)按主段比,缺段补 0。
|
|
48
|
+
*/
|
|
49
|
+
function compareVersion(a, b) {
|
|
50
|
+
const left = String(a || '0').split('-')[0].split('.').map(Number);
|
|
51
|
+
const right = String(b || '0').split('-')[0].split('.').map(Number);
|
|
52
|
+
for (let i = 0; i < 3; i += 1) {
|
|
53
|
+
const l = Number.isFinite(left[i]) ? left[i] : 0;
|
|
54
|
+
const r = Number.isFinite(right[i]) ? right[i] : 0;
|
|
55
|
+
if (l !== r) return l - r;
|
|
56
|
+
}
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** 注册表的错误:带上一个稳定 code,路由与面板照着说人话。 */
|
|
61
|
+
class RegistryError extends Error {
|
|
62
|
+
constructor(code, message) {
|
|
63
|
+
super(message);
|
|
64
|
+
this.name = 'RegistryError';
|
|
65
|
+
this.code = code;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 问一次「线上最新是几」。
|
|
71
|
+
*
|
|
72
|
+
* `fetchImpl` 注入只为测试(真跑时用全局 fetch);`timeoutMs` 同理。
|
|
73
|
+
*/
|
|
74
|
+
async function latestVersion({ base, name = DEFAULT_NAME, fetchImpl, timeoutMs = REQUEST_TIMEOUT_MS } = {}) {
|
|
75
|
+
const doFetch = fetchImpl || globalThis.fetch;
|
|
76
|
+
if (typeof doFetch !== 'function') {
|
|
77
|
+
throw new RegistryError('registry-unavailable', '这个运行时里没有 fetch,问不了 npm 注册表');
|
|
78
|
+
}
|
|
79
|
+
const url = `${base || registryBase()}/${name}/latest`;
|
|
80
|
+
const controller = typeof AbortController === 'function' ? new AbortController() : null;
|
|
81
|
+
const timer = controller ? setTimeout(() => controller.abort(), timeoutMs) : null;
|
|
82
|
+
let response;
|
|
83
|
+
try {
|
|
84
|
+
response = await doFetch(url, { headers: { accept: 'application/json' }, signal: controller ? controller.signal : undefined });
|
|
85
|
+
} catch (error) {
|
|
86
|
+
throw new RegistryError('registry-unreachable', `连不上 npm 注册表:${error && error.message ? error.message : error}`);
|
|
87
|
+
} finally {
|
|
88
|
+
if (timer) clearTimeout(timer);
|
|
89
|
+
}
|
|
90
|
+
if (!response || response.ok !== true) {
|
|
91
|
+
const status = response && Number.isFinite(response.status) ? response.status : 0;
|
|
92
|
+
if (status === 404) throw new RegistryError('registry-not-found', `npm 上找不到 ${name}(404)`);
|
|
93
|
+
throw new RegistryError(`registry-http-${status || 'unknown'}`, `npm 注册表返回 HTTP ${status || '未知'}`);
|
|
94
|
+
}
|
|
95
|
+
let body = null;
|
|
96
|
+
try {
|
|
97
|
+
body = await response.json();
|
|
98
|
+
} catch {
|
|
99
|
+
body = null;
|
|
100
|
+
}
|
|
101
|
+
const version = body && typeof body.version === 'string' ? body.version.trim() : '';
|
|
102
|
+
if (!version) throw new RegistryError('registry-bad-response', 'npm 注册表没有给出可读的版本号');
|
|
103
|
+
return version;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 查一次更新。
|
|
108
|
+
*
|
|
109
|
+
* `installedVersion` 由调用方给(外壳的「盘上版本」,没有外壳时是这一代自己的版本号);
|
|
110
|
+
* 给不出来时如实返回 `installedVersion: null` + `updateAvailable: false` —— 比不出来就
|
|
111
|
+
* 不喊「有新版」,免得把用户引去装一次什么都没变的更新。
|
|
112
|
+
*/
|
|
113
|
+
async function checkForUpdate({
|
|
114
|
+
installedVersion = null,
|
|
115
|
+
source = null,
|
|
116
|
+
base,
|
|
117
|
+
name = DEFAULT_NAME,
|
|
118
|
+
fetchImpl,
|
|
119
|
+
timeoutMs = REQUEST_TIMEOUT_MS,
|
|
120
|
+
now = Date.now,
|
|
121
|
+
} = {}) {
|
|
122
|
+
const latest = await latestVersion({ base, name, fetchImpl, timeoutMs });
|
|
123
|
+
const installed = typeof installedVersion === 'string' && installedVersion ? installedVersion : null;
|
|
124
|
+
return {
|
|
125
|
+
installedVersion: installed,
|
|
126
|
+
latestVersion: latest,
|
|
127
|
+
updateAvailable: installed !== null && compareVersion(latest, installed) > 0,
|
|
128
|
+
source: typeof source === 'string' && source ? source : null,
|
|
129
|
+
checkedAt: new Date(now()).toISOString(),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = {
|
|
134
|
+
checkForUpdate,
|
|
135
|
+
compareVersion,
|
|
136
|
+
registryBase,
|
|
137
|
+
latestVersion,
|
|
138
|
+
RegistryError,
|
|
139
|
+
DEFAULT_REGISTRY,
|
|
140
|
+
DEFAULT_NAME,
|
|
141
|
+
REQUEST_TIMEOUT_MS,
|
|
142
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-mobile-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.61",
|
|
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",
|