@dsh-workbench/shared 0.1.16 → 0.1.18

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 (42) hide show
  1. package/dist/api/unit-declaration.d.ts +29 -0
  2. package/dist/api/unit-declaration.js +189 -0
  3. package/dist/api/unit.d.ts +0 -2
  4. package/dist/api/workbench-contract.d.ts +1 -1
  5. package/dist/components/ApprovalDetail.js +1 -1
  6. package/dist/components/board-module.d.ts +4 -2
  7. package/dist/host/plugin-ledger.d.ts +32 -0
  8. package/dist/host/plugin-ledger.js +99 -0
  9. package/dist/host/provision.d.ts +1 -1
  10. package/dist/host/provision.js +6 -0
  11. package/dist/host/unit-face.d.ts +7 -2
  12. package/dist/host.d.ts +2 -0
  13. package/dist/host.js +2 -0
  14. package/dist/i18n/en.js +17 -4
  15. package/dist/i18n/index.d.ts +18 -5
  16. package/dist/i18n/zh.d.ts +17 -4
  17. package/dist/i18n/zh.js +17 -4
  18. package/dist/index.d.ts +1 -0
  19. package/dist/index.js +1 -0
  20. package/dist/testing/declaration.d.ts +2 -0
  21. package/dist/testing/declaration.js +21 -0
  22. package/dist/testing.d.ts +1 -0
  23. package/dist/testing.js +1 -0
  24. package/package.json +1 -1
  25. package/src/api/__tests__/unit-declaration.test.ts +52 -0
  26. package/src/api/unit-declaration.ts +244 -0
  27. package/src/api/unit.ts +0 -2
  28. package/src/api/workbench-contract.ts +3 -1
  29. package/src/components/ApprovalDetail.tsx +1 -1
  30. package/src/components/board-module.ts +5 -2
  31. package/src/host/__tests__/plugin-ledger.test.ts +94 -0
  32. package/src/host/__tests__/provision.test.ts +18 -0
  33. package/src/host/plugin-ledger.ts +152 -0
  34. package/src/host/provision.ts +4 -0
  35. package/src/host/unit-face.ts +8 -2
  36. package/src/host.ts +2 -0
  37. package/src/i18n/en.ts +18 -4
  38. package/src/i18n/zh.ts +18 -4
  39. package/src/index.ts +1 -0
  40. package/src/testing/__tests__/declaration.test.ts +48 -0
  41. package/src/testing/declaration.ts +23 -0
  42. package/src/testing.ts +1 -0
