tensorgrid-ui 1.0.0 → 1.0.2
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/client.js +59 -21
- package/lib/update-check.js +128 -114
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -183,14 +183,20 @@ window.__ModuleLoader__.load({
|
|
|
183
183
|
'update.working': 'Working…',
|
|
184
184
|
'update.checking': 'Checking…',
|
|
185
185
|
'update.upToDate': 'Latest version installed',
|
|
186
|
-
'update.available': '
|
|
187
|
-
'update.
|
|
186
|
+
'update.available': 'Version {version} is available',
|
|
187
|
+
'update.availableUnknown': 'A new version is available',
|
|
188
|
+
'update.installed': 'Installed {version}, verified on dsh {dsh}',
|
|
188
189
|
'update.doneReload': 'Updated. Reload the page.',
|
|
189
190
|
'update.doneRestart': 'Updated. Restart the application.',
|
|
190
191
|
'update.failed': 'Update failed',
|
|
192
|
+
'update.problem.noStamp': 'No installation record — reinstall',
|
|
193
|
+
'update.problem.noPackage': 'Installed package not found — reinstall',
|
|
194
|
+
'update.problem.registryStatus': 'The registry refused the request',
|
|
195
|
+
'update.problem.registryUnreachable': 'Could not reach the registry',
|
|
196
|
+
'update.problem.badVersion': 'The registry returned no valid version',
|
|
197
|
+
'update.problem.noProfile': 'The installation record has no profile — reinstall',
|
|
198
|
+
'update.problem.installFailed': 'Installing from the registry failed',
|
|
191
199
|
'update.dshMismatch': 'Warning: dsh {actual} is installed, but this version was verified on {expected}',
|
|
192
|
-
'update.ahead': 'Installed version is {count} commits ahead of the released one',
|
|
193
|
-
'update.unknownHistory': 'Installed version is missing from history — reinstall',
|
|
194
200
|
}
|
|
195
201
|
|
|
196
202
|
const ZH_DICT = {
|
|
@@ -208,14 +214,20 @@ window.__ModuleLoader__.load({
|
|
|
208
214
|
'update.working': '处理中…',
|
|
209
215
|
'update.checking': '检查中…',
|
|
210
216
|
'update.upToDate': '已是最新版本',
|
|
211
|
-
'update.available': '
|
|
212
|
-
'update.
|
|
217
|
+
'update.available': '有新版本 {version}',
|
|
218
|
+
'update.availableUnknown': '有新版本可用',
|
|
219
|
+
'update.installed': '已安装 {version},在 dsh {dsh} 上验证',
|
|
213
220
|
'update.doneReload': '已更新。请刷新页面。',
|
|
214
221
|
'update.doneRestart': '已更新。请重启应用。',
|
|
215
222
|
'update.failed': '更新失败',
|
|
223
|
+
'update.problem.noStamp': '没有安装记录 — 请重新安装',
|
|
224
|
+
'update.problem.noPackage': '未找到已安装的包 — 请重新安装',
|
|
225
|
+
'update.problem.registryStatus': '注册表拒绝了请求',
|
|
226
|
+
'update.problem.registryUnreachable': '无法连接到注册表',
|
|
227
|
+
'update.problem.badVersion': '注册表未返回有效版本',
|
|
228
|
+
'update.problem.noProfile': '安装记录中没有配置文件 — 请重新安装',
|
|
229
|
+
'update.problem.installFailed': '从注册表安装失败',
|
|
216
230
|
'update.dshMismatch': '注意:已安装 dsh {actual},但此版本在 {expected} 上验证',
|
|
217
|
-
'update.ahead': '已安装版本比已发布版本新 {count} 次提交',
|
|
218
|
-
'update.unknownHistory': '已安装版本不在历史记录中 — 请重新安装',
|
|
219
231
|
}
|
|
220
232
|
|
|
221
233
|
// ── RU-PACK:BEGIN — генерируется tools/build-ru.mjs, руками не править ──
|
|
@@ -1530,14 +1542,20 @@ window.__ModuleLoader__.load({
|
|
|
1530
1542
|
'update.working': 'Работаю…',
|
|
1531
1543
|
'update.checking': 'Проверяю…',
|
|
1532
1544
|
'update.upToDate': 'Установлена последняя версия',
|
|
1533
|
-
'update.available': 'Доступна
|
|
1534
|
-
'update.
|
|
1545
|
+
'update.available': 'Доступна версия {version}',
|
|
1546
|
+
'update.availableUnknown': 'Доступна новая версия',
|
|
1547
|
+
'update.installed': 'Установлена {version}, проверено на dsh {dsh}',
|
|
1535
1548
|
'update.doneReload': 'Обновлено. Обновите страницу.',
|
|
1536
1549
|
'update.doneRestart': 'Обновлено. Перезапустите приложение.',
|
|
1537
1550
|
'update.failed': 'Обновить не удалось',
|
|
1551
|
+
'update.problem.noStamp': 'Нет отметки об установке — переустановите',
|
|
1552
|
+
'update.problem.noPackage': 'Установленный пакет не найден — переустановите',
|
|
1553
|
+
'update.problem.registryStatus': 'Реестр отклонил запрос',
|
|
1554
|
+
'update.problem.registryUnreachable': 'Не удалось связаться с реестром',
|
|
1555
|
+
'update.problem.badVersion': 'Реестр не сообщил корректную версию',
|
|
1556
|
+
'update.problem.noProfile': 'В отметке об установке нет профиля — переустановите',
|
|
1557
|
+
'update.problem.installFailed': 'Не удалось установить из реестра',
|
|
1538
1558
|
'update.dshMismatch': 'Внимание: установлен dsh {actual}, а эта версия проверялась на {expected}',
|
|
1539
|
-
'update.ahead': 'Установленная версия на {count} коммитов новее выпущенной',
|
|
1540
|
-
'update.unknownHistory': 'Установленной версии нет в истории — переустановите',
|
|
1541
1559
|
}
|
|
1542
1560
|
|
|
1543
1561
|
// ── Стили ────────────────────────────────────────────────────────────
|
|
@@ -1922,20 +1940,40 @@ window.__ModuleLoader__.load({
|
|
|
1922
1940
|
}
|
|
1923
1941
|
}, [])
|
|
1924
1942
|
|
|
1943
|
+
// Host сообщает и код проблемы, и её текст. Код переводится, текст
|
|
1944
|
+
// остаётся запасным вариантом: сообщение с Host всегда по-русски, и
|
|
1945
|
+
// без кода англоязычный пользователь увидел бы кириллицу.
|
|
1946
|
+
const problemText = (value) => {
|
|
1947
|
+
if (!value.problemCode) return value.problem
|
|
1948
|
+
const key = 'update.problem.' + value.problemCode
|
|
1949
|
+
const translated = t(key)
|
|
1950
|
+
return translated === key ? value.problem : translated
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
// Подставляем значение, только если оно действительно пришло. Иначе
|
|
1954
|
+
// берём формулировку без него.
|
|
1955
|
+
//
|
|
1956
|
+
// Это не перестраховка: при обновлении Host перечитывает свою
|
|
1957
|
+
// половину сразу, а браузерный бандл — только после перезагрузки
|
|
1958
|
+
// страницы. В этом промежутке старый клиент получает ответ новой
|
|
1959
|
+
// формы, и слепая подстановка выдавала пользователю «undefined».
|
|
1960
|
+
const fill = (key, fallbackKey, name, value) =>
|
|
1961
|
+
typeof value === 'string' && value !== ''
|
|
1962
|
+
? t(key).replace('{' + name + '}', value)
|
|
1963
|
+
: t(fallbackKey)
|
|
1964
|
+
|
|
1925
1965
|
let summary = t('update.checking')
|
|
1926
1966
|
if (state !== null) {
|
|
1927
|
-
if (state.problem) summary = state
|
|
1928
|
-
else if (state.
|
|
1929
|
-
else if (state.updateAvailable) summary = t('update.available').replace('{count}', String(state.behind))
|
|
1930
|
-
// Установленное опережает выпущенное — так выглядит машина
|
|
1931
|
-
// разработчика, который поставил себе ещё не запушенное.
|
|
1932
|
-
else if (state.ahead > 0) summary = t('update.ahead').replace('{count}', String(state.ahead))
|
|
1967
|
+
if (state.problem) summary = problemText(state)
|
|
1968
|
+
else if (state.updateAvailable) summary = fill('update.available', 'update.availableUnknown', 'version', state.latestVersion)
|
|
1933
1969
|
else summary = t('update.upToDate')
|
|
1934
1970
|
}
|
|
1935
1971
|
|
|
1936
|
-
const version = state === null ||
|
|
1972
|
+
const version = state === null || typeof state.installedVersion !== 'string'
|
|
1937
1973
|
? ''
|
|
1938
|
-
: t('update.installed')
|
|
1974
|
+
: t('update.installed')
|
|
1975
|
+
.replace('{version}', state.installedVersion)
|
|
1976
|
+
.replace('{dsh}', typeof state.dshExpected === 'string' ? state.dshExpected : '—')
|
|
1939
1977
|
|
|
1940
1978
|
// Расхождение версий dsh — самый тихий способ всё сломать: пакет
|
|
1941
1979
|
// опирается на контракты, а проверяли его на другой версии. Пользователь
|
|
@@ -1963,7 +2001,7 @@ window.__ModuleLoader__.load({
|
|
|
1963
2001
|
className: 'dsx-setting__hint dsx-setting__hint--strong',
|
|
1964
2002
|
children: result.ok
|
|
1965
2003
|
? (result.needsRestart ? t('update.doneRestart') : t('update.doneReload'))
|
|
1966
|
-
: (result
|
|
2004
|
+
: (problemText(result) || t('update.failed')),
|
|
1967
2005
|
}),
|
|
1968
2006
|
],
|
|
1969
2007
|
}),
|
package/lib/update-check.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Проверка и применение обновлений — Host-сторона.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* поломка обнаружится у пользователя.
|
|
4
|
+
* Источник истины — реестр npm, а не git. Прежняя версия сравнивала
|
|
5
|
+
* коммиты, и это требовало от каждой машины установленного git и доступа к
|
|
6
|
+
* приватному репозиторию. Реестр отдаёт версию анонимно обычным HTTP.
|
|
8
7
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* Обновления приходят с двух сторон: от разработчика (новая версия пакета)
|
|
9
|
+
* и от DeepSeek (новая версия dsh). Здесь показывается только первое —
|
|
10
|
+
* dsh пользователь обновлять сам не должен, иначе контракты разъедутся, а
|
|
11
|
+
* проверку никто не прогонял.
|
|
12
12
|
*
|
|
13
13
|
* Импортируются только встроенные модули Node: строка композиции грузится
|
|
14
14
|
* раньше, чем о пакете что-либо известно, и неразрешимый импорт уронил бы
|
|
@@ -16,14 +16,29 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { execFile } from 'node:child_process'
|
|
18
18
|
import { readFile } from 'node:fs/promises'
|
|
19
|
-
import { existsSync } from 'node:fs'
|
|
19
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
20
|
+
import { createHash } from 'node:crypto'
|
|
20
21
|
import { dirname, join } from 'node:path'
|
|
21
22
|
import { fileURLToPath } from 'node:url'
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
const PACKAGE_NAME = 'tensorgrid-ui'
|
|
25
|
+
const REGISTRY = 'https://registry.npmjs.org'
|
|
26
|
+
|
|
27
|
+
/** Версия из реестра попадает в командную строку — пускаем только безопасное. */
|
|
28
|
+
const SAFE_VERSION = /^[0-9A-Za-z.\-+]{1,64}$/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Запуск команды через оболочку на Windows.
|
|
32
|
+
*
|
|
33
|
+
* Напрямую `dsh` из Node не запускается: там это `dsh.cmd`, а execFile без
|
|
34
|
+
* оболочки исполняемым его не считает.
|
|
35
|
+
*/
|
|
36
|
+
function run(command, args, cwd, timeout = 60000) {
|
|
25
37
|
return new Promise((resolve) => {
|
|
26
|
-
|
|
38
|
+
const isWindows = process.platform === 'win32'
|
|
39
|
+
const file = isWindows ? 'cmd' : command
|
|
40
|
+
const argv = isWindows ? ['/c', command, ...args] : args
|
|
41
|
+
execFile(file, argv, { cwd, timeout, windowsHide: true }, (error, stdout, stderr) => {
|
|
27
42
|
resolve({
|
|
28
43
|
ok: !error,
|
|
29
44
|
stdout: String(stdout ?? '').trim(),
|
|
@@ -35,13 +50,13 @@ function run(command, args, cwd, timeout = 20000) {
|
|
|
35
50
|
}
|
|
36
51
|
|
|
37
52
|
/**
|
|
38
|
-
* Каталог профиля: отсюда
|
|
39
|
-
* `<профиль>/
|
|
40
|
-
* установке — так путь не зависит от
|
|
53
|
+
* Каталог профиля: отсюда модуль лежит в
|
|
54
|
+
* `<профиль>/node_modules/<пакет>/lib/`. Поднимаемся, пока не найдём
|
|
55
|
+
* отметку об установке — так путь не зависит от глубины вложения.
|
|
41
56
|
*/
|
|
42
57
|
function findProfileRoot(startDir) {
|
|
43
58
|
let dir = startDir
|
|
44
|
-
for (let depth = 0; depth <
|
|
59
|
+
for (let depth = 0; depth < 8; depth += 1) {
|
|
45
60
|
if (existsSync(join(dir, 'tensorgrid-installed.json'))) return dir
|
|
46
61
|
const parent = dirname(dir)
|
|
47
62
|
if (parent === dir) break
|
|
@@ -50,13 +65,12 @@ function findProfileRoot(startDir) {
|
|
|
50
65
|
return null
|
|
51
66
|
}
|
|
52
67
|
|
|
53
|
-
async function
|
|
54
|
-
if (
|
|
68
|
+
async function readJson(path) {
|
|
69
|
+
if (path === '') return null
|
|
55
70
|
try {
|
|
56
|
-
const text = await readFile(
|
|
57
|
-
// BOM срезается намеренно: файл
|
|
58
|
-
//
|
|
59
|
-
// обновлений молча решила бы, что установки не было.
|
|
71
|
+
const text = await readFile(path, 'utf8')
|
|
72
|
+
// BOM срезается намеренно: файл мог быть записан инструментом Windows,
|
|
73
|
+
// а JSON.parse на строке с \uFEFF падает.
|
|
60
74
|
return JSON.parse(text.replace(/^\uFEFF/, ''))
|
|
61
75
|
} catch {
|
|
62
76
|
return null
|
|
@@ -64,104 +78,116 @@ async function readStamp(profileRoot) {
|
|
|
64
78
|
}
|
|
65
79
|
|
|
66
80
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
81
|
+
* Сравнение версий в объёме, который нам нужен: числовые части слева
|
|
82
|
+
* направо, предрелиз считается СТАРШЕ своего релиза (1.0.0-rc.1 < 1.0.0).
|
|
83
|
+
* @returns отрицательное, если a меньше b.
|
|
69
84
|
*/
|
|
85
|
+
export function compareVersions(a, b) {
|
|
86
|
+
const split = (value) => {
|
|
87
|
+
const text = String(value)
|
|
88
|
+
const dash = text.indexOf('-')
|
|
89
|
+
const core = dash < 0 ? text : text.slice(0, dash)
|
|
90
|
+
const pre = dash < 0 ? '' : text.slice(dash + 1)
|
|
91
|
+
return { parts: core.split('.').map((n) => Number.parseInt(n, 10) || 0), pre }
|
|
92
|
+
}
|
|
93
|
+
const left = split(a)
|
|
94
|
+
const right = split(b)
|
|
95
|
+
for (let i = 0; i < 3; i += 1) {
|
|
96
|
+
const diff = (left.parts[i] ?? 0) - (right.parts[i] ?? 0)
|
|
97
|
+
if (diff !== 0) return diff
|
|
98
|
+
}
|
|
99
|
+
if (left.pre === right.pre) return 0
|
|
100
|
+
if (left.pre === '') return 1
|
|
101
|
+
if (right.pre === '') return -1
|
|
102
|
+
return left.pre < right.pre ? -1 : 1
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Отпечаток host-половины: по нему видно, хватит ли перезагрузки страницы. */
|
|
106
|
+
function hostHalfDigest(profileRoot) {
|
|
107
|
+
try {
|
|
108
|
+
const path = join(profileRoot, 'node_modules', PACKAGE_NAME, 'lib', 'index.js')
|
|
109
|
+
return createHash('sha1').update(readFileSync(path)).digest('hex')
|
|
110
|
+
} catch {
|
|
111
|
+
return null
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Состояние обновлений. Ничего не меняет. */
|
|
70
116
|
export async function collectStatus() {
|
|
71
117
|
const here = dirname(fileURLToPath(import.meta.url))
|
|
72
118
|
const profileRoot = findProfileRoot(here)
|
|
73
|
-
const stamp = await
|
|
119
|
+
const stamp = await readJson(profileRoot === null ? '' : join(profileRoot, 'tensorgrid-installed.json'))
|
|
74
120
|
|
|
75
121
|
const status = {
|
|
122
|
+
installedVersion: null,
|
|
123
|
+
latestVersion: null,
|
|
76
124
|
installedCommit: stamp?.commit ?? null,
|
|
77
125
|
installedAt: stamp?.installedAt ?? null,
|
|
78
126
|
dshExpected: stamp?.dshPinned ?? null,
|
|
79
127
|
dshActual: stamp?.dshActual ?? null,
|
|
80
|
-
|
|
81
|
-
available: null,
|
|
82
|
-
behind: 0,
|
|
128
|
+
profileId: stamp?.profileId ?? null,
|
|
83
129
|
updateAvailable: false,
|
|
84
130
|
canUpdate: false,
|
|
85
131
|
problem: null,
|
|
132
|
+
problemCode: null,
|
|
86
133
|
checkedAt: new Date().toISOString(),
|
|
87
134
|
}
|
|
88
135
|
|
|
89
|
-
if (stamp === null) {
|
|
90
|
-
status.problem = 'нет отметки об установке —
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
if (status.source === null || !existsSync(status.source)) {
|
|
94
|
-
status.problem = 'каталог с исходниками недоступен; обновление через интерфейс невозможно'
|
|
136
|
+
if (profileRoot === null || stamp === null) {
|
|
137
|
+
status.problem = 'нет отметки об установке — переустановите'
|
|
138
|
+
status.problemCode = 'noStamp'
|
|
95
139
|
return status
|
|
96
140
|
}
|
|
97
141
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
status.problem = `не удалось связаться с репозиторием: ${fetched.stderr || fetched.message || 'неизвестно'}`
|
|
142
|
+
const manifest = await readJson(join(profileRoot, 'node_modules', PACKAGE_NAME, 'package.json'))
|
|
143
|
+
if (manifest === null || typeof manifest.version !== 'string') {
|
|
144
|
+
status.problem = 'установленный пакет не найден — переустановите'
|
|
145
|
+
status.problemCode = 'noPackage'
|
|
103
146
|
return status
|
|
104
147
|
}
|
|
148
|
+
status.installedVersion = manifest.version
|
|
105
149
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
status.
|
|
150
|
+
let published
|
|
151
|
+
try {
|
|
152
|
+
// Сокращённый формат ответа: полный документ пакета весит на порядок
|
|
153
|
+
// больше и целиком нам не нужен.
|
|
154
|
+
const response = await fetch(`${REGISTRY}/${PACKAGE_NAME}`, {
|
|
155
|
+
headers: { accept: 'application/vnd.npm.install-v1+json' },
|
|
156
|
+
signal: AbortSignal.timeout(20000),
|
|
157
|
+
})
|
|
158
|
+
if (!response.ok) {
|
|
159
|
+
status.problem = `реестр ответил ${response.status}`
|
|
160
|
+
status.problemCode = 'registryStatus'
|
|
161
|
+
return status
|
|
162
|
+
}
|
|
163
|
+
published = await response.json()
|
|
164
|
+
} catch (error) {
|
|
165
|
+
status.problem = `не удалось связаться с реестром: ${String(error?.message ?? error)}`
|
|
166
|
+
status.problemCode = 'registryUnreachable'
|
|
123
167
|
return status
|
|
124
168
|
}
|
|
125
169
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
status.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
// ОПЕРЕЖАТЬ выпущенное: так выглядит машина разработчика, который уже
|
|
132
|
-
// поставил себе то, что ещё не запушил. Решает направление, а не факт
|
|
133
|
-
// расхождения.
|
|
134
|
-
const behind = await run('git', ['rev-list', '--count', `${installed}..${remoteRef}`], status.source)
|
|
135
|
-
const ahead = await run('git', ['rev-list', '--count', `${remoteRef}..${installed}`], status.source)
|
|
136
|
-
|
|
137
|
-
if (!behind.ok) {
|
|
138
|
-
// Установленного коммита нет в истории — например, ветку переписали.
|
|
139
|
-
// Сравнить нечем, но молчать нельзя.
|
|
140
|
-
status.behind = 0
|
|
141
|
-
status.updateAvailable = true
|
|
142
|
-
status.unknownHistory = true
|
|
143
|
-
} else {
|
|
144
|
-
status.behind = Number.parseInt(behind.stdout, 10) || 0
|
|
145
|
-
status.ahead = ahead.ok ? Number.parseInt(ahead.stdout, 10) || 0 : 0
|
|
146
|
-
status.updateAvailable = status.behind > 0
|
|
147
|
-
}
|
|
170
|
+
const latest = published?.['dist-tags']?.latest
|
|
171
|
+
if (typeof latest !== 'string' || !SAFE_VERSION.test(latest)) {
|
|
172
|
+
status.problem = 'реестр не сообщил корректную версию'
|
|
173
|
+
status.problemCode = 'badVersion'
|
|
174
|
+
return status
|
|
148
175
|
}
|
|
176
|
+
status.latestVersion = latest
|
|
149
177
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
status.canUpdate = status.updateAvailable && !isDirty && head.ok
|
|
155
|
-
if (status.updateAvailable && isDirty) {
|
|
156
|
-
status.problem = 'в каталоге с исходниками есть несохранённые правки — обновление через интерфейс отключено'
|
|
178
|
+
status.updateAvailable = compareVersions(status.installedVersion, latest) < 0
|
|
179
|
+
if (status.updateAvailable && typeof status.profileId !== 'string') {
|
|
180
|
+
status.problem = 'в отметке об установке нет профиля — переустановите'
|
|
181
|
+
status.problemCode = 'noProfile'
|
|
157
182
|
}
|
|
183
|
+
status.canUpdate = status.updateAvailable && typeof status.profileId === 'string'
|
|
158
184
|
|
|
159
185
|
return status
|
|
160
186
|
}
|
|
161
187
|
|
|
162
188
|
/**
|
|
163
|
-
*
|
|
164
|
-
*
|
|
189
|
+
* Ставит последнюю версию из реестра и сообщает, достаточно ли
|
|
190
|
+
* перезагрузки страницы.
|
|
165
191
|
*/
|
|
166
192
|
export async function applyUpdate() {
|
|
167
193
|
const status = await collectStatus()
|
|
@@ -169,47 +195,35 @@ export async function applyUpdate() {
|
|
|
169
195
|
return { ok: false, problem: status.problem ?? 'обновлять нечего', status }
|
|
170
196
|
}
|
|
171
197
|
|
|
172
|
-
const
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
return { ok: false, problem: `git pull не удался: ${pulled.stderr || pulled.message}`, status }
|
|
176
|
-
}
|
|
177
|
-
const after = await run('git', ['rev-parse', 'HEAD'], status.source)
|
|
178
|
-
|
|
179
|
-
// Что именно изменилось, решает, хватит ли перезагрузки страницы.
|
|
180
|
-
// Браузерный бандл читается при загрузке страницы, а host-половина,
|
|
181
|
-
// патч-слой и пресет — только при старте приложения.
|
|
182
|
-
const changed = await run('git', ['diff', '--name-only', before.stdout, after.stdout], status.source)
|
|
183
|
-
const files = changed.ok ? changed.stdout.split('\n').map((line) => line.trim()).filter(Boolean) : []
|
|
184
|
-
const needsRestart = files.some((file) =>
|
|
185
|
-
file === 'lib/index.js' || file.startsWith('profile/') || file.startsWith('preset/') || file === 'install.ps1',
|
|
186
|
-
)
|
|
187
|
-
|
|
188
|
-
const installer = join(status.source, 'install.ps1')
|
|
189
|
-
if (!existsSync(installer)) {
|
|
190
|
-
return { ok: false, problem: `не найден установщик: ${installer}`, status }
|
|
191
|
-
}
|
|
198
|
+
const here = dirname(fileURLToPath(import.meta.url))
|
|
199
|
+
const profileRoot = findProfileRoot(here)
|
|
200
|
+
const before = hostHalfDigest(profileRoot)
|
|
192
201
|
|
|
193
202
|
const installed = await run(
|
|
194
|
-
'
|
|
195
|
-
['
|
|
196
|
-
|
|
203
|
+
'dsh',
|
|
204
|
+
['plugin', '--profile', status.profileId, 'add', `${PACKAGE_NAME}@${status.latestVersion}`],
|
|
205
|
+
profileRoot,
|
|
197
206
|
180000,
|
|
198
207
|
)
|
|
199
208
|
if (!installed.ok) {
|
|
200
209
|
return {
|
|
201
210
|
ok: false,
|
|
202
|
-
problem: '
|
|
203
|
-
|
|
211
|
+
problem: 'установка из реестра не удалась',
|
|
212
|
+
problemCode: 'installFailed',
|
|
213
|
+
detail: (installed.stderr || installed.stdout || installed.message || '').split('\n').slice(-8).join('\n'),
|
|
204
214
|
status,
|
|
205
215
|
}
|
|
206
216
|
}
|
|
207
217
|
|
|
218
|
+
// Браузерный бандл читается при загрузке страницы, а host-половина —
|
|
219
|
+
// только при старте приложения. Решаем по её отпечатку, а не наугад.
|
|
220
|
+
const after = hostHalfDigest(profileRoot)
|
|
221
|
+
const needsRestart = before === null || after === null || before !== after
|
|
222
|
+
|
|
208
223
|
return {
|
|
209
224
|
ok: true,
|
|
210
|
-
from:
|
|
211
|
-
to:
|
|
212
|
-
files: files.length,
|
|
225
|
+
from: status.installedVersion,
|
|
226
|
+
to: status.latestVersion,
|
|
213
227
|
needsRestart,
|
|
214
228
|
status: await collectStatus(),
|
|
215
229
|
}
|
package/package.json
CHANGED