ygc-dsh-plugin-autoupdate 0.9.6 → 1.0.0
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/client.js +0 -249
- package/index.js +0 -154
- package/lib/api.js +0 -61
- package/lib/core.js +0 -33
- package/package.json +2 -4
- package/lib/machine.js +0 -156
- package/lib/share.js +0 -902
package/client.js
CHANGED
|
@@ -46,7 +46,6 @@ const CSS = `
|
|
|
46
46
|
.pau-switch{display:inline-flex;align-items:center;gap:8px;font-size:13px;cursor:pointer;user-select:none}
|
|
47
47
|
.pau-switch input{width:16px;height:16px;cursor:pointer}
|
|
48
48
|
.pau-actions{display:flex;gap:10px;flex-wrap:wrap;align-items:center}
|
|
49
|
-
.pau-share-path{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px;word-break:break-all;color:var(--dsw-alias-label-secondary,#555)}
|
|
50
49
|
.pau-tag-mine{background:var(--dsw-alias-info-tint,#e0f2fe);color:var(--dsw-alias-info,#0369a1)}
|
|
51
50
|
.pau-grid{display:flex;flex-direction:column;gap:8px}
|
|
52
51
|
.pau-other{display:flex;align-items:center;gap:10px;padding:8px 0;border-bottom:1px solid var(--dsw-alias-border-l25,#f0f0f0);flex-wrap:wrap}
|
|
@@ -96,7 +95,6 @@ function makePanel(React) {
|
|
|
96
95
|
|
|
97
96
|
return function AutoUpdatePanel() {
|
|
98
97
|
const [snap, setSnap] = useState(null);
|
|
99
|
-
const [shareDetail, setShareDetail] = useState(null);
|
|
100
98
|
const [busy, setBusy] = useState('');
|
|
101
99
|
const [error, setError] = useState('');
|
|
102
100
|
const [note, setNote] = useState('');
|
|
@@ -105,18 +103,6 @@ function makePanel(React) {
|
|
|
105
103
|
const apply = useCallback((next) => {
|
|
106
104
|
if (!next) return;
|
|
107
105
|
setSnap(next);
|
|
108
|
-
// 共享面板的明细单独取一次:它是扫盘结果,比状态文件重,不跟 15 秒轮询一起刷
|
|
109
|
-
if (next.share && next.share.detail) setShareDetail(next.share.detail);
|
|
110
|
-
}, []);
|
|
111
|
-
|
|
112
|
-
const loadShareDetail = useCallback(async () => {
|
|
113
|
-
try {
|
|
114
|
-
const data = await call('/share');
|
|
115
|
-
if (data && data.share && data.share.detail) setShareDetail(data.share.detail);
|
|
116
|
-
if (data) setSnap(data);
|
|
117
|
-
} catch (err) {
|
|
118
|
-
setError(String(err.message || err));
|
|
119
|
-
}
|
|
120
106
|
}, []);
|
|
121
107
|
|
|
122
108
|
const refresh = useCallback(async () => {
|
|
@@ -137,11 +123,6 @@ function makePanel(React) {
|
|
|
137
123
|
};
|
|
138
124
|
}, [refresh]);
|
|
139
125
|
|
|
140
|
-
useEffect(() => {
|
|
141
|
-
// 共享明细只取一次,之后靠「立即同步」/ 安装后刷新
|
|
142
|
-
loadShareDetail();
|
|
143
|
-
}, [loadShareDetail]);
|
|
144
|
-
|
|
145
126
|
const run = async (name, fn) => {
|
|
146
127
|
setBusy(name);
|
|
147
128
|
setError('');
|
|
@@ -676,236 +657,7 @@ function makePanel(React) {
|
|
|
676
657
|
),
|
|
677
658
|
);
|
|
678
659
|
|
|
679
|
-
const share = snap.share || {};
|
|
680
|
-
const detail = shareDetail || null;
|
|
681
|
-
const myFeature = (detail && detail.myFeature) || share.effectiveFeature || '';
|
|
682
|
-
const libRoot = (detail && detail.libraryRoot) || share.effectiveRoot || '';
|
|
683
|
-
const others = (detail && Array.isArray(detail.others) ? detail.others : []).filter((p) => !p.installed);
|
|
684
|
-
const installedFromOthers = (detail && Array.isArray(detail.others) ? detail.others : []).filter(
|
|
685
|
-
(p) => p.installed,
|
|
686
|
-
);
|
|
687
|
-
const sources = (detail && Array.isArray(detail.sources) ? detail.sources : []) || [];
|
|
688
660
|
|
|
689
|
-
const shareCard = h(
|
|
690
|
-
'div',
|
|
691
|
-
{ className: 'pau-card' },
|
|
692
|
-
h(
|
|
693
|
-
'div',
|
|
694
|
-
{ className: 'pau-card-h' },
|
|
695
|
-
h('h3', { className: 'pau-card-t' }, '插件共享(特征文件夹 · 只存清单)'),
|
|
696
|
-
h(
|
|
697
|
-
'span',
|
|
698
|
-
{ className: share.enabled ? 'pau-tag pau-tag-ok' : 'pau-tag' },
|
|
699
|
-
share.enabled ? '每次巡检自动同步' : '自动同步已关闭',
|
|
700
|
-
),
|
|
701
|
-
),
|
|
702
|
-
h(
|
|
703
|
-
'p',
|
|
704
|
-
{ className: 'pau-sub' },
|
|
705
|
-
'插件库是共享文件夹,一个人一格:我的插件清单、我自己的汇总都写在「插件库\\<我的特征>」这一格里面,**只存清单,不存任何插件文件**。别人要装,按清单里的安装坐标去 registry 联网拉取,不使用本地副本。别人的格子只读,汇总也是各机扫一遍自己算,不放在公共位置。',
|
|
706
|
-
),
|
|
707
|
-
h(
|
|
708
|
-
'label',
|
|
709
|
-
{ className: 'pau-switch' },
|
|
710
|
-
h('input', {
|
|
711
|
-
type: 'checkbox',
|
|
712
|
-
checked: !!share.enabled,
|
|
713
|
-
onChange: (e) => patchSettings({ shareEnabled: e.target.checked }),
|
|
714
|
-
}),
|
|
715
|
-
'每次巡检顺带同步一次特征文件夹',
|
|
716
|
-
),
|
|
717
|
-
h(
|
|
718
|
-
'div',
|
|
719
|
-
{ className: 'pau-field' },
|
|
720
|
-
h('span', { className: 'pau-field-l' }, '插件库根目录'),
|
|
721
|
-
h('input', {
|
|
722
|
-
className: 'pau-input',
|
|
723
|
-
style: { minWidth: 320, flex: 1 },
|
|
724
|
-
placeholder: '留空用默认 D:\\Ai工作空间\\插件库',
|
|
725
|
-
defaultValue: share.libraryRoot || '',
|
|
726
|
-
onBlur: (e) => patchSettings({ shareLibraryRoot: e.target.value.trim() }),
|
|
727
|
-
}),
|
|
728
|
-
),
|
|
729
|
-
h(
|
|
730
|
-
'div',
|
|
731
|
-
{ className: 'pau-field' },
|
|
732
|
-
h('span', { className: 'pau-field-l' }, '我的特征文件夹名'),
|
|
733
|
-
h('input', {
|
|
734
|
-
className: 'pau-input',
|
|
735
|
-
style: { minWidth: 220 },
|
|
736
|
-
placeholder: '名称部分,留空用电脑名',
|
|
737
|
-
defaultValue: share.featureName || '',
|
|
738
|
-
onBlur: (e) => patchSettings({ shareFeatureName: e.target.value.trim() }),
|
|
739
|
-
}),
|
|
740
|
-
h(
|
|
741
|
-
'span',
|
|
742
|
-
{ className: 'pau-row-d' },
|
|
743
|
-
share.machineHash ? `特征值 ${share.machineHash}(已固定,改名字不会变)` : '特征值首次同步时固定',
|
|
744
|
-
),
|
|
745
|
-
),
|
|
746
|
-
(detail && detail.identity && detail.identity.sharesWithSyncBackup) !== false
|
|
747
|
-
? h(
|
|
748
|
-
'div',
|
|
749
|
-
{ className: 'pau-row-d' },
|
|
750
|
-
'与会话同步插件共用同一个本机标识,两台机器之间只有「一个我」。',
|
|
751
|
-
)
|
|
752
|
-
: h(
|
|
753
|
-
'div',
|
|
754
|
-
{ className: 'pau-row-d' },
|
|
755
|
-
'注意:没读到会话同步插件的标识,本插件用了自己的特征值。',
|
|
756
|
-
),
|
|
757
|
-
h('div', { className: 'pau-row-d' }, `我的特征目录:`),
|
|
758
|
-
h('div', { className: 'pau-share-path' }, `${libRoot}${myFeature ? `\\${myFeature}` : ''}`),
|
|
759
|
-
h(
|
|
760
|
-
'div',
|
|
761
|
-
{ className: 'pau-row-d' },
|
|
762
|
-
`我的汇总(只给本机看,各机自己汇集):${myFeature ? `\\${myFeature}\\OTHERS-PLUGINS.json` : ''}`,
|
|
763
|
-
),
|
|
764
|
-
h(
|
|
765
|
-
'div',
|
|
766
|
-
{ className: 'pau-row-d', style: { marginTop: 6 } },
|
|
767
|
-
`上次同步:${fmtAgo(share.lastSyncAt)}${
|
|
768
|
-
share.lastResult
|
|
769
|
-
? ` · 本机 ${share.lastResult.total} 个插件(可联网装 ${share.lastResult.localInstallable ?? '-'},装不了 ${share.lastResult.localUninstallable ?? '-'}) · 他人 ${share.lastResult.others} 个,可装 ${share.lastResult.installable}`
|
|
770
|
-
: ''
|
|
771
|
-
}`,
|
|
772
|
-
),
|
|
773
|
-
share.lastError
|
|
774
|
-
? h('div', { className: 'pau-banner pau-banner-warn', style: { marginTop: 8 } }, `上次同步失败:${share.lastError}`)
|
|
775
|
-
: null,
|
|
776
|
-
h(
|
|
777
|
-
'div',
|
|
778
|
-
{ className: 'pau-actions', style: { marginTop: 12 } },
|
|
779
|
-
h(
|
|
780
|
-
'button',
|
|
781
|
-
{
|
|
782
|
-
className: 'pau-btn pau-btn-primary',
|
|
783
|
-
disabled: !!busy,
|
|
784
|
-
onClick: () =>
|
|
785
|
-
run('share-sync', async () => {
|
|
786
|
-
const next = await call('/share/sync', {});
|
|
787
|
-
const result = next.result || {};
|
|
788
|
-
if (result && result.ok === false) {
|
|
789
|
-
setNote(`同步失败:${result.error || '未知错误'}`);
|
|
790
|
-
} else {
|
|
791
|
-
const counts = (result.mine && result.mine.counts) || {};
|
|
792
|
-
const others2 = (result.aggregate && result.aggregate.counts) || {};
|
|
793
|
-
setNote(
|
|
794
|
-
`同步完成:本机 ${counts.total || 0} 个插件(可联网装 ${counts.installable || 0},装不了 ${counts.uninstallable || 0}),他人 ${others2.othersTotal || 0} 个,可装 ${others2.installable || 0} 个`,
|
|
795
|
-
);
|
|
796
|
-
}
|
|
797
|
-
if (next.share && next.share.detail) setShareDetail(next.share.detail);
|
|
798
|
-
return next;
|
|
799
|
-
}),
|
|
800
|
-
},
|
|
801
|
-
busy === 'share-sync' ? '同步中…' : '立即同步一次',
|
|
802
|
-
),
|
|
803
|
-
h(
|
|
804
|
-
'button',
|
|
805
|
-
{
|
|
806
|
-
className: 'pau-btn',
|
|
807
|
-
disabled: !!busy,
|
|
808
|
-
onClick: () => run('share-reload', async () => {
|
|
809
|
-
await loadShareDetail();
|
|
810
|
-
setNote('已重新读取特征文件夹');
|
|
811
|
-
return null;
|
|
812
|
-
}),
|
|
813
|
-
},
|
|
814
|
-
'重新读取',
|
|
815
|
-
),
|
|
816
|
-
),
|
|
817
|
-
h(
|
|
818
|
-
'div',
|
|
819
|
-
{ className: 'pau-card-h', style: { marginTop: 18, marginBottom: 6 } },
|
|
820
|
-
h('h4', { className: 'pau-card-t' }, `他人插件(本机没有的 ${others.length} 个)`),
|
|
821
|
-
h(
|
|
822
|
-
'span',
|
|
823
|
-
{ className: 'pau-row-d' },
|
|
824
|
-
sources.length ? `${sources.filter((s) => !s.mine).length} 本外部特征` : '还没有拉到别人的特征',
|
|
825
|
-
),
|
|
826
|
-
),
|
|
827
|
-
others.length
|
|
828
|
-
? h(
|
|
829
|
-
'div',
|
|
830
|
-
{ className: 'pau-scroll' },
|
|
831
|
-
others.map((plugin) =>
|
|
832
|
-
h(
|
|
833
|
-
'div',
|
|
834
|
-
{ className: 'pau-other', key: plugin.name },
|
|
835
|
-
h(
|
|
836
|
-
'div',
|
|
837
|
-
{ className: 'pau-other-main' },
|
|
838
|
-
h(
|
|
839
|
-
'div',
|
|
840
|
-
{ className: 'pau-row-t' },
|
|
841
|
-
h('span', { className: 'pau-mono' }, plugin.name),
|
|
842
|
-
plugin.version ? h('span', { className: 'pau-tag' }, `v${plugin.version}`) : null,
|
|
843
|
-
h(
|
|
844
|
-
'span',
|
|
845
|
-
{
|
|
846
|
-
className: plugin.installable ? 'pau-tag pau-tag-ok' : 'pau-tag pau-tag-warn',
|
|
847
|
-
},
|
|
848
|
-
plugin.installable
|
|
849
|
-
? plugin.kind === 'git'
|
|
850
|
-
? 'Git 联网拉取'
|
|
851
|
-
: '联网拉取'
|
|
852
|
-
: '无安装坐标,装不了',
|
|
853
|
-
),
|
|
854
|
-
h('span', { className: 'pau-tag pau-tag-mine' }, plugin.features.join(' / ')),
|
|
855
|
-
),
|
|
856
|
-
plugin.description
|
|
857
|
-
? h('div', { className: 'pau-row-d' }, plugin.description.slice(0, 140))
|
|
858
|
-
: null,
|
|
859
|
-
),
|
|
860
|
-
h(
|
|
861
|
-
'button',
|
|
862
|
-
{
|
|
863
|
-
className: 'pau-btn pau-btn-sm pau-btn-primary',
|
|
864
|
-
disabled: !!busy || !plugin.installable,
|
|
865
|
-
title: plugin.installable
|
|
866
|
-
? `从 registry 联网安装 ${plugin.installSpec || plugin.name}`
|
|
867
|
-
: '这个插件没有可联网的安装坐标(对方未发布到 registry)',
|
|
868
|
-
onClick: () =>
|
|
869
|
-
run(`share-install:${plugin.name}`, async () => {
|
|
870
|
-
const next = await call('/share/install', { name: plugin.name });
|
|
871
|
-
setNote((next.result && next.result.message) || `${plugin.name} 已安装`);
|
|
872
|
-
if (next.share && next.share.detail) setShareDetail(next.share.detail);
|
|
873
|
-
return next;
|
|
874
|
-
}),
|
|
875
|
-
},
|
|
876
|
-
busy === `share-install:${plugin.name}` ? '安装中…' : '安装',
|
|
877
|
-
),
|
|
878
|
-
),
|
|
879
|
-
),
|
|
880
|
-
)
|
|
881
|
-
: h(
|
|
882
|
-
'div',
|
|
883
|
-
{ className: 'pau-empty' },
|
|
884
|
-
sources.length ? '他人清单里的插件本机都有了' : '把别人的特征文件夹直接放进 插件库\\ 后点「重新读取」',
|
|
885
|
-
),
|
|
886
|
-
installedFromOthers.length
|
|
887
|
-
? h(
|
|
888
|
-
'div',
|
|
889
|
-
{ className: 'pau-row-d', style: { marginTop: 10 } },
|
|
890
|
-
`已从共享装上 ${installedFromOthers.length} 个:${installedFromOthers
|
|
891
|
-
.map((p) => p.name)
|
|
892
|
-
.slice(0, 8)
|
|
893
|
-
.join('、')}${installedFromOthers.length > 8 ? ' 等' : ''}`,
|
|
894
|
-
)
|
|
895
|
-
: null,
|
|
896
|
-
(detail && detail.warnings && detail.warnings.length) || (detail && detail.myManifest && detail.myManifest.skipped && detail.myManifest.skipped.length)
|
|
897
|
-
? h(
|
|
898
|
-
'div',
|
|
899
|
-
{ className: 'pau-row-d', style: { marginTop: 10, color: 'var(--dsw-alias-warning,#a16207)' } },
|
|
900
|
-
[
|
|
901
|
-
...((detail && detail.warnings) || []),
|
|
902
|
-
...((detail && detail.myManifest && detail.myManifest.skipped) || []).map(
|
|
903
|
-
(item) => `${item.name}:${item.reason}`,
|
|
904
|
-
),
|
|
905
|
-
].slice(0, 5),
|
|
906
|
-
)
|
|
907
|
-
: null,
|
|
908
|
-
);
|
|
909
661
|
|
|
910
662
|
const historyCard = h(
|
|
911
663
|
'div',
|
|
@@ -938,7 +690,6 @@ function makePanel(React) {
|
|
|
938
690
|
masterCard,
|
|
939
691
|
policyCard,
|
|
940
692
|
pluginsCard,
|
|
941
|
-
shareCard,
|
|
942
693
|
restartCard,
|
|
943
694
|
historyCard,
|
|
944
695
|
);
|
package/index.js
CHANGED
|
@@ -38,8 +38,6 @@ import { backupProfile, installSpec, installVersion } from './lib/updater.js';
|
|
|
38
38
|
import { gracefulHost, scheduleRestart } from './lib/restart.js';
|
|
39
39
|
import { createUninstall } from './lib/uninstall.js';
|
|
40
40
|
import { createApi } from './lib/api.js';
|
|
41
|
-
import { installSharedPlugin, libraryRoot, shareSnapshot, syncMyFeature, writeAggregate } from './lib/share.js';
|
|
42
|
-
import { resolveIdentity } from './lib/machine.js';
|
|
43
41
|
import fs from 'node:fs';
|
|
44
42
|
import path from 'node:path';
|
|
45
43
|
|
|
@@ -259,141 +257,6 @@ export function apply(ctx, config = {}) {
|
|
|
259
257
|
return '';
|
|
260
258
|
};
|
|
261
259
|
|
|
262
|
-
/* -------------------------------------------------------------- 共享 */
|
|
263
|
-
|
|
264
|
-
/** 共享相关的设置(目录、特征名)从状态里读,面板改完立即生效。 */
|
|
265
|
-
const shareConfig = () => {
|
|
266
|
-
const state = loadState();
|
|
267
|
-
return {
|
|
268
|
-
libraryRoot: state.shareLibraryRoot || '',
|
|
269
|
-
featureName: state.shareFeatureName || '',
|
|
270
|
-
// 已固定的特征值必须原样传回:不传就会重新推导,目录会漂移
|
|
271
|
-
machineHash: state.shareMachineHash || '',
|
|
272
|
-
publishedNames: state.publishedNames || [],
|
|
273
|
-
};
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* 把本机标识固定进状态。
|
|
278
|
-
*
|
|
279
|
-
* 规则与会话同步插件一致:第一次确定后永久不变。所以只有 state 里没有特征值时
|
|
280
|
-
* 才解析一次并落盘;已有值绝不重算(哪怕主机名改了、换用户名了)。
|
|
281
|
-
*/
|
|
282
|
-
const ensureIdentityFixed = () => {
|
|
283
|
-
const state = loadState();
|
|
284
|
-
if (state.shareMachineHash) return state.shareMachineHash;
|
|
285
|
-
const identity = resolveIdentity({ name: state.shareFeatureName });
|
|
286
|
-
const next = loadState();
|
|
287
|
-
next.shareMachineHash = identity.machineHash;
|
|
288
|
-
next.shareHashFixedAt = Date.now();
|
|
289
|
-
if (!next.shareFeatureName) next.shareFeatureName = identity.machineName;
|
|
290
|
-
saveState(next);
|
|
291
|
-
log(`本机标识已固定:${identity.machineId}(特征值来源:${identity.hashSource})`);
|
|
292
|
-
return identity.machineHash;
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
let sharing = false;
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* 同步我自己的特征文件夹 + 汇集他人清单。
|
|
299
|
-
* 巡检每轮跑一次;面板上的「立即同步」也走这里。
|
|
300
|
-
*/
|
|
301
|
-
const runShareSync = async ({ reason } = {}) => {
|
|
302
|
-
const state = loadState();
|
|
303
|
-
if (!state.shareEnabled && reason !== '手动') {
|
|
304
|
-
return { ok: true, skipped: '共享同步已关闭' };
|
|
305
|
-
}
|
|
306
|
-
if (sharing) return { ok: false, error: '正在同步中' };
|
|
307
|
-
sharing = true;
|
|
308
|
-
try {
|
|
309
|
-
// 同步前先把本机标识钉死:第一次运行时确定,之后永久不变
|
|
310
|
-
ensureIdentityFixed();
|
|
311
|
-
const config = shareConfig();
|
|
312
|
-
const mine = await syncMyFeature({
|
|
313
|
-
config,
|
|
314
|
-
publishedNames: config.publishedNames,
|
|
315
|
-
log,
|
|
316
|
-
});
|
|
317
|
-
const aggregate = writeAggregate({
|
|
318
|
-
config,
|
|
319
|
-
publishedNames: config.publishedNames,
|
|
320
|
-
log,
|
|
321
|
-
});
|
|
322
|
-
const next = loadState();
|
|
323
|
-
next.shareLastSyncAt = Date.now();
|
|
324
|
-
next.shareLastResult = {
|
|
325
|
-
feature: mine.feature,
|
|
326
|
-
root: mine.root,
|
|
327
|
-
total: mine.counts.total,
|
|
328
|
-
// 只存清单:bundled / totalMb 恒为 0,保留字段是为了旧面板与旧历史记录不炸
|
|
329
|
-
bundled: 0,
|
|
330
|
-
skipped: 0,
|
|
331
|
-
totalMb: 0,
|
|
332
|
-
// 新口径:本机清单里「能联网装」与「装不了」各多少
|
|
333
|
-
localInstallable: mine.counts.installable,
|
|
334
|
-
localUninstallable: mine.counts.uninstallable,
|
|
335
|
-
legacyPurged: mine.legacyPurged?.removed?.length || 0,
|
|
336
|
-
others: aggregate.doc.counts.othersTotal,
|
|
337
|
-
installable: aggregate.doc.counts.installable,
|
|
338
|
-
sources: aggregate.doc.counts.sources,
|
|
339
|
-
reason: reason || '定时',
|
|
340
|
-
};
|
|
341
|
-
next.shareLastError = '';
|
|
342
|
-
pushHistory(next, {
|
|
343
|
-
kind: 'share',
|
|
344
|
-
reason: reason || '定时',
|
|
345
|
-
text:
|
|
346
|
-
`共享同步:本机 ${mine.counts.total} 个插件(可联网装 ${mine.counts.installable},装不了 ${mine.counts.uninstallable});` +
|
|
347
|
-
`他人 ${aggregate.doc.counts.othersTotal} 个,可装 ${aggregate.doc.counts.installable}` +
|
|
348
|
-
(mine.legacyPurged?.removed?.length ? `;清理历史副本 ${mine.legacyPurged.removed.length} 个` : ''),
|
|
349
|
-
});
|
|
350
|
-
saveState(next);
|
|
351
|
-
return { ok: true, mine, aggregate: { counts: aggregate.doc.counts }, root: libraryRoot(config) };
|
|
352
|
-
} catch (error) {
|
|
353
|
-
const message = String(error?.message || error);
|
|
354
|
-
const next = loadState();
|
|
355
|
-
next.shareLastError = message;
|
|
356
|
-
pushHistory(next, { kind: 'share-failed', text: `共享同步失败:${message}` });
|
|
357
|
-
saveState(next);
|
|
358
|
-
log(`共享同步失败:${message}`);
|
|
359
|
-
return { ok: false, error: message };
|
|
360
|
-
} finally {
|
|
361
|
-
sharing = false;
|
|
362
|
-
}
|
|
363
|
-
};
|
|
364
|
-
|
|
365
|
-
const shareState = () => {
|
|
366
|
-
try {
|
|
367
|
-
ensureIdentityFixed();
|
|
368
|
-
} catch {
|
|
369
|
-
/* 标识固定失败也要能看面板(会用临时值显示) */
|
|
370
|
-
}
|
|
371
|
-
const config = shareConfig();
|
|
372
|
-
return shareSnapshot({ config, publishedNames: config.publishedNames });
|
|
373
|
-
};
|
|
374
|
-
|
|
375
|
-
/** 人工安装他人清单里的一个插件。 */
|
|
376
|
-
const installFromShare = async (name) => {
|
|
377
|
-
ensureIdentityFixed();
|
|
378
|
-
const config = shareConfig();
|
|
379
|
-
const result = await installSharedPlugin({
|
|
380
|
-
ctx,
|
|
381
|
-
config,
|
|
382
|
-
publishedNames: config.publishedNames,
|
|
383
|
-
name,
|
|
384
|
-
installSpec,
|
|
385
|
-
log,
|
|
386
|
-
});
|
|
387
|
-
const next = loadState();
|
|
388
|
-
pushHistory(next, {
|
|
389
|
-
kind: result.ok ? 'share-install' : 'share-install-failed',
|
|
390
|
-
name,
|
|
391
|
-
text: result.ok ? `从共享安装 ${name}:${result.message}` : `从共享安装 ${name} 失败:${result.message}`,
|
|
392
|
-
});
|
|
393
|
-
saveState(next);
|
|
394
|
-
return result;
|
|
395
|
-
};
|
|
396
|
-
|
|
397
260
|
/* -------------------------------------------------------------- 检查 */
|
|
398
261
|
|
|
399
262
|
/**
|
|
@@ -732,7 +595,6 @@ export function apply(ctx, config = {}) {
|
|
|
732
595
|
// 两件事各自排期:自动更新按「启用 + 间隔」跑;共享同步只受它自己的开关约束。
|
|
733
596
|
// 共用同一个 tick,但状态独立——用户把自动更新关掉、只想要清单同步是完全合理的。
|
|
734
597
|
let updateDueAt = 0;
|
|
735
|
-
let shareDueAt = 0;
|
|
736
598
|
let booted = false;
|
|
737
599
|
let lastIntervalMs = 0;
|
|
738
600
|
|
|
@@ -747,7 +609,6 @@ export function apply(ctx, config = {}) {
|
|
|
747
609
|
booted = true;
|
|
748
610
|
// 开机首跑稍晚,避开启动高峰
|
|
749
611
|
updateDueAt = state.enabled ? Date.now() + 20 * 1000 : 0;
|
|
750
|
-
shareDueAt = state.shareEnabled ? Date.now() + 40 * 1000 : 0;
|
|
751
612
|
lastIntervalMs = intervalMsOf(state);
|
|
752
613
|
return;
|
|
753
614
|
}
|
|
@@ -772,18 +633,6 @@ export function apply(ctx, config = {}) {
|
|
|
772
633
|
}
|
|
773
634
|
}
|
|
774
635
|
|
|
775
|
-
// ---- 共享同步:每次巡检跑一次 ----
|
|
776
|
-
if (!state.shareEnabled) {
|
|
777
|
-
shareDueAt = 0;
|
|
778
|
-
} else {
|
|
779
|
-
if (!shareDueAt) shareDueAt = Date.now() + 1000;
|
|
780
|
-
if (Date.now() >= shareDueAt) {
|
|
781
|
-
shareDueAt = Date.now() + intervalMs;
|
|
782
|
-
runShareSync({ reason: '定时' }).catch((error) =>
|
|
783
|
-
log(`共享同步失败:${error?.message || error}`),
|
|
784
|
-
);
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
636
|
} catch (error) {
|
|
788
637
|
log(`调度失败:${error?.message || error}`);
|
|
789
638
|
}
|
|
@@ -884,9 +733,6 @@ export function apply(ctx, config = {}) {
|
|
|
884
733
|
requestRestart,
|
|
885
734
|
isBusy,
|
|
886
735
|
log,
|
|
887
|
-
runShareSync,
|
|
888
|
-
shareState,
|
|
889
|
-
installFromShare,
|
|
890
736
|
// 卸载:走宿主官方 pluginManager.removeBundle,本插件不自己改清单/包目录
|
|
891
737
|
uninstall: createUninstall({
|
|
892
738
|
getManager: () => {
|
package/lib/api.js
CHANGED
|
@@ -22,7 +22,6 @@ import {
|
|
|
22
22
|
} from './core.js';
|
|
23
23
|
import { normalizeRegistry, queryLatest } from './registry.js';
|
|
24
24
|
import { restoreBackup, listBackups } from './backup.js';
|
|
25
|
-
import { SHARE_POLICY, libraryRoot, myFeatureName } from './share.js';
|
|
26
25
|
|
|
27
26
|
export const API_PREFIX = '/api/local/plugin-autoupdate';
|
|
28
27
|
|
|
@@ -149,25 +148,6 @@ function snapshot(deps) {
|
|
|
149
148
|
return { hidden: true, count: names.length, names: names.slice(0, 200) };
|
|
150
149
|
})(),
|
|
151
150
|
history: (state.history || []).slice(0, 40),
|
|
152
|
-
share: {
|
|
153
|
-
enabled: state.shareEnabled,
|
|
154
|
-
libraryRoot: state.shareLibraryRoot || '',
|
|
155
|
-
featureName: state.shareFeatureName || '',
|
|
156
|
-
machineHash: state.shareMachineHash || '',
|
|
157
|
-
hashFixedAt: state.shareHashFixedAt || 0,
|
|
158
|
-
lastSyncAt: state.shareLastSyncAt,
|
|
159
|
-
lastError: state.shareLastError,
|
|
160
|
-
lastResult: state.shareLastResult,
|
|
161
|
-
// 展示用:面板没填时告诉用户实际会落到哪。
|
|
162
|
-
// 这就是最终目录名本身(`<名称>-<特征值>`),不要再过 safeFeatureName 二次加工。
|
|
163
|
-
effectiveRoot: libraryRoot({ libraryRoot: state.shareLibraryRoot }),
|
|
164
|
-
effectiveFeature: myFeatureName({
|
|
165
|
-
featureName: state.shareFeatureName,
|
|
166
|
-
machineHash: state.shareMachineHash,
|
|
167
|
-
}),
|
|
168
|
-
// 口径声明:特征文件夹只存清单、安装走 registry。面板据此显示,不再提「副本」
|
|
169
|
-
policy: SHARE_POLICY,
|
|
170
|
-
},
|
|
171
151
|
limits: {
|
|
172
152
|
minIntervalMinutes: MIN_INTERVAL_MINUTES,
|
|
173
153
|
defaultIntervalMinutes: DEFAULT_INTERVAL_MINUTES,
|
|
@@ -202,18 +182,6 @@ export function createApi(deps) {
|
|
|
202
182
|
if (body.autoRestart !== undefined) state.autoRestart = body.autoRestart === true;
|
|
203
183
|
if (body.restartOnlyWhenIdle !== undefined) state.restartOnlyWhenIdle = body.restartOnlyWhenIdle !== false;
|
|
204
184
|
if (body.restartDelaySeconds !== undefined) state.restartDelaySeconds = body.restartDelaySeconds;
|
|
205
|
-
if (body.shareEnabled !== undefined) state.shareEnabled = body.shareEnabled === true;
|
|
206
|
-
if (body.shareLibraryRoot !== undefined) {
|
|
207
|
-
state.shareLibraryRoot = typeof body.shareLibraryRoot === 'string' ? body.shareLibraryRoot.trim() : '';
|
|
208
|
-
}
|
|
209
|
-
if (body.shareFeatureName !== undefined) {
|
|
210
|
-
state.shareFeatureName = typeof body.shareFeatureName === 'string' ? body.shareFeatureName.trim() : '';
|
|
211
|
-
}
|
|
212
|
-
if (body.shareResetHash === true) {
|
|
213
|
-
// 明确要求重算才重算:清掉固定值,下次同步重新解析
|
|
214
|
-
state.shareMachineHash = '';
|
|
215
|
-
state.shareHashFixedAt = 0;
|
|
216
|
-
}
|
|
217
185
|
if (Array.isArray(body.publishedNames)) {
|
|
218
186
|
// 「已发布的自有插件」名单:file:/link: 依赖但已在 registry 有正式产物
|
|
219
187
|
state.publishedNames = body.publishedNames
|
|
@@ -436,35 +404,6 @@ export function createApi(deps) {
|
|
|
436
404
|
json(res, 200, { ok: answer.ok, message: answer.message });
|
|
437
405
|
},
|
|
438
406
|
|
|
439
|
-
/** 共享面板:我的特征文件夹 + 他人清单(detail 里是扫盘得到的最新现状)。 */
|
|
440
|
-
'GET /share': async (req, res) => {
|
|
441
|
-
const snap = snapshot(deps);
|
|
442
|
-
json(res, 200, { ...snap, share: { ...snap.share, detail: deps.shareState() } });
|
|
443
|
-
},
|
|
444
|
-
|
|
445
|
-
'POST /share/sync': async (req, res) => {
|
|
446
|
-
const result = await deps.runShareSync({ reason: '手动' });
|
|
447
|
-
const snap = snapshot(deps);
|
|
448
|
-
json(res, 200, { ...snap, share: { ...snap.share, detail: deps.shareState() }, result });
|
|
449
|
-
},
|
|
450
|
-
|
|
451
|
-
/** 人工安装他人清单里的某个插件(不自动装,必须逐个点)。 */
|
|
452
|
-
'POST /share/install': async (req, res) => {
|
|
453
|
-
const body = await readJson(req);
|
|
454
|
-
const name = typeof body.name === 'string' ? body.name.trim() : '';
|
|
455
|
-
if (!name) {
|
|
456
|
-
json(res, 400, { ok: false, error: '缺少 name' });
|
|
457
|
-
return;
|
|
458
|
-
}
|
|
459
|
-
const result = await deps.installFromShare(name);
|
|
460
|
-
const snap = snapshot(deps);
|
|
461
|
-
json(res, result.ok ? 200 : 400, {
|
|
462
|
-
...snap,
|
|
463
|
-
share: { ...snap.share, detail: deps.shareState() },
|
|
464
|
-
result,
|
|
465
|
-
});
|
|
466
|
-
},
|
|
467
|
-
|
|
468
407
|
'GET /backups': async (req, res) => {
|
|
469
408
|
json(res, 200, { ok: true, backups: listBackups() });
|
|
470
409
|
},
|
package/lib/core.js
CHANGED
|
@@ -306,26 +306,6 @@ export function defaultState() {
|
|
|
306
306
|
* 必须显式列出,不靠猜——file: 指向的目录里有没有 publishConfig 都不能证明它发过。
|
|
307
307
|
*/
|
|
308
308
|
publishedNames: [],
|
|
309
|
-
/* ---- 插件共享(特征文件夹) ---- */
|
|
310
|
-
/** 每次巡检顺带同步自己的特征文件夹。 */
|
|
311
|
-
shareEnabled: true,
|
|
312
|
-
/** 插件库根目录;空 = 用默认 `D:\Ai工作空间\插件库`。 */
|
|
313
|
-
shareLibraryRoot: '',
|
|
314
|
-
/** 我的特征本名字;空 = 用会话同步插件的本机标识(`<名称>-<特征值>`)。 */
|
|
315
|
-
shareFeatureName: '',
|
|
316
|
-
/**
|
|
317
|
-
* 本机特征值(12 位十六进制)。与会话同步插件共用同一身份,
|
|
318
|
-
* 第一次读到时固定下来,此后永久不变——绝不在没有旧值时重算。
|
|
319
|
-
* 有旧值就必须原样传回,否则特征目录会漂移、别人汇总出两个「我」。
|
|
320
|
-
*/
|
|
321
|
-
shareMachineHash: '',
|
|
322
|
-
/** 特征值第一次固定的时间,便于排查漂移。 */
|
|
323
|
-
shareHashFixedAt: 0,
|
|
324
|
-
/** 上次同步时间与结果摘要。 */
|
|
325
|
-
shareLastSyncAt: 0,
|
|
326
|
-
shareLastResult: null,
|
|
327
|
-
shareLastError: '',
|
|
328
|
-
|
|
329
309
|
/** 运行态:上次检查时间 / 上次结果 / 待生效的更新队列。 */
|
|
330
310
|
lastCheckAt: 0,
|
|
331
311
|
lastCheckError: '',
|
|
@@ -407,19 +387,6 @@ export function normalizeState(raw) {
|
|
|
407
387
|
|
|
408
388
|
out.history = Array.isArray(value.history) ? value.history.slice(0, 100) : [];
|
|
409
389
|
|
|
410
|
-
out.shareEnabled = value.shareEnabled !== false;
|
|
411
|
-
out.shareLibraryRoot =
|
|
412
|
-
typeof value.shareLibraryRoot === 'string' ? value.shareLibraryRoot.trim().slice(0, 400) : '';
|
|
413
|
-
out.shareFeatureName =
|
|
414
|
-
typeof value.shareFeatureName === 'string' ? value.shareFeatureName.trim().slice(0, 80) : '';
|
|
415
|
-
out.shareMachineHash =
|
|
416
|
-
typeof value.shareMachineHash === 'string' ? value.shareMachineHash.trim().slice(0, 64) : '';
|
|
417
|
-
out.shareHashFixedAt = Number(value.shareHashFixedAt) || 0;
|
|
418
|
-
out.shareLastSyncAt = Number(value.shareLastSyncAt) || 0;
|
|
419
|
-
out.shareLastResult =
|
|
420
|
-
value.shareLastResult && typeof value.shareLastResult === 'object' ? value.shareLastResult : null;
|
|
421
|
-
out.shareLastError = typeof value.shareLastError === 'string' ? value.shareLastError.slice(0, 800) : '';
|
|
422
|
-
|
|
423
390
|
out.lastCheckAt = Number(value.lastCheckAt) || 0;
|
|
424
391
|
out.lastCheckError = typeof value.lastCheckError === 'string' ? value.lastCheckError : '';
|
|
425
392
|
out.updatedAt = Number(value.updatedAt) || 0;
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ygc-dsh-plugin-autoupdate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "DSH
|
|
5
|
+
"description": "DSH 插件自动更新器:定时检查已装插件的新版本并按策略升级、装完按需重启、可回滚。只管「已装插件的新版本」,插件清单共享已移交 ygc-dsh-sync。",
|
|
6
6
|
"icon": "icon.svg",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"dsh",
|
|
@@ -28,11 +28,9 @@
|
|
|
28
28
|
"lib/backup.js",
|
|
29
29
|
"lib/core.js",
|
|
30
30
|
"lib/gitref.js",
|
|
31
|
-
"lib/machine.js",
|
|
32
31
|
"lib/registry.js",
|
|
33
32
|
"lib/restart-helper.js",
|
|
34
33
|
"lib/restart.js",
|
|
35
|
-
"lib/share.js",
|
|
36
34
|
"lib/uninstall.js",
|
|
37
35
|
"lib/updater.js",
|
|
38
36
|
"locale/*.json",
|