@dsh-workbench/shared 0.3.11 → 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (403) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +97 -0
  3. package/dist/api/blocker-table.d.ts +3 -3
  4. package/dist/api/blocker-table.js +47 -23
  5. package/dist/api/dsh.d.ts +1 -1
  6. package/dist/api/workbench-client.js +2 -1
  7. package/dist/components/Activity.css +0 -1
  8. package/dist/components/ApprovalDetail.css +1 -2
  9. package/dist/components/ApprovalDetail.d.ts +1 -1
  10. package/dist/components/ApprovalPlan.css +0 -1
  11. package/dist/components/Card.css +0 -1
  12. package/dist/components/Card.d.ts +1 -1
  13. package/dist/components/CardBoundary.d.ts +1 -1
  14. package/dist/components/CardHead.css +0 -1
  15. package/dist/components/CardHead.d.ts +1 -1
  16. package/dist/components/ChoiceGroup.css +1 -2
  17. package/dist/components/ConfirmBar.d.ts +1 -1
  18. package/dist/components/ConfirmBar.js +1 -1
  19. package/dist/components/Form.css +1 -1
  20. package/dist/components/FormActions.js +1 -1
  21. package/dist/components/FormNodes.js +5 -1
  22. package/dist/components/FormRows.css +1 -0
  23. package/dist/components/FormRows.d.ts +4 -2
  24. package/dist/components/FormRows.js +5 -4
  25. package/dist/components/LongTaskRow.css +1 -2
  26. package/dist/components/LongTaskRow.d.ts +1 -1
  27. package/dist/components/LongTaskRow.js +1 -1
  28. package/dist/components/Note.css +0 -1
  29. package/dist/components/ResourceStates.css +0 -1
  30. package/dist/components/SettingsSection.css +0 -1
  31. package/dist/components/Spinner.css +0 -1
  32. package/dist/components/Stack.css +0 -1
  33. package/dist/components/StateBlock.css +0 -1
  34. package/dist/components/board-module.d.ts +3 -3
  35. package/dist/components/icons.d.ts +1 -1
  36. package/dist/components/icons.js +11 -4
  37. package/dist/config/constants.d.ts +2 -4
  38. package/dist/config/constants.js +2 -7
  39. package/dist/contract/adapters.d.ts +10 -0
  40. package/dist/contract/cli.d.ts +3 -0
  41. package/dist/contract/command.d.ts +22 -0
  42. package/dist/contract/data.d.ts +127 -0
  43. package/dist/contract/extension.d.ts +14 -0
  44. package/dist/contract/extension.js +1 -0
  45. package/dist/{api → contract}/facts.d.ts +12 -3
  46. package/dist/{api → contract}/facts.js +19 -1
  47. package/dist/contract/git.d.ts +38 -0
  48. package/dist/contract/git.js +1 -0
  49. package/dist/contract/lib.d.ts +4 -0
  50. package/dist/contract/lib.js +1 -0
  51. package/dist/contract/needs.d.ts +132 -0
  52. package/dist/contract/needs.js +232 -0
  53. package/dist/contract/plugin-source.d.ts +1 -0
  54. package/dist/contract/plugin-source.js +1 -0
  55. package/dist/contract/protocol/bundle-install.d.ts +136 -0
  56. package/dist/contract/protocol/bundle-install.js +12 -0
  57. package/dist/contract/protocol/config-sync.d.ts +55 -0
  58. package/dist/contract/protocol/config-sync.js +2 -0
  59. package/dist/contract/protocol/diy.d.ts +4 -0
  60. package/dist/contract/protocol/diy.js +4 -0
  61. package/dist/contract/protocol/dsh-plugins.d.ts +11 -0
  62. package/dist/contract/protocol/dsh-plugins.js +1 -0
  63. package/dist/contract/protocol/long-tasks.d.ts +13 -0
  64. package/dist/contract/protocol/long-tasks.js +1 -0
  65. package/dist/contract/protocol/market-index.d.ts +195 -0
  66. package/dist/contract/protocol/market-index.js +7 -0
  67. package/dist/contract/protocol/platform.d.ts +44 -0
  68. package/dist/contract/protocol/platform.js +1 -0
  69. package/dist/contract/protocol/preflight-wake.d.ts +7 -0
  70. package/dist/contract/protocol/preflight-wake.js +1 -0
  71. package/dist/contract/protocol/profile-create.d.ts +30 -0
  72. package/dist/contract/protocol/profile-create.js +2 -0
  73. package/dist/contract/protocol/sidebar-edition.d.ts +14 -0
  74. package/dist/contract/protocol/sidebar-edition.js +2 -0
  75. package/dist/contract/protocol/unit-manifests.d.ts +6 -0
  76. package/dist/contract/protocol/unit-manifests.js +1 -0
  77. package/dist/contract/unit-board.js +1 -0
  78. package/dist/contract/unit-capability.d.ts +34 -0
  79. package/dist/contract/unit-capability.js +1 -0
  80. package/dist/{host → contract}/unit-declaration.d.ts +7 -5
  81. package/dist/{api → contract}/unit-declaration.js +19 -13
  82. package/dist/contract/unit.d.ts +57 -0
  83. package/dist/contract/unit.js +24 -0
  84. package/dist/contract/workbench-contract.d.ts +15 -0
  85. package/dist/contract/workbench-contract.js +1 -0
  86. package/dist/host/adapter-set.d.ts +26 -0
  87. package/dist/host/adapter-set.js +244 -0
  88. package/dist/host/adapter-types.d.ts +79 -0
  89. package/dist/host/adapter-types.js +23 -0
  90. package/dist/host/adapters.d.ts +28 -0
  91. package/dist/host/adapters.js +9 -0
  92. package/dist/host/browser.d.ts +3 -0
  93. package/dist/host/browser.js +3 -0
  94. package/dist/host/changelog.d.ts +21 -0
  95. package/dist/host/changelog.js +188 -0
  96. package/dist/host/channel-plugin.d.ts +15 -0
  97. package/dist/host/channel-plugin.js +42 -0
  98. package/dist/host/cli.d.ts +4 -0
  99. package/dist/host/cli.js +7 -0
  100. package/dist/host/command-name.d.ts +1 -0
  101. package/dist/host/command-name.js +14 -0
  102. package/dist/host/data-error.d.ts +13 -0
  103. package/dist/host/data-error.js +20 -0
  104. package/dist/host/data-root.js +3 -0
  105. package/dist/host/diy-debug.js +2 -1
  106. package/dist/host/diy-record.js +2 -1
  107. package/dist/host/extension.d.ts +7 -0
  108. package/dist/host/extension.js +39 -0
  109. package/dist/host/facts.d.ts +1 -1
  110. package/dist/host/facts.js +3 -2
  111. package/dist/host/git.d.ts +2 -33
  112. package/dist/host/git.js +2 -2
  113. package/dist/host/http.js +2 -2
  114. package/dist/host/lib-capabilities.d.ts +22 -0
  115. package/dist/host/lib-capabilities.js +37 -0
  116. package/dist/host/lib-install.d.ts +9 -0
  117. package/dist/host/lib-install.js +10 -0
  118. package/dist/host/long-task.d.ts +1 -1
  119. package/dist/host/long-task.js +4 -3
  120. package/dist/host/market-index.d.ts +22 -0
  121. package/dist/host/market-index.js +379 -0
  122. package/dist/host/needs.d.ts +4 -0
  123. package/dist/host/needs.js +2 -0
  124. package/dist/host/patch.d.ts +9 -0
  125. package/dist/host/patch.js +78 -0
  126. package/dist/host/paths.d.ts +12 -0
  127. package/dist/host/paths.js +28 -0
  128. package/dist/host/platform.d.ts +14 -0
  129. package/dist/host/platform.js +40 -0
  130. package/dist/host/plugin-source.d.ts +8 -6
  131. package/dist/host/plugin-source.js +43 -33
  132. package/dist/host/plugin.d.ts +4 -0
  133. package/dist/host/plugin.js +39 -0
  134. package/dist/host/port-types.d.ts +192 -0
  135. package/dist/host/port-types.js +1 -0
  136. package/dist/host/preflight.d.ts +28 -0
  137. package/dist/host/preflight.js +1 -0
  138. package/dist/host/profile-port.d.ts +4 -0
  139. package/dist/host/profile-port.js +4 -0
  140. package/dist/host/provision.d.ts +2 -2
  141. package/dist/host/provision.js +5 -3
  142. package/dist/host/recipe-rules.d.ts +5 -0
  143. package/dist/host/recipe-rules.js +382 -0
  144. package/dist/host/recipe.d.ts +14 -0
  145. package/dist/host/recipe.js +283 -0
  146. package/dist/host/release-packages.generated.d.ts +1 -0
  147. package/dist/host/release-packages.generated.js +10 -0
  148. package/dist/host/shell-text.d.ts +9 -2
  149. package/dist/host/shell-text.js +15 -3
  150. package/dist/host/shell.d.ts +2 -0
  151. package/dist/host/shell.js +7 -0
  152. package/dist/host/spec.d.ts +8 -0
  153. package/dist/host/spec.js +24 -0
  154. package/dist/host/supply-index.d.ts +34 -0
  155. package/dist/host/supply-index.js +202 -0
  156. package/dist/host/supply-port.d.ts +5 -0
  157. package/dist/host/supply-port.js +39 -0
  158. package/dist/host/time.d.ts +12 -0
  159. package/dist/host/time.js +19 -0
  160. package/dist/host/tokens.d.ts +26 -0
  161. package/dist/host/tokens.js +154 -0
  162. package/dist/host/tool-kit.d.ts +0 -6
  163. package/dist/host/tool-kit.js +0 -22
  164. package/dist/host/unit-face.d.ts +12 -4
  165. package/dist/host/version-range.d.ts +2 -0
  166. package/dist/host/version-range.js +13 -0
  167. package/dist/host/version.d.ts +4 -0
  168. package/dist/host/version.js +27 -0
  169. package/dist/host.d.ts +53 -16
  170. package/dist/host.js +53 -16
  171. package/dist/i18n/en.js +225 -71
  172. package/dist/i18n/index.d.ts +299 -73
  173. package/dist/i18n/merge.js +1 -1
  174. package/dist/i18n/need-text.d.ts +26 -0
  175. package/dist/i18n/need-text.js +26 -0
  176. package/dist/i18n/settings.d.ts +70 -0
  177. package/dist/i18n/settings.js +136 -0
  178. package/dist/i18n/zh.d.ts +298 -72
  179. package/dist/i18n/zh.js +227 -73
  180. package/dist/index.d.ts +27 -7
  181. package/dist/index.js +27 -7
  182. package/dist/{api → lib}/dsh-client.d.ts +11 -1
  183. package/dist/lib/dsh-client.js +1 -0
  184. package/dist/lib/market-index.d.ts +7 -0
  185. package/dist/lib/market-index.js +25 -0
  186. package/dist/lib/react.d.ts +0 -40
  187. package/dist/lib/react.js +0 -36
  188. package/dist/lib/ui.js +3 -9
  189. package/dist/lib/version.d.ts +4 -0
  190. package/dist/lib/version.js +21 -0
  191. package/dist/testing/data.d.ts +13 -0
  192. package/dist/testing/data.js +46 -0
  193. package/dist/testing/declaration.d.ts +1 -1
  194. package/dist/testing/declaration.js +1 -1
  195. package/dist/testing/host-fakes.d.ts +0 -13
  196. package/dist/testing/host-fakes.js +0 -24
  197. package/dist/testing/needs.d.ts +16 -0
  198. package/dist/testing/needs.js +46 -0
  199. package/dist/testing.d.ts +2 -0
  200. package/dist/testing.js +2 -0
  201. package/package.json +22 -4
  202. package/src/api/__tests__/blocker-table.test.ts +135 -86
  203. package/src/api/blocker-table.ts +52 -26
  204. package/src/api/dsh.ts +1 -1
  205. package/src/api/host-write.ts +0 -1
  206. package/src/api/workbench-client.test.ts +2 -2
  207. package/src/api/workbench-client.ts +2 -1
  208. package/src/components/Activity.css +0 -1
  209. package/src/components/ApprovalDetail.css +1 -2
  210. package/src/components/ApprovalDetail.tsx +1 -1
  211. package/src/components/ApprovalPlan.css +0 -1
  212. package/src/components/Card.css +0 -1
  213. package/src/components/Card.tsx +1 -1
  214. package/src/components/CardAnchor.test.tsx +1 -1
  215. package/src/components/CardBoundary.tsx +1 -1
  216. package/src/components/CardHead.css +0 -1
  217. package/src/components/CardHead.tsx +1 -1
  218. package/src/components/ChoiceGroup.css +1 -2
  219. package/src/components/ConfirmBar.tsx +1 -1
  220. package/src/components/Form.css +1 -1
  221. package/src/components/Form.test.tsx +6 -0
  222. package/src/components/Form.tsx +1 -1
  223. package/src/components/FormActions.tsx +1 -1
  224. package/src/components/FormNodes.tsx +6 -1
  225. package/src/components/FormRows.css +1 -0
  226. package/src/components/FormRows.test.tsx +30 -0
  227. package/src/components/FormRows.tsx +9 -5
  228. package/src/components/LongTaskRow.css +1 -2
  229. package/src/components/LongTaskRow.tsx +1 -1
  230. package/src/components/Note.css +0 -1
  231. package/src/components/ResourceStates.css +0 -1
  232. package/src/components/SelectFieldNodes.tsx +0 -1
  233. package/src/components/SettingsSection.css +0 -1
  234. package/src/components/Spinner.css +0 -1
  235. package/src/components/Stack.css +0 -1
  236. package/src/components/StateBlock.css +0 -1
  237. package/src/components/__tests__/long-task-row.test.ts +1 -1
  238. package/src/components/board-module.ts +3 -3
  239. package/src/components/icons.tsx +16 -8
  240. package/src/config/constants.ts +2 -8
  241. package/src/{api → contract}/__tests__/facts.test.ts +73 -25
  242. package/src/{api → contract}/__tests__/long-task.test.ts +3 -3
  243. package/src/contract/__tests__/needs.test.ts +74 -0
  244. package/src/{host → contract}/__tests__/unit-declaration.test.ts +1 -1
  245. package/src/contract/__tests__/unit.test.ts +43 -0
  246. package/src/contract/adapters.ts +9 -0
  247. package/src/contract/cli.ts +3 -0
  248. package/src/contract/command.ts +25 -0
  249. package/src/contract/data.ts +146 -0
  250. package/src/contract/extension.ts +20 -0
  251. package/src/{api → contract}/facts.ts +34 -4
  252. package/src/contract/git.ts +20 -0
  253. package/src/contract/lib.ts +5 -0
  254. package/src/contract/needs.ts +331 -0
  255. package/src/contract/plugin-source.ts +1 -0
  256. package/src/contract/protocol/bundle-install.ts +198 -0
  257. package/src/contract/protocol/config-sync.ts +65 -0
  258. package/src/contract/protocol/diy.ts +4 -0
  259. package/src/contract/protocol/dsh-plugins.ts +13 -0
  260. package/src/contract/protocol/long-tasks.ts +16 -0
  261. package/src/contract/protocol/market-index.ts +246 -0
  262. package/src/contract/protocol/platform.ts +47 -0
  263. package/src/contract/protocol/preflight-wake.ts +17 -0
  264. package/src/contract/protocol/profile-create.ts +36 -0
  265. package/src/contract/protocol/sidebar-edition.ts +19 -0
  266. package/src/contract/protocol/unit-manifests.ts +8 -0
  267. package/src/{api → contract}/unit-board.ts +0 -1
  268. package/src/contract/unit-capability.ts +37 -0
  269. package/src/{host → contract}/unit-declaration.ts +22 -18
  270. package/src/contract/unit.ts +76 -0
  271. package/src/contract/workbench-contract.ts +30 -0
  272. package/src/host/__tests__/adapter-set.test.ts +228 -0
  273. package/src/host/__tests__/adapters.test.ts +27 -0
  274. package/src/host/__tests__/changelog.test.ts +102 -0
  275. package/src/host/__tests__/command-name.test.ts +16 -0
  276. package/src/host/__tests__/diy-record.test.ts +3 -3
  277. package/src/host/__tests__/facts.test.ts +5 -5
  278. package/src/host/__tests__/long-task.test.ts +1 -1
  279. package/src/host/__tests__/market-index.test.ts +179 -0
  280. package/src/host/__tests__/plugin-ledger.test.ts +10 -10
  281. package/src/host/__tests__/provision.test.ts +7 -6
  282. package/src/host/__tests__/shell-text.test.ts +16 -1
  283. package/src/host/__tests__/supply-index.test.ts +137 -0
  284. package/src/host/__tests__/tokens.test.ts +110 -0
  285. package/src/host/__tests__/version-range.test.ts +21 -0
  286. package/src/host/adapter-set.ts +299 -0
  287. package/src/host/adapter-types.ts +105 -0
  288. package/src/host/adapters.ts +50 -0
  289. package/src/host/browser.ts +3 -0
  290. package/src/host/changelog.ts +212 -0
  291. package/src/host/channel-plugin.ts +55 -0
  292. package/src/host/cli.ts +11 -0
  293. package/src/host/command-name.ts +15 -0
  294. package/src/host/data-error.ts +21 -0
  295. package/src/host/data-root.ts +2 -0
  296. package/src/host/diy-debug.ts +2 -1
  297. package/src/host/diy-record.ts +2 -1
  298. package/src/host/extension.test.ts +55 -0
  299. package/src/host/extension.ts +57 -0
  300. package/src/host/facts.ts +4 -3
  301. package/src/host/git.ts +8 -16
  302. package/src/host/http.ts +2 -2
  303. package/src/host/lib-capabilities.ts +52 -0
  304. package/src/host/lib-install.ts +18 -0
  305. package/src/host/long-task.ts +4 -3
  306. package/src/host/market-index.ts +383 -0
  307. package/src/host/needs.ts +6 -0
  308. package/src/host/patch.ts +93 -0
  309. package/src/host/paths.ts +37 -0
  310. package/src/host/platform.ts +48 -0
  311. package/src/host/plugin-source.ts +48 -56
  312. package/src/host/plugin.ts +39 -0
  313. package/src/host/port-types.ts +223 -0
  314. package/src/host/preflight.ts +31 -0
  315. package/src/host/profile-port.ts +9 -0
  316. package/src/host/provision.ts +7 -4
  317. package/src/host/recipe-rules.ts +330 -0
  318. package/src/host/recipe.test.ts +334 -0
  319. package/src/host/recipe.ts +323 -0
  320. package/src/host/release-packages.generated.ts +10 -0
  321. package/src/host/shell-text.ts +15 -3
  322. package/src/host/shell.ts +9 -0
  323. package/src/host/spec.ts +32 -0
  324. package/src/host/supply-index.ts +256 -0
  325. package/src/host/supply-port.ts +45 -0
  326. package/src/host/time.ts +22 -0
  327. package/src/host/tokens.ts +186 -0
  328. package/src/host/tool-kit.ts +0 -35
  329. package/src/host/unit-face.ts +12 -4
  330. package/src/host/version-range.ts +13 -0
  331. package/src/host/version.ts +30 -0
  332. package/src/host.ts +53 -16
  333. package/src/i18n/en.ts +225 -71
  334. package/src/i18n/merge.test.ts +6 -6
  335. package/src/i18n/merge.ts +1 -1
  336. package/src/i18n/need-text.ts +27 -0
  337. package/src/i18n/settings.ts +141 -0
  338. package/src/i18n/zh.ts +228 -73
  339. package/src/index.ts +27 -7
  340. package/src/lib/blocker-action.ts +0 -1
  341. package/src/{api → lib}/dsh-client.ts +12 -1
  342. package/src/lib/market-index.ts +36 -0
  343. package/src/lib/react.ts +0 -40
  344. package/src/lib/text.test.ts +2 -2
  345. package/src/lib/ui.test.ts +6 -5
  346. package/src/lib/ui.ts +3 -9
  347. package/src/testing/__tests__/declaration.test.ts +1 -1
  348. package/src/testing/data.ts +66 -0
  349. package/src/testing/declaration.ts +2 -2
  350. package/src/testing/host-fakes.ts +0 -38
  351. package/src/testing/needs.ts +80 -0
  352. package/src/testing.ts +2 -0
  353. package/dist/api/unit-declaration.d.ts +0 -65
  354. package/dist/api/unit.d.ts +0 -85
  355. package/dist/api/unit.js +0 -8
  356. package/dist/api/workbench-contract.d.ts +0 -44
  357. package/dist/components/MockStrip.css +0 -39
  358. package/dist/components/MockStrip.d.ts +0 -15
  359. package/dist/components/MockStrip.js +0 -8
  360. package/dist/components/SaveBar.css +0 -7
  361. package/dist/host/browser-find.d.ts +0 -38
  362. package/dist/host/browser-find.js +0 -242
  363. package/dist/host/browser-read.d.ts +0 -28
  364. package/dist/host/browser-read.js +0 -243
  365. package/dist/host/opencli-runtime.d.ts +0 -25
  366. package/dist/host/opencli-runtime.js +0 -56
  367. package/dist/host/opencli-window.d.ts +0 -2
  368. package/dist/host/opencli-window.js +0 -8
  369. package/dist/host/opencli.d.ts +0 -37
  370. package/dist/host/opencli.js +0 -155
  371. package/dist/host/unit-declaration.js +0 -213
  372. package/dist/host/unit-libs.d.ts +0 -37
  373. package/dist/lib/diy-debug.d.ts +0 -80
  374. package/dist/lib/diy-debug.js +0 -88
  375. package/dist/lib/diy-record.d.ts +0 -135
  376. package/dist/lib/diy-record.js +0 -162
  377. package/dist/lib/lint-probe.d.ts +0 -1
  378. package/dist/lib/lint-probe.js +0 -6
  379. package/dist/lib/panel-state.d.ts +0 -120
  380. package/dist/lib/panel-state.js +0 -176
  381. package/src/api/__tests__/unit.test.ts +0 -38
  382. package/src/api/unit.ts +0 -93
  383. package/src/api/workbench-contract.ts +0 -59
  384. package/src/host/__tests__/browser-find.test.ts +0 -330
  385. package/src/host/__tests__/browser-read.test.ts +0 -228
  386. package/src/host/__tests__/opencli-runtime.test.ts +0 -46
  387. package/src/host/browser-find.ts +0 -311
  388. package/src/host/browser-read.ts +0 -311
  389. package/src/host/opencli-runtime.ts +0 -79
  390. package/src/host/opencli-window.ts +0 -11
  391. package/src/host/opencli.ts +0 -213
  392. package/src/host/unit-libs.ts +0 -41
  393. /package/dist/{api/blocker.js → contract/adapters.js} +0 -0
  394. /package/dist/{api → contract}/blocker.d.ts +0 -0
  395. /package/dist/{api/dsh-client.js → contract/blocker.js} +0 -0
  396. /package/dist/{api/unit-board.js → contract/cli.js} +0 -0
  397. /package/dist/{api/workbench-contract.js → contract/command.js} +0 -0
  398. /package/dist/{host/unit-libs.js → contract/data.js} +0 -0
  399. /package/dist/{api → contract}/long-task.d.ts +0 -0
  400. /package/dist/{api → contract}/long-task.js +0 -0
  401. /package/dist/{api → contract}/unit-board.d.ts +0 -0
  402. /package/src/{api → contract}/blocker.ts +0 -0
  403. /package/src/{api → contract}/long-task.ts +0 -0
