ygc-dsh-sync 1.0.0 → 1.0.1

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.
Files changed (3) hide show
  1. package/client.js +41 -0
  2. package/lib/plugins.js +112 -5
  3. 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,82 @@ function bytesOf(file) {
377
380
  }
378
381
  }
379
382
 
383
+ /* ------------------------------------------------------------ 来源描述 */
384
+
385
+ /**
386
+ * 描述一个插件的**来源**(v1.0.1 新增)。
387
+ *
388
+ * 要记的是「这个插件从哪来」,不是「它的代码长什么样」——所以这里只给出
389
+ * 类型与坐标,绝不读、也不写任何源码。
390
+ *
391
+ * 四种形态:
392
+ * npm registry 包:registry 地址 + 包名@版本 + profile 里的原始写法
393
+ * git git 仓库:**仓库 URL** + 钉住的 ref(分支/标签/commit)+ 原始写法
394
+ * local 本地路径(file: / link: / workspace: / 直链 tar):形式 + 路径
395
+ * installation 随桌面端安装提供:没有可复现的来源,如实写明
396
+ *
397
+ * @param {{name?:string, kind?:string, spec?:string|null, version?:string|null}} plugin
398
+ * @returns {{type:string}}
399
+ */
400
+ export function describeSource(plugin) {
401
+ const name = String(plugin?.name || '');
402
+ const kind = String(plugin?.kind || '');
403
+ const version = plugin?.version ? String(plugin.version) : '';
404
+ const spec = typeof plugin?.spec === 'string' ? plugin.spec.trim() : '';
405
+
406
+ if (kind === 'registry') {
407
+ return {
408
+ type: 'npm',
409
+ registry: DEFAULT_REGISTRY,
410
+ package: name,
411
+ version,
412
+ // 别人拿去安装的坐标,与 installSpec 一致
413
+ spec: version ? `${name}@${version}` : name,
414
+ // profile 里的原始写法(纯版本号 / ^ / ~ 都可能),能看出当初怎么声明的
415
+ declared: spec || undefined,
416
+ };
417
+ }
418
+
419
+ if (kind === 'git') {
420
+ const parsed = parseGitSpec(spec);
421
+ return {
422
+ type: 'git',
423
+ // 仓库地址:优先归一成 https 形式,别人那台机器能直接 clone
424
+ url: (parsed && parsed.url) || spec || undefined,
425
+ // 钉住的 ref(分支 / 标签 / commit);没有就是跟默认分支
426
+ ref: (parsed && parsed.ref) || undefined,
427
+ owner: (parsed && parsed.owner) || undefined,
428
+ repo: (parsed && parsed.repo) || undefined,
429
+ // 原始写法照录:`github:o/r#v1` 与 `git+https://…` 归一后看不出区别
430
+ raw: spec || undefined,
431
+ version,
432
+ };
433
+ }
434
+
435
+ if (kind === 'local') {
436
+ const m = /^(file|link|workspace|portal):(.*)$/i.exec(spec);
437
+ const form = m ? m[1].toLowerCase() : /^https?:\/\//i.test(spec) ? 'tarball' : 'unknown';
438
+ return {
439
+ type: 'local',
440
+ form,
441
+ // 路径照录:这是本机事实,别人装不了,但要知道它指向哪
442
+ path: m ? m[2].trim() : spec || undefined,
443
+ version,
444
+ };
445
+ }
446
+
447
+ if (kind === 'installation') {
448
+ return {
449
+ type: 'installation',
450
+ // 随 DSH 桌面端一起发布,版本跟桌面端走,没有独立的可复现来源
451
+ bundledWith: 'dsh-desktop',
452
+ version: version || undefined,
453
+ };
454
+ }
455
+
456
+ return { type: 'unknown', kind: kind || undefined, spec: spec || undefined };
457
+ }
458
+
380
459
  /* ------------------------------------------------------- 本机插件枚举 */
381
460
 
