@pandablessing/dsh-user-sync 1.0.8 → 1.0.11
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 +9 -40
- package/lib/index.js +63 -1
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -162,24 +162,12 @@ if (typeof window !== 'undefined' && window.__ModuleLoader__) {
|
|
|
162
162
|
}
|
|
163
163
|
return parts.length > 0 ? parts.join(' · ') : '尚未同步';
|
|
164
164
|
}
|
|
165
|
-
function ResultCard({ notice,
|
|
165
|
+
function ResultCard({ notice, onConfirmModules, onDismiss, onForceUpload }) {
|
|
166
166
|
if (!notice)
|
|
167
167
|
return null;
|
|
168
168
|
const { kind, data } = notice;
|
|
169
169
|
if (kind === 'check') {
|
|
170
170
|
const decision = data.decision ?? {};
|
|
171
|
-
if (decision.action === 'pull' || decision.action === 'confirm') {
|
|
172
|
-
const plan = data.plan ?? { added: [], overwritten: [], skipped: [] };
|
|
173
|
-
const missing = data.plugins?.missing ?? [];
|
|
174
|
-
const linkDeps = data.plugins?.linkDeps ?? [];
|
|
175
|
-
return R.createElement('div', { style: styles.card }, R.createElement('strong', null, decision.action === 'confirm' ? '两侧都有更新,需要你决定' : '远端有更新'), R.createElement('p', { style: styles.muted }, `远端包 ${data.latest?.name ?? ''}(${data.meta?.createdAt ?? '时间未知'}),新增 ${(plan.added ?? []).length} / 覆盖 ${(plan.overwritten ?? []).length} / 跳过 ${(plan.skipped ?? []).length} 个文件。`), missing.length > 0
|
|
176
|
-
? R.createElement('p', { style: styles.muted }, `缺失插件:${missing.map((item) => `${item.name}@${item.spec}`).join('、')}`)
|
|
177
|
-
: null, linkDeps.length > 0
|
|
178
|
-
? R.createElement('p', { style: styles.muted }, `本地包需手动放回:${linkDeps.map((item) => item.name).join('、')}`)
|
|
179
|
-
: null, R.createElement('div', { style: styles.actions }, missing.length > 0
|
|
180
|
-
? R.createElement('button', { style: styles.primary, onClick: () => onInstallPlugins(missing) }, '先安装缺失插件')
|
|
181
|
-
: null, R.createElement('button', { style: styles.secondary, onClick: () => onDismiss() }, '取消')), R.createElement('p', { style: styles.muted }, '覆盖动作会在弹窗中确认。'));
|
|
182
|
-
}
|
|
183
171
|
return R.createElement('div', { style: styles.card }, R.createElement('strong', null, decision.action === 'skip' ? '本机已是最新' : '本机有改动'), R.createElement('p', { style: styles.muted }, `判定:${decision.reason ?? decision.action}`));
|
|
184
172
|
}
|
|
185
173
|
if (kind === 'error') {
|
|
@@ -225,6 +213,8 @@ if (typeof window !== 'undefined' && window.__ModuleLoader__) {
|
|
|
225
213
|
const [confirmDialog, setConfirmDialog] = R.useState(null);
|
|
226
214
|
// 覆盖确认弹窗:上传发现远端更新 / 从远端同步发现远端更新时弹出(不再用结果卡按钮)
|
|
227
215
|
const [overwriteDialog, setOverwriteDialog] = R.useState(null);
|
|
216
|
+
// 版本检查结果:有新版本时在面板顶部提示(不自动安装)
|
|
217
|
+
const [updateInfo, setUpdateInfo] = R.useState(null);
|
|
228
218
|
// 当前展开文件清单的模块(null 表示都收起)
|
|
229
219
|
const [expandedModule, setExpandedModule] = R.useState(null);
|
|
230
220
|
// 设置改动后自动保存(600ms 防抖),不再提供「保存设置」按钮
|
|
@@ -270,6 +260,10 @@ if (typeof window !== 'undefined' && window.__ModuleLoader__) {
|
|
|
270
260
|
};
|
|
271
261
|
R.useEffect(() => {
|
|
272
262
|
refresh();
|
|
263
|
+
// 版本检查失败不影响面板(网络不可用时静默)
|
|
264
|
+
fetchJson(`${API}/update-check`)
|
|
265
|
+
.then((data) => setUpdateInfo(data))
|
|
266
|
+
.catch(() => setUpdateInfo(null));
|
|
273
267
|
}, []);
|
|
274
268
|
// 设置改动后 600ms 自动保存(输入间隔时不会每敲一下都发请求)
|
|
275
269
|
R.useEffect(() => {
|
|
@@ -411,29 +405,6 @@ if (typeof window !== 'undefined' && window.__ModuleLoader__) {
|
|
|
411
405
|
setBusy(false);
|
|
412
406
|
}
|
|
413
407
|
};
|
|
414
|
-
const installPlugins = async (packages) => {
|
|
415
|
-
setBusy(true);
|
|
416
|
-
try {
|
|
417
|
-
const result = await fetchJson(`${API}/install`, {
|
|
418
|
-
method: 'POST',
|
|
419
|
-
headers: { 'content-type': 'application/json' },
|
|
420
|
-
body: JSON.stringify({ packages }),
|
|
421
|
-
});
|
|
422
|
-
if (result.ok) {
|
|
423
|
-
setNotice({ kind: 'result', data: { detail: `已安装 ${result.results?.length ?? 0} 个插件,重启 DSH 后生效` } });
|
|
424
|
-
}
|
|
425
|
-
else {
|
|
426
|
-
setNotice({ kind: 'error', data: { detail: `安装失败:${(result.results ?? []).filter((item) => !item.ok).map((item) => `${item.name}(${item.error})`).join(';') || result.error}` } });
|
|
427
|
-
}
|
|
428
|
-
refresh();
|
|
429
|
-
}
|
|
430
|
-
catch (error) {
|
|
431
|
-
setNotice({ kind: 'error', data: { detail: String(error) } });
|
|
432
|
-
}
|
|
433
|
-
finally {
|
|
434
|
-
setBusy(false);
|
|
435
|
-
}
|
|
436
|
-
};
|
|
437
408
|
const checkRemote = async () => {
|
|
438
409
|
setBusy(true);
|
|
439
410
|
setNotice(null);
|
|
@@ -488,8 +459,8 @@ if (typeof window !== 'undefined' && window.__ModuleLoader__) {
|
|
|
488
459
|
// 计数优先用 status 的宿主探针(与 /scan 同一条扫描路径),
|
|
489
460
|
// 这样 /scan 请求失败时面板仍能显示真实数量。
|
|
490
461
|
const counts = status?.probe?.counts ?? scan?.counts ?? {};
|
|
491
|
-
return R.createElement('div', { style: styles.section }, R.createElement('h2', { style: styles.heading }, '设置同步'),
|
|
492
|
-
? R.createElement('
|
|
462
|
+
return R.createElement('div', { style: styles.section }, R.createElement('h2', { style: styles.heading }, '设置同步'), updateInfo?.available
|
|
463
|
+
? R.createElement('p', { style: styles.warn }, `插件有新版本 v${updateInfo.latest}(当前 v${updateInfo.current}):pnpm add @pandablessing/dsh-user-sync@${updateInfo.latest}`)
|
|
493
464
|
: null, !consentGiven
|
|
494
465
|
? R.createElement('div', { style: styles.card }, R.createElement('strong', null, '启用前请确认'), R.createElement('p', { style: styles.muted }, '上传内容:技能、插件清单、配置、用户记忆。数据经运营方服务中转并存放在运营方账号(非 DSH 官方服务器);远端只保留 1 份,上传即覆盖。'), R.createElement('div', { style: styles.actions },
|
|
495
466
|
// 用普通按钮:主色按钮看起来像「已选中」,同意是主动作但不应暗示状态
|
|
@@ -552,10 +523,8 @@ if (typeof window !== 'undefined' && window.__ModuleLoader__) {
|
|
|
552
523
|
// 服务地址只读展示:默认内置,用户覆盖后标注来源(面板不再提供输入框)
|
|
553
524
|
R.createElement('p', { style: styles.muted }, `服务地址:${status?.baseUrl ?? '(未解析)'}${status?.baseUrlSource === 'user' ? '(自定义)' : '(内置)'}`), R.createElement(ResultCard, {
|
|
554
525
|
notice,
|
|
555
|
-
onApply: applyRemote,
|
|
556
526
|
onConfirmModules: confirmModules,
|
|
557
527
|
onDismiss: () => setNotice(null),
|
|
558
|
-
onInstallPlugins: installPlugins,
|
|
559
528
|
onForceUpload: () => upload(true),
|
|
560
529
|
}), confirmDialog
|
|
561
530
|
? R.createElement('div', { style: styles.overlay, onClick: () => setConfirmDialog(null) }, R.createElement('div', { style: styles.dialog, onClick: (event) => event.stopPropagation?.() }, R.createElement('strong', null, '同步项目/会话内容?'), R.createElement('p', { style: styles.muted }, `${confirmDialog.warning.map((id) => MODULE_LABELS[id] ?? id).join('、')} 属于项目/会话级,可能包含具体工作内容。确认要跨机器同步吗?`), R.createElement('div', { style: styles.actions }, R.createElement('button', { style: styles.primary, onClick: () => confirmModules(confirmDialog.modules) }, '仍要同步'), R.createElement('button', { style: styles.secondary, onClick: () => setConfirmDialog(null) }, '取消'))))
|
package/lib/index.js
CHANGED
|
@@ -353,7 +353,62 @@ export function apply(ctx) {
|
|
|
353
353
|
}));
|
|
354
354
|
return { installed, linkDeps: plan.linkDeps.map(({ name, spec }) => ({ name, spec })) };
|
|
355
355
|
};
|
|
356
|
+
/** 判断 latest 是否比 current 新(只比较 x.y.z 数字段)。 */
|
|
357
|
+
const isNewerVersion = (latest, current) => {
|
|
358
|
+
const toParts = (value) => value.split('-')[0].split('.').map((part) => Number.parseInt(part, 10) || 0);
|
|
359
|
+
const a = toParts(latest);
|
|
360
|
+
const b = toParts(current);
|
|
361
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
362
|
+
const left = a[i] ?? 0;
|
|
363
|
+
const right = b[i] ?? 0;
|
|
364
|
+
if (left !== right)
|
|
365
|
+
return left > right;
|
|
366
|
+
}
|
|
367
|
+
return false;
|
|
368
|
+
};
|
|
369
|
+
/** 版本检查:查 npm registry 的 dist-tags 与当前版本比对(30 分钟缓存,失败静默)。 */
|
|
370
|
+
let versionCache = null;
|
|
371
|
+
const checkLatestVersion = async () => {
|
|
372
|
+
const now = Date.now();
|
|
373
|
+
if (versionCache && now - versionCache.at < 30 * 60_000) {
|
|
374
|
+
const latest = versionCache.latest;
|
|
375
|
+
return { current: PLUGIN_VERSION, latest, available: Boolean(latest && isNewerVersion(latest, PLUGIN_VERSION)) };
|
|
376
|
+
}
|
|
377
|
+
const registry = process.env.NPM_REGISTRY || 'https://registry.npmjs.org';
|
|
378
|
+
const encoded = encodeURIComponent('@pandablessing/dsh-user-sync').replace('%2F', '%2f');
|
|
379
|
+
const controller = new AbortController();
|
|
380
|
+
const timer = setTimeout(() => controller.abort(), 5000);
|
|
381
|
+
try {
|
|
382
|
+
const response = await fetch(`${registry}/-/package/${encoded}/dist-tags`, { signal: controller.signal });
|
|
383
|
+
const tags = (await response.json());
|
|
384
|
+
const latest = typeof tags.latest === 'string' ? tags.latest : null;
|
|
385
|
+
versionCache = { at: now, latest };
|
|
386
|
+
return { current: PLUGIN_VERSION, latest, available: Boolean(latest && isNewerVersion(latest, PLUGIN_VERSION)) };
|
|
387
|
+
}
|
|
388
|
+
catch {
|
|
389
|
+
versionCache = { at: now, latest: null };
|
|
390
|
+
return { current: PLUGIN_VERSION, latest: null, available: false };
|
|
391
|
+
}
|
|
392
|
+
finally {
|
|
393
|
+
clearTimeout(timer);
|
|
394
|
+
}
|
|
395
|
+
};
|
|
356
396
|
const routes = [
|
|
397
|
+
{
|
|
398
|
+
// 版本检查:面板据此提示「有新版本可升级」,不做自动安装
|
|
399
|
+
path: '/api/dsh-user-sync/update-check',
|
|
400
|
+
methods: ['GET'],
|
|
401
|
+
requestBody: 'buffered',
|
|
402
|
+
fetch: async () => {
|
|
403
|
+
try {
|
|
404
|
+
return json(await checkLatestVersion());
|
|
405
|
+
}
|
|
406
|
+
catch (error) {
|
|
407
|
+
recordHandlerError('update-check', error);
|
|
408
|
+
return json({ current: PLUGIN_VERSION, latest: null, available: false });
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
},
|
|
357
412
|
{
|
|
358
413
|
// 最小同步路由:用于区分「同步 handler 本身不可用」与「status/scan 具体逻辑抛错」
|
|
359
414
|
path: '/api/dsh-user-sync/ping',
|
|
@@ -558,7 +613,11 @@ export function apply(ctx) {
|
|
|
558
613
|
catch {
|
|
559
614
|
// 无 body(面板首次点击)时按未确认处理
|
|
560
615
|
}
|
|
561
|
-
|
|
616
|
+
const result = (await sync.upload({ force }));
|
|
617
|
+
// 上传成功说明远端已与本机一致:立刻重算检查结论,底部状态行不再显示过期的「发现更新」
|
|
618
|
+
if (result.ok)
|
|
619
|
+
void scheduler.runOnce();
|
|
620
|
+
return json(result);
|
|
562
621
|
},
|
|
563
622
|
},
|
|
564
623
|
{
|
|
@@ -653,6 +712,9 @@ export function apply(ctx) {
|
|
|
653
712
|
result.installError = error?.message ?? String(error);
|
|
654
713
|
}
|
|
655
714
|
}
|
|
715
|
+
// 应用成功后远端与本机一致:重算检查结论
|
|
716
|
+
if (result.ok)
|
|
717
|
+
void scheduler.runOnce();
|
|
656
718
|
return json(result);
|
|
657
719
|
},
|
|
658
720
|
},
|