@@ -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,4 +1,3 @@
1
-
2
1
  .wb-longTask {
3
2
  padding-top: var(--wb-len-12);
4
3
  border-top: 0.5px solid var(--dsw-alias-border-l2);
@@ -97,4 +96,4 @@
97
96
  line-height: var(--wb-line-16);
98
97
  color: var(--dsw-alias-state-error-primary);
99
98
  overflow-wrap: anywhere;
100
- }
99
+ }
@@ -1,4 +1,4 @@
1
- import { longTaskDot, longTaskStopPress, longTaskStopStage, LONG_TASK_STOP_ARM_MS, type LongTaskStatus } from '../api/long-task.js';
1
+ import { longTaskDot, longTaskStopPress, longTaskStopStage, LONG_TASK_STOP_ARM_MS, type LongTaskStatus } from '../contract/long-task.js';
2
2
  import { Link } from './Link.js';
3
3
  import { Fragment, h, useEffect, useState } from '../lib/react.js';
4
4
  import {
@@ -1,4 +1,3 @@
1
-
2
1
  .wb-note {
3
2
  margin: 0;
4
3
  font-size: var(--dsw-font-xxs-12-font-size);
@@ -1,4 +1,3 @@
1
-
2
1
  .wb-centered.wb-centeredCompact {
3
2
  flex: none;
4
3
  min-height: 0;
@@ -98,4 +98,3 @@ export function selectLoadingNode(args: {
98
98
  </button>
99
99
  );
100
100
  }
101
-
@@ -1,4 +1,3 @@
1
-
2
1
  section.wb-settings {
3
2
  display: flex;
4
3
  flex-direction: column;
@@ -1,4 +1,3 @@
1
-
2
1
  .wb-spinner {
3
2
  display: inline-flex;
4
3
  align-items: center;
@@ -1,4 +1,3 @@
1
-
2
1
  .wb-stack {
3
2
  display: flex;
4
3
  flex-direction: column;
@@ -1,4 +1,3 @@
1
-
2
1
  .wb-stateBlock {
3
2
  display: flex;
4
3
  flex-direction: column;
@@ -43,7 +43,7 @@ describe('两按停止钮:接线(这仓的组件测试是 node 环境,读
43
43
  });
44
44
 
45
45
  it('★★ 上膛后 3 秒自己落膛:用 LONG_TASK_STOP_ARM_MS,卸载 / 落膛都清掉计时器', () => {
46
- expect(ROW).toContain("from '../api/long-task.js'");
46
+ expect(ROW).toContain("from '../contract/long-task.js'");
47
47
  expect(ROW).toContain('setTimeout(() => setArmed(false), LONG_TASK_STOP_ARM_MS)');
48
48
  expect(ROW).toContain('clearTimeout(timer)');
49
49
  expect(ROW).toContain('}, [armed]);');
@@ -1,7 +1,7 @@
1
1
  import type { ReactNode } from 'react';
2
- import type { SessionsSnapshot, Translate, WorkspaceSnapshot, WorkspaceView } from '../api/dsh-client.js';
3
- import type { UnitManifest } from '../api/unit.js';
4
- import type { CapabilityPayload } from '../api/workbench-contract.js';
2
+ import type { SessionsSnapshot, Translate, WorkspaceSnapshot, WorkspaceView } from '../lib/dsh-client.js';
3
+ import type { UnitManifest } from '../contract/unit.js';
4
+ import type { CapabilityPayload } from '../contract/workbench-contract.js';
5
5
  import type { Dictionaries } from '../i18n/index.js';
6
6
 
7
7
  /**
@@ -51,20 +51,28 @@ export function IconConversation({ size = 16 }: IconProps) {
51
51
  );
52
52
  }
53
53
 
54
+ const BRAND_FRAME = 'M10 6H54A5 5 0 0 1 59 11V43A5 5 0 0 1 54 48H10A5 5 0 0 1 5 43V11A5 5 0 0 1 10 6Z M12 10H52A3 3 0 0 1 55 13V41A3 3 0 0 1 52 44H12A3 3 0 0 1 9 41V13A3 3 0 0 1 12 10Z';
55
+
56
+ const BRAND_TAIL =
57
+ 'M8.844 13.742C8.967 12.328 8.45 10.4 8.45 9.65C8.45 8.94 8.88 8.43 9.6 8.43C11.285 8.43 12.106 8.281 12.685 8.104C13.71 7.791 14.585 6.768 15.055 5.945C15.137 5.803 14.99 5.641 14.829 5.671C13.829 5.86 12.828 5.376 11.827 4.978C10.659 4.514 9.491 4.707 8.935 4.876C8.805 4.915 8.658 4.819 8.636 4.686C8.468 3.643 7.405 2.615 5.498 2.238C4.54 2.048 3.748 1.574 3.347 1.202C3.252 1.113 3.088 1.125 3.03 1.242C2.628 2.059 2.168 3.82 5.248 6.115C5.82 6.494 6.31 6.785 6.574 7.637C6.72 8.104 6.157 9.168 6.061 9.368C5.157 11.27 5.089 12.19 4.926 13.742Z';
58
+
59
+ const BRAND_TAIL_TRANSFORM = 'translate(14.6263 10.5853) scale(1.95)';
60
+
54
61
  /**
55
- * 品牌标:侧栏顶上那枚工作台方块标;默认 24px(它是品牌物,别拿它当通用图标)。
62
+ * 品牌标:一台显示器里放着 dsh 的鲸鱼尾巴;默认 24px(它是品牌物,别拿它当通用图标)。
56
63
  * @param props.size - 边长像素,同时给 `width` 与 `height`。
57
64
  * @returns 一枚 `aria-hidden` 的 svg。
58
65
  */
59
66
  export function IconBrandMark({ size = 24 }: IconProps) {
60
67
  return (
61
- <Shell size={size} strokeWidth={1.6} viewBox="0 0 24 24">
62
- <rect x={3} y={3} width={18} height={18} rx={5} stroke="currentColor" />
63
- <path
64
- d="M8 9.5h8M8 12.5h8M8 15.5h5"
65
- stroke="currentColor"
66
- strokeLinecap="round"
67
- />
68
+ <Shell size={size} viewBox="0 0 64 64">
69
+ <path d={BRAND_FRAME} fill="currentColor" fillRule="evenodd" />
70
+ <rect x={27.5} y={48} width={9} height={4.75} fill="currentColor" />
71
+ <rect x={19} y={52.75} width={26} height={4.75} rx={2.375} fill="currentColor" />
72
+ <g transform={BRAND_TAIL_TRANSFORM}>
73
+ <path d={BRAND_TAIL} fill="currentColor" />
74
+ <circle cx={6.885} cy={13.742} r={1.95} fill="currentColor" />
75
+ </g>
68
76
  </Shell>
69
77
  );
70
78
  }
@@ -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
 
@@ -40,8 +34,8 @@ export const PANEL_STATE_PREFIX = `${API_PREFIX}/${PANEL_STATE_PATH}`;
40
34
  /** 钉住的 opencli 插件版本:`opencli-runtime` 按它校验、取那一份。 */
41
35
  export const OPENCLI_PIN = "1.8.8";
42
36
 
43
- /** 缺陷看板套件的 dsh 服务名(`workbenchCodingIssues`):套件照它登记自己那份服务。 */
44
- export const CODING_ISSUES_SERVICE = "workbenchCodingIssues";
37
+ /** 缺陷看板套件的 dsh 服务名(`workbenchIssues`):套件照它登记自己那份服务。 */
38
+ export const ISSUES_SERVICE = "workbenchIssues";
45
39
 
46
40
  /** 面板套件的 dsh 服务名(`workbenchPanels`):宿主经它取套件公布的清单与订阅。 */
47
41
  export const PANELS_SERVICE = "workbenchPanels";
@@ -5,6 +5,7 @@ import {
5
5
  parseFactFrame,
6
6
  type FactFrame,
7
7
  type FactTransport,
8
+ type FactTransportMode,
8
9
  } from '../facts.js';
9
10
 
10
11
  function fakeTransport(): {
@@ -52,17 +53,17 @@ describe('createFactChannel', () => {
52
53
  it('★ 之后的帧只惊动它点名的事实(别的读者不该白跑一次)', () => {
53
54
  const wire = fakeTransport();
54
55
  const channel = createFactChannel({ transport: wire.transport });
55
- const cmdb = vi.fn();
56
+ const inventory = vi.fn();
56
57
  const issues = vi.fn();
57
- channel.subscribe('cmdb/summary', cmdb);
58
+ channel.subscribe('inventory/summary', inventory);
58
59
  channel.subscribe('issues', issues);
59
60
  channel.applyFrame({ epoch: 'e1', rev: 1, facts: [] });
60
61
 
61
- channel.applyFrame({ epoch: 'e1', rev: 2, facts: ['cmdb/summary'] });
62
+ channel.applyFrame({ epoch: 'e1', rev: 2, facts: ['inventory/summary'] });
62
63
 
63
- expect(cmdb).toHaveBeenCalledTimes(1);
64
+ expect(inventory).toHaveBeenCalledTimes(1);
64
65
  expect(issues).not.toHaveBeenCalled();
65
- expect(channel.revisionOf('cmdb/summary')).toBe(2);
66
+ expect(channel.revisionOf('inventory/summary')).toBe(2);
66
67
  expect(channel.revisionOf('issues')).toBe(0);
67
68
  });
68
69
 
@@ -132,33 +133,33 @@ describe('createFactChannel', () => {
132
133
  const summary = vi.fn();
133
134
  const builds = vi.fn();
134
135
  const issues = vi.fn();
135
- channel.subscribe('cmdb/summary', summary);
136
- channel.subscribe('cmdb/builds?env=innettest', builds);
137
- channel.subscribe('coding-issues/list', issues);
136
+ channel.subscribe('inventory/summary', summary);
137
+ channel.subscribe('inventory/builds?env=innettest', builds);
138
+ channel.subscribe('issues/list', issues);
138
139
  channel.applyFrame({ epoch: 'e1', rev: 1, facts: [] });
139
140
 
140
- channel.applyFrame({ epoch: 'e1', rev: 2, facts: ['capability', 'panels', 'cmdb*'] });
141
+ channel.applyFrame({ epoch: 'e1', rev: 2, facts: ['capability', 'panels', 'inventory*'] });
141
142
 
142
143
  expect(summary).toHaveBeenCalledTimes(1);
143
144
  expect(builds).toHaveBeenCalledTimes(1);
144
145
  expect(issues).not.toHaveBeenCalled();
145
- expect(channel.revisionOf('cmdb/summary')).toBeGreaterThan(1);
146
+ expect(channel.revisionOf('inventory/summary')).toBeGreaterThan(1);
146
147
  });
147
148
 
148
149
  it('命名空间不许凭空造出订阅(订阅表只由读者写),也不许越界到别的模块', () => {
149
150
  const wire = fakeTransport();
150
151
  const channel = createFactChannel({ transport: wire.transport });
151
- const codingAnalysis = vi.fn();
152
+ const demoAnalysis = vi.fn();
152
153
  const lookalike = vi.fn();
153
- channel.subscribe('coding-analysis/summary', codingAnalysis);
154
- channel.subscribe('cmdb-extra/summary', lookalike);
154
+ channel.subscribe('analysis/summary', demoAnalysis);
155
+ channel.subscribe('inventory-extra/summary', lookalike);
155
156
  channel.applyFrame({ epoch: 'e1', rev: 1, facts: [] });
156
157
 
157
- channel.applyFrame({ epoch: 'e1', rev: 2, facts: ['cmdb*'] });
158
+ channel.applyFrame({ epoch: 'e1', rev: 2, facts: ['inventory*'] });
158
159
 
159
- expect(codingAnalysis).not.toHaveBeenCalled();
160
+ expect(demoAnalysis).not.toHaveBeenCalled();
160
161
  expect(lookalike).not.toHaveBeenCalled();
161
- expect(channel.revisionOf('cmdb/never-subscribed')).toBe(0);
162
+ expect(channel.revisionOf('inventory/never-subscribed')).toBe(0);
162
163
  expect(channel.subscriberCount()).toBe(2);
163
164
  });
164
165
 
@@ -166,26 +167,26 @@ describe('createFactChannel', () => {
166
167
  const wire = fakeTransport();
167
168
  const channel = createFactChannel({ transport: wire.transport });
168
169
  const listener = vi.fn();
169
- channel.subscribe('cmdb/builds?env=innettest', listener);
170
+ channel.subscribe('inventory/builds?env=innettest', listener);
170
171
 
171
- channel.applyFrame({ epoch: 'e1', rev: 4, facts: ['cmdb*'] });
172
+ channel.applyFrame({ epoch: 'e1', rev: 4, facts: ['inventory*'] });
172
173
 
173
174
  expect(listener).not.toHaveBeenCalled();
174
- expect(channel.revisionOf('cmdb/builds?env=innettest')).toBeGreaterThan(0);
175
+ expect(channel.revisionOf('inventory/builds?env=innettest')).toBeGreaterThan(0);
175
176
  });
176
177
 
177
178
  it('refresh(命名空间) 只惊动它下面的事实(本地失效也按模块点名,不是一律全量)', () => {
178
179
  const wire = fakeTransport();
179
180
  const channel = createFactChannel({ transport: wire.transport });
180
- const cmdb = vi.fn();
181
+ const inventory = vi.fn();
181
182
  const issues = vi.fn();
182
- channel.subscribe('cmdb/summary', cmdb);
183
- channel.subscribe('coding-issues/list', issues);
183
+ channel.subscribe('inventory/summary', inventory);
184
+ channel.subscribe('issues/list', issues);
184
185
  channel.applyFrame({ epoch: 'e1', rev: 1, facts: [] });
185
186
 
186
- channel.refresh('cmdb*');
187
+ channel.refresh('inventory*');
187
188
 
188
- expect(cmdb).toHaveBeenCalledTimes(1);
189
+ expect(inventory).toHaveBeenCalledTimes(1);
189
190
  expect(issues).not.toHaveBeenCalled();
190
191
  });
191
192
 
@@ -204,6 +205,53 @@ describe('createFactChannel', () => {
204
205
 
205
206
  expect(seen).toEqual([4]);
206
207
  });
208
+
209
+ it('★ 传输来路:没建连是 null,报上来就换档,同一档不重复惊动', () => {
210
+ const wire: { report: ((mode: FactTransportMode) => void) | null } = { report: null };
211
+ const channel = createFactChannel({
212
+ transport: {
213
+ start(handlers) {
214
+ wire.report = handlers.onMode ?? null;
215
+ return () => {};
216
+ },
217
+ },
218
+ });
219
+ const listener = vi.fn();
220
+ expect(channel.mode()).toBeNull();
221
+ channel.subscribeMode(listener);
222
+ channel.subscribe('panels', vi.fn());
223
+
224
+ wire.report?.('polling');
225
+ expect(channel.mode()).toBe('polling');
226
+ wire.report?.('polling');
227
+ expect(listener).toHaveBeenCalledTimes(1);
228
+
229
+ wire.report?.('sse');
230
+ expect(channel.mode()).toBe('sse');
231
+ expect(listener).toHaveBeenCalledTimes(2);
232
+ });
233
+
234
+ it('★ 最后一个订阅走掉、传输停了,来路回到没建连', () => {
235
+ const wire: { report: ((mode: FactTransportMode) => void) | null } = { report: null };
236
+ const channel = createFactChannel({
237
+ transport: {
238
+ start(handlers) {
239
+ wire.report = handlers.onMode ?? null;
240
+ return () => {};
241
+ },
242
+ },
243
+ });
244
+ const listener = vi.fn();
245
+ channel.subscribeMode(listener);
246
+ const off = channel.subscribe('panels', vi.fn());
247
+ wire.report?.('sse');
248
+ listener.mockClear();
249
+
250
+ off();
251
+
252
+ expect(channel.mode()).toBeNull();
253
+ expect(listener).toHaveBeenCalledTimes(1);
254
+ });
207
255
  });
208
256
 
209
257
  describe('parseFactFrame', () => {
@@ -255,7 +303,7 @@ describe('parseFactFrame', () => {
255
303
 
256
304
  describe('factOfRoute', () => {
257
305
  it('事实名就是路由名(不另起一套词汇,读与订阅不可能各说各话)', () => {
258
- expect(factOfRoute('cmdb/summary')).toBe('cmdb/summary');
306
+ expect(factOfRoute('inventory/summary')).toBe('inventory/summary');
259
307
  expect(factOfRoute('capability')).toBe('capability');
260
308
  });
261
309
  });
@@ -13,16 +13,16 @@ import {
13
13
 
14
14
  describe('runsRouteOf', () => {
15
15
  it('★ 事实名 = 路由名(三处同一个来源,改一处不可能漏另一处)', () => {
16
- expect(runsRouteOf('cmdb')).toBe('cmdb/runs');
16
+ expect(runsRouteOf('inventory')).toBe('inventory/runs');
17
17
  expect(runsRouteOf('issues')).toBe('issues/runs');
18
18
  });
19
19
  });
20
20
 
21
21
  describe('stopRouteOf(停止那条写路由)', () => {
22
22
  it('★ 就是读路由 + `/stop`(读口与写口同一个单元名,不另造一套命名)', () => {
23
- expect(stopRouteOf('cmdb')).toBe('cmdb/runs/stop');
23
+ expect(stopRouteOf('inventory')).toBe('inventory/runs/stop');
24
24
  expect(stopRouteOf('issues')).toBe('issues/runs/stop');
25
- expect(stopRouteOf('cmdb')).toBe(`${runsRouteOf('cmdb')}/stop`);
25
+ expect(stopRouteOf('inventory')).toBe(`${runsRouteOf('inventory')}/stop`);
26
26
  });
27
27
  });
28
28
 
@@ -0,0 +1,74 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ NEED_KINDS,
4
+ NEED_KIND_SPECS,
5
+ SUPPLY_SOURCE_KINDS,
6
+ needProblemsOf,
7
+ needsProblemsOf,
8
+ } from '../needs.js';
9
+
10
+ const npm = { kind: 'npm', package: '@acme/plugin-demo', registryDefault: 'https://registry.test/' };
11
+ const base = { kind: 'lib', name: 'demo', purpose: '测试', sources: [npm] };
12
+
13
+ function errorsOf(need: unknown): readonly string[] {
14
+ return needProblemsOf('demo', need, 0).errors;
15
+ }
16
+
17
+ describe('needs 校验矩阵', () => {
18
+ it('★ 四种 kind 的最小声明都过;注册表与 NEED_KINDS 一一对应', () => {
19
+ expect(
20
+ needsProblemsOf('demo', [
21
+ { kind: 'bin', name: 'gitops', purpose: '部署' },
22
+ { kind: 'extension', runtime: 'opencli', name: 'demo', purpose: '面板', sources: [npm] },
23
+ base,
24
+ { kind: 'env', name: 'DSH_TOKEN', secret: true, purpose: '只读接口' },
25
+ ]).errors,
26
+ ).toEqual([]);
27
+ expect(Object.keys(NEED_KIND_SPECS).sort()).toEqual([...NEED_KINDS].sort());
28
+ });
29
+
30
+ it('未知 kind / 未知 source.kind → 红,并点名', () => {
31
+ const unknownKind = errorsOf({ kind: 'mcp', name: 'x', purpose: '测试' });
32
+ expect(unknownKind.join(' ')).toContain('needs[0].kind 不认识');
33
+ const unknownSource = errorsOf({ ...base, sources: [{ kind: 'tarball', url: 'x' }] });
34
+ expect(unknownSource.join(' ')).toContain('sources[0].kind 不认识');
35
+ expect(new Set(Object.values(NEED_KIND_SPECS).flatMap((spec) => spec.sources))).toEqual(
36
+ new Set(SUPPLY_SOURCE_KINDS),
37
+ );
38
+ });
39
+
40
+ it('bin / env 写了 sources → 红(没有安装路径)', () => {
41
+ expect(errorsOf({ kind: 'bin', name: 'x', purpose: '测试', sources: [npm] }).join(' ')).toContain(
42
+ '不支持 sources',
43
+ );
44
+ expect(errorsOf({ kind: 'env', name: 'X', purpose: '测试', sources: [npm] }).join(' ')).toContain(
45
+ '不支持 sources',
46
+ );
47
+ });
48
+
49
+ it('lib 只认 npm 源;sources 空数组 → 红(不代装就整条删掉)', () => {
50
+ expect(errorsOf({ ...base, sources: [{ kind: 'git', url: 'github:acme/demo' }] }).join(' ')).toContain(
51
+ '只支持 npm',
52
+ );
53
+ expect(errorsOf({ ...base, sources: [] }).join(' ')).toContain('空数组');
54
+ });
55
+
56
+ it('必填项:purpose、extension.runtime、name、required 类型、id 空串', () => {
57
+ expect(errorsOf({ ...base, purpose: '' }).join(' ')).toContain('purpose 没写');
58
+ expect(errorsOf({ kind: 'extension', name: 'x', purpose: '测试' }).join(' ')).toContain('runtime 没写');
59
+ expect(errorsOf({ ...base, name: '' }).join(' ')).toContain('name 没写');
60
+ expect(errorsOf({ ...base, required: 'yes' }).join(' ')).toContain('required 要是布尔');
61
+ expect(errorsOf({ ...base, id: '' }).join(' ')).toContain('id 是空串');
62
+ });
63
+
64
+ it('★ need 内未知字段只是警告;x- 前缀直接忽略(不为可选字段升协议版本)', () => {
65
+ const read = needProblemsOf('demo', { ...base, future: 1, 'x-vendor': true }, 0);
66
+ expect(read.errors).toEqual([]);
67
+ expect(read.warnings.join(' ')).toContain('不认识的字段 future');
68
+ expect(read.warnings.join(' ')).not.toContain('x-vendor');
69
+ });
70
+
71
+ it('没写 needs → 空表不报', () => {
72
+ expect(needsProblemsOf('demo', undefined)).toEqual({ errors: [], warnings: [] });
73
+ });
74
+ });
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest';
2
2
  import { declarationProblemsOf, readDeclaration } from '../unit-declaration.js';
3
3
 
4
4
  const declared = (tools: unknown) => ({
5
- apiVersion: 1,
5
+ apiVersion: 2,
6
6
  unit: 'demo',
7
7
  card: null,
8
8
  tools,
@@ -0,0 +1,43 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import type { Need } from '../needs.js';
3
+ import { needsOf, provisionableNeedsOf, type UnitManifest } from '../unit.js';
4
+
5
+ function manifest(needs?: readonly Need[]): UnitManifest {
6
+ return {
7
+ id: 'issues',
8
+ card: { titleKey: 'issues.mine', order: 10 },
9
+ tools: [],
10
+ host: { inject: [] },
11
+ ...(needs === undefined ? {} : { needs }),
12
+ };
13
+ }
14
+
15
+ const npm = { kind: 'npm' as const, registryEnv: 'WB_NPM', registryDefault: 'https://registry.example/' };
16
+
17
+ describe('provisionableNeedsOf(缺来源时那条回退口径,宿主与浏览器共用一份)', () => {
18
+ it('写了 sources 的 extension / lib → 宿主可代装', () => {
19
+ const needs: Need[] = [
20
+ { kind: 'extension', runtime: 'opencli', name: 'demo', purpose: '测试', sources: [npm] },
21
+ { kind: 'lib', name: 'opencli-workflow', purpose: '测试', sources: [npm] },
22
+ ];
23
+ expect(provisionableNeedsOf(manifest(needs)).map((need) => need.name)).toEqual([
24
+ 'demo',
25
+ 'opencli-workflow',
26
+ ]);
27
+ });
28
+
29
+ it('★ 没写 sources 的 extension、bin、env 都不算供给 —— 它们不是市场门槛,只是「我自己装,你帮我判」', () => {
30
+ const needs: Need[] = [
31
+ { kind: 'extension', runtime: 'opencli', name: 'demo', purpose: '测试' },
32
+ { kind: 'bin', name: 'gitops', purpose: '测试' },
33
+ { kind: 'env', name: 'DSH_TOKEN', purpose: '测试' },
34
+ ];
35
+ expect(provisionableNeedsOf(manifest(needs))).toEqual([]);
36
+ expect(provisionableNeedsOf(manifest([]))).toEqual([]);
37
+ expect(provisionableNeedsOf(manifest())).toEqual([]);
38
+ });
39
+
40
+ it('needsOf 没写就是空表', () => {
41
+ expect(needsOf(manifest())).toEqual([]);
42
+ });
43
+ });
@@ -0,0 +1,9 @@
1
+ export interface BrowserPort {
2
+ read(input: unknown): Promise<unknown>;
3
+ find(input: unknown): Promise<unknown>;
4
+ }
5
+
6
+ export interface PreflightPort {
7
+ run(options?: { readonly hard?: boolean }): Promise<unknown>;
8
+ snapshot(): unknown;
9
+ }
@@ -0,0 +1,3 @@
1
+ export interface CliPort {
2
+ readonly command: string;
3
+ }
@@ -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
+ }