@@ -67,7 +67,7 @@ export declare const dictionaries: {
67
67
  readonly 'panels.market.missing': "未装";
68
68
  readonly 'panels.market.unchecked': "状态未知";
69
69
  readonly 'panels.market.checking': "检测中";
70
- readonly 'panels.market.sectionInstalled': "已安装";
70
+ readonly 'panels.market.sectionInstalled': "已安装模块";
71
71
  readonly 'panels.market.sectionPending': "未安装";
72
72
  readonly 'panels.market.sectionShared': "共用依赖";
73
73
  readonly 'panels.market.sharedUsedBy': "供 {modules} 使用";
@@ -95,11 +95,19 @@ export declare const dictionaries: {
95
95
  readonly 'panels.market.noSource': "这个模块没声明怎么装,只能自己动手";
96
96
  readonly 'panels.market.badSource': "安装方式没写全,只能自己动手";
97
97
  readonly 'panels.market.empty': "暂无可用模块";
98
- readonly 'panels.market.installedSection': "已安装";
99
- readonly 'panels.market.sectionLocal': "本地装的";
98
+ readonly 'panels.market.sectionLocal': "本地模块";
100
99
  readonly 'panels.market.sectionUnlisted': "不是从市场装的";
100
+ readonly 'panels.market.sectionPlugins': "已安装 opencli 插件";
101
101
  readonly 'panels.market.moduleOutdated': "有新版";
102
+ readonly 'panels.market.moduleRefused': "这个模块是给旧版本的工作台写的,现在用不了。更新它,或者卸载。";
103
+ readonly 'panels.market.moduleRefusedDetail': "装不上的原因:{reason}";
102
104
  readonly 'panels.market.moduleRestartHint': "装好了但还没生效 —— 重启一次 dsh 就会出来";
105
+ readonly 'panels.market.pluginKeptInUse': "文件先留着:{names} 还要用它。";
106
+ readonly 'panels.market.pluginKeptUnsure': "文件先留着:这台机器上还有模块看不出用了什么,说不准谁在用它。";
107
+ readonly 'panels.market.pluginKeptHeld': "文件留着:你自己装的那一份,模块放手不动它。";
108
+ readonly 'panels.market.pluginKeptUnnamed': "文件先留着:看不出这是哪个插件,说不准谁在用它。";
109
+ readonly 'panels.market.pluginAlsoKnownAs': "包名是 {pkg}(更新与卸载按它来)";
110
+ readonly 'panels.market.pluginBroken': "这份装坏了:看不出它是个插件。可以卸载,或者去上面按包名再装一次。";
103
111
  readonly 'panels.market.restartNeeded': "新版装好了,重启一次才生效。";
104
112
  readonly 'panels.market.foot': "装好后到那个项目的面板页勾选它,模块才会出现。";
105
113
  readonly 'panels.market.recheck': "重新检查更新";
@@ -147,7 +155,11 @@ export declare const dictionaries: {
147
155
  readonly 'panels.market.add.problemInstalled': "该模块已安装";
148
156
  readonly 'panels.market.add.problemNotFound': "未找到相关模块";
149
157
  readonly 'panels.market.add.problemNotPackage': "该路径不存在或不是有效的模块包";
150
- readonly 'panels.market.add.problemNotBundle': "这个包没有声明组合包,无法作为模块安装:{reason}";
158
+ readonly 'panels.market.add.problemNotBundle': "这个包装不进这里:{reason}。要是它其实是个 opencli 插件,请改用包名(例如 opencli-plugin-cmdb)或者本地目录 —— 从 git 地址和 tarball 判断不出它是什么。";
159
+ readonly 'panels.market.add.problemNotWorkbenchModule': "这个包跟现在的工作台对不上,装上也用不了。换一个版本,或者找这个模块的作者。";
160
+ readonly 'panels.market.add.problemNotModuleOrPlugin': "这个包既不是工作台的模块,也不是 opencli 的插件,装了也用不上。换一个版本,或者找这个包的作者。";
161
+ readonly 'panels.market.add.failureNotModuleOrPlugin': "这个包既不是工作台的模块,也不是 opencli 的插件,装了也用不上";
162
+ readonly 'panels.market.add.installedPlugin': "装上了:opencli 插件 {name}";
151
163
  readonly 'panels.market.add.problemNetwork': "无法连接模块源,请检查网络后重试";
152
164
  readonly 'panels.market.add.problemUnknown': "无法获取模块信息:{reason}";
153
165
  readonly 'panels.market.add.failureNetwork': "网络连接失败";
@@ -178,6 +190,7 @@ export declare const dictionaries: {
178
190
  readonly 'panels.market.add.registryPnpm': "pnpm 自己配的源";
179
191
  readonly 'panels.market.add.check': "预检";
180
192
  readonly 'panels.market.add.checking': "正在预检…";
193
+ readonly 'panels.market.add.classifying': "判定中…";
181
194
  readonly 'panels.market.add.change': "改一下";
182
195
  readonly 'panels.market.add.previewTitle': "将要安装";
183
196
  readonly 'panels.market.add.fieldName': "包名";
@@ -313,7 +326,7 @@ export declare const dictionaries: {
313
326
  readonly 'panel.conversation': "对话";
314
327
  readonly 'panels.label': "全局面板";
315
328
  };
316
- en: Record<"time.now" | "time.minutes" | "time.hours" | "time.days" | "blocker.retry" | "blocker.copy" | "blocker.unknown.title" | "blocker.login.go" | "blocker.login.titleGeneric" | "blocker.login.title" | "blocker.login.manual" | "blocker.login.hint" | "blocker.forbidden.open" | "blocker.forbidden.titleGeneric" | "blocker.forbidden.title" | "blocker.forbidden.hint" | "blocker.timeout.hint" | "blocker.exec.hint" | "blocker.fix.install" | "blocker.fix.update" | "blocker.market.open" | "env.unit.failed" | "env.unit.failedHint" | "env.unit.missingPlugin" | "env.unit.hint" | "env.unit.missingCommands" | "env.unit.missingCommandsHint" | "env.unit.unchecked" | "env.unit.uncheckedHint" | "brand.name" | "toggle.open" | "toggle.collapse" | "project.label" | "workspace.section" | "project.none" | "project.empty" | "project.add" | "project.search.label" | "project.search.placeholder" | "project.search.empty" | "project.rename.row" | "project.rename.current" | "project.rename.title" | "project.rename.description" | "project.rename.field" | "project.rename.placeholder" | "project.rename.hint" | "project.rename.save" | "project.rename.saving" | "project.rename.cancel" | "project.rename.failed" | "session.new" | "session.new.label" | "session.empty" | "session.noProject" | "session.pinned" | "session.actions" | "status.idle" | "status.running" | "status.waitingApproval" | "status.planReview" | "status.waitingAnswer" | "status.completed" | "status.subagentsRunning.one" | "status.subagentsRunning.other" | "panels.title" | "panels.reset" | "panels.enabled" | "panels.disabled" | "panels.choose" | "panels.buildModule" | "panels.failed" | "panels.hintEffect" | "panels.dragHint" | "panels.unknownStep" | "panels.labelWithHint" | "panels.dep.failed" | "panels.dep.missingPlugin" | "panels.dep.missingCommands" | "panels.dep.unchecked" | "panels.tab.modules" | "panels.tab.market" | "panels.browseMarket" | "panels.noneInstalled" | "panels.noneInstalledHint" | "panels.market.installed" | "panels.market.outdated" | "panels.market.hasNew" | "panels.market.missing" | "panels.market.unchecked" | "panels.market.checking" | "panels.market.sectionInstalled" | "panels.market.sectionPending" | "panels.market.sectionShared" | "panels.market.sharedUsedBy" | "panels.market.listSeparator" | "panels.market.sharedMissing" | "panels.market.sharedOutdated" | "panels.market.sharedUnsatisfied" | "panels.market.sharedOk" | "panels.market.versionArrow" | "panels.market.lead" | "panels.market.install" | "panels.market.update" | "panels.market.uninstall" | "panels.market.uninstallLabel" | "panels.market.installing" | "panels.market.updating" | "panels.market.uninstalling" | "panels.market.confirmUninstallTitle" | "panels.market.confirmUninstallDetail" | "panels.market.confirmUninstallAck" | "panels.market.confirmYes" | "panels.market.confirmNo" | "panels.market.failed" | "panels.market.needEnv" | "panels.market.noSource" | "panels.market.badSource" | "panels.market.empty" | "panels.market.installedSection" | "panels.market.sectionLocal" | "panels.market.sectionUnlisted" | "panels.market.moduleOutdated" | "panels.market.moduleRestartHint" | "panels.market.restartNeeded" | "panels.market.foot" | "panels.market.recheck" | "panels.market.checkUpdates" | "panels.market.checkedAt" | "panels.market.justNow" | "panels.market.minutesAgo" | "panels.market.hoursAgo" | "panels.market.add.description" | "panels.market.add.specPlaceholder" | "panels.market.add.guideToggle" | "panels.market.add.guideHide" | "panels.market.add.guideIdTitle" | "panels.market.add.guideIdExample" | "panels.market.add.guideIdHint" | "panels.market.add.guideGitTitle" | "panels.market.add.guideGitExample" | "panels.market.add.guideGitHint" | "panels.market.add.guidePathTitle" | "panels.market.add.guidePathExample" | "panels.market.add.guidePathHint" | "panels.market.add.guideExampleLabel" | "panels.market.add.guideFill" | "panels.market.add.guideSafety" | "panels.market.add.registryLegend" | "panels.market.add.registryOfficial" | "panels.market.add.registryMirror" | "panels.market.add.registryWithHost" | "panels.market.add.registryToggle" | "panels.market.add.installVersion" | "panels.market.add.registryDefault" | "panels.market.add.registryCustom" | "panels.market.add.registryCustomPlaceholder" | "panels.market.add.registryCustomHint" | "panels.market.add.registryCustomInvalid" | "panels.market.add.run" | "panels.market.add.edit" | "panels.market.add.changeRegistry" | "panels.market.add.installingTitle" | "panels.market.add.installedTitle" | "panels.market.add.failedTitle" | "panels.market.add.cancelledTitle" | "panels.market.add.failureNetworkHost" | "panels.market.add.problemInvalid" | "panels.market.add.problemInstalled" | "panels.market.add.problemNotFound" | "panels.market.add.problemNotPackage" | "panels.market.add.problemNotBundle" | "panels.market.add.problemNetwork" | "panels.market.add.problemUnknown" | "panels.market.add.failureNetwork" | "panels.market.add.failureNotFound" | "panels.market.add.failureNoMatchingVersion" | "panels.market.add.failureDiskFull" | "panels.market.add.failurePermission" | "panels.market.add.failureBuildBlocked" | "panels.market.add.failureIntegrity" | "panels.market.add.failureTimeout" | "panels.market.add.failurePnpmMissing" | "panels.market.add.failureGeneric" | "panels.market.add.approveTitle" | "panels.market.add.approveCaution" | "panels.market.add.approveRun" | "panels.market.add.restart" | "panels.market.add.tookEffect" | "panels.market.add.details" | "panels.market.add.open" | "panels.market.add.title" | "panels.market.add.intro" | "panels.market.add.specLabel" | "panels.market.add.specHint" | "panels.market.add.exampleTitle" | "panels.market.add.exampleFill" | "panels.market.add.registryLabel" | "panels.market.add.registryProfile" | "panels.market.add.registryPnpm" | "panels.market.add.check" | "panels.market.add.checking" | "panels.market.add.change" | "panels.market.add.previewTitle" | "panels.market.add.fieldName" | "panels.market.add.fieldVersion" | "panels.market.add.fieldDescription" | "panels.market.add.fieldSource" | "panels.market.add.install" | "panels.market.add.installing" | "panels.market.add.cancel" | "panels.market.add.close" | "panels.market.add.retry" | "panels.market.add.changeSource" | "panels.market.add.approveBuilds" | "panels.market.add.approveHint" | "panels.market.add.doneApplied" | "panels.market.add.doneRestart" | "panels.market.add.cancelled" | "panels.market.add.failed" | "panels.market.add.removed" | "panels.market.add.supplyPending" | "panels.market.add.supplyFailed" | "panels.market.add.notModule" | "panels.market.add.removedRestart" | "panels.market.add.remove" | "panels.market.add.problem.invalidSpec" | "panels.market.add.problem.alreadyInstalled" | "panels.market.add.problem.notFound" | "panels.market.add.problem.notAPackage" | "panels.market.add.problem.notABundle" | "panels.market.add.problem.network" | "panels.market.add.problem.unknown" | "panels.market.add.reason.invalidSpec" | "panels.market.add.reason.incompatible" | "panels.market.add.reason.notBundle" | "panels.market.add.reason.notRemovable" | "panels.market.add.reason.bundleInUse" | "panels.market.add.reason.stopProfile" | "panels.market.add.reason.staleApproval" | "panels.market.add.reason.operationError" | "panels.market.add.reason.buildBlocked" | "panels.market.add.reason.pnpmMissing" | "panels.market.add.reason.noMatchingVersion" | "panels.market.add.reason.diskFull" | "panels.market.add.reason.permission" | "panels.market.add.reason.integrity" | "panels.market.add.reason.timeout" | "panels.market.add.reason.network" | "panels.market.add.reason.notFound" | "panels.market.add.reason.unknown" | "blocker.fix.running" | "blocker.fix.failed" | "blocker.retry.running" | "panels.reading" | "panels.cardBroken" | "panels.cardRetry" | "panels.empty" | "panels.emptyHint" | "env.title" | "env.checking" | "env.installing" | "env.failed" | "env.elapsed" | "env.stepFailed.install" | "env.stepFailed.path" | "env.stepFailed.shell" | "env.bridge.ok" | "env.bridge.short" | "env.bridge.why" | "env.bridge.daemon" | "env.bridge.extension" | "env.bridge.extensionLink" | "env.bridge.profile" | "env.bridge.stale" | "env.bridge.unknown" | "env.fix.title" | "env.fix.why" | "env.fix.whyPath" | "env.fix.whyShell" | "env.fix.step" | "env.fix.stepPath" | "env.fix.stepShell" | "env.fix.permission" | "env.fix.network" | "env.fix.details" | "env.retry" | "env.copyCommand" | "env.copied" | "env.step.opencli" | "env.step.codingPlugin" | "env.step.bridge" | "env.step.warm" | "env.warm.done" | "env.warm.short" | "rightbar.label" | "rightbar.expand" | "rightbar.collapse" | "rightbar.column.label" | "rightbar.column.workbench" | "rightbar.column.dsh" | "rightbar.refresh" | "rightbar.refreshing" | "rightbar.updatedAt" | "rightbar.noProject" | "rightbar.openPlatform" | "coding.notARepo.title" | "error.unknown" | "board.title" | "panel.conversation" | "panels.label", string>;
329
+ en: Record<"time.now" | "time.minutes" | "time.hours" | "time.days" | "blocker.retry" | "blocker.copy" | "blocker.unknown.title" | "blocker.login.go" | "blocker.login.titleGeneric" | "blocker.login.title" | "blocker.login.manual" | "blocker.login.hint" | "blocker.forbidden.open" | "blocker.forbidden.titleGeneric" | "blocker.forbidden.title" | "blocker.forbidden.hint" | "blocker.timeout.hint" | "blocker.exec.hint" | "blocker.fix.install" | "blocker.fix.update" | "blocker.market.open" | "env.unit.failed" | "env.unit.failedHint" | "env.unit.missingPlugin" | "env.unit.hint" | "env.unit.missingCommands" | "env.unit.missingCommandsHint" | "env.unit.unchecked" | "env.unit.uncheckedHint" | "brand.name" | "toggle.open" | "toggle.collapse" | "project.label" | "workspace.section" | "project.none" | "project.empty" | "project.add" | "project.search.label" | "project.search.placeholder" | "project.search.empty" | "project.rename.row" | "project.rename.current" | "project.rename.title" | "project.rename.description" | "project.rename.field" | "project.rename.placeholder" | "project.rename.hint" | "project.rename.save" | "project.rename.saving" | "project.rename.cancel" | "project.rename.failed" | "session.new" | "session.new.label" | "session.empty" | "session.noProject" | "session.pinned" | "session.actions" | "status.idle" | "status.running" | "status.waitingApproval" | "status.planReview" | "status.waitingAnswer" | "status.completed" | "status.subagentsRunning.one" | "status.subagentsRunning.other" | "panels.title" | "panels.reset" | "panels.enabled" | "panels.disabled" | "panels.choose" | "panels.buildModule" | "panels.failed" | "panels.hintEffect" | "panels.dragHint" | "panels.unknownStep" | "panels.labelWithHint" | "panels.dep.failed" | "panels.dep.missingPlugin" | "panels.dep.missingCommands" | "panels.dep.unchecked" | "panels.tab.modules" | "panels.tab.market" | "panels.browseMarket" | "panels.noneInstalled" | "panels.noneInstalledHint" | "panels.market.installed" | "panels.market.outdated" | "panels.market.hasNew" | "panels.market.missing" | "panels.market.unchecked" | "panels.market.checking" | "panels.market.sectionInstalled" | "panels.market.sectionPending" | "panels.market.sectionShared" | "panels.market.sharedUsedBy" | "panels.market.listSeparator" | "panels.market.sharedMissing" | "panels.market.sharedOutdated" | "panels.market.sharedUnsatisfied" | "panels.market.sharedOk" | "panels.market.versionArrow" | "panels.market.lead" | "panels.market.install" | "panels.market.update" | "panels.market.uninstall" | "panels.market.uninstallLabel" | "panels.market.installing" | "panels.market.updating" | "panels.market.uninstalling" | "panels.market.confirmUninstallTitle" | "panels.market.confirmUninstallDetail" | "panels.market.confirmUninstallAck" | "panels.market.confirmYes" | "panels.market.confirmNo" | "panels.market.failed" | "panels.market.needEnv" | "panels.market.noSource" | "panels.market.badSource" | "panels.market.empty" | "panels.market.sectionLocal" | "panels.market.sectionUnlisted" | "panels.market.sectionPlugins" | "panels.market.moduleOutdated" | "panels.market.moduleRefused" | "panels.market.moduleRefusedDetail" | "panels.market.moduleRestartHint" | "panels.market.pluginKeptInUse" | "panels.market.pluginKeptUnsure" | "panels.market.pluginKeptHeld" | "panels.market.pluginKeptUnnamed" | "panels.market.pluginAlsoKnownAs" | "panels.market.pluginBroken" | "panels.market.restartNeeded" | "panels.market.foot" | "panels.market.recheck" | "panels.market.checkUpdates" | "panels.market.checkedAt" | "panels.market.justNow" | "panels.market.minutesAgo" | "panels.market.hoursAgo" | "panels.market.add.description" | "panels.market.add.specPlaceholder" | "panels.market.add.guideToggle" | "panels.market.add.guideHide" | "panels.market.add.guideIdTitle" | "panels.market.add.guideIdExample" | "panels.market.add.guideIdHint" | "panels.market.add.guideGitTitle" | "panels.market.add.guideGitExample" | "panels.market.add.guideGitHint" | "panels.market.add.guidePathTitle" | "panels.market.add.guidePathExample" | "panels.market.add.guidePathHint" | "panels.market.add.guideExampleLabel" | "panels.market.add.guideFill" | "panels.market.add.guideSafety" | "panels.market.add.registryLegend" | "panels.market.add.registryOfficial" | "panels.market.add.registryMirror" | "panels.market.add.registryWithHost" | "panels.market.add.registryToggle" | "panels.market.add.installVersion" | "panels.market.add.registryDefault" | "panels.market.add.registryCustom" | "panels.market.add.registryCustomPlaceholder" | "panels.market.add.registryCustomHint" | "panels.market.add.registryCustomInvalid" | "panels.market.add.run" | "panels.market.add.edit" | "panels.market.add.changeRegistry" | "panels.market.add.installingTitle" | "panels.market.add.installedTitle" | "panels.market.add.failedTitle" | "panels.market.add.cancelledTitle" | "panels.market.add.failureNetworkHost" | "panels.market.add.problemInvalid" | "panels.market.add.problemInstalled" | "panels.market.add.problemNotFound" | "panels.market.add.problemNotPackage" | "panels.market.add.problemNotBundle" | "panels.market.add.problemNotWorkbenchModule" | "panels.market.add.problemNotModuleOrPlugin" | "panels.market.add.failureNotModuleOrPlugin" | "panels.market.add.installedPlugin" | "panels.market.add.problemNetwork" | "panels.market.add.problemUnknown" | "panels.market.add.failureNetwork" | "panels.market.add.failureNotFound" | "panels.market.add.failureNoMatchingVersion" | "panels.market.add.failureDiskFull" | "panels.market.add.failurePermission" | "panels.market.add.failureBuildBlocked" | "panels.market.add.failureIntegrity" | "panels.market.add.failureTimeout" | "panels.market.add.failurePnpmMissing" | "panels.market.add.failureGeneric" | "panels.market.add.approveTitle" | "panels.market.add.approveCaution" | "panels.market.add.approveRun" | "panels.market.add.restart" | "panels.market.add.tookEffect" | "panels.market.add.details" | "panels.market.add.open" | "panels.market.add.title" | "panels.market.add.intro" | "panels.market.add.specLabel" | "panels.market.add.specHint" | "panels.market.add.exampleTitle" | "panels.market.add.exampleFill" | "panels.market.add.registryLabel" | "panels.market.add.registryProfile" | "panels.market.add.registryPnpm" | "panels.market.add.check" | "panels.market.add.checking" | "panels.market.add.classifying" | "panels.market.add.change" | "panels.market.add.previewTitle" | "panels.market.add.fieldName" | "panels.market.add.fieldVersion" | "panels.market.add.fieldDescription" | "panels.market.add.fieldSource" | "panels.market.add.install" | "panels.market.add.installing" | "panels.market.add.cancel" | "panels.market.add.close" | "panels.market.add.retry" | "panels.market.add.changeSource" | "panels.market.add.approveBuilds" | "panels.market.add.approveHint" | "panels.market.add.doneApplied" | "panels.market.add.doneRestart" | "panels.market.add.cancelled" | "panels.market.add.failed" | "panels.market.add.removed" | "panels.market.add.supplyPending" | "panels.market.add.supplyFailed" | "panels.market.add.notModule" | "panels.market.add.removedRestart" | "panels.market.add.remove" | "panels.market.add.problem.invalidSpec" | "panels.market.add.problem.alreadyInstalled" | "panels.market.add.problem.notFound" | "panels.market.add.problem.notAPackage" | "panels.market.add.problem.notABundle" | "panels.market.add.problem.network" | "panels.market.add.problem.unknown" | "panels.market.add.reason.invalidSpec" | "panels.market.add.reason.incompatible" | "panels.market.add.reason.notBundle" | "panels.market.add.reason.notRemovable" | "panels.market.add.reason.bundleInUse" | "panels.market.add.reason.stopProfile" | "panels.market.add.reason.staleApproval" | "panels.market.add.reason.operationError" | "panels.market.add.reason.buildBlocked" | "panels.market.add.reason.pnpmMissing" | "panels.market.add.reason.noMatchingVersion" | "panels.market.add.reason.diskFull" | "panels.market.add.reason.permission" | "panels.market.add.reason.integrity" | "panels.market.add.reason.timeout" | "panels.market.add.reason.network" | "panels.market.add.reason.notFound" | "panels.market.add.reason.unknown" | "blocker.fix.running" | "blocker.fix.failed" | "blocker.retry.running" | "panels.reading" | "panels.cardBroken" | "panels.cardRetry" | "panels.empty" | "panels.emptyHint" | "env.title" | "env.checking" | "env.installing" | "env.failed" | "env.elapsed" | "env.stepFailed.install" | "env.stepFailed.path" | "env.stepFailed.shell" | "env.bridge.ok" | "env.bridge.short" | "env.bridge.why" | "env.bridge.daemon" | "env.bridge.extension" | "env.bridge.extensionLink" | "env.bridge.profile" | "env.bridge.stale" | "env.bridge.unknown" | "env.fix.title" | "env.fix.why" | "env.fix.whyPath" | "env.fix.whyShell" | "env.fix.step" | "env.fix.stepPath" | "env.fix.stepShell" | "env.fix.permission" | "env.fix.network" | "env.fix.details" | "env.retry" | "env.copyCommand" | "env.copied" | "env.step.opencli" | "env.step.codingPlugin" | "env.step.bridge" | "env.step.warm" | "env.warm.done" | "env.warm.short" | "rightbar.label" | "rightbar.expand" | "rightbar.collapse" | "rightbar.column.label" | "rightbar.column.workbench" | "rightbar.column.dsh" | "rightbar.refresh" | "rightbar.refreshing" | "rightbar.updatedAt" | "rightbar.noProject" | "rightbar.openPlatform" | "coding.notARepo.title" | "error.unknown" | "board.title" | "panel.conversation" | "panels.label", string>;
317
330
  };
318
331
  export type { WorkbenchMessages };
319
332
  export interface Dictionaries {
package/dist/i18n/zh.d.ts CHANGED
@@ -65,7 +65,7 @@ export declare const zh: {
65
65
  readonly 'panels.market.missing': "未装";
66
66
  readonly 'panels.market.unchecked': "状态未知";
67
67
  readonly 'panels.market.checking': "检测中";
68
- readonly 'panels.market.sectionInstalled': "已安装";
68
+ readonly 'panels.market.sectionInstalled': "已安装模块";
69
69
  readonly 'panels.market.sectionPending': "未安装";
70
70
  readonly 'panels.market.sectionShared': "共用依赖";
71
71
  readonly 'panels.market.sharedUsedBy': "供 {modules} 使用";
@@ -93,11 +93,19 @@ export declare const zh: {
93
93
  readonly 'panels.market.noSource': "这个模块没声明怎么装,只能自己动手";
94
94
  readonly 'panels.market.badSource': "安装方式没写全,只能自己动手";
95
95
  readonly 'panels.market.empty': "暂无可用模块";
96
- readonly 'panels.market.installedSection': "已安装";
97
- readonly 'panels.market.sectionLocal': "本地装的";
96
+ readonly 'panels.market.sectionLocal': "本地模块";
98
97
  readonly 'panels.market.sectionUnlisted': "不是从市场装的";
98
+ readonly 'panels.market.sectionPlugins': "已安装 opencli 插件";
99
99
  readonly 'panels.market.moduleOutdated': "有新版";
100
+ readonly 'panels.market.moduleRefused': "这个模块是给旧版本的工作台写的,现在用不了。更新它,或者卸载。";
101
+ readonly 'panels.market.moduleRefusedDetail': "装不上的原因:{reason}";
100
102
  readonly 'panels.market.moduleRestartHint': "装好了但还没生效 —— 重启一次 dsh 就会出来";
103
+ readonly 'panels.market.pluginKeptInUse': "文件先留着:{names} 还要用它。";
104
+ readonly 'panels.market.pluginKeptUnsure': "文件先留着:这台机器上还有模块看不出用了什么,说不准谁在用它。";
105
+ readonly 'panels.market.pluginKeptHeld': "文件留着:你自己装的那一份,模块放手不动它。";
106
+ readonly 'panels.market.pluginKeptUnnamed': "文件先留着:看不出这是哪个插件,说不准谁在用它。";
107
+ readonly 'panels.market.pluginAlsoKnownAs': "包名是 {pkg}(更新与卸载按它来)";
108
+ readonly 'panels.market.pluginBroken': "这份装坏了:看不出它是个插件。可以卸载,或者去上面按包名再装一次。";
101
109
  readonly 'panels.market.restartNeeded': "新版装好了,重启一次才生效。";
102
110
  readonly 'panels.market.foot': "装好后到那个项目的面板页勾选它,模块才会出现。";
103
111
  readonly 'panels.market.recheck': "重新检查更新";
@@ -145,7 +153,11 @@ export declare const zh: {
145
153
  readonly 'panels.market.add.problemInstalled': "该模块已安装";
146
154
  readonly 'panels.market.add.problemNotFound': "未找到相关模块";
147
155
  readonly 'panels.market.add.problemNotPackage': "该路径不存在或不是有效的模块包";
148
- readonly 'panels.market.add.problemNotBundle': "这个包没有声明组合包,无法作为模块安装:{reason}";
156
+ readonly 'panels.market.add.problemNotBundle': "这个包装不进这里:{reason}。要是它其实是个 opencli 插件,请改用包名(例如 opencli-plugin-cmdb)或者本地目录 —— 从 git 地址和 tarball 判断不出它是什么。";
157
+ readonly 'panels.market.add.problemNotWorkbenchModule': "这个包跟现在的工作台对不上,装上也用不了。换一个版本,或者找这个模块的作者。";
158
+ readonly 'panels.market.add.problemNotModuleOrPlugin': "这个包既不是工作台的模块,也不是 opencli 的插件,装了也用不上。换一个版本,或者找这个包的作者。";
159
+ readonly 'panels.market.add.failureNotModuleOrPlugin': "这个包既不是工作台的模块,也不是 opencli 的插件,装了也用不上";
160
+ readonly 'panels.market.add.installedPlugin': "装上了:opencli 插件 {name}";
149
161
  readonly 'panels.market.add.problemNetwork': "无法连接模块源,请检查网络后重试";
150
162
  readonly 'panels.market.add.problemUnknown': "无法获取模块信息:{reason}";
151
163
  readonly 'panels.market.add.failureNetwork': "网络连接失败";
@@ -176,6 +188,7 @@ export declare const zh: {
176
188
  readonly 'panels.market.add.registryPnpm': "pnpm 自己配的源";
177
189
  readonly 'panels.market.add.check': "预检";
178
190
  readonly 'panels.market.add.checking': "正在预检…";
191
+ readonly 'panels.market.add.classifying': "判定中…";
179
192
  readonly 'panels.market.add.change': "改一下";
180
193
  readonly 'panels.market.add.previewTitle': "将要安装";
181
194
  readonly 'panels.market.add.fieldName': "包名";
package/dist/i18n/zh.js CHANGED
@@ -65,7 +65,7 @@ export const zh = {
65
65
  'panels.market.missing': '未装',
66
66
  'panels.market.unchecked': '状态未知',
67
67
  'panels.market.checking': '检测中',
68
- 'panels.market.sectionInstalled': '已安装',
68
+ 'panels.market.sectionInstalled': '已安装模块',
69
69
  'panels.market.sectionPending': '未安装',
70
70
  'panels.market.sectionShared': '共用依赖',
71
71
  'panels.market.sharedUsedBy': '供 {modules} 使用',
@@ -93,11 +93,19 @@ export const zh = {
93
93
  'panels.market.noSource': '这个模块没声明怎么装,只能自己动手',
94
94
  'panels.market.badSource': '安装方式没写全,只能自己动手',
95
95
  'panels.market.empty': '暂无可用模块',
96
- 'panels.market.installedSection': '已安装',
97
- 'panels.market.sectionLocal': '本地装的',
96
+ 'panels.market.sectionLocal': '本地模块',
98
97
  'panels.market.sectionUnlisted': '不是从市场装的',
98
+ 'panels.market.sectionPlugins': '已安装 opencli 插件',
99
99
  'panels.market.moduleOutdated': '有新版',
100
+ 'panels.market.moduleRefused': '这个模块是给旧版本的工作台写的,现在用不了。更新它,或者卸载。',
101
+ 'panels.market.moduleRefusedDetail': '装不上的原因:{reason}',
100
102
  'panels.market.moduleRestartHint': '装好了但还没生效 —— 重启一次 dsh 就会出来',
103
+ 'panels.market.pluginKeptInUse': '文件先留着:{names} 还要用它。',
104
+ 'panels.market.pluginKeptUnsure': '文件先留着:这台机器上还有模块看不出用了什么,说不准谁在用它。',
105
+ 'panels.market.pluginKeptHeld': '文件留着:你自己装的那一份,模块放手不动它。',
106
+ 'panels.market.pluginKeptUnnamed': '文件先留着:看不出这是哪个插件,说不准谁在用它。',
107
+ 'panels.market.pluginAlsoKnownAs': '包名是 {pkg}(更新与卸载按它来)',
108
+ 'panels.market.pluginBroken': '这份装坏了:看不出它是个插件。可以卸载,或者去上面按包名再装一次。',
101
109
  'panels.market.restartNeeded': '新版装好了,重启一次才生效。',
102
110
  'panels.market.foot': '装好后到那个项目的面板页勾选它,模块才会出现。',
103
111
  'panels.market.recheck': '重新检查更新',
@@ -145,7 +153,11 @@ export const zh = {
145
153
  'panels.market.add.problemInstalled': '该模块已安装',
146
154
  'panels.market.add.problemNotFound': '未找到相关模块',
147
155
  'panels.market.add.problemNotPackage': '该路径不存在或不是有效的模块包',
148
- 'panels.market.add.problemNotBundle': '这个包没有声明组合包,无法作为模块安装:{reason}',
156
+ 'panels.market.add.problemNotBundle': '这个包装不进这里:{reason}。要是它其实是个 opencli 插件,请改用包名(例如 opencli-plugin-cmdb)或者本地目录 —— 从 git 地址和 tarball 判断不出它是什么。',
157
+ 'panels.market.add.problemNotWorkbenchModule': '这个包跟现在的工作台对不上,装上也用不了。换一个版本,或者找这个模块的作者。',
158
+ 'panels.market.add.problemNotModuleOrPlugin': '这个包既不是工作台的模块,也不是 opencli 的插件,装了也用不上。换一个版本,或者找这个包的作者。',
159
+ 'panels.market.add.failureNotModuleOrPlugin': '这个包既不是工作台的模块,也不是 opencli 的插件,装了也用不上',
160
+ 'panels.market.add.installedPlugin': '装上了:opencli 插件 {name}',
149
161
  'panels.market.add.problemNetwork': '无法连接模块源,请检查网络后重试',
150
162
  'panels.market.add.problemUnknown': '无法获取模块信息:{reason}',
151
163
  'panels.market.add.failureNetwork': '网络连接失败',
@@ -176,6 +188,7 @@ export const zh = {
176
188
  'panels.market.add.registryPnpm': 'pnpm 自己配的源',
177
189
  'panels.market.add.check': '预检',
178
190
  'panels.market.add.checking': '正在预检…',
191
+ 'panels.market.add.classifying': '判定中…',
179
192
  'panels.market.add.change': '改一下',
180
193
  'panels.market.add.previewTitle': '将要安装',
181
194
  'panels.market.add.fieldName': '包名',
package/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@ export * from './api/host-write.js';
8
8
  export * from './api/long-task.js';
9
9
  export * from './api/unit-board.js';
10
10
  export * from './api/unit.js';
11
+ export * from './api/unit-declaration.js';
11
12
  export * from './api/workbench-client.js';
12
13
  export * from './api/workbench-contract.js';
13
14
  export * from './components/Activity.js';
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ export * from './api/host-write.js';
8
8
  export * from './api/long-task.js';
9
9
  export * from './api/unit-board.js';
10
10
  export * from './api/unit.js';
11
+ export * from './api/unit-declaration.js';
11
12
  export * from './api/workbench-client.js';
12
13
  export * from './api/workbench-contract.js';
13
14
  export * from './components/Activity.js';
@@ -0,0 +1,2 @@
1
+ import type { UnitManifest } from '../api/unit.js';
2
+ export declare function declarationOfPackage(pkg: string, packageJson: URL | string): UnitManifest;
@@ -0,0 +1,21 @@
1
+ import fs from 'node:fs';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { readDeclaration } from '../api/unit-declaration.js';
4
+ export function declarationOfPackage(pkg, packageJson) {
5
+ const at = typeof packageJson === 'string' ? packageJson : fileURLToPath(packageJson);
6
+ let parsed;
7
+ try {
8
+ parsed = JSON.parse(fs.readFileSync(at, 'utf8'));
9
+ }
10
+ catch (error) {
11
+ throw new Error(`${pkg}: 读不动 ${at} —— ${String(error)}`, { cause: error });
12
+ }
13
+ const declared = typeof parsed === 'object' && parsed !== null
14
+ ? parsed.dsh?.workbench
15
+ : undefined;
16
+ const read = readDeclaration(pkg, declared);
17
+ if (read.manifest === undefined) {
18
+ throw new Error(`${pkg} 的 dsh.workbench 读不出来:${read.problems.join(';')}`);
19
+ }
20
+ return read.manifest;
21
+ }
package/dist/testing.d.ts CHANGED
@@ -1 +1,2 @@
1
1
  export * from './testing/host-fakes.js';
2
+ export * from './testing/declaration.js';
package/dist/testing.js CHANGED
@@ -1 +1,2 @@
1
1
  export * from './testing/host-fakes.js';
2
+ export * from './testing/declaration.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-workbench/shared",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "description": "工作台与模块共享的纯代码与契约类型:类型、纯逻辑、哑组件、平台解析。宿主侧的东西从 /host 进(不影响浏览器产物)。",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -0,0 +1,52 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { declarationProblemsOf, readDeclaration } from '../unit-declaration.js';
3
+
4
+ const declared = (tools: unknown) => ({
5
+ apiVersion: 1,
6
+ unit: 'demo',
7
+ card: null,
8
+ tools,
9
+ host: { inject: [] },
10
+ });
11
+
12
+ describe('清单里的工具声明', () => {
13
+ it('key 是可选的:不给就只是没有生成物可对', () => {
14
+ expect(declarationProblemsOf('@t/demo', declared([{ name: 'workbench_demo_sync', writes: true }]))).toEqual([]);
15
+ });
16
+
17
+ it('给了 key 就得是能当代码键名的 lowerCamelCase', () => {
18
+ expect(
19
+ declarationProblemsOf('@t/demo', declared([{ name: 'workbench_demo_sync', writes: true, key: 'sync' }])),
20
+ ).toEqual([]);
21
+ expect(
22
+ declarationProblemsOf('@t/demo', declared([{ name: 'workbench_demo_sync', writes: true, key: 'podLogs' }])),
23
+ ).toEqual([]);
24
+
25
+ for (const bad of ['Sync', 'pod-logs', 'pod_logs', '1sync', '', 'pod logs', '__proto__']) {
26
+ const problems = declarationProblemsOf(
27
+ '@t/demo',
28
+ declared([{ name: 'workbench_demo_sync', writes: true, key: bad }]),
29
+ );
30
+ expect(problems.join('\n'), `key=${JSON.stringify(bad)} 该红`).toMatch(/\.key 要是 lowerCamelCase/);
31
+ }
32
+ });
33
+
34
+ it('★ key 撞了当场红(生成物里的键只能有一个,不然生成出来是静默覆盖)', () => {
35
+ const problems = declarationProblemsOf(
36
+ '@t/demo',
37
+ declared([
38
+ { name: 'workbench_demo_sync', writes: true, key: 'sync' },
39
+ { name: 'workbench_demo_apply', writes: true, key: 'sync' },
40
+ ]),
41
+ );
42
+
43
+ expect(problems.join('\n')).toMatch(/key「sync」与前面那条撞了/);
44
+ });
45
+
46
+ it('key 不进外壳那份清单(外壳不读它,它是给代码用的)', () => {
47
+ const read = readDeclaration('@t/demo', declared([{ name: 'workbench_demo_sync', writes: true, key: 'sync' }]));
48
+
49
+ expect(read.problems).toEqual([]);
50
+ expect(read.manifest?.tools).toEqual([{ name: 'workbench_demo_sync', writes: true }]);
51
+ });
52
+ });
@@ -0,0 +1,244 @@
1
+ import type { UnitEnvNeeds, UnitHostNeeds, UnitManifest } from './unit.js';
2
+
3
+ export const WORKBENCH_API_VERSION = 1;
4
+
5
+ export const SUPPORTED_API_VERSIONS: readonly number[] = [WORKBENCH_API_VERSION];
6
+
7
+ export interface UnitCardDeclaration {
8
+ readonly titleKey: string;
9
+ readonly order: number;
10
+ }
11
+
12
+ export interface UnitToolDeclaration {
13
+ readonly name: string;
14
+ readonly writes: boolean;
15
+ readonly key?: string;
16
+ }
17
+
18
+ export interface UnitDeclaration {
19
+ readonly apiVersion: number;
20
+ readonly unit: string;
21
+ readonly card: UnitCardDeclaration | null;
22
+ readonly tools: readonly UnitToolDeclaration[];
23
+ readonly host: UnitHostNeeds;
24
+ readonly env?: UnitEnvNeeds;
25
+ }
26
+
27
+ export const DECLARATION_KEYS: readonly string[] = [
28
+ 'apiVersion',
29
+ 'unit',
30
+ 'card',
31
+ 'tools',
32
+ 'host',
33
+ 'env',
34
+ ];
35
+
36
+ export const DECLARATION_REQUIRED_KEYS: readonly string[] = [
37
+ 'apiVersion',
38
+ 'unit',
39
+ 'card',
40
+ 'tools',
41
+ 'host',
42
+ ];
43
+
44
+ const CARD_KEYS = ['titleKey', 'order'];
45
+ const TOOL_KEYS = ['name', 'writes', 'key'];
46
+
47
+ const TOOL_KEY_SHAPE = /^[a-z][a-zA-Z0-9]*$/;
48
+ const HOST_KEYS = ['inject', 'optional'];
49
+
50
+ function text(value: unknown): string {
51
+ return typeof value === 'string' ? value.trim() : '';
52
+ }
53
+
54
+ function isRecord(value: unknown): value is Readonly<Record<string, unknown>> {
55
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
56
+ }
57
+
58
+ function outsideKeys(value: Readonly<Record<string, unknown>>, allowed: readonly string[]): string[] {
59
+ return Object.keys(value).filter((key) => !allowed.includes(key));
60
+ }
61
+
62
+ function listOf(keys: readonly string[]): string {
63
+ return keys.map((key) => JSON.stringify(key)).join(' / ');
64
+ }
65
+
66
+ function stringArrayProblems(at: string, value: unknown): string[] {
67
+ if (!Array.isArray(value)) return [`${at} 要是一串字符串,现在是 ${JSON.stringify(value)}`];
68
+ return value.flatMap((item, index) =>
69
+ text(item) === '' ? [`${at}[${index}] 不是非空字符串:${JSON.stringify(item)}`] : [],
70
+ );
71
+ }
72
+
73
+ export function declarationProblemsOf(subject: string, raw: unknown): readonly string[] {
74
+ if (!isRecord(raw)) return [`${subject}: dsh.workbench 不是一个对象 —— ${JSON.stringify(raw)}`];
75
+
76
+ const version = raw.apiVersion;
77
+ if (version === undefined) {
78
+ return [
79
+ `${subject}: dsh.workbench 没有 apiVersion —— 协议从 v${WORKBENCH_API_VERSION} 起,` +
80
+ `写 \`"apiVersion": ${WORKBENCH_API_VERSION}\`。缺它 = 外壳不认这个模块(ADR-0038 §2.3)`,
81
+ ];
82
+ }
83
+ if (!Number.isInteger(version) || !SUPPORTED_API_VERSIONS.includes(version as number)) {
84
+ return [
85
+ `${subject}: dsh.workbench.apiVersion 是 ${JSON.stringify(version)},这里只认 ` +
86
+ `${SUPPORTED_API_VERSIONS.join(' / ')}(要整数)`,
87
+ ];
88
+ }
89
+
90
+ const problems: string[] = [];
91
+
92
+ const unknown = outsideKeys(raw, DECLARATION_KEYS);
93
+ if (unknown.length > 0) {
94
+ problems.push(
95
+ `${subject}: dsh.workbench 里有协议 v${WORKBENCH_API_VERSION} 不认识的键 ${listOf(unknown)} —— ` +
96
+ `只有 ${listOf(DECLARATION_KEYS)}。(拼错的键会被外壳**静默忽略**:那句声明等于没写)`,
97
+ );
98
+ }
99
+
100
+ for (const key of DECLARATION_REQUIRED_KEYS) {
101
+ if (raw[key] === undefined) {
102
+ problems.push(
103
+ `${subject}: dsh.workbench 缺 ${key}` +
104
+ (key === 'card' ? '(这个模块没有卡片就写 `"card": null`)' : ''),
105
+ );
106
+ }
107
+ }
108
+
109
+ const card = raw.card;
110
+ if (card !== undefined && card !== null) {
111
+ if (!isRecord(card)) {
112
+ problems.push(`${subject}: dsh.workbench.card 要么是对象、要么是 null,现在是 ${JSON.stringify(card)}`);
113
+ } else {
114
+ const extra = outsideKeys(card, CARD_KEYS);
115
+ if (extra.length > 0) {
116
+ problems.push(
117
+ `${subject}: dsh.workbench.card 里有不认的键 ${listOf(extra)} —— card 只有 ${listOf(CARD_KEYS)}` +
118
+ `(卡片默认显不显示由运行期的面板配置决定,清单里没有这一位)`,
119
+ );
120
+ }
121
+ if (text(card.titleKey) === '') {
122
+ problems.push(`${subject}: dsh.workbench.card.titleKey 要是非空字符串(面板那一格的文案键)`);
123
+ }
124
+ if (typeof card.order !== 'number' || !Number.isFinite(card.order)) {
125
+ problems.push(`${subject}: dsh.workbench.card.order 要是数字,现在是 ${JSON.stringify(card.order)}`);
126
+ }
127
+ }
128
+ }
129
+
130
+ const tools = raw.tools;
131
+ if (tools !== undefined) {
132
+ if (!Array.isArray(tools)) {
133
+ problems.push(`${subject}: dsh.workbench.tools 要是一串声明,现在是 ${JSON.stringify(tools)}`);
134
+ } else {
135
+ const seen = new Set<string>();
136
+ const seenKeys = new Set<string>();
137
+ tools.forEach((tool, index) => {
138
+ const at = `dsh.workbench.tools[${index}]`;
139
+ if (!isRecord(tool)) {
140
+ problems.push(`${subject}: ${at} 要是对象,现在是 ${JSON.stringify(tool)}`);
141
+ return;
142
+ }
143
+ const extra = outsideKeys(tool, TOOL_KEYS);
144
+ if (extra.length > 0) {
145
+ problems.push(`${subject}: ${at} 里有不认的键 ${listOf(extra)} —— tools 只有 ${listOf(TOOL_KEYS)}`);
146
+ }
147
+ const name = text(tool.name);
148
+ if (name === '') {
149
+ problems.push(`${subject}: ${at}.name 要是非空字符串`);
150
+ } else if (seen.has(name)) {
151
+ problems.push(`${subject}: ${at}.name「${name}」与前面那条撞了 —— 工具名会进 dsh 的工具表,不许重`);
152
+ } else {
153
+ seen.add(name);
154
+ }
155
+ if (typeof tool.writes !== 'boolean') {
156
+ problems.push(
157
+ `${subject}: ${at}.writes 要是 true / false(它是"这会写"的唯一声明,缺了审批无从认领)`,
158
+ );
159
+ }
160
+ if (tool.key !== undefined) {
161
+ const key = text(tool.key);
162
+ if (!TOOL_KEY_SHAPE.test(key)) {
163
+ problems.push(
164
+ `${subject}: ${at}.key 要是 lowerCamelCase 的标识符(如 \`sync\` / \`podLogs\`),` +
165
+ `现在是 ${JSON.stringify(tool.key)} —— 它会变成生成物里的键名,所以不引号、不许带点或横线`,
166
+ );
167
+ } else if (seenKeys.has(key)) {
168
+ problems.push(`${subject}: ${at}.key「${key}」与前面那条撞了 —— 生成物里的键只能有一个`);
169
+ } else {
170
+ seenKeys.add(key);
171
+ }
172
+ }
173
+ });
174
+ }
175
+ }
176
+
177
+ const host = raw.host;
178
+ if (host !== undefined) {
179
+ if (!isRecord(host)) {
180
+ problems.push(`${subject}: dsh.workbench.host 要是对象,现在是 ${JSON.stringify(host)}`);
181
+ } else {
182
+ const extra = outsideKeys(host, HOST_KEYS);
183
+ if (extra.length > 0) {
184
+ problems.push(`${subject}: dsh.workbench.host 里有不认的键 ${listOf(extra)} —— host 只有 ${listOf(HOST_KEYS)}`);
185
+ }
186
+ if (host.inject === undefined) {
187
+ problems.push(
188
+ `${subject}: dsh.workbench.host.inject 没写 —— 它声明这个模块要外壳的哪几个服务,` +
189
+ `外壳靠它判"能不能挂"。一个都不要也要写 \`"inject": []\``,
190
+ );
191
+ } else {
192
+ problems.push(...stringArrayProblems(`${subject}: dsh.workbench.host.inject`, host.inject));
193
+ }
194
+ if (host.optional !== undefined) {
195
+ problems.push(...stringArrayProblems(`${subject}: dsh.workbench.host.optional`, host.optional));
196
+ }
197
+ }
198
+ }
199
+
200
+ if (raw.env !== undefined && !isRecord(raw.env)) {
201
+ problems.push(
202
+ `${subject}: dsh.workbench.env 要是对象(command / plugins / libs)—— ` +
203
+ `语义由门禁的 env 判据查,这里只查形状`,
204
+ );
205
+ }
206
+
207
+ return problems;
208
+ }
209
+
210
+ export interface DeclarationRead {
211
+ readonly declaration?: UnitDeclaration;
212
+ readonly manifest?: UnitManifest;
213
+ readonly problems: readonly string[];
214
+ }
215
+
216
+ export function readDeclaration(subject: string, raw: unknown): DeclarationRead {
217
+ const problems = declarationProblemsOf(subject, raw);
218
+ if (problems.length > 0) return { problems };
219
+ const declaration = raw as UnitDeclaration;
220
+ const card = declaration.card;
221
+ return {
222
+ declaration,
223
+ problems: [],
224
+ manifest: {
225
+ id: text(declaration.unit),
226
+ card:
227
+ card === null
228
+ ? null
229
+ : {
230
+ titleKey: text(card.titleKey),
231
+ order: card.order,
232
+ },
233
+ tools: declaration.tools.map((tool) => ({
234
+ name: text(tool.name),
235
+ writes: tool.writes,
236
+ })),
237
+ host: {
238
+ inject: [...(declaration.host.inject ?? [])],
239
+ ...(declaration.host.optional === undefined ? {} : { optional: [...declaration.host.optional] }),
240
+ },
241
+ ...(declaration.env === undefined ? {} : { env: declaration.env }),
242
+ },
243
+ };
244
+ }
package/src/api/unit.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  export interface UnitCard {
2
2
  readonly titleKey: string;
3
3
  readonly order: number;
4
- readonly enabledByDefault: boolean;
5
4
  }
6
5
 
7
6
  export interface UnitTool {
@@ -51,7 +50,6 @@ export interface PluginRelease {
51
50
 
52
51
  export interface UnitManifest {
53
52
  readonly id: string;
54
- readonly kind: 'board' | 'ui';
55
53
  readonly card: UnitCard | null;
56
54
  readonly tools: readonly UnitTool[];
57
55
  readonly host: UnitHostNeeds;
@@ -8,7 +8,9 @@ export type ProvisionAction =
8
8
  | 'install-bundle'
9
9
  | 'update-bundle'
10
10
  | 'remove-bundle'
11
- | 'cancel-install';
11
+ | 'cancel-install'
12
+ | 'update-plugin'
13
+ | 'uninstall-plugin';
12
14
 
13
15
  export interface PluginStatePayload {
14
16
  readonly name: string;
@@ -57,7 +57,7 @@ export function ApprovalDetail(props: ApprovalDetailProps) {
57
57
  if (owner?.approval !== undefined) {
58
58
  const Render = owner.approval.render;
59
59
  return (
60
- <CardBoundary unit={owner.manifest.id} resetKey={props.callId ?? ''} t={t}>
60
+ <CardBoundary unit={owner.unit} resetKey={props.callId ?? ''} t={t}>
61
61
  <Render input={input} t={t} />
62
62
  </CardBoundary>
63
63
  );
@@ -44,13 +44,12 @@ export interface BoardModule {
44
44
  readonly id: string;
45
45
  readonly titleKey: string;
46
46
  readonly order: number;
47
- readonly enabledByDefault: boolean;
48
47
  readonly View: (props: BoardModuleProps) => ReactNode;
49
48
  readonly steps?: Readonly<Record<string, UnitStep>>;
50
49
  }
51
50
 
52
51
  export interface UnitClientFace {
53
- readonly manifest: UnitManifest;
52
+ readonly unit: string;
54
53
  readonly card: (props: BoardModuleProps) => ReactNode;
55
54
  readonly steps?: Readonly<Record<string, UnitStep>>;
56
55
  readonly messages?: Dictionaries;
@@ -62,6 +61,10 @@ export interface UnitClientFace {
62
61
  | undefined;
63
62
  }
64
63
 
64
+ export interface UnitOwnedClientFace extends UnitClientFace {
65
+ readonly manifest: UnitManifest;
66
+ }
67
+
65
68
  export interface ApprovalInput {
66
69
  readonly tool: string;
67
70
  readonly args: Readonly<Record<string, unknown>>;