382
461
  /**
@@ -417,6 +496,8 @@ export function collectLocalPlugins(options) {
417
496
  installable: Boolean(installSpec),
418
497
  description: meta.description || '',
419
498
  homepage: meta.homepage || '',
499
+ // 来源:npm / git(含仓库地址与 ref)/ local / installation
500
+ source: describeSource({ name, kind, spec, version }),
420
501
  reason: registryLike
421
502
  ? 'registry 来源,按名称+版本联网拉取'
422
503
  : kind === 'git'
@@ -461,7 +542,8 @@ export async function syncMyFeature({ config, machineId, machineHash, machineNam
461
542
  const purged = purgeLegacyBundles(root);
462
543
 
463
544
  const manifest = {
464
- schemaVersion: 2,
545
+ // 3:新增 source(记录来源类型与坐标,不含任何源码)
546
+ schemaVersion: 3,
465
547
  feature,
466
548
  machine: machineName || '',
467
549
  machineId: feature,
@@ -481,6 +563,10 @@ export async function syncMyFeature({ config, machineId, machineHash, machineNam
481
563
  manifestOnly: true,
482
564
  storesBundles: false,
483
565
  installVia: 'registry',
566
+ // 来源记录的边界:只记「从哪来」,不记「代码长什么样」
567
+ recordsSource: true,
568
+ storesSourceCode: false,
569
+ sourceKinds: ['npm', 'git', 'local', 'installation'],
484
570
  note: '特征文件夹只存清单;安装一律按 installSpec 联网拉取,不使用本地副本。',
485
571
  },
486
572
  plugins,
@@ -631,15 +717,22 @@ export function collectSharedPlugins({ config, machineId, publishedNames } = {})
631
717
  : kind === 'git' && typeof plugin.spec === 'string'
632
718
  ? plugin.spec
633
719
  : '';
720
+ const specOf = typeof plugin.spec === 'string' ? plugin.spec : '';
634
721
  const record = {
635
722
  name: plugin.name,
636
723
  version,
637
724
  kind,
638
- spec: typeof plugin.spec === 'string' ? plugin.spec : '',
725
+ spec: specOf,
639
726
  installSpec,
640
727
  installable: Boolean(installSpec),
641
728
  description: typeof plugin.description === 'string' ? plugin.description : '',
642
729
  homepage: typeof plugin.homepage === 'string' ? plugin.homepage : '',
730
+ // 来源:新清单自带就用它;老清单没这个字段就按现有字段现推一份,
731
+ // 这样别人那台跑旧版插件时,本机看到的来源也不会是空的
732
+ source:
733
+ plugin.source && typeof plugin.source === 'object'
734
+ ? plugin.source
735
+ : describeSource({ name: plugin.name, kind, spec: specOf, version }),
643
736
  features: [feature],
644
737
  mine: isMine || Boolean(plugin.mine),
645
738
  };
@@ -659,6 +752,7 @@ export function collectSharedPlugins({ config, machineId, publishedNames } = {})
659
752
  old.installable = Boolean(old.installSpec);
660
753
  old.description = record.description || old.description;
661
754
  old.homepage = record.homepage || old.homepage;
755
+ old.source = record.source || old.source;
662
756
  } else if (!old.installSpec && record.installSpec) {
663
757
  old.installSpec = record.installSpec;
664
758
  old.installable = true;
@@ -685,6 +779,19 @@ export function writeAggregate({ config, machineId, publishedNames, log } = {})
685
779
  installSpec: plugin.installSpec || '',
686
780
  }));
687
781
 
782
+ /** 来源的人读形式:git 给仓库 URL,npm 给 registry 坐标,local 给路径。 */
783
+ const sourceText = (src) => {
784
+ if (!src || typeof src !== 'object') return '-';
785
+ if (src.type === 'npm') return `npm · ${src.spec || src.package || ''}`;
786
+ if (src.type === 'git') {
787
+ const ref = src.ref ? `#${src.ref}` : '';
788
+ return src.url ? `git · ${src.url}${ref}` : `git · ${src.raw || '-'}`;
789
+ }
790
+ if (src.type === 'local') return `local · ${src.form || ''} ${src.path || ''}`.trim();
791
+ if (src.type === 'installation') return '随桌面端安装';
792
+ return src.type || '-';
793
+ };
794
+
688
795
  const others = items.filter((item) => !item.mine);
689
796
  const installable = others.filter((item) => !item.installed && item.installKind !== 'none');
690
797
 
@@ -732,11 +839,11 @@ export function writeAggregate({ config, machineId, publishedNames, log } = {})
732
839
  '',
733
840
  '## 他人插件',
734
841
  '',
735
- '| 插件 | 版本 | 来源 | 本机 | 安装方式 | 安装坐标 |',
736
- '|---|---|---|---|---|---|',
842
+ '| 插件 | 版本 | 来源 | 出处 | 本机 | 安装方式 | 安装坐标 |',
843
+ '|---|---|---|---|---|---|---|',
737
844
  ...others.map(
738
845
  (item) =>
739
- `| \`${item.name}\` | ${item.version || '-'} | ${item.features.join(', ')} | ${
846
+ `| \`${item.name}\` | ${item.version || '-'} | ${item.features.join(', ')} | ${sourceText(item.source)} | ${
740
847
  item.installed ? `已装 ${item.installedVersion}` : '无'
741
848
  } | ${item.installKind} | ${item.installSpec ? `\`${item.installSpec}\`` : '-'} |`,
742
849
  ),
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "ygc-dsh-sync",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "type": "module",
5
- "description": "DSH 同步插件:会话按工作区落地、按机器隔离的跨机备份与拉取;技能本机↔母版双向同步(含记忆插件技能优化闭环);插件清单共享(特征文件夹,只存清单、只能联网装)。",
5
+ "description": "DSH 同步插件:会话按工作区落地、按机器隔离的跨机备份与拉取;技能本机↔母版双向同步(含记忆插件技能优化闭环);插件清单共享(只存清单与来源,不存源码,只能联网装)。",
6
6
  "icon": "icon.svg",
7
7
  "keywords": [
8
8
  "dsh",