@dsh-workbench/shared 1.0.0-beta.0 → 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.
Files changed (82) hide show
  1. package/dist/components/FormNodes.js +5 -1
  2. package/dist/components/FormRows.css +1 -0
  3. package/dist/components/FormRows.d.ts +4 -2
  4. package/dist/components/FormRows.js +5 -4
  5. package/dist/config/constants.d.ts +0 -2
  6. package/dist/config/constants.js +0 -5
  7. package/dist/contract/command.d.ts +22 -0
  8. package/dist/contract/command.js +1 -0
  9. package/dist/contract/data.d.ts +2 -1
  10. package/dist/contract/extension.d.ts +14 -0
  11. package/dist/contract/extension.js +1 -0
  12. package/dist/contract/git.d.ts +38 -0
  13. package/dist/contract/git.js +1 -0
  14. package/dist/contract/lib.d.ts +4 -0
  15. package/dist/contract/lib.js +1 -0
  16. package/dist/contract/protocol/config-sync.d.ts +1 -21
  17. package/dist/contract/protocol/market-index.d.ts +1 -0
  18. package/dist/contract/protocol/profile-create.d.ts +0 -3
  19. package/dist/host/adapter-set.js +1 -0
  20. package/dist/host/adapters.d.ts +6 -0
  21. package/dist/host/extension.d.ts +7 -0
  22. package/dist/host/extension.js +39 -0
  23. package/dist/host/git.d.ts +2 -52
  24. package/dist/host/git.js +1 -64
  25. package/dist/host/http.js +1 -1
  26. package/dist/host/market-index.js +3 -44
  27. package/dist/host/port-types.d.ts +1 -0
  28. package/dist/host/release-packages.generated.d.ts +1 -0
  29. package/dist/host/release-packages.generated.js +10 -0
  30. package/dist/host/tool-kit.d.ts +0 -6
  31. package/dist/host/tool-kit.js +0 -22
  32. package/dist/host/unit-face.d.ts +7 -0
  33. package/dist/host/version-range.js +7 -67
  34. package/dist/host/version.d.ts +3 -2
  35. package/dist/host/version.js +17 -19
  36. package/dist/host.d.ts +6 -0
  37. package/dist/host.js +6 -0
  38. package/dist/i18n/en.js +3 -1
  39. package/dist/i18n/index.d.ts +9 -21
  40. package/dist/i18n/settings.d.ts +5 -19
  41. package/dist/i18n/settings.js +7 -35
  42. package/dist/i18n/zh.d.ts +8 -20
  43. package/dist/i18n/zh.js +3 -1
  44. package/dist/index.d.ts +4 -0
  45. package/dist/index.js +4 -0
  46. package/dist/lib/version.d.ts +4 -0
  47. package/dist/lib/version.js +21 -0
  48. package/dist/testing/git.d.ts +3 -0
  49. package/dist/testing/git.js +36 -0
  50. package/package.json +6 -4
  51. package/src/components/Form.test.tsx +6 -0
  52. package/src/components/FormNodes.tsx +6 -1
  53. package/src/components/FormRows.css +1 -0
  54. package/src/components/FormRows.test.tsx +30 -0
  55. package/src/components/FormRows.tsx +9 -5
  56. package/src/config/constants.ts +0 -6
  57. package/src/contract/command.ts +25 -0
  58. package/src/contract/data.ts +2 -1
  59. package/src/contract/extension.ts +20 -0
  60. package/src/contract/git.ts +20 -0
  61. package/src/contract/lib.ts +5 -0
  62. package/src/contract/protocol/config-sync.ts +1 -24
  63. package/src/contract/protocol/market-index.ts +1 -0
  64. package/src/contract/protocol/profile-create.ts +0 -3
  65. package/src/host/adapter-set.ts +1 -0
  66. package/src/host/adapters.ts +6 -0
  67. package/src/host/extension.test.ts +55 -0
  68. package/src/host/extension.ts +57 -0
  69. package/src/host/git.ts +6 -92
  70. package/src/host/http.ts +1 -1
  71. package/src/host/market-index.ts +3 -33
  72. package/src/host/port-types.ts +1 -0
  73. package/src/host/release-packages.generated.ts +10 -0
  74. package/src/host/tool-kit.ts +0 -35
  75. package/src/host/unit-face.ts +7 -0
  76. package/src/host/version-range.ts +6 -64
  77. package/src/host/version.ts +18 -18
  78. package/src/host.ts +6 -0
  79. package/src/i18n/en.ts +3 -1
  80. package/src/i18n/settings.ts +7 -35
  81. package/src/i18n/zh.ts +3 -1
  82. package/src/index.ts +4 -0
@@ -1,17 +1,13 @@
1
1
  /** 「工作台设置」这一组的词条:配置同步 / 新建工作台。 */
2
2
  export const zhSettings = {
3
3
  'profileCreate.nav': '新建工作台',
4
- 'profileCreate.lead': '以已有 profile 为模板,建一个独立的新工作台。建好后关掉当前实例,再启动新的。',
4
+ 'profileCreate.lead': '以已有 profile 为模板,建一个独立的新工作台。会话 / 项目 / 附件跟 web 共用,这里只复制设置。建好后关掉当前实例,再启动新的。',
5
5
  'profileCreate.name': '新工作台的名字',
6
6
  'profileCreate.nameHint': '例如 wb2(只用字母、数字、点、下划线或短横线)',
7
7
  'profileCreate.source': '来源 profile',
8
8
  'profileCreate.current': '当前',
9
9
  'profileCreate.copy': '从来源复制',
10
10
  'profileCreate.settings': '设置(模型、主题等)',
11
- 'profileCreate.plugins': '本地插件文件',
12
- 'profileCreate.pluginsRequired': '设置里引用了,不能单独去掉',
13
- 'profileCreate.projects': '项目列表',
14
- 'profileCreate.sessions': '会话记录',
15
11
  'profileCreate.create': '创建',
16
12
  'profileCreate.creating': '正在创建…',
17
13
  'profileCreate.created': '已建好 {profile}。',
@@ -22,25 +18,16 @@ export const zhSettings = {
22
18
  'profileCreate.noProfiles': '这台机器上还没有可用的来源 profile。',
23
19
  'profileCreate.failed': '创建没有成功,看看下面的输出。',
24
20
  'configSync.nav': '配置同步',
25
- 'configSync.lead': '只搬设置行和它们引用的本地文件;不搬项目列表和会话记录 —— 要整套新建工作台用「新建工作台」。',
21
+ 'configSync.lead': '只同步「改已有插件的设置」的行;挂新插件的行不同步。会话 / 项目 / 附件本来就共用,不用同步。要整套新建工作台用「新建工作台」。',
26
22
  'configSync.source': '来源',
27
23
  'configSync.sourceBroken': '读不到它的配置',
28
- 'configSync.sourceRows': '{name} 这份里你有 {count} 行可搬',
24
+ 'configSync.sourceRows': '{name} 这份里你有 {count} 行可同步',
29
25
  'configSync.sourceDead': '读不到',
30
26
  'configSync.refresh': '重新读一遍',
31
27
  'configSync.loading': '正在读来源的配置…',
32
28
  'configSync.readHint': '换个来源,或再读一遍。',
33
29
  'configSync.tag.new': '新增',
34
30
  'configSync.tag.differs': '差异',
35
- 'configSync.tag.asset': '缺文件',
36
- 'configSync.tag.fileDiffers': '文件不同',
37
- 'configSync.assetOnly': '设置内容一样,只差文件。',
38
- 'configSync.assetMissingOne': '还要 1 个文件:{path}',
39
- 'configSync.assetMissingMany': '还要 {count} 个文件:{path}',
40
- 'configSync.assetDiffers': '文件不一样:{path}',
41
- 'configSync.assetDiffersMany': '文件有差异:{path}({count} 个)',
42
- 'configSync.assetExternal': '这个文件搬不过来:{path}({reason})',
43
- 'configSync.assetOverwrite': '覆盖工作台里的同名文件',
44
31
  'configSync.conflictNote': '应用后工作台这份会被来源覆盖',
45
32
  'configSync.code.lang': 'yaml',
46
33
  'configSync.code.copy': '复制',
@@ -51,35 +38,30 @@ export const zhSettings = {
51
38
  'configSync.code.collapseAria': '收起这一行的配置',
52
39
  'configSync.code.expandAria': '展开差异(还有 {count} 行)',
53
40
  'configSync.code.expand': '展开剩余 {count} 行',
54
- 'configSync.missing': '{pkg} 还没装:这一行搬过来也不会生效 —— 先在模块市场里装它',
41
+ 'configSync.missing': '{pkg} 还没装:这一行同步过来也不会生效 —— 先在模块市场里装它',
55
42
  'configSync.apply': '同步选中的 {count} 项',
56
43
  'configSync.applyNone': '请选择同步内容',
57
44
  'configSync.applying': '正在写入…',
58
45
  'configSync.applied': '已写入并检查通过',
59
46
  'configSync.appliedUnverified': '已写入;重启后检查是否生效',
60
- 'configSync.appliedFiles': '复制了 {count} 个文件',
61
47
  'configSync.appliedIds': '这次写进去:{ids}',
62
48
  'configSync.skippedIds': '这几行工作台自己已有,没动:{ids}',
63
49
  'configSync.nothing': '没有需要同步的内容',
64
50
  'configSync.unreadable': '读不到 {path}:{reason}',
65
51
  'configSync.noSource': '这台机器上只有工作台这一个 profile,没有可同步的来源',
66
- 'configSync.emptyProviders': '工作台还没有任何模型供应商 —— 去「配置同步」从某个 profile 把模型供应商那几行搬过来,或自己去「模型」里配。',
52
+ 'configSync.emptyProviders': '工作台还没有任何模型供应商 —— 去「配置同步」从某个 profile 把模型供应商那几行同步过来,或自己去「模型」里配。',
67
53
  'configSync.failed': '同步没成功,再试一次。',
68
54
  };
69
55
  /** 上面那组的中文对照。 */
70
56
  export const enSettings = {
71
57
  'profileCreate.nav': 'New workbench',
72
- 'profileCreate.lead': 'Create a separate workbench from an existing profile. Stop this instance, then start the new one.',
58
+ 'profileCreate.lead': 'Create a separate workbench from an existing profile. Chats, projects and attachments are shared with web; only settings are copied. Stop this instance, then start the new one.',
73
59
  'profileCreate.name': 'New workbench name',
74
60
  'profileCreate.nameHint': 'e.g. wb2 (letters, digits, dot, underscore or dash)',
75
61
  'profileCreate.source': 'Source profile',
76
62
  'profileCreate.current': 'current',
77
63
  'profileCreate.copy': 'Copy from the source',
78
64
  'profileCreate.settings': 'Settings (models, theme, …)',
79
- 'profileCreate.plugins': 'Local plugin files',
80
- 'profileCreate.pluginsRequired': 'Referenced by settings; copied together',
81
- 'profileCreate.projects': 'Project list',
82
- 'profileCreate.sessions': 'Chat history',
83
65
  'profileCreate.create': 'Create',
84
66
  'profileCreate.creating': 'Creating…',
85
67
  'profileCreate.created': 'Created {profile}.',
@@ -90,7 +72,7 @@ export const enSettings = {
90
72
  'profileCreate.noProfiles': 'This machine has no source profile yet.',
91
73
  'profileCreate.failed': 'The workbench was not created; see the output below.',
92
74
  'configSync.nav': 'Config sync',
93
- 'configSync.lead': 'Only setting rows and the local files they reference are copied; project lists and sessions are not. Use “New workbench” to create a full profile.',
75
+ 'configSync.lead': 'Only rows that change settings of plugins the workbench already has are copied. Rows that mount a new plugin are not; chats, projects and attachments are shared already. Use “New workbench” to create a full profile.',
94
76
  'configSync.source': 'Source',
95
77
  'configSync.sourceBroken': 'its config is unreadable',
96
78
  'configSync.sourceRows': '{name}: {count} rows are new or different for the workbench',
@@ -100,15 +82,6 @@ export const enSettings = {
100
82
  'configSync.readHint': 'Pick another source, or read again.',
101
83
  'configSync.tag.new': 'New',
102
84
  'configSync.tag.differs': 'Differs',
103
- 'configSync.tag.asset': 'File missing',
104
- 'configSync.tag.fileDiffers': 'File differs',
105
- 'configSync.assetOnly': 'The setting is the same; only its file is missing.',
106
- 'configSync.assetMissingOne': '1 file is still needed: {path}',
107
- 'configSync.assetMissingMany': '{count} files are still needed: {path}',
108
- 'configSync.assetDiffers': 'The file differs: {path}',
109
- 'configSync.assetDiffersMany': 'The files differ: {path} ({count} files)',
110
- 'configSync.assetExternal': 'This file cannot be copied: {path} ({reason})',
111
- 'configSync.assetOverwrite': 'Overwrite the workbench copy',
112
85
  'configSync.conflictNote': 'Applying replaces this workbench copy with the source',
113
86
  'configSync.code.lang': 'yaml',
114
87
  'configSync.code.copy': 'Copy',
@@ -125,7 +98,6 @@ export const enSettings = {
125
98
  'configSync.applying': 'Writing…',
126
99
  'configSync.applied': 'Written and checked',
127
100
  'configSync.appliedUnverified': 'Written; restart to check whether it took effect',
128
- 'configSync.appliedFiles': 'Copied {count} files',
129
101
  'configSync.appliedIds': 'Written this time: {ids}',
130
102
  'configSync.skippedIds': 'Left alone (already here): {ids}',
131
103
  'configSync.nothing': 'Nothing to sync',
package/dist/i18n/zh.d.ts CHANGED
@@ -275,6 +275,7 @@ export declare const zh: {
275
275
  readonly 'panels.discover.loading': "正在读取索引";
276
276
  readonly 'panels.discover.lead': "从索引源浏览模块;装上就能用。";
277
277
  readonly 'panels.discover.addSource': "添加";
278
+ readonly 'panels.discover.addingSource': "正在添加…";
278
279
  readonly 'panels.discover.addFirstSource': "添加索引源";
279
280
  readonly 'panels.discover.removeSource': "移除";
280
281
  readonly 'panels.discover.removeSourceNamed': "移除 {url}";
@@ -375,7 +376,6 @@ export declare const zh: {
375
376
  readonly 'panels.discover.tabOverview': "概览";
376
377
  readonly 'panels.discover.tabReadme': "说明";
377
378
  readonly 'panels.discover.tabChangelog': "更新日志";
378
- readonly 'panels.discover.docTitle': "{version} 的{kind}";
379
379
  readonly 'panels.discover.docReadme': "说明";
380
380
  readonly 'panels.discover.docChangelog': "更新日志";
381
381
  readonly 'panels.discover.docLoading': "正在读取…";
@@ -612,6 +612,8 @@ export declare const zh: {
612
612
  readonly 'approval.publish.module': "模块";
613
613
  readonly 'approval.publish.target': "目标";
614
614
  readonly 'approval.publish.version': "版本";
615
+ readonly 'approval.publish.author': "作者";
616
+ readonly 'approval.publish.authorMissing': "未署名(提交用本机 git 身份)";
615
617
  readonly 'approval.publish.registry': "源";
616
618
  readonly 'approval.publish.repo': "仓库";
617
619
  readonly 'approval.publish.branch': "分支";
@@ -634,17 +636,13 @@ export declare const zh: {
634
636
  readonly 'markdown.copied': "已复制";
635
637
  readonly 'markdown.footnotes': "脚注";
636
638
  readonly 'profileCreate.nav': "新建工作台";
637
- readonly 'profileCreate.lead': "以已有 profile 为模板,建一个独立的新工作台。建好后关掉当前实例,再启动新的。";
639
+ readonly 'profileCreate.lead': "以已有 profile 为模板,建一个独立的新工作台。会话 / 项目 / 附件跟 web 共用,这里只复制设置。建好后关掉当前实例,再启动新的。";
638
640
  readonly 'profileCreate.name': "新工作台的名字";
639
641
  readonly 'profileCreate.nameHint': "例如 wb2(只用字母、数字、点、下划线或短横线)";
640
642
  readonly 'profileCreate.source': "来源 profile";
641
643
  readonly 'profileCreate.current': "当前";
642
644
  readonly 'profileCreate.copy': "从来源复制";
643
645
  readonly 'profileCreate.settings': "设置(模型、主题等)";
644
- readonly 'profileCreate.plugins': "本地插件文件";
645
- readonly 'profileCreate.pluginsRequired': "设置里引用了,不能单独去掉";
646
- readonly 'profileCreate.projects': "项目列表";
647
- readonly 'profileCreate.sessions': "会话记录";
648
646
  readonly 'profileCreate.create': "创建";
649
647
  readonly 'profileCreate.creating': "正在创建…";
650
648
  readonly 'profileCreate.created': "已建好 {profile}。";
@@ -655,25 +653,16 @@ export declare const zh: {
655
653
  readonly 'profileCreate.noProfiles': "这台机器上还没有可用的来源 profile。";
656
654
  readonly 'profileCreate.failed': "创建没有成功,看看下面的输出。";
657
655
  readonly 'configSync.nav': "配置同步";
658
- readonly 'configSync.lead': "只搬设置行和它们引用的本地文件;不搬项目列表和会话记录 —— 要整套新建工作台用「新建工作台」。";
656
+ readonly 'configSync.lead': "只同步「改已有插件的设置」的行;挂新插件的行不同步。会话 / 项目 / 附件本来就共用,不用同步。要整套新建工作台用「新建工作台」。";
659
657
  readonly 'configSync.source': "来源";
660
658
  readonly 'configSync.sourceBroken': "读不到它的配置";
661
- readonly 'configSync.sourceRows': "{name} 这份里你有 {count} 行可搬";
659
+ readonly 'configSync.sourceRows': "{name} 这份里你有 {count} 行可同步";
662
660
  readonly 'configSync.sourceDead': "读不到";
663
661
  readonly 'configSync.refresh': "重新读一遍";
664
662
  readonly 'configSync.loading': "正在读来源的配置…";
665
663
  readonly 'configSync.readHint': "换个来源,或再读一遍。";
666
664
  readonly 'configSync.tag.new': "新增";
667
665
  readonly 'configSync.tag.differs': "差异";
668
- readonly 'configSync.tag.asset': "缺文件";
669
- readonly 'configSync.tag.fileDiffers': "文件不同";
670
- readonly 'configSync.assetOnly': "设置内容一样,只差文件。";
671
- readonly 'configSync.assetMissingOne': "还要 1 个文件:{path}";
672
- readonly 'configSync.assetMissingMany': "还要 {count} 个文件:{path}";
673
- readonly 'configSync.assetDiffers': "文件不一样:{path}";
674
- readonly 'configSync.assetDiffersMany': "文件有差异:{path}({count} 个)";
675
- readonly 'configSync.assetExternal': "这个文件搬不过来:{path}({reason})";
676
- readonly 'configSync.assetOverwrite': "覆盖工作台里的同名文件";
677
666
  readonly 'configSync.conflictNote': "应用后工作台这份会被来源覆盖";
678
667
  readonly 'configSync.code.lang': "yaml";
679
668
  readonly 'configSync.code.copy': "复制";
@@ -684,19 +673,18 @@ export declare const zh: {
684
673
  readonly 'configSync.code.collapseAria': "收起这一行的配置";
685
674
  readonly 'configSync.code.expandAria': "展开差异(还有 {count} 行)";
686
675
  readonly 'configSync.code.expand': "展开剩余 {count} 行";
687
- readonly 'configSync.missing': "{pkg} 还没装:这一行搬过来也不会生效 —— 先在模块市场里装它";
676
+ readonly 'configSync.missing': "{pkg} 还没装:这一行同步过来也不会生效 —— 先在模块市场里装它";
688
677
  readonly 'configSync.apply': "同步选中的 {count} 项";
689
678
  readonly 'configSync.applyNone': "请选择同步内容";
690
679
  readonly 'configSync.applying': "正在写入…";
691
680
  readonly 'configSync.applied': "已写入并检查通过";
692
681
  readonly 'configSync.appliedUnverified': "已写入;重启后检查是否生效";
693
- readonly 'configSync.appliedFiles': "复制了 {count} 个文件";
694
682
  readonly 'configSync.appliedIds': "这次写进去:{ids}";
695
683
  readonly 'configSync.skippedIds': "这几行工作台自己已有,没动:{ids}";
696
684
  readonly 'configSync.nothing': "没有需要同步的内容";
697
685
  readonly 'configSync.unreadable': "读不到 {path}:{reason}";
698
686
  readonly 'configSync.noSource': "这台机器上只有工作台这一个 profile,没有可同步的来源";
699
- readonly 'configSync.emptyProviders': "工作台还没有任何模型供应商 —— 去「配置同步」从某个 profile 把模型供应商那几行搬过来,或自己去「模型」里配。";
687
+ readonly 'configSync.emptyProviders': "工作台还没有任何模型供应商 —— 去「配置同步」从某个 profile 把模型供应商那几行同步过来,或自己去「模型」里配。";
700
688
  readonly 'configSync.failed': "同步没成功,再试一次。";
701
689
  };
702
690
  /** 中文词表的键类型:`en` 与模块词表都得覆盖同一批键。 */
package/dist/i18n/zh.js CHANGED
@@ -267,6 +267,7 @@ export const zh = {
267
267
  'panels.discover.loading': '正在读取索引',
268
268
  'panels.discover.lead': '从索引源浏览模块;装上就能用。',
269
269
  'panels.discover.addSource': '添加',
270
+ 'panels.discover.addingSource': '正在添加…',
270
271
  'panels.discover.addFirstSource': '添加索引源',
271
272
  'panels.discover.removeSource': '移除',
272
273
  'panels.discover.removeSourceNamed': '移除 {url}',
@@ -367,7 +368,6 @@ export const zh = {
367
368
  'panels.discover.tabOverview': '概览',
368
369
  'panels.discover.tabReadme': '说明',
369
370
  'panels.discover.tabChangelog': '更新日志',
370
- 'panels.discover.docTitle': '{version} 的{kind}',
371
371
  'panels.discover.docReadme': '说明',
372
372
  'panels.discover.docChangelog': '更新日志',
373
373
  'panels.discover.docLoading': '正在读取…',
@@ -604,6 +604,8 @@ export const zh = {
604
604
  'approval.publish.module': '模块',
605
605
  'approval.publish.target': '目标',
606
606
  'approval.publish.version': '版本',
607
+ 'approval.publish.author': '作者',
608
+ 'approval.publish.authorMissing': '未署名(提交用本机 git 身份)',
607
609
  'approval.publish.registry': '源',
608
610
  'approval.publish.repo': '仓库',
609
611
  'approval.publish.branch': '分支',
package/dist/index.d.ts CHANGED
@@ -37,6 +37,10 @@ export * from './config/constants.js';
37
37
  export * from './config/storage.js';
38
38
  export * from './contract/blocker.js';
39
39
  export * from './contract/data.js';
40
+ export * from './contract/extension.js';
41
+ export * from './contract/lib.js';
42
+ export * from './contract/git.js';
43
+ export * from './contract/command.js';
40
44
  export * from './contract/facts.js';
41
45
  export * from './contract/long-task.js';
42
46
  export * from './contract/protocol/bundle-install.js';
package/dist/index.js CHANGED
@@ -37,6 +37,10 @@ export * from './config/constants.js';
37
37
  export * from './config/storage.js';
38
38
  export * from './contract/blocker.js';
39
39
  export * from './contract/data.js';
40
+ export * from './contract/extension.js';
41
+ export * from './contract/lib.js';
42
+ export * from './contract/git.js';
43
+ export * from './contract/command.js';
40
44
  export * from './contract/facts.js';
41
45
  export * from './contract/long-task.js';
42
46
  export * from './contract/protocol/bundle-install.js';
@@ -0,0 +1,4 @@
1
+ export declare function isValidVersion(value: string): boolean;
2
+ export declare function isPrereleaseVersion(value: string): boolean;
3
+ export declare function compareVersions(left: string, right: string): number | undefined;
4
+ export declare function isNewerVersion(latest: string, installed: string): boolean;
@@ -0,0 +1,21 @@
1
+ import semver from 'semver';
2
+ export function isValidVersion(value) {
3
+ return semver.valid(value.trim()) !== null;
4
+ }
5
+ export function isPrereleaseVersion(value) {
6
+ const parsed = semver.valid(value.trim());
7
+ return parsed !== null && semver.prerelease(parsed) !== null;
8
+ }
9
+ export function compareVersions(left, right) {
10
+ const a = semver.valid(left.trim());
11
+ const b = semver.valid(right.trim());
12
+ if (a === null || b === null)
13
+ return undefined;
14
+ return semver.compare(a, b);
15
+ }
16
+ export function isNewerVersion(latest, installed) {
17
+ if (latest === installed)
18
+ return false;
19
+ const compared = compareVersions(latest, installed);
20
+ return compared === undefined ? true : compared > 0;
21
+ }
@@ -0,0 +1,3 @@
1
+ import type { DshShell } from '../api/dsh.js';
2
+ import type { GitReader } from '../contract/git.js';
3
+ export declare function createFakeGit(shell: DshShell): GitReader;
@@ -0,0 +1,36 @@
1
+ import { quoteShellArg, tailLines } from '../host/shell-text.js';
2
+ import { runShell } from '../host/shell.js';
3
+ export function createFakeGit(shell) {
4
+ return {
5
+ async origin(path) {
6
+ const key = path.trim();
7
+ if (!key)
8
+ return { kind: 'unknown', reason: 'no-path' };
9
+ const result = await runShell(shell, `git -C ${quoteShellArg(key)} remote get-url origin`);
10
+ if (result.exitCode !== 0)
11
+ return { kind: 'no-origin', detail: tailLines(result.stderr, 3) };
12
+ return { kind: 'ok', remote: result.stdout.trim() };
13
+ },
14
+ async branch(path) {
15
+ const key = path.trim();
16
+ if (!key)
17
+ return { kind: 'unknown', reason: 'no-path' };
18
+ const result = await runShell(shell, `git -C ${quoteShellArg(key)} rev-parse --abbrev-ref HEAD`);
19
+ const name = result.stdout.trim();
20
+ if (result.exitCode !== 0)
21
+ return { kind: 'no-branch', detail: tailLines(result.stderr, 3) };
22
+ if (name === '' || name === 'HEAD')
23
+ return { kind: 'no-branch', detail: name };
24
+ return { kind: 'ok', branch: name };
25
+ },
26
+ async head(path) {
27
+ const key = path.trim();
28
+ if (!key)
29
+ return { kind: 'unknown', reason: 'no-path' };
30
+ const result = await runShell(shell, `git -C ${quoteShellArg(key)} rev-parse HEAD`);
31
+ if (result.exitCode !== 0)
32
+ return { kind: 'no-head', detail: tailLines(result.stderr, 3) };
33
+ return { kind: 'ok', commit: result.stdout.trim() };
34
+ },
35
+ };
36
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-workbench/shared",
3
- "version": "1.0.0-beta.0",
3
+ "version": "1.0.0",
4
4
  "description": "工作台与模块共享的纯代码与契约类型:类型、纯逻辑、哑组件、平台解析。宿主侧的东西从 /host 进(不影响浏览器产物)。",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -37,13 +37,15 @@
37
37
  "@deepseek-ai/dsh-client-ui-primitives": "0.2.1-rc.2",
38
38
  "@types/node": "^24.19.0",
39
39
  "@types/react": "^19.0.0",
40
+ "@types/semver": "^7.8.0",
40
41
  "@vitest/coverage-v8": "^5.0.2",
41
42
  "react": "^18.0.0",
42
43
  "typescript": "5.9",
43
44
  "vitest": "^5.0.2"
44
45
  },
45
46
  "dependencies": {
46
- "dayjs": "^1.11.23"
47
+ "dayjs": "^1.11.23",
48
+ "semver": "^7.8.5"
47
49
  },
48
50
  "repository": {
49
51
  "type": "git",
@@ -55,12 +57,12 @@
55
57
  "url": "https://github.com/novaeth/dsh-workbench/issues"
56
58
  },
57
59
  "scripts": {
58
- "build": "tsc -p tsconfig.build.json && node scripts/copy-css.mjs",
60
+ "build": "node scripts/gen-release-packages.mjs && tsc -p tsconfig.build.json && node scripts/copy-css.mjs",
59
61
  "typecheck": "tsc --noEmit --incremental --tsBuildInfoFile node_modules/.cache/tsconfig.tsbuildinfo",
60
62
  "test": "vitest run",
61
63
  "coverage": "vitest run --coverage",
62
64
  "barrels": "node scripts/barrels.mjs",
63
- "selfcheck": "node scripts/check-state.mjs",
65
+ "selfcheck": "node scripts/check-state.mjs && node scripts/gen-release-packages.mjs --check",
64
66
  "arch:report": "arch-guard --report-only",
65
67
  "arch": "arch-guard"
66
68
  }
@@ -403,6 +403,12 @@ describe('formRowsOf', () => {
403
403
  expect(propsOf(rows[0])['children']).toBe('env:wb-form-env:true:false');
404
404
  });
405
405
 
406
+ it('★ 标签挂到同一格的控件上(htmlFor 与绑定 id 同一个值)', () => {
407
+ const rows = formRowsOf({ fields: [field('env', 'prod')], problems, showProblem: true, busy: false });
408
+
409
+ expect(propsOf(rows[0])['htmlFor']).toBe('wb-form-env');
410
+ });
411
+
406
412
  it('★ 这一格的 value / onChange 从字段配置原样进绑定(值只写一处)', () => {
407
413
  const onChange = (): void => {};
408
414
  const probe: FormField = {
@@ -193,6 +193,7 @@ export function formRowsOf<Values extends object>(args: {
193
193
  h(FormRow, {
194
194
  key: field.key,
195
195
  label: field.label,
196
+ htmlFor: controlIdOf(field.key),
196
197
  hint: field.hint,
197
198
  problem: args.showProblem ? args.problems[field.key] : undefined,
198
199
  layout: field.layout,
@@ -200,6 +201,10 @@ export function formRowsOf<Values extends object>(args: {
200
201
  );
201
202
  }
202
203
 
204
+ function controlIdOf(key: string): string {
205
+ return `wb-form-${key}`;
206
+ }
207
+
203
208
  function controlOf<K extends string, V, Vals extends object>(
204
209
  field: FormField<K, V, Vals>,
205
210
  problems: Readonly<Record<string, ReactNode | undefined>>,
@@ -207,7 +212,7 @@ function controlOf<K extends string, V, Vals extends object>(
207
212
  busy: boolean,
208
213
  ): ReactNode {
209
214
  return field.control({
210
- id: `wb-form-${field.key}`,
215
+ id: controlIdOf(field.key),
211
216
  name: field.key,
212
217
  invalid: showProblem && Boolean(problems[field.key]),
213
218
  disabled: busy || field.disabled === true,
@@ -29,6 +29,7 @@
29
29
  }
30
30
 
31
31
  .wb-formLabel {
32
+ justify-self: start;
32
33
  min-width: 0;
33
34
  font-size: var(--dsw-font-xs-13-font-size);
34
35
  color: var(--dsw-alias-label-secondary);
@@ -48,6 +48,35 @@ describe('FormRow', () => {
48
48
  expect(childrenOf(node).filter((one) => one !== null)).toHaveLength(2);
49
49
  });
50
50
 
51
+ it('★ 行是 <div>,<label> 只包标签文字 —— 控件、提示与整行空白都不在标签里', () => {
52
+ const node = FormRow({
53
+ label: '项目',
54
+ htmlFor: 'wb-form-project',
55
+ hint: '选错了也能改',
56
+ children: '下拉',
57
+ }) as unknown as { type: unknown; props: { children: unknown[] } };
58
+ const [labelNode, controlNode, hintNode] = node.props.children as {
59
+ type: unknown;
60
+ props: Record<string, unknown>;
61
+ }[];
62
+
63
+ expect(node.type).toBe('div');
64
+ expect(labelNode?.type).toBe('label');
65
+ expect(labelNode?.props['htmlFor']).toBe('wb-form-project');
66
+ expect(texts(labelNode)).toEqual(['项目']);
67
+ expect(controlNode?.props['className']).toBe('wb-formControl');
68
+ expect(hintNode?.props['className']).toBe('wb-formHint');
69
+ });
70
+
71
+ it('没给 htmlFor 就不挂 for(排布照旧,只是点标签不聚焦)', () => {
72
+ const node = FormRow({ label: '项目', children: '下拉' }) as unknown as {
73
+ props: { children: unknown[] };
74
+ };
75
+ const labelNode = node.props.children[0] as { props: Record<string, unknown> };
76
+
77
+ expect(labelNode.props['htmlFor']).toBeUndefined();
78
+ });
79
+
51
80
  it('有 problem 就顶掉 hint(同一格,二选一),错误行挂 data-wb-problem 给聚焦那件找', () => {
52
81
  const node = FormRow({
53
82
  label: '项目',
@@ -150,6 +179,7 @@ describe('标签列:同一个 FormRows 共用一列(ADR-0052 §11)', () =>
150
179
 
151
180
  it('★ 标签不折行;stacked 还是单列、标签照常换行', () => {
152
181
  expect(declarations('.wb-formLabel')).toContain('white-space: nowrap');
182
+ expect(declarations('.wb-formLabel')).toContain('justify-self: start');
153
183
  expect(declarations(".wb-formRow[data-layout='stacked'] .wb-formLabel")).toContain(
154
184
  'white-space: normal',
155
185
  );
@@ -23,6 +23,7 @@ export function FormRows({ children }: FormRowsProps) {
23
23
  */
24
24
  export interface FormRowProps {
25
25
  readonly label: ReactNode;
26
+ readonly htmlFor?: string | undefined;
26
27
  readonly hint?: ReactNode | undefined;
27
28
  readonly problem?: ReactNode | undefined;
28
29
  readonly layout?: 'inline' | 'stacked' | undefined;
@@ -32,20 +33,23 @@ export interface FormRowProps {
32
33
  /**
33
34
  * 表单一行:左边标签、右边控件,提示与错误占控件下面那一格。
34
35
  * @param props.label - 这一行的标签;进了这一行,控件自己就别再给 `label`,否则会多一个标签。
36
+ * @param props.htmlFor - 标签挂到哪个控件(同一个 `id`);不给就只是排布,点标签不聚焦 —— `Form` 会自己接好。
35
37
  * @param props.hint - 控件下的说明;给了 `problem` 就不画它 —— 同一格不叠两段话。
36
38
  * @param props.problem - 校验失败那句话;给了就顶掉 `hint` 并给整行加上出错标记。
37
39
  * @param props.layout - `stacked` 才是标签在上、控件在下(默认 `inline`:标签在左)。
38
40
  * @param props.children - 控件本体。
39
- * @returns 一个 `<label>`。
41
+ * @returns 一个 `<div>`:里面的 `<label>` 只包标签文字,不包控件、提示与整行的空白。
40
42
  */
41
- export function FormRow({ label, hint, problem, layout = 'inline', children }: FormRowProps) {
43
+ export function FormRow({ label, htmlFor, hint, problem, layout = 'inline', children }: FormRowProps) {
42
44
  return (
43
- <label
45
+ <div
44
46
  className="wb-formRow"
45
47
  data-layout={layout}
46
48
  data-wb-problem={problem === undefined ? undefined : ''}
47
49
  >
48
- <span className="wb-formLabel">{label}</span>
50
+ <label className="wb-formLabel" htmlFor={htmlFor}>
51
+ {label}
52
+ </label>
49
53
  <span className="wb-formControl">{children}</span>
50
54
  {problem === undefined ? (
51
55
  hint === undefined ? null : (
@@ -54,6 +58,6 @@ export function FormRow({ label, hint, problem, layout = 'inline', children }: F
54
58
  ) : (
55
59
  <span className="wb-formProblem">{problem}</span>
56
60
  )}
57
- </label>
61
+ </div>
58
62
  );
59
63
  }
@@ -1,12 +1,6 @@
1
1
  /** 外壳插件的包名:浏览器模块表与宿主按它注册、认领这枚插件。 */
2
2
  export const PLUGIN_ID = "@dsh-workbench/core";
3
3
 
4
- /** 与外壳同批发布的兄弟包名:`release.ts` 用它判一版三包是否都已在源上。 */
5
- export const RELEASE_SIBLINGS = [
6
- "@dsh-workbench/shared",
7
- "@dsh-workbench/module-tools",
8
- ] as const;
9
-
10
4
  /** 外壳宿主 HTTP API 的根前缀;prefs / actions / provision 等路由都挂在它下面。 */
11
5
  export const API_PREFIX = "/workbench/api";
12
6
 
@@ -0,0 +1,25 @@
1
+ export interface CommandFile {
2
+ readonly content: string;
3
+ readonly suffix?: string | undefined;
4
+ }
5
+
6
+ export interface CommandRequest {
7
+ readonly argv: readonly string[];
8
+ readonly timeoutMs?: number | undefined;
9
+ readonly cwd?: string | undefined;
10
+ readonly files?: readonly CommandFile[] | undefined;
11
+ readonly appendFileArgs?: string | undefined;
12
+ }
13
+
14
+ export interface CommandResult {
15
+ readonly stdout: string;
16
+ readonly stderr: string;
17
+ readonly exitCode: number;
18
+ readonly timedOut: boolean;
19
+ readonly filePaths: readonly string[];
20
+ readonly error?: string | undefined;
21
+ }
22
+
23
+ export interface CommandReader {
24
+ run(request: CommandRequest): Promise<CommandResult>;
25
+ }
@@ -19,7 +19,8 @@ export interface BrowserDataRequest extends DataRequestBase {
19
19
 
20
20
  export interface CommandDataRequest extends DataRequestBase {
21
21
  readonly kind: 'command';
22
- readonly command: string;
22
+ readonly adapter: string;
23
+ readonly argv: readonly string[];
23
24
  }
24
25
 
25
26
  export type DataRequest = HttpDataRequest | BrowserDataRequest | CommandDataRequest;
@@ -0,0 +1,20 @@
1
+ export type ExtensionSession = 'persistent' | 'ephemeral' | 'none';
2
+
3
+ export interface ExtensionCallOptions {
4
+ readonly key?: string;
5
+ readonly workdir?: string;
6
+ readonly session?: ExtensionSession;
7
+ readonly timeoutMs?: number;
8
+ }
9
+
10
+ export interface ExtensionValue<T = unknown> {
11
+ readonly value: T;
12
+ readonly at: number;
13
+ }
14
+
15
+ export interface ExtensionCaller {
16
+ json(
17
+ args: readonly string[],
18
+ options?: ExtensionCallOptions,
19
+ ): Promise<ExtensionValue>;
20
+ }
@@ -0,0 +1,20 @@
1
+ export type OriginRead =
2
+ | { readonly kind: 'ok'; readonly remote: string }
3
+ | { readonly kind: 'no-origin'; readonly detail?: string }
4
+ | { readonly kind: 'unknown'; readonly reason: string; readonly detail?: string };
5
+
6
+ export type BranchRead =
7
+ | { readonly kind: 'ok'; readonly branch: string }
8
+ | { readonly kind: 'no-branch'; readonly detail?: string | undefined }
9
+ | { readonly kind: 'unknown'; readonly reason: string; readonly detail?: string | undefined };
10
+
11
+ export type HeadRead =
12
+ | { readonly kind: 'ok'; readonly commit: string }
13
+ | { readonly kind: 'no-head'; readonly detail?: string }
14
+ | { readonly kind: 'unknown'; readonly reason: string; readonly detail?: string };
15
+
16
+ export interface GitReader {
17
+ origin(path: string): Promise<OriginRead>;
18
+ branch(path: string): Promise<BranchRead>;
19
+ head(path: string): Promise<HeadRead>;
20
+ }
@@ -0,0 +1,5 @@
1
+ import type { LibNeed } from './needs.js';
2
+
3
+ export interface LibReader {
4
+ load<T = unknown>(need: LibNeed, entry: string): Promise<T>;
5
+ }