@dsh-workbench/shared 0.1.18 → 0.2.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.
- package/dist/api/unit.d.ts +1 -0
- package/dist/api/unit.js +3 -1
- package/dist/api/workbench-contract.d.ts +1 -1
- package/dist/components/CardBoundary.d.ts +1 -0
- package/dist/components/CardBoundary.js +3 -1
- package/dist/components/board-module.d.ts +1 -1
- package/dist/components/icons.d.ts +2 -0
- package/dist/components/icons.js +14 -0
- package/dist/config/constants.d.ts +3 -0
- package/dist/config/constants.js +3 -0
- package/dist/host/http.d.ts +2 -0
- package/dist/host/http.js +31 -1
- package/dist/host/provision.d.ts +1 -1
- package/dist/host/provision.js +5 -0
- package/dist/host/tool-approval.d.ts +5 -0
- package/dist/host/tool-approval.js +47 -9
- package/dist/host/unit-face.d.ts +1 -0
- package/dist/host.d.ts +1 -0
- package/dist/host.js +1 -0
- package/dist/i18n/en.js +30 -13
- package/dist/i18n/index.d.ts +31 -14
- package/dist/i18n/zh.d.ts +30 -13
- package/dist/i18n/zh.js +30 -13
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/panel-state.d.ts +60 -0
- package/dist/lib/panel-state.js +146 -0
- package/package.json +1 -1
- package/src/api/__tests__/unit.test.ts +38 -0
- package/src/api/unit.ts +4 -0
- package/src/api/workbench-contract.ts +2 -1
- package/src/components/CardBoundary.tsx +4 -1
- package/src/components/board-module.ts +1 -1
- package/src/components/icons.tsx +38 -0
- package/src/config/constants.ts +6 -0
- package/src/host/__tests__/provision.test.ts +13 -0
- package/src/host/http.ts +30 -1
- package/src/host/provision.ts +3 -0
- package/src/host/tool-approval.ts +54 -8
- package/src/host/unit-face.ts +1 -0
- package/src/host.ts +1 -0
- package/src/i18n/en.ts +31 -13
- package/src/i18n/zh.ts +31 -13
- package/src/index.ts +1 -0
- package/src/lib/panel-state.test.ts +123 -0
- package/src/lib/panel-state.ts +209 -0
package/dist/i18n/zh.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export declare const zh: {
|
|
|
7
7
|
readonly 'project.none': "未选择项目";
|
|
8
8
|
readonly 'project.empty': "还没有项目。用下面的「添加项目」选一个目录。";
|
|
9
9
|
readonly 'project.add': "添加项目…";
|
|
10
|
+
readonly 'project.add.busy': "正在新建工作区…";
|
|
11
|
+
readonly 'project.add.failed': "新建工作区没成功";
|
|
10
12
|
readonly 'project.search.label': "搜索工作区";
|
|
11
13
|
readonly 'project.search.placeholder': "搜索工作区(目录名 / 中文名 / 路径)";
|
|
12
14
|
readonly 'project.search.empty': "没有匹配「{query}」的工作区。";
|
|
@@ -40,13 +42,22 @@ export declare const zh: {
|
|
|
40
42
|
readonly 'time.hours': "{n} 小时前";
|
|
41
43
|
readonly 'time.days': "{n} 天前";
|
|
42
44
|
readonly 'panels.title': "面板模块";
|
|
43
|
-
readonly 'panels.
|
|
45
|
+
readonly 'panels.groupBoardless': "无看板模块";
|
|
46
|
+
readonly 'panels.groupPlugins': "opencli 插件";
|
|
47
|
+
readonly 'panels.market.defaultHint': "打开后所有工作区默认开启,也可在工作区面板配置中单独关闭";
|
|
48
|
+
readonly 'panels.market.defaultLabel': "全局默认开启";
|
|
49
|
+
readonly 'panels.market.defaultLocked': "这份现在用不了,先更新或卸载";
|
|
50
|
+
readonly 'panels.followAll': "全部恢复默认";
|
|
51
|
+
readonly 'panels.followAllConfirmTitle': "全部恢复默认?";
|
|
52
|
+
readonly 'panels.followAllConfirmDetail': "这个工作区里改过的每一行都会清掉,之后都跟随全局。";
|
|
53
|
+
readonly 'panels.followAllConfirmAck': "我明白:清掉之后,想再按这个工作区配,得一行一行重新改。";
|
|
54
|
+
readonly 'panels.followAllConfirmYes': "恢复默认";
|
|
55
|
+
readonly 'panels.followAllConfirmNo': "取消";
|
|
44
56
|
readonly 'panels.enabled': "这个项目已启用";
|
|
45
57
|
readonly 'panels.disabled': "这个项目未启用";
|
|
46
58
|
readonly 'panels.choose': "选择模块";
|
|
47
59
|
readonly 'panels.buildModule': "造一个模块";
|
|
48
60
|
readonly 'panels.failed': "改配置失败了,再试一次。";
|
|
49
|
-
readonly 'panels.hintEffect': "停用 = agent 拿不到相关数据";
|
|
50
61
|
readonly 'panels.dragHint': "拖动调整顺序";
|
|
51
62
|
readonly 'panels.unknownStep': "这一层没有套件提供";
|
|
52
63
|
readonly 'panels.labelWithHint': "{label}({hint})";
|
|
@@ -60,7 +71,9 @@ export declare const zh: {
|
|
|
60
71
|
readonly 'panels.noneInstalled': "这些模块要的插件都还没装";
|
|
61
72
|
readonly 'panels.noneInstalledHint': "装好之后它们会回到这里,再由你决定放哪几个上板。";
|
|
62
73
|
readonly 'panels.market.installed': "已安装";
|
|
63
|
-
readonly 'panels.market.
|
|
74
|
+
readonly 'panels.market.notActive': "未生效";
|
|
75
|
+
readonly 'panels.market.notUsable': "不可用";
|
|
76
|
+
readonly 'panels.market.broken': "装坏了";
|
|
64
77
|
readonly 'panels.market.hasNew': "有新版本";
|
|
65
78
|
readonly 'panels.market.missing': "未装";
|
|
66
79
|
readonly 'panels.market.unchecked': "状态未知";
|
|
@@ -68,7 +81,7 @@ export declare const zh: {
|
|
|
68
81
|
readonly 'panels.market.sectionInstalled': "已安装模块";
|
|
69
82
|
readonly 'panels.market.sectionPending': "未安装";
|
|
70
83
|
readonly 'panels.market.sectionShared': "共用依赖";
|
|
71
|
-
readonly 'panels.market.sharedUsedBy': "供 {modules}
|
|
84
|
+
readonly 'panels.market.sharedUsedBy': "供 {modules} 模块使用";
|
|
72
85
|
readonly 'panels.market.listSeparator': "、";
|
|
73
86
|
readonly 'panels.market.sharedMissing': "未装";
|
|
74
87
|
readonly 'panels.market.sharedOutdated': "有新版";
|
|
@@ -90,22 +103,24 @@ export declare const zh: {
|
|
|
90
103
|
readonly 'panels.market.confirmNo': "取消";
|
|
91
104
|
readonly 'panels.market.failed': "没成功:{reason}";
|
|
92
105
|
readonly 'panels.market.needEnv': "先在启动工作台的终端里设 {env}";
|
|
106
|
+
readonly 'panels.market.needCommands': "缺这几条命令:{commands}";
|
|
107
|
+
readonly 'panels.market.localMark': "本地";
|
|
108
|
+
readonly 'panels.market.localHint': "本机那份(不从源上取)。";
|
|
93
109
|
readonly 'panels.market.noSource': "这个模块没声明怎么装,只能自己动手";
|
|
94
110
|
readonly 'panels.market.badSource': "安装方式没写全,只能自己动手";
|
|
95
111
|
readonly 'panels.market.empty': "暂无可用模块";
|
|
96
|
-
readonly 'panels.market.sectionLocal': "本地模块";
|
|
97
|
-
readonly 'panels.market.sectionUnlisted': "不是从市场装的";
|
|
98
112
|
readonly 'panels.market.sectionPlugins': "已安装 opencli 插件";
|
|
99
113
|
readonly 'panels.market.moduleOutdated': "有新版";
|
|
100
|
-
readonly 'panels.market.moduleRefused': "
|
|
114
|
+
readonly 'panels.market.moduleRefused': "模块版本与工作台不匹配,暂时无法使用";
|
|
101
115
|
readonly 'panels.market.moduleRefusedDetail': "装不上的原因:{reason}";
|
|
102
|
-
readonly 'panels.market.moduleRestartHint': "
|
|
116
|
+
readonly 'panels.market.moduleRestartHint': "重启后生效";
|
|
117
|
+
readonly 'panels.market.workbenchName': "工作台";
|
|
118
|
+
readonly 'panels.market.workbenchLocal': "这份是本仓正在开发的那一份,不从源上取。";
|
|
103
119
|
readonly 'panels.market.pluginKeptInUse': "文件先留着:{names} 还要用它。";
|
|
104
120
|
readonly 'panels.market.pluginKeptUnsure': "文件先留着:这台机器上还有模块看不出用了什么,说不准谁在用它。";
|
|
105
121
|
readonly 'panels.market.pluginKeptHeld': "文件留着:你自己装的那一份,模块放手不动它。";
|
|
106
122
|
readonly 'panels.market.pluginKeptUnnamed': "文件先留着:看不出这是哪个插件,说不准谁在用它。";
|
|
107
|
-
readonly 'panels.market.
|
|
108
|
-
readonly 'panels.market.pluginBroken': "这份装坏了:看不出它是个插件。可以卸载,或者去上面按包名再装一次。";
|
|
123
|
+
readonly 'panels.market.pluginBroken': "这份装坏了:看不出它是个插件。可以卸载,或者直接跟 agent 说:用 workbench_modules 的 install-bundle 按包名再装一次。";
|
|
109
124
|
readonly 'panels.market.restartNeeded': "新版装好了,重启一次才生效。";
|
|
110
125
|
readonly 'panels.market.foot': "装好后到那个项目的面板页勾选它,模块才会出现。";
|
|
111
126
|
readonly 'panels.market.recheck': "重新检查更新";
|
|
@@ -114,7 +129,7 @@ export declare const zh: {
|
|
|
114
129
|
readonly 'panels.market.justNow': "刚刚";
|
|
115
130
|
readonly 'panels.market.minutesAgo': "{n} 分钟前";
|
|
116
131
|
readonly 'panels.market.hoursAgo': "{n} 小时前";
|
|
117
|
-
readonly 'panels.market.add.description': "
|
|
132
|
+
readonly 'panels.market.add.description': "输入模块或 opencli 插件的包名、Git 仓库地址或本地目录路径。";
|
|
118
133
|
readonly 'panels.market.add.specPlaceholder': "例如 @dsh-workbench/module-hello";
|
|
119
134
|
readonly 'panels.market.add.guideToggle': "模块安装引导和示例";
|
|
120
135
|
readonly 'panels.market.add.guideHide': "收起引导";
|
|
@@ -153,11 +168,13 @@ export declare const zh: {
|
|
|
153
168
|
readonly 'panels.market.add.problemInstalled': "该模块已安装";
|
|
154
169
|
readonly 'panels.market.add.problemNotFound': "未找到相关模块";
|
|
155
170
|
readonly 'panels.market.add.problemNotPackage': "该路径不存在或不是有效的模块包";
|
|
156
|
-
readonly 'panels.market.add.problemNotBundle': "这个包装不进这里:{reason}
|
|
171
|
+
readonly 'panels.market.add.problemNotBundle': "这个包装不进这里:{reason}。这个入口按包名认工作台模块和 opencli 插件,两种都能装;但 git 地址和 tarball 判断不出它是什么 —— 要是它其实是这两样之一,请改填包名或本地目录。";
|
|
157
172
|
readonly 'panels.market.add.problemNotWorkbenchModule': "这个包跟现在的工作台对不上,装上也用不了。换一个版本,或者找这个模块的作者。";
|
|
158
173
|
readonly 'panels.market.add.problemNotModuleOrPlugin': "这个包既不是工作台的模块,也不是 opencli 的插件,装了也用不上。换一个版本,或者找这个包的作者。";
|
|
159
174
|
readonly 'panels.market.add.failureNotModuleOrPlugin': "这个包既不是工作台的模块,也不是 opencli 的插件,装了也用不上";
|
|
160
175
|
readonly 'panels.market.add.installedPlugin': "装上了:opencli 插件 {name}";
|
|
176
|
+
readonly 'panels.market.add.pluginPreview': "这是一份 opencli 插件:装进 opencli 的插件区,装完就能用,不用重启。";
|
|
177
|
+
readonly 'panels.market.add.pluginAlias': "插件名:{name}";
|
|
161
178
|
readonly 'panels.market.add.problemNetwork': "无法连接模块源,请检查网络后重试";
|
|
162
179
|
readonly 'panels.market.add.problemUnknown': "无法获取模块信息:{reason}";
|
|
163
180
|
readonly 'panels.market.add.failureNetwork': "网络连接失败";
|
|
@@ -188,7 +205,7 @@ export declare const zh: {
|
|
|
188
205
|
readonly 'panels.market.add.registryPnpm': "pnpm 自己配的源";
|
|
189
206
|
readonly 'panels.market.add.check': "预检";
|
|
190
207
|
readonly 'panels.market.add.checking': "正在预检…";
|
|
191
|
-
readonly 'panels.market.add.classifying': "
|
|
208
|
+
readonly 'panels.market.add.classifying': "检测中…";
|
|
192
209
|
readonly 'panels.market.add.change': "改一下";
|
|
193
210
|
readonly 'panels.market.add.previewTitle': "将要安装";
|
|
194
211
|
readonly 'panels.market.add.fieldName': "包名";
|
package/dist/i18n/zh.js
CHANGED
|
@@ -7,6 +7,8 @@ export const zh = {
|
|
|
7
7
|
'project.none': '未选择项目',
|
|
8
8
|
'project.empty': '还没有项目。用下面的「添加项目」选一个目录。',
|
|
9
9
|
'project.add': '添加项目…',
|
|
10
|
+
'project.add.busy': '正在新建工作区…',
|
|
11
|
+
'project.add.failed': '新建工作区没成功',
|
|
10
12
|
'project.search.label': '搜索工作区',
|
|
11
13
|
'project.search.placeholder': '搜索工作区(目录名 / 中文名 / 路径)',
|
|
12
14
|
'project.search.empty': '没有匹配「{query}」的工作区。',
|
|
@@ -40,13 +42,22 @@ export const zh = {
|
|
|
40
42
|
'time.hours': '{n} 小时前',
|
|
41
43
|
'time.days': '{n} 天前',
|
|
42
44
|
'panels.title': '面板模块',
|
|
43
|
-
'panels.
|
|
45
|
+
'panels.groupBoardless': '无看板模块',
|
|
46
|
+
'panels.groupPlugins': 'opencli 插件',
|
|
47
|
+
'panels.market.defaultHint': '打开后所有工作区默认开启,也可在工作区面板配置中单独关闭',
|
|
48
|
+
'panels.market.defaultLabel': '全局默认开启',
|
|
49
|
+
'panels.market.defaultLocked': '这份现在用不了,先更新或卸载',
|
|
50
|
+
'panels.followAll': '全部恢复默认',
|
|
51
|
+
'panels.followAllConfirmTitle': '全部恢复默认?',
|
|
52
|
+
'panels.followAllConfirmDetail': '这个工作区里改过的每一行都会清掉,之后都跟随全局。',
|
|
53
|
+
'panels.followAllConfirmAck': '我明白:清掉之后,想再按这个工作区配,得一行一行重新改。',
|
|
54
|
+
'panels.followAllConfirmYes': '恢复默认',
|
|
55
|
+
'panels.followAllConfirmNo': '取消',
|
|
44
56
|
'panels.enabled': '这个项目已启用',
|
|
45
57
|
'panels.disabled': '这个项目未启用',
|
|
46
58
|
'panels.choose': '选择模块',
|
|
47
59
|
'panels.buildModule': '造一个模块',
|
|
48
60
|
'panels.failed': '改配置失败了,再试一次。',
|
|
49
|
-
'panels.hintEffect': '停用 = agent 拿不到相关数据',
|
|
50
61
|
'panels.dragHint': '拖动调整顺序',
|
|
51
62
|
'panels.unknownStep': '这一层没有套件提供',
|
|
52
63
|
'panels.labelWithHint': '{label}({hint})',
|
|
@@ -60,7 +71,9 @@ export const zh = {
|
|
|
60
71
|
'panels.noneInstalled': '这些模块要的插件都还没装',
|
|
61
72
|
'panels.noneInstalledHint': '装好之后它们会回到这里,再由你决定放哪几个上板。',
|
|
62
73
|
'panels.market.installed': '已安装',
|
|
63
|
-
'panels.market.
|
|
74
|
+
'panels.market.notActive': '未生效',
|
|
75
|
+
'panels.market.notUsable': '不可用',
|
|
76
|
+
'panels.market.broken': '装坏了',
|
|
64
77
|
'panels.market.hasNew': '有新版本',
|
|
65
78
|
'panels.market.missing': '未装',
|
|
66
79
|
'panels.market.unchecked': '状态未知',
|
|
@@ -68,7 +81,7 @@ export const zh = {
|
|
|
68
81
|
'panels.market.sectionInstalled': '已安装模块',
|
|
69
82
|
'panels.market.sectionPending': '未安装',
|
|
70
83
|
'panels.market.sectionShared': '共用依赖',
|
|
71
|
-
'panels.market.sharedUsedBy': '供 {modules}
|
|
84
|
+
'panels.market.sharedUsedBy': '供 {modules} 模块使用',
|
|
72
85
|
'panels.market.listSeparator': '、',
|
|
73
86
|
'panels.market.sharedMissing': '未装',
|
|
74
87
|
'panels.market.sharedOutdated': '有新版',
|
|
@@ -90,22 +103,24 @@ export const zh = {
|
|
|
90
103
|
'panels.market.confirmNo': '取消',
|
|
91
104
|
'panels.market.failed': '没成功:{reason}',
|
|
92
105
|
'panels.market.needEnv': '先在启动工作台的终端里设 {env}',
|
|
106
|
+
'panels.market.needCommands': '缺这几条命令:{commands}',
|
|
107
|
+
'panels.market.localMark': '本地',
|
|
108
|
+
'panels.market.localHint': '本机那份(不从源上取)。',
|
|
93
109
|
'panels.market.noSource': '这个模块没声明怎么装,只能自己动手',
|
|
94
110
|
'panels.market.badSource': '安装方式没写全,只能自己动手',
|
|
95
111
|
'panels.market.empty': '暂无可用模块',
|
|
96
|
-
'panels.market.sectionLocal': '本地模块',
|
|
97
|
-
'panels.market.sectionUnlisted': '不是从市场装的',
|
|
98
112
|
'panels.market.sectionPlugins': '已安装 opencli 插件',
|
|
99
113
|
'panels.market.moduleOutdated': '有新版',
|
|
100
|
-
'panels.market.moduleRefused': '
|
|
114
|
+
'panels.market.moduleRefused': '模块版本与工作台不匹配,暂时无法使用',
|
|
101
115
|
'panels.market.moduleRefusedDetail': '装不上的原因:{reason}',
|
|
102
|
-
'panels.market.moduleRestartHint': '
|
|
116
|
+
'panels.market.moduleRestartHint': '重启后生效',
|
|
117
|
+
'panels.market.workbenchName': '工作台',
|
|
118
|
+
'panels.market.workbenchLocal': '这份是本仓正在开发的那一份,不从源上取。',
|
|
103
119
|
'panels.market.pluginKeptInUse': '文件先留着:{names} 还要用它。',
|
|
104
120
|
'panels.market.pluginKeptUnsure': '文件先留着:这台机器上还有模块看不出用了什么,说不准谁在用它。',
|
|
105
121
|
'panels.market.pluginKeptHeld': '文件留着:你自己装的那一份,模块放手不动它。',
|
|
106
122
|
'panels.market.pluginKeptUnnamed': '文件先留着:看不出这是哪个插件,说不准谁在用它。',
|
|
107
|
-
'panels.market.
|
|
108
|
-
'panels.market.pluginBroken': '这份装坏了:看不出它是个插件。可以卸载,或者去上面按包名再装一次。',
|
|
123
|
+
'panels.market.pluginBroken': '这份装坏了:看不出它是个插件。可以卸载,或者直接跟 agent 说:用 workbench_modules 的 install-bundle 按包名再装一次。',
|
|
109
124
|
'panels.market.restartNeeded': '新版装好了,重启一次才生效。',
|
|
110
125
|
'panels.market.foot': '装好后到那个项目的面板页勾选它,模块才会出现。',
|
|
111
126
|
'panels.market.recheck': '重新检查更新',
|
|
@@ -114,7 +129,7 @@ export const zh = {
|
|
|
114
129
|
'panels.market.justNow': '刚刚',
|
|
115
130
|
'panels.market.minutesAgo': '{n} 分钟前',
|
|
116
131
|
'panels.market.hoursAgo': '{n} 小时前',
|
|
117
|
-
'panels.market.add.description': '
|
|
132
|
+
'panels.market.add.description': '输入模块或 opencli 插件的包名、Git 仓库地址或本地目录路径。',
|
|
118
133
|
'panels.market.add.specPlaceholder': '例如 @dsh-workbench/module-hello',
|
|
119
134
|
'panels.market.add.guideToggle': '模块安装引导和示例',
|
|
120
135
|
'panels.market.add.guideHide': '收起引导',
|
|
@@ -153,11 +168,13 @@ export const zh = {
|
|
|
153
168
|
'panels.market.add.problemInstalled': '该模块已安装',
|
|
154
169
|
'panels.market.add.problemNotFound': '未找到相关模块',
|
|
155
170
|
'panels.market.add.problemNotPackage': '该路径不存在或不是有效的模块包',
|
|
156
|
-
'panels.market.add.problemNotBundle': '这个包装不进这里:{reason}
|
|
171
|
+
'panels.market.add.problemNotBundle': '这个包装不进这里:{reason}。这个入口按包名认工作台模块和 opencli 插件,两种都能装;但 git 地址和 tarball 判断不出它是什么 —— 要是它其实是这两样之一,请改填包名或本地目录。',
|
|
157
172
|
'panels.market.add.problemNotWorkbenchModule': '这个包跟现在的工作台对不上,装上也用不了。换一个版本,或者找这个模块的作者。',
|
|
158
173
|
'panels.market.add.problemNotModuleOrPlugin': '这个包既不是工作台的模块,也不是 opencli 的插件,装了也用不上。换一个版本,或者找这个包的作者。',
|
|
159
174
|
'panels.market.add.failureNotModuleOrPlugin': '这个包既不是工作台的模块,也不是 opencli 的插件,装了也用不上',
|
|
160
175
|
'panels.market.add.installedPlugin': '装上了:opencli 插件 {name}',
|
|
176
|
+
'panels.market.add.pluginPreview': '这是一份 opencli 插件:装进 opencli 的插件区,装完就能用,不用重启。',
|
|
177
|
+
'panels.market.add.pluginAlias': '插件名:{name}',
|
|
161
178
|
'panels.market.add.problemNetwork': '无法连接模块源,请检查网络后重试',
|
|
162
179
|
'panels.market.add.problemUnknown': '无法获取模块信息:{reason}',
|
|
163
180
|
'panels.market.add.failureNetwork': '网络连接失败',
|
|
@@ -188,7 +205,7 @@ export const zh = {
|
|
|
188
205
|
'panels.market.add.registryPnpm': 'pnpm 自己配的源',
|
|
189
206
|
'panels.market.add.check': '预检',
|
|
190
207
|
'panels.market.add.checking': '正在预检…',
|
|
191
|
-
'panels.market.add.classifying': '
|
|
208
|
+
'panels.market.add.classifying': '检测中…',
|
|
192
209
|
'panels.market.add.change': '改一下',
|
|
193
210
|
'panels.market.add.previewTitle': '将要安装',
|
|
194
211
|
'panels.market.add.fieldName': '包名',
|
package/dist/index.d.ts
CHANGED
|
@@ -40,6 +40,7 @@ export * from './i18n/merge.js';
|
|
|
40
40
|
export * from './i18n/zh.js';
|
|
41
41
|
export * from './lib/blocker-action.js';
|
|
42
42
|
export * from './lib/busy.js';
|
|
43
|
+
export * from './lib/panel-state.js';
|
|
43
44
|
export * from './lib/react.js';
|
|
44
45
|
export * from './lib/text.js';
|
|
45
46
|
export * from './lib/ui.js';
|
package/dist/index.js
CHANGED
|
@@ -40,6 +40,7 @@ export * from './i18n/merge.js';
|
|
|
40
40
|
export * from './i18n/zh.js';
|
|
41
41
|
export * from './lib/blocker-action.js';
|
|
42
42
|
export * from './lib/busy.js';
|
|
43
|
+
export * from './lib/panel-state.js';
|
|
43
44
|
export * from './lib/react.js';
|
|
44
45
|
export * from './lib/text.js';
|
|
45
46
|
export * from './lib/ui.js';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export type PanelCardPhase = 'idle' | 'loading' | 'ready' | 'error';
|
|
2
|
+
export interface PanelCardError {
|
|
3
|
+
readonly code: string;
|
|
4
|
+
readonly message: string;
|
|
5
|
+
}
|
|
6
|
+
export interface PanelCardRead {
|
|
7
|
+
readonly unit: string;
|
|
8
|
+
readonly route: string;
|
|
9
|
+
readonly phase: PanelCardPhase;
|
|
10
|
+
readonly at: number;
|
|
11
|
+
readonly error?: PanelCardError;
|
|
12
|
+
}
|
|
13
|
+
export interface PanelCardCrash {
|
|
14
|
+
readonly unit: string;
|
|
15
|
+
readonly reason: string;
|
|
16
|
+
readonly at: number;
|
|
17
|
+
readonly count: number;
|
|
18
|
+
}
|
|
19
|
+
export interface PanelUnitReport {
|
|
20
|
+
readonly unit: string;
|
|
21
|
+
readonly at: number;
|
|
22
|
+
readonly okAt: number | null;
|
|
23
|
+
readonly cards: readonly PanelCardRead[];
|
|
24
|
+
readonly crash?: PanelCardCrash;
|
|
25
|
+
}
|
|
26
|
+
export interface PanelWorkspaceReport {
|
|
27
|
+
readonly workspace: string | null;
|
|
28
|
+
readonly at: number;
|
|
29
|
+
readonly units: readonly PanelUnitReport[];
|
|
30
|
+
}
|
|
31
|
+
export interface PanelCardsReport {
|
|
32
|
+
readonly workspace: string;
|
|
33
|
+
readonly cards: readonly PanelCardRead[];
|
|
34
|
+
}
|
|
35
|
+
export interface PanelCrashReport {
|
|
36
|
+
readonly workspace: string;
|
|
37
|
+
readonly unit: string;
|
|
38
|
+
readonly reason: string;
|
|
39
|
+
readonly at: number;
|
|
40
|
+
}
|
|
41
|
+
export interface PanelStateStore {
|
|
42
|
+
cards(report: PanelCardsReport): void;
|
|
43
|
+
crash(report: PanelCrashReport): void;
|
|
44
|
+
snapshot(workspace: string | null): PanelWorkspaceReport;
|
|
45
|
+
}
|
|
46
|
+
export declare const PANEL_STATE_MAX_UNITS = 64;
|
|
47
|
+
export declare const PANEL_STATE_MAX_CARDS = 12;
|
|
48
|
+
export declare const PANEL_STATE_MAX_REPORTS = 32;
|
|
49
|
+
export declare const PANEL_STATE_CRASH_WINDOW_MS = 600000;
|
|
50
|
+
export declare const PANEL_STATE_MAX_TEXT = 2000;
|
|
51
|
+
export type PanelParse<T> = {
|
|
52
|
+
readonly ok: true;
|
|
53
|
+
readonly value: T;
|
|
54
|
+
} | {
|
|
55
|
+
readonly ok: false;
|
|
56
|
+
readonly message: string;
|
|
57
|
+
};
|
|
58
|
+
export declare function parseCardsReport(payload: unknown): PanelParse<PanelCardsReport>;
|
|
59
|
+
export declare function parseCrashReport(payload: unknown): PanelParse<PanelCrashReport>;
|
|
60
|
+
export declare function createPanelStateStore(): PanelStateStore;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
export const PANEL_STATE_MAX_UNITS = 64;
|
|
2
|
+
export const PANEL_STATE_MAX_CARDS = 12;
|
|
3
|
+
export const PANEL_STATE_MAX_REPORTS = 32;
|
|
4
|
+
export const PANEL_STATE_CRASH_WINDOW_MS = 600000;
|
|
5
|
+
export const PANEL_STATE_MAX_TEXT = 2000;
|
|
6
|
+
const PHASES = ['idle', 'loading', 'ready', 'error'];
|
|
7
|
+
function asRecord(value) {
|
|
8
|
+
return typeof value === 'object' && value !== null ? value : undefined;
|
|
9
|
+
}
|
|
10
|
+
function textOf(value, max) {
|
|
11
|
+
if (typeof value !== 'string')
|
|
12
|
+
return '';
|
|
13
|
+
const trimmed = value.trim();
|
|
14
|
+
return trimmed.length > max ? trimmed.slice(0, max) : trimmed;
|
|
15
|
+
}
|
|
16
|
+
function atOf(value) {
|
|
17
|
+
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
18
|
+
return null;
|
|
19
|
+
if (value < 0)
|
|
20
|
+
return null;
|
|
21
|
+
return Math.trunc(value);
|
|
22
|
+
}
|
|
23
|
+
function errorOf(raw) {
|
|
24
|
+
const record = asRecord(raw);
|
|
25
|
+
if (record === undefined)
|
|
26
|
+
return undefined;
|
|
27
|
+
const code = textOf(record['code'], 120);
|
|
28
|
+
const message = textOf(record['message'], PANEL_STATE_MAX_TEXT);
|
|
29
|
+
if (code === '' && message === '')
|
|
30
|
+
return undefined;
|
|
31
|
+
return { code: code === '' ? 'unknown' : code, message };
|
|
32
|
+
}
|
|
33
|
+
export function parseCardsReport(payload) {
|
|
34
|
+
const record = asRecord(payload);
|
|
35
|
+
if (record === undefined)
|
|
36
|
+
return { ok: false, message: '请求体要是一个对象' };
|
|
37
|
+
const workspace = textOf(record['workspace'], PANEL_STATE_MAX_TEXT);
|
|
38
|
+
if (workspace === '')
|
|
39
|
+
return { ok: false, message: 'workspace 不能是空的 —— 面板按工作区存' };
|
|
40
|
+
const raw = record['cards'];
|
|
41
|
+
if (!Array.isArray(raw))
|
|
42
|
+
return { ok: false, message: 'cards 要是一个数组' };
|
|
43
|
+
const cards = [];
|
|
44
|
+
for (const item of raw.slice(0, PANEL_STATE_MAX_REPORTS)) {
|
|
45
|
+
const card = asRecord(item);
|
|
46
|
+
if (card === undefined)
|
|
47
|
+
continue;
|
|
48
|
+
const unit = textOf(card['unit'], 200);
|
|
49
|
+
const route = textOf(card['route'], 400);
|
|
50
|
+
const phase = textOf(card['phase'], 20);
|
|
51
|
+
const at = atOf(card['at']);
|
|
52
|
+
if (unit === '' || route === '' || at === null || !PHASES.includes(phase))
|
|
53
|
+
continue;
|
|
54
|
+
const error = errorOf(card['error']);
|
|
55
|
+
cards.push({ unit, route, phase, at, ...(error === undefined ? {} : { error }) });
|
|
56
|
+
}
|
|
57
|
+
if (cards.length === 0)
|
|
58
|
+
return { ok: false, message: 'cards 里没有一条能认的(unit / route / phase / at 都要有)' };
|
|
59
|
+
return { ok: true, value: { workspace, cards } };
|
|
60
|
+
}
|
|
61
|
+
export function parseCrashReport(payload) {
|
|
62
|
+
const record = asRecord(payload);
|
|
63
|
+
if (record === undefined)
|
|
64
|
+
return { ok: false, message: '请求体要是一个对象' };
|
|
65
|
+
const workspace = textOf(record['workspace'], PANEL_STATE_MAX_TEXT);
|
|
66
|
+
const unit = textOf(record['unit'], 200);
|
|
67
|
+
const reason = textOf(record['reason'], PANEL_STATE_MAX_TEXT);
|
|
68
|
+
if (workspace === '')
|
|
69
|
+
return { ok: false, message: 'workspace 不能是空的' };
|
|
70
|
+
if (unit === '')
|
|
71
|
+
return { ok: false, message: 'unit 不能是空的' };
|
|
72
|
+
if (reason === '')
|
|
73
|
+
return { ok: false, message: 'reason 不能是空的(没有原因的崩溃没法查)' };
|
|
74
|
+
return { ok: true, value: { workspace, unit, reason, at: atOf(record['at']) ?? Date.now() } };
|
|
75
|
+
}
|
|
76
|
+
export function createPanelStateStore() {
|
|
77
|
+
const workspaces = new Map();
|
|
78
|
+
function trim(entry, max) {
|
|
79
|
+
while (entry.size > max) {
|
|
80
|
+
const oldest = entry.keys().next();
|
|
81
|
+
if (oldest.done === true)
|
|
82
|
+
return;
|
|
83
|
+
entry.delete(oldest.value);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function workspaceEntryOf(workspace) {
|
|
87
|
+
const existing = workspaces.get(workspace);
|
|
88
|
+
if (existing !== undefined)
|
|
89
|
+
return existing;
|
|
90
|
+
const created = { at: 0, units: new Map() };
|
|
91
|
+
workspaces.set(workspace, created);
|
|
92
|
+
trim(workspaces, PANEL_STATE_MAX_UNITS);
|
|
93
|
+
return created;
|
|
94
|
+
}
|
|
95
|
+
function unitEntryOf(entry, unit) {
|
|
96
|
+
const existing = entry.units.get(unit);
|
|
97
|
+
if (existing !== undefined)
|
|
98
|
+
return existing;
|
|
99
|
+
const created = { at: 0, cards: new Map() };
|
|
100
|
+
entry.units.set(unit, created);
|
|
101
|
+
return created;
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
cards(report) {
|
|
105
|
+
const entry = workspaceEntryOf(report.workspace);
|
|
106
|
+
for (const card of report.cards) {
|
|
107
|
+
const unit = unitEntryOf(entry, card.unit);
|
|
108
|
+
unit.cards.delete(card.route);
|
|
109
|
+
unit.cards.set(card.route, card);
|
|
110
|
+
trim(unit.cards, PANEL_STATE_MAX_CARDS);
|
|
111
|
+
if (card.at > unit.at)
|
|
112
|
+
unit.at = card.at;
|
|
113
|
+
if (card.at > entry.at)
|
|
114
|
+
entry.at = card.at;
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
crash(report) {
|
|
118
|
+
const entry = workspaceEntryOf(report.workspace);
|
|
119
|
+
const unit = unitEntryOf(entry, report.unit);
|
|
120
|
+
const previous = unit.crash;
|
|
121
|
+
const counted = previous !== undefined &&
|
|
122
|
+
previous.reason === report.reason &&
|
|
123
|
+
report.at >= previous.at &&
|
|
124
|
+
report.at - previous.at <= PANEL_STATE_CRASH_WINDOW_MS;
|
|
125
|
+
unit.crash = { unit: report.unit, reason: report.reason, at: report.at, count: counted ? previous.count + 1 : 1 };
|
|
126
|
+
if (report.at > unit.at)
|
|
127
|
+
unit.at = report.at;
|
|
128
|
+
if (report.at > entry.at)
|
|
129
|
+
entry.at = report.at;
|
|
130
|
+
},
|
|
131
|
+
snapshot(workspace) {
|
|
132
|
+
if (workspace === null)
|
|
133
|
+
return { workspace: null, at: 0, units: [] };
|
|
134
|
+
const entry = workspaces.get(workspace);
|
|
135
|
+
if (entry === undefined)
|
|
136
|
+
return { workspace, at: 0, units: [] };
|
|
137
|
+
const units = [...entry.units.entries()].map(([unit, one]) => {
|
|
138
|
+
const cards = [...one.cards.values()].sort((a, b) => a.route.localeCompare(b.route));
|
|
139
|
+
const okAt = cards.reduce((best, card) => (card.phase === 'ready' && (best === null || card.at > best) ? card.at : best), null);
|
|
140
|
+
return { unit, at: one.at, okAt, cards, ...(one.crash === undefined ? {} : { crash: one.crash }) };
|
|
141
|
+
});
|
|
142
|
+
units.sort((a, b) => b.at - a.at || a.unit.localeCompare(b.unit));
|
|
143
|
+
return { workspace, at: entry.at, units };
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { hasEnvSupply, type UnitManifest } from '../unit.js';
|
|
3
|
+
|
|
4
|
+
function manifest(env?: UnitManifest['env']): UnitManifest {
|
|
5
|
+
return {
|
|
6
|
+
id: 'issues',
|
|
7
|
+
card: { titleKey: 'issues.mine', order: 10 },
|
|
8
|
+
tools: [],
|
|
9
|
+
host: { inject: [] },
|
|
10
|
+
...(env === undefined ? {} : { env }),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('hasEnvSupply(缺来源时那条回退口径,宿主与浏览器共用一份)', () => {
|
|
15
|
+
it('声明了插件或库 → 有供给', () => {
|
|
16
|
+
expect(hasEnvSupply(manifest({ plugins: [{ name: 'coding', commands: ['x'] }] }))).toBe(true);
|
|
17
|
+
expect(
|
|
18
|
+
hasEnvSupply(
|
|
19
|
+
manifest({
|
|
20
|
+
libs: [
|
|
21
|
+
{
|
|
22
|
+
name: 'opencli-workflow',
|
|
23
|
+
registryEnv: 'WB_NPM',
|
|
24
|
+
registryDefault: 'https://registry.example/',
|
|
25
|
+
purpose: '夹具',
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
}),
|
|
29
|
+
),
|
|
30
|
+
).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('★ 只有基座命令(无卡片模块那种)不算供给 —— 它不是市场门槛,只是缺来源时的回退', () => {
|
|
34
|
+
expect(hasEnvSupply(manifest({ command: 'gitops' }))).toBe(false);
|
|
35
|
+
expect(hasEnvSupply(manifest({ plugins: [], libs: [] }))).toBe(false);
|
|
36
|
+
expect(hasEnvSupply(manifest())).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
});
|
package/src/api/unit.ts
CHANGED
|
@@ -55,3 +55,7 @@ export interface UnitManifest {
|
|
|
55
55
|
readonly host: UnitHostNeeds;
|
|
56
56
|
readonly env?: UnitEnvNeeds;
|
|
57
57
|
}
|
|
58
|
+
|
|
59
|
+
export function hasEnvSupply(manifest: UnitManifest): boolean {
|
|
60
|
+
return (manifest.env?.plugins ?? []).length > 0 || (manifest.env?.libs ?? []).length > 0;
|
|
61
|
+
}
|
|
@@ -8,6 +8,7 @@ export interface CardBoundaryProps {
|
|
|
8
8
|
readonly unit: string;
|
|
9
9
|
readonly resetKey: string;
|
|
10
10
|
readonly t: Translate;
|
|
11
|
+
readonly onFailure?: ((reason: string) => void) | undefined;
|
|
11
12
|
readonly children?: ReactNode | undefined;
|
|
12
13
|
}
|
|
13
14
|
|
|
@@ -28,7 +29,9 @@ export class CardBoundary extends Component<CardBoundaryProps, CardBoundaryState
|
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
override componentDidCatch(error: unknown): void {
|
|
31
|
-
|
|
32
|
+
const reason = errorReasonOf(error);
|
|
33
|
+
console.error(`[dsh-workbench] 模块 ${this.props.unit} 渲染失败:${reason}`, error);
|
|
34
|
+
this.props.onFailure?.(reason);
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
override componentDidUpdate(previous: CardBoundaryProps): void {
|
|
@@ -50,7 +50,7 @@ export interface BoardModule {
|
|
|
50
50
|
|
|
51
51
|
export interface UnitClientFace {
|
|
52
52
|
readonly unit: string;
|
|
53
|
-
readonly card
|
|
53
|
+
readonly card?: (props: BoardModuleProps) => ReactNode;
|
|
54
54
|
readonly steps?: Readonly<Record<string, UnitStep>>;
|
|
55
55
|
readonly messages?: Dictionaries;
|
|
56
56
|
readonly approval?:
|
package/src/components/icons.tsx
CHANGED
|
@@ -56,6 +56,44 @@ export function IconBrandMark({ size = 24 }: IconProps) {
|
|
|
56
56
|
);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
export function IconOpencli({ size = 16 }: IconProps) {
|
|
60
|
+
return (
|
|
61
|
+
<Shell size={size} viewBox="0 0 64 64">
|
|
62
|
+
<defs>
|
|
63
|
+
<linearGradient id="opencli-mark" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
64
|
+
<stop offset="0%" stopColor="#00e5a0" />
|
|
65
|
+
<stop offset="50%" stopColor="#00b4d8" />
|
|
66
|
+
<stop offset="100%" stopColor="#7b61ff" />
|
|
67
|
+
</linearGradient>
|
|
68
|
+
</defs>
|
|
69
|
+
<rect width={64} height={64} rx={14} fill="#0a0a0f" />
|
|
70
|
+
<text
|
|
71
|
+
x="50%"
|
|
72
|
+
y="54%"
|
|
73
|
+
dominantBaseline="middle"
|
|
74
|
+
textAnchor="middle"
|
|
75
|
+
fontFamily="monospace"
|
|
76
|
+
fontWeight={700}
|
|
77
|
+
fontSize={28}
|
|
78
|
+
fill="url(#opencli-mark)"
|
|
79
|
+
>
|
|
80
|
+
>_
|
|
81
|
+
</text>
|
|
82
|
+
</Shell>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function IconNpm({ size = 16 }: IconProps) {
|
|
87
|
+
return (
|
|
88
|
+
<Shell size={size} viewBox="0 0 1024 1024">
|
|
89
|
+
<path
|
|
90
|
+
d="M64 337.76h896v298.72H512v49.792H312.896v-49.824H64z m49.792 248.928h99.552v-149.376h49.76v149.344h49.792v-199.104H113.792z m248.896-199.136v248.928h99.552v-49.792h99.552v-199.136z m99.552 49.792H512v99.584h-49.792z m149.344-49.792v199.136h99.552v-149.376h49.792v149.344h49.792v-149.344h49.792v149.344h49.792v-199.104z"
|
|
91
|
+
fill="#CB3837"
|
|
92
|
+
/>
|
|
93
|
+
</Shell>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
59
97
|
export function IconModules({ size = 16 }: IconProps) {
|
|
60
98
|
return (
|
|
61
99
|
<svg width={size} height={size} viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
package/src/config/constants.ts
CHANGED
|
@@ -9,6 +9,10 @@ export const ACTIONS_PREFIX = `${API_PREFIX}/actions`;
|
|
|
9
9
|
|
|
10
10
|
export const PROVISION_PREFIX = `${API_PREFIX}/provision`;
|
|
11
11
|
|
|
12
|
+
export const PANEL_STATE_PATH = "panel-state";
|
|
13
|
+
|
|
14
|
+
export const PANEL_STATE_PREFIX = `${API_PREFIX}/${PANEL_STATE_PATH}`;
|
|
15
|
+
|
|
12
16
|
export const OPENCLI_PIN = "1.8.8";
|
|
13
17
|
|
|
14
18
|
export const CODING_ISSUES_SERVICE = "workbenchCodingIssues";
|
|
@@ -17,6 +21,8 @@ export const PANELS_SERVICE = "workbenchPanels";
|
|
|
17
21
|
|
|
18
22
|
export const UNITS_SERVICE = "workbenchUnits";
|
|
19
23
|
|
|
24
|
+
export const PANEL_STATE_SERVICE = "workbenchPanelState";
|
|
25
|
+
|
|
20
26
|
export const PANEL_LIST_LIMIT = 50;
|
|
21
27
|
|
|
22
28
|
export const OPENCLI_TIMEOUT_MS = 120_000;
|