ygc-dsh-sync 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/client.js +41 -0
- package/lib/plugins.js +152 -5
- package/package.json +2 -2
package/client.js
CHANGED
|
@@ -927,6 +927,32 @@ function makePanel(React) {
|
|
|
927
927
|
const others = ((detail && detail.others) || []).filter((p) => !p.installed);
|
|
928
928
|
const installedFromOthers = ((detail && detail.others) || []).filter((p) => p.installed);
|
|
929
929
|
const sources = (detail && detail.sources) || [];
|
|
930
|
+
|
|
931
|
+
/**
|
|
932
|
+
* 来源的人读形式。
|
|
933
|
+
*
|
|
934
|
+
* 需求是「备份来源」:npm 就记 npm,git 就记仓库地址。
|
|
935
|
+
* 这里只负责展示——数据来自清单里的 source 对象(由 lib/plugins.js 生成)。
|
|
936
|
+
*/
|
|
937
|
+
const sourceText = (src) => {
|
|
938
|
+
if (!src || typeof src !== 'object') return '';
|
|
939
|
+
if (src.type === 'npm') return `npm · ${src.spec || src.package || ''}`;
|
|
940
|
+
if (src.type === 'git') {
|
|
941
|
+
const ref = src.ref ? `#${src.ref}` : '';
|
|
942
|
+
return src.url ? `git · ${src.url}${ref}` : `git · ${src.raw || ''}`;
|
|
943
|
+
}
|
|
944
|
+
if (src.type === 'local') return `local · ${[src.form, src.path].filter(Boolean).join(' ')}`;
|
|
945
|
+
if (src.type === 'installation') return '随桌面端安装';
|
|
946
|
+
return src.type || '';
|
|
947
|
+
};
|
|
948
|
+
|
|
949
|
+
/** 来源标签的配色:git 用强调色,装不了的用警告色。 */
|
|
950
|
+
const sourceTagClass = (src) => {
|
|
951
|
+
if (!src || typeof src !== 'object') return 'dsb-tag';
|
|
952
|
+
if (src.type === 'git') return 'dsb-tag dsb-tag-ok';
|
|
953
|
+
if (src.type === 'local') return 'dsb-tag dsb-tag-warn';
|
|
954
|
+
return 'dsb-tag';
|
|
955
|
+
};
|
|
930
956
|
const last = st.lastResult || null;
|
|
931
957
|
return h(
|
|
932
958
|
'div',
|
|
@@ -1049,6 +1075,21 @@ function makePanel(React) {
|
|
|
1049
1075
|
h('span', { className: 'dsb-tag' }, (p.features || []).join(' / ')),
|
|
1050
1076
|
),
|
|
1051
1077
|
p.description ? h('div', { className: 'dsb-row-d' }, String(p.description).slice(0, 140)) : null,
|
|
1078
|
+
// 来源:npm 给 registry 坐标,git 给**仓库地址**与 ref
|
|
1079
|
+
sourceText(p.source)
|
|
1080
|
+
? h(
|
|
1081
|
+
'div',
|
|
1082
|
+
{ className: 'dsb-row-d', style: { marginTop: 2 } },
|
|
1083
|
+
'来源 ',
|
|
1084
|
+
h('span', { className: sourceTagClass(p.source) }, sourceText(p.source)),
|
|
1085
|
+
p.source && p.source.declared && p.source.type === 'npm'
|
|
1086
|
+
? h('span', { style: { opacity: 0.6 } }, ` (profile 里声明为 ${p.source.declared})`)
|
|
1087
|
+
: null,
|
|
1088
|
+
p.source && p.source.type === 'git' && p.source.raw && p.source.url !== p.source.raw
|
|
1089
|
+
? h('span', { style: { opacity: 0.6 } }, ` (原始写法 ${p.source.raw})`)
|
|
1090
|
+
: null,
|
|
1091
|
+
)
|
|
1092
|
+
: null,
|
|
1052
1093
|
),
|
|
1053
1094
|
h(
|
|
1054
1095
|
'button',
|
package/lib/plugins.js
CHANGED
|
@@ -29,6 +29,9 @@ import os from 'node:os';
|
|
|
29
29
|
|
|
30
30
|
/* ---------------------------------------------------------------- 路径 */
|
|
31
31
|
|
|
32
|
+
/** 默认 registry(写进来源里,让别人知道这个包是从哪查到的)。 */
|
|
33
|
+
export const DEFAULT_REGISTRY = 'https://registry.npmjs.org/';
|
|
34
|
+
|
|
32
35
|
/** 插件库默认根:D 盘 Ai工作空间 下的「插件库」。 */
|
|
33
36
|
export const DEFAULT_LIBRARY_ROOT = 'D:\\Ai工作空间\\插件库';
|
|
34
37
|
|
|
@@ -377,6 +380,121 @@ function bytesOf(file) {
|
|
|
377
380
|
}
|
|
378
381
|
}
|
|
379
382
|
|
|
383
|
+
/**
|
|
384
|
+
* 从 pnpm-lock.yaml 里读出某个包**实际装到的** commit。
|
|
385
|
+
*
|
|
386
|
+
* git 依赖的 lockfile 记录长这样:
|
|
387
|
+
* dsh-purge-community:
|
|
388
|
+
* specifier: github:owner/repo#v1.6.97
|
|
389
|
+
* version: https://codeload.github.com/owner/repo/tar.gz/<40 位 commit>
|
|
390
|
+
*
|
|
391
|
+
* 为什么记 commit 而不只记 tag:tag 可以移动,commit 不会。只写 tag 的话,
|
|
392
|
+
* 将来对方重打 tag,别人按清单装出来的就不是同一份代码。
|
|
393
|
+
*
|
|
394
|
+
* 读不到就返回 undefined(比如用 file:/registry 装的、或 lockfile 不在),
|
|
395
|
+
* 上层会退化成只写 ref,不编造。
|
|
396
|
+
*
|
|
397
|
+
* @param {string} name 包名
|
|
398
|
+
* @returns {string|undefined}
|
|
399
|
+
*/
|
|
400
|
+
export function installedGitCommit(name) {
|
|
401
|
+
let text = '';
|
|
402
|
+
try {
|
|
403
|
+
text = fs.readFileSync(path.join(profileDir(), 'pnpm-lock.yaml'), 'utf8');
|
|
404
|
+
} catch {
|
|
405
|
+
return undefined;
|
|
406
|
+
}
|
|
407
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
408
|
+
const directRe = new RegExp(
|
|
409
|
+
escaped +
|
|
410
|
+
'@https:\\/\\/codeload\\.github\\.com\\/[^/]+\\/[^/]+\\/tar\\.gz\\/([0-9a-f]{40})',
|
|
411
|
+
);
|
|
412
|
+
const hit = directRe.exec(text);
|
|
413
|
+
if (hit) return hit[1];
|
|
414
|
+
const looseRe = new RegExp(escaped + ':[\\s\\S]{0,400}?tar\\.gz\\/([0-9a-f]{40})');
|
|
415
|
+
const m = looseRe.exec(text);
|
|
416
|
+
return m ? m[1] : undefined;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/* ------------------------------------------------------------ 来源描述 */
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* 描述一个插件的**来源**(v1.0.1 新增)。
|
|
423
|
+
*
|
|
424
|
+
* 要记的是「这个插件从哪来」,不是「它的代码长什么样」——所以这里只给出
|
|
425
|
+
* 类型与坐标,绝不读、也不写任何源码。
|
|
426
|
+
*
|
|
427
|
+
* 四种形态:
|
|
428
|
+
* npm registry 包:registry 地址 + 包名@版本 + profile 里的原始写法
|
|
429
|
+
* git git 仓库:**仓库 URL** + 钉住的 ref(分支/标签/commit)+ 原始写法
|
|
430
|
+
* local 本地路径(file: / link: / workspace: / 直链 tar):形式 + 路径
|
|
431
|
+
* installation 随桌面端安装提供:没有可复现的来源,如实写明
|
|
432
|
+
*
|
|
433
|
+
* @param {{name?:string, kind?:string, spec?:string|null, version?:string|null}} plugin
|
|
434
|
+
* @returns {{type:string}}
|
|
435
|
+
*/
|
|
436
|
+
export function describeSource(plugin) {
|
|
437
|
+
const name = String(plugin?.name || '');
|
|
438
|
+
const kind = String(plugin?.kind || '');
|
|
439
|
+
const version = plugin?.version ? String(plugin.version) : '';
|
|
440
|
+
const spec = typeof plugin?.spec === 'string' ? plugin.spec.trim() : '';
|
|
441
|
+
|
|
442
|
+
if (kind === 'registry') {
|
|
443
|
+
return {
|
|
444
|
+
type: 'npm',
|
|
445
|
+
registry: DEFAULT_REGISTRY,
|
|
446
|
+
package: name,
|
|
447
|
+
version,
|
|
448
|
+
// 别人拿去安装的坐标,与 installSpec 一致
|
|
449
|
+
spec: version ? `${name}@${version}` : name,
|
|
450
|
+
// profile 里的原始写法(纯版本号 / ^ / ~ 都可能),能看出当初怎么声明的
|
|
451
|
+
declared: spec || undefined,
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (kind === 'git') {
|
|
456
|
+
const parsed = parseGitSpec(spec);
|
|
457
|
+
return {
|
|
458
|
+
type: 'git',
|
|
459
|
+
// 仓库地址:优先归一成 https 形式,别人那台机器能直接 clone
|
|
460
|
+
url: (parsed && parsed.url) || spec || undefined,
|
|
461
|
+
// 钉住的 ref(分支 / 标签 / commit);没有就是跟默认分支
|
|
462
|
+
ref: (parsed && parsed.ref) || undefined,
|
|
463
|
+
owner: (parsed && parsed.owner) || undefined,
|
|
464
|
+
repo: (parsed && parsed.repo) || undefined,
|
|
465
|
+
// 原始写法照录:`github:o/r#v1` 与 `git+https://…` 归一后看不出区别
|
|
466
|
+
raw: spec || undefined,
|
|
467
|
+
// 实际装到的 commit:ref 是 tag 时它可以被移动,commit 不会。
|
|
468
|
+
// 读不到就留空,不编造。
|
|
469
|
+
commit: installedGitCommit(name) || undefined,
|
|
470
|
+
version,
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (kind === 'local') {
|
|
475
|
+
const m = /^(file|link|workspace|portal):(.*)$/i.exec(spec);
|
|
476
|
+
const form = m ? m[1].toLowerCase() : /^https?:\/\//i.test(spec) ? 'tarball' : 'unknown';
|
|
477
|
+
return {
|
|
478
|
+
type: 'local',
|
|
479
|
+
form,
|
|
480
|
+
// 路径照录:这是本机事实,别人装不了,但要知道它指向哪
|
|
481
|
+
path: m ? m[2].trim() : spec || undefined,
|
|
482
|
+
version,
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (kind === 'installation') {
|
|
487
|
+
return {
|
|
488
|
+
type: 'installation',
|
|
489
|
+
// 随 DSH 桌面端一起发布,版本跟桌面端走,没有独立的可复现来源
|
|
490
|
+
bundledWith: 'dsh-desktop',
|
|
491
|
+
version: version || undefined,
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
return { type: 'unknown', kind: kind || undefined, spec: spec || undefined };
|
|
496
|
+
}
|
|
497
|
+
|
|
380
498
|
/* ------------------------------------------------------- 本机插件枚举 */
|
|
381
499
|
|
|
382
500
|
/**
|
|
@@ -417,6 +535,8 @@ export function collectLocalPlugins(options) {
|
|
|
417
535
|
installable: Boolean(installSpec),
|
|
418
536
|
description: meta.description || '',
|
|
419
537
|
homepage: meta.homepage || '',
|
|
538
|
+
// 来源:npm / git(含仓库地址与 ref)/ local / installation
|
|
539
|
+
source: describeSource({ name, kind, spec, version }),
|
|
420
540
|
reason: registryLike
|
|
421
541
|
? 'registry 来源,按名称+版本联网拉取'
|
|
422
542
|
: kind === 'git'
|
|
@@ -461,7 +581,8 @@ export async function syncMyFeature({ config, machineId, machineHash, machineNam
|
|
|
461
581
|
const purged = purgeLegacyBundles(root);
|
|
462
582
|
|
|
463
583
|
const manifest = {
|
|
464
|
-
|
|
584
|
+
// 3:新增 source(记录来源类型与坐标,不含任何源码)
|
|
585
|
+
schemaVersion: 3,
|
|
465
586
|
feature,
|
|
466
587
|
machine: machineName || '',
|
|
467
588
|
machineId: feature,
|
|
@@ -481,6 +602,10 @@ export async function syncMyFeature({ config, machineId, machineHash, machineNam
|
|
|
481
602
|
manifestOnly: true,
|
|
482
603
|
storesBundles: false,
|
|
483
604
|
installVia: 'registry',
|
|
605
|
+
// 来源记录的边界:只记「从哪来」,不记「代码长什么样」
|
|
606
|
+
recordsSource: true,
|
|
607
|
+
storesSourceCode: false,
|
|
608
|
+
sourceKinds: ['npm', 'git', 'local', 'installation'],
|
|
484
609
|
note: '特征文件夹只存清单;安装一律按 installSpec 联网拉取,不使用本地副本。',
|
|
485
610
|
},
|
|
486
611
|
plugins,
|
|
@@ -631,15 +756,22 @@ export function collectSharedPlugins({ config, machineId, publishedNames } = {})
|
|
|
631
756
|
: kind === 'git' && typeof plugin.spec === 'string'
|
|
632
757
|
? plugin.spec
|
|
633
758
|
: '';
|
|
759
|
+
const specOf = typeof plugin.spec === 'string' ? plugin.spec : '';
|
|
634
760
|
const record = {
|
|
635
761
|
name: plugin.name,
|
|
636
762
|
version,
|
|
637
763
|
kind,
|
|
638
|
-
spec:
|
|
764
|
+
spec: specOf,
|
|
639
765
|
installSpec,
|
|
640
766
|
installable: Boolean(installSpec),
|
|
641
767
|
description: typeof plugin.description === 'string' ? plugin.description : '',
|
|
642
768
|
homepage: typeof plugin.homepage === 'string' ? plugin.homepage : '',
|
|
769
|
+
// 来源:新清单自带就用它;老清单没这个字段就按现有字段现推一份,
|
|
770
|
+
// 这样别人那台跑旧版插件时,本机看到的来源也不会是空的
|
|
771
|
+
source:
|
|
772
|
+
plugin.source && typeof plugin.source === 'object'
|
|
773
|
+
? plugin.source
|
|
774
|
+
: describeSource({ name: plugin.name, kind, spec: specOf, version }),
|
|
643
775
|
features: [feature],
|
|
644
776
|
mine: isMine || Boolean(plugin.mine),
|
|
645
777
|
};
|
|
@@ -659,6 +791,7 @@ export function collectSharedPlugins({ config, machineId, publishedNames } = {})
|
|
|
659
791
|
old.installable = Boolean(old.installSpec);
|
|
660
792
|
old.description = record.description || old.description;
|
|
661
793
|
old.homepage = record.homepage || old.homepage;
|
|
794
|
+
old.source = record.source || old.source;
|
|
662
795
|
} else if (!old.installSpec && record.installSpec) {
|
|
663
796
|
old.installSpec = record.installSpec;
|
|
664
797
|
old.installable = true;
|
|
@@ -685,6 +818,20 @@ export function writeAggregate({ config, machineId, publishedNames, log } = {})
|
|
|
685
818
|
installSpec: plugin.installSpec || '',
|
|
686
819
|
}));
|
|
687
820
|
|
|
821
|
+
/** 来源的人读形式:git 给仓库 URL,npm 给 registry 坐标,local 给路径。 */
|
|
822
|
+
const sourceText = (src) => {
|
|
823
|
+
if (!src || typeof src !== 'object') return '-';
|
|
824
|
+
if (src.type === 'npm') return `npm · ${src.spec || src.package || ''}`;
|
|
825
|
+
if (src.type === 'git') {
|
|
826
|
+
const ref = src.ref ? `#${src.ref}` : '';
|
|
827
|
+
const commit = src.commit ? ` @${String(src.commit).slice(0, 12)}` : '';
|
|
828
|
+
return src.url ? `git · ${src.url}${ref}${commit}` : `git · ${src.raw || '-'}`;
|
|
829
|
+
}
|
|
830
|
+
if (src.type === 'local') return `local · ${src.form || ''} ${src.path || ''}`.trim();
|
|
831
|
+
if (src.type === 'installation') return '随桌面端安装';
|
|
832
|
+
return src.type || '-';
|
|
833
|
+
};
|
|
834
|
+
|
|
688
835
|
const others = items.filter((item) => !item.mine);
|
|
689
836
|
const installable = others.filter((item) => !item.installed && item.installKind !== 'none');
|
|
690
837
|
|
|
@@ -732,11 +879,11 @@ export function writeAggregate({ config, machineId, publishedNames, log } = {})
|
|
|
732
879
|
'',
|
|
733
880
|
'## 他人插件',
|
|
734
881
|
'',
|
|
735
|
-
'| 插件 | 版本 | 来源 | 本机 | 安装方式 | 安装坐标 |',
|
|
736
|
-
'
|
|
882
|
+
'| 插件 | 版本 | 来源 | 出处 | 本机 | 安装方式 | 安装坐标 |',
|
|
883
|
+
'|---|---|---|---|---|---|---|',
|
|
737
884
|
...others.map(
|
|
738
885
|
(item) =>
|
|
739
|
-
`| \`${item.name}\` | ${item.version || '-'} | ${item.features.join(', ')} | ${
|
|
886
|
+
`| \`${item.name}\` | ${item.version || '-'} | ${item.features.join(', ')} | ${sourceText(item.source)} | ${
|
|
740
887
|
item.installed ? `已装 ${item.installedVersion}` : '无'
|
|
741
888
|
} | ${item.installKind} | ${item.installSpec ? `\`${item.installSpec}\`` : '-'} |`,
|
|
742
889
|
),
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ygc-dsh-sync",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "DSH
|
|
5
|
+
"description": "DSH 同步插件:会话按工作区落地、按机器隔离的跨机备份与拉取;技能本机↔母版双向同步(含记忆插件技能优化闭环);插件清单共享(只存清单与来源,不存源码,只能联网装)。",
|
|
6
6
|
"icon": "icon.svg",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"dsh",
|