@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
@@ -0,0 +1,299 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { createRequire } from 'node:module';
3
+ import { dirname, join } from 'node:path';
4
+ import { pathToFileURL } from 'node:url';
5
+ import type { DshHostContext } from '../api/dsh.js';
6
+ import {
7
+ ADAPTER_SET_API_VERSION,
8
+ SUPPORTED_ADAPTER_API_VERSIONS,
9
+ type AdapterSet,
10
+ type CoreAdapterSet,
11
+ } from './adapters.js';
12
+ import { isPackageName } from './spec.js';
13
+
14
+ export interface WorkbenchConfig {
15
+ readonly adapterSet?: string;
16
+ }
17
+
18
+ export type AdapterSetFailure =
19
+ | 'not-configured'
20
+ | 'no-profile'
21
+ | 'malformed'
22
+ | 'not-resolvable'
23
+ | 'import-failed'
24
+ | 'bad-export'
25
+ | 'factory-failed'
26
+ | 'incompatible-version'
27
+ | 'missing-member';
28
+
29
+ export type AdapterSetLoad =
30
+ | { readonly ok: true; readonly spec: string; readonly set: CoreAdapterSet }
31
+ | {
32
+ readonly ok: false;
33
+ readonly spec?: string;
34
+ readonly failure: AdapterSetFailure;
35
+ readonly detail: string;
36
+ readonly cause?: unknown;
37
+ };
38
+
39
+ const CORE_MEMBERS = ['data', 'supply', 'profile', 'probe'] as const;
40
+
41
+ const CORE_METHODS = [
42
+ ['data', 'read'],
43
+ ['data', 'request'],
44
+ ['data', 'requestMany'],
45
+ ['data', 'submit'],
46
+ ['supply', 'resolvePluginSource'],
47
+ ['supply', 'libInstallCommand'],
48
+ ['supply', 'pluginInstallPlan'],
49
+ ['supply', 'latestVersion'],
50
+ ['supply', 'missingReleaseParts'],
51
+ ['supply', 'probeRelease'],
52
+ ['supply', 'splitNpmSpec'],
53
+ ['profile', 'dirOf'],
54
+ ['profile', 'dshPlugins'],
55
+ ['profile', 'selfUpdateView'],
56
+ ['probe', 'probeUnits'],
57
+ ['probe', 'registerPreflight'],
58
+ ['probe', 'declaredPluginCommands'],
59
+ ] as const;
60
+
61
+ const FULL_MEMBERS = ['modules', 'runtime', 'plugins', 'actions'] as const;
62
+
63
+ const FULL_METHODS = [
64
+ ['modules', 'isolatedHome'],
65
+ ['modules', 'cliCatalogPath'],
66
+ ['modules', 'vendorPluginsDir'],
67
+ ['modules', 'opencliInstallCommand'],
68
+ ['modules', 'profileDirOf'],
69
+ ['modules', 'profileModules'],
70
+ ['modules', 'readInstalledModule'],
71
+ ['modules', 'readProfilePackage'],
72
+ ['modules', 'readBundleManager'],
73
+ ['modules', 'createRegistryNotebook'],
74
+ ['modules', 'declarationGaps'],
75
+ ['modules', 'moduleDeclarationOf'],
76
+ ['modules', 'registryDeclarationOf'],
77
+ ['modules', 'pathDeclarationOf'],
78
+ ['modules', 'declarationRefusalOf'],
79
+ ['modules', 'refusalReason'],
80
+ ['modules', 'packageRouteOf'],
81
+ ['modules', 'isNewerVersion'],
82
+ ['modules', 'startSelfUpdate'],
83
+ ['runtime', 'packageRoot'],
84
+ ['runtime', 'opencliCommand'],
85
+ ['runtime', 'seed'],
86
+ ['runtime', 'warnUnwritableRoot'],
87
+ ['plugins', 'declarationLookup'],
88
+ ['plugins', 'createUnits'],
89
+ ['actions', 'configSyncHandler'],
90
+ ['actions', 'profileCreateHandler'],
91
+ ['actions', 'configSyncAction'],
92
+ ['actions', 'profileCreateAction'],
93
+ ] as const;
94
+
95
+ function methodOf(value: Record<string, unknown>, member: string, method: string): unknown {
96
+ const part = value[member];
97
+ if (typeof part !== 'object' || part === null) return undefined;
98
+ return (part as Record<string, unknown>)[method];
99
+ }
100
+
101
+ function failure(
102
+ failureKind: AdapterSetFailure,
103
+ detail: string,
104
+ spec?: string,
105
+ cause?: unknown,
106
+ ): AdapterSetLoad {
107
+ return {
108
+ ok: false,
109
+ ...(spec === undefined ? {} : { spec }),
110
+ failure: failureKind,
111
+ detail,
112
+ ...(cause === undefined ? {} : { cause }),
113
+ };
114
+ }
115
+
116
+ function profileDirOf(ctx: DshHostContext): string | null {
117
+ const profile = ctx.get<{ dir?: unknown }>('profileContext');
118
+ const dir = typeof profile?.dir === 'string' ? profile.dir.trim() : '';
119
+ return dir === '' ? null : dir;
120
+ }
121
+
122
+ function packageDirOf(spec: string, from: string): string | undefined {
123
+ let dir = from;
124
+ for (;;) {
125
+ const candidate = join(dir, 'node_modules', spec);
126
+ if (existsSync(join(candidate, 'package.json'))) return candidate;
127
+ const up = dirname(dir);
128
+ if (up === dir) return undefined;
129
+ dir = up;
130
+ }
131
+ }
132
+
133
+ function entryOf(value: unknown): string | undefined {
134
+ if (typeof value === 'string') return value;
135
+ if (typeof value !== 'object' || value === null) return undefined;
136
+ const record = value as Record<string, unknown>;
137
+ for (const key of ['import', 'node', 'module', 'default', 'require']) {
138
+ const found = entryOf(record[key]);
139
+ if (found !== undefined) return found;
140
+ }
141
+ return undefined;
142
+ }
143
+
144
+ function exportsEntryOf(value: unknown): string | undefined {
145
+ if (typeof value !== 'object' || value === null) return entryOf(value);
146
+ const record = value as Record<string, unknown>;
147
+ return '.' in record ? entryOf(record['.']) : entryOf(record);
148
+ }
149
+
150
+ function esmEntryUrl(spec: string, profileDir: string): string | undefined {
151
+ const dir = packageDirOf(spec, profileDir);
152
+ if (dir === undefined) return undefined;
153
+ let pkg: Record<string, unknown>;
154
+ try {
155
+ pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')) as Record<string, unknown>;
156
+ } catch {
157
+ return undefined;
158
+ }
159
+ const main = typeof pkg['main'] === 'string' ? pkg['main'] : undefined;
160
+ const module = typeof pkg['module'] === 'string' ? pkg['module'] : undefined;
161
+ const entry = exportsEntryOf(pkg['exports']) ?? module ?? main ?? 'index.js';
162
+ const file = join(dir, entry);
163
+ return existsSync(file) ? pathToFileURL(file).href : undefined;
164
+ }
165
+
166
+ function resolveFromProfile(spec: string, profileDir: string): string | undefined {
167
+ const esm = esmEntryUrl(spec, profileDir);
168
+ if (esm !== undefined) return esm;
169
+ const parent = pathToFileURL(join(profileDir, 'package.json')).href;
170
+ try {
171
+ return pathToFileURL(createRequire(parent).resolve(spec)).href;
172
+ } catch {
173
+ return undefined;
174
+ }
175
+ }
176
+
177
+ function versionOf(value: Record<string, unknown>): number | undefined {
178
+ const version = value['apiVersion'];
179
+ return typeof version === 'number' && Number.isInteger(version) ? version : undefined;
180
+ }
181
+
182
+ export function validateAdapterSet(value: unknown, spec: string): AdapterSetLoad {
183
+ if (typeof value !== 'object' || value === null) {
184
+ return failure('missing-member', `${spec} 的 createAdapterSet(ctx) 没回一个对象`, spec);
185
+ }
186
+ const record = value as Record<string, unknown>;
187
+ const version = versionOf(record);
188
+ if (version === undefined || !SUPPORTED_ADAPTER_API_VERSIONS.includes(version)) {
189
+ return failure(
190
+ 'incompatible-version',
191
+ `${spec} 的适配器协议版本是 ${String(record['apiVersion'])},工作台只认 v${ADAPTER_SET_API_VERSION}`,
192
+ spec,
193
+ );
194
+ }
195
+ const missing: string[] = CORE_MEMBERS.filter(
196
+ (member) => record[member] === undefined || record[member] === null,
197
+ );
198
+ for (const [member, method] of CORE_METHODS) {
199
+ if (typeof methodOf(record, member, method) !== 'function') missing.push(`${member}.${method}`);
200
+ }
201
+ if (typeof record['id'] !== 'string' || record['id'].trim() === '') missing.push('id');
202
+ if (typeof record['version'] !== 'string' || record['version'].trim() === '') missing.push('version');
203
+ if (FULL_MEMBERS.some((member) => record[member] !== undefined)) {
204
+ for (const member of FULL_MEMBERS) {
205
+ if (typeof record[member] !== 'object' || record[member] === null) missing.push(member);
206
+ }
207
+ for (const [member, method] of FULL_METHODS) {
208
+ if (typeof methodOf(record, member, method) !== 'function') missing.push(`${member}.${method}`);
209
+ }
210
+ }
211
+ if (missing.length > 0) {
212
+ return failure('missing-member', `${spec} 少了 ${missing.join(' / ')}`, spec);
213
+ }
214
+ return { ok: true, spec, set: record as unknown as CoreAdapterSet };
215
+ }
216
+
217
+ export async function loadAdapterSet(
218
+ ctx: DshHostContext,
219
+ spec: string | undefined,
220
+ ): Promise<AdapterSetLoad> {
221
+ const wanted = typeof spec === 'string' ? spec.trim() : spec === undefined ? '' : String(spec).trim();
222
+ if (wanted === '') return failure('not-configured', '没有配置 adapterSet');
223
+ const profileDir = profileDirOf(ctx);
224
+ if (profileDir === null) return failure('no-profile', '找不到 profile 目录,无法从 profile 解析适配器集合', wanted);
225
+ if (!isPackageName(wanted)) {
226
+ return failure('malformed', `适配器集合要写包名(如 @acme/adapters-set),收到 ${JSON.stringify(wanted)}`, wanted);
227
+ }
228
+
229
+ const url = resolveFromProfile(wanted, profileDir);
230
+ if (url === undefined) {
231
+ return failure('not-resolvable', `在 profile 里解析不到 ${wanted},先把它装进这个 profile`, wanted);
232
+ }
233
+
234
+ let mod: Record<string, unknown>;
235
+ try {
236
+ mod = (await import(url)) as Record<string, unknown>;
237
+ } catch (error) {
238
+ return failure('import-failed', `${wanted} 的入口没加载成:${String(error)}`, wanted, error);
239
+ }
240
+
241
+ const factory = mod['createAdapterSet'] ?? mod['default'];
242
+ if (typeof factory !== 'function') {
243
+ return failure('bad-export', `${wanted} 没导出 createAdapterSet(ctx),导出键:${Object.keys(mod).join(', ')}`, wanted);
244
+ }
245
+
246
+ let value: Record<string, unknown>;
247
+ try {
248
+ const raw: unknown = await factory(ctx);
249
+ if (typeof raw !== 'object' || raw === null) {
250
+ return failure('missing-member', `${wanted} 的 createAdapterSet(ctx) 没回一个对象`, wanted);
251
+ }
252
+ value = raw as Record<string, unknown>;
253
+ } catch (error) {
254
+ return failure('factory-failed', `${wanted} 的 createAdapterSet(ctx) 抛了:${String(error)}`, wanted, error);
255
+ }
256
+
257
+ return validateAdapterSet(value, wanted);
258
+ }
259
+
260
+ export function withDefaultAdapterSet(defaults: AdapterSet, loaded: AdapterSetLoad): AdapterSet {
261
+ if (!loaded.ok) return defaults;
262
+ const set = loaded.set as Partial<AdapterSet>;
263
+ return {
264
+ apiVersion: set.apiVersion ?? defaults.apiVersion,
265
+ id: set.id ?? defaults.id,
266
+ version: set.version ?? defaults.version,
267
+ data: set.data ?? defaults.data,
268
+ libs: set.libs ?? defaults.libs,
269
+ supply: set.supply ?? defaults.supply,
270
+ modules: set.modules ?? defaults.modules,
271
+ profile: set.profile ?? defaults.profile,
272
+ probe: set.probe ?? defaults.probe,
273
+ runtime: set.runtime ?? defaults.runtime,
274
+ plugins: set.plugins ?? defaults.plugins,
275
+ actions: set.actions ?? defaults.actions,
276
+ };
277
+ }
278
+
279
+ function isFullAdapterSet(value: CoreAdapterSet): value is AdapterSet {
280
+ const candidate = value as unknown as Record<string, unknown>;
281
+ return ['modules', 'runtime', 'plugins', 'actions'].every(
282
+ (member) => typeof candidate[member] === 'object' && candidate[member] !== null,
283
+ );
284
+ }
285
+
286
+ export async function resolveAdapterSet(
287
+ ctx: DshHostContext,
288
+ spec: string | undefined,
289
+ makeDefaults: () => Promise<AdapterSet>,
290
+ ): Promise<{ readonly set: AdapterSet; readonly load: AdapterSetLoad; readonly usedDefaults: boolean }> {
291
+ const load = await loadAdapterSet(ctx, spec);
292
+ if (load.ok && isFullAdapterSet(load.set)) {
293
+ return { set: load.set, load, usedDefaults: false };
294
+ }
295
+ const defaults = await makeDefaults();
296
+ return { set: withDefaultAdapterSet(defaults, load), load, usedDefaults: true };
297
+ }
298
+
299
+ export type { AdapterSet, CoreAdapterSet };
@@ -0,0 +1,105 @@
1
+ import type {
2
+ BundleCancellation,
3
+ BundleChange,
4
+ BundleInspection,
5
+ BundleInstallRequest,
6
+ BundleRegistries,
7
+ BundleRegistry,
8
+ } from '../contract/protocol/bundle-install.js';
9
+
10
+ export type InstalledModule =
11
+ | {
12
+ readonly kind: 'module';
13
+ readonly pkg: string;
14
+ readonly unit: string;
15
+ readonly version: string;
16
+ readonly registry?: string;
17
+ readonly local?: boolean;
18
+ }
19
+ | { readonly kind: 'not-module'; readonly pkg: string }
20
+ | { readonly kind: 'unknown'; readonly pkg: string };
21
+
22
+ export interface ProfileModule {
23
+ readonly pkg: string;
24
+ readonly unit: string;
25
+ readonly version: string;
26
+ readonly registry?: string;
27
+ readonly local?: boolean;
28
+ }
29
+
30
+ export interface ProfilePackage {
31
+ readonly pkg: string;
32
+ readonly version: string;
33
+ readonly registry?: string;
34
+ readonly local?: boolean;
35
+ }
36
+
37
+ export interface BundleManager {
38
+ registries(): Promise<BundleRegistries | null>;
39
+ inspect(spec: string, registry?: BundleRegistry): Promise<BundleInspection | null>;
40
+ install(request: BundleInstallRequest): Promise<BundleChange | null>;
41
+ remove(name: string): Promise<BundleChange | null>;
42
+ setEnabled?(name: string, enabled: boolean): Promise<BundleChange | null>;
43
+ cancel(requestId: string): Promise<BundleCancellation | null>;
44
+ }
45
+
46
+ export interface RegistryEntry {
47
+ readonly scope: string;
48
+ readonly registry: string;
49
+ }
50
+
51
+ export interface RegistryNotebook {
52
+ sources(): readonly RegistryEntry[];
53
+ remember(entries: readonly RegistryEntry[]): readonly string[];
54
+ }
55
+
56
+ export interface SelfUpdatePaths {
57
+ readonly dir: string;
58
+ readonly script: string;
59
+ readonly marker: string;
60
+ readonly log: string;
61
+ }
62
+
63
+ export interface SelfUpdateRequest {
64
+ readonly dshHome: string;
65
+ readonly profile: string;
66
+ readonly pkg: string;
67
+ readonly from: string;
68
+ readonly to: string;
69
+ readonly pinned: boolean;
70
+ }
71
+
72
+ export interface SelfUpdateStart {
73
+ readonly started: boolean;
74
+ readonly paths: SelfUpdatePaths;
75
+ readonly forward: string;
76
+ }
77
+
78
+ export interface OpencliResolution {
79
+ readonly command: string;
80
+ readonly source: 'env' | 'isolated' | 'path';
81
+ readonly isolated: string | null;
82
+ }
83
+
84
+ export function scopeOf(pkg: string): string | null {
85
+ const slash = pkg.indexOf('/');
86
+ return pkg.startsWith('@') && slash > 1 ? pkg.slice(0, slash) : null;
87
+ }
88
+
89
+ export function normalizeRegistry(url: string): string {
90
+ const trimmed = url.trim();
91
+ return trimmed.endsWith('/') ? trimmed : `${trimmed}/`;
92
+ }
93
+
94
+ export function sameRegistry(a: string | null | undefined, b: string | null | undefined): boolean {
95
+ if (a === null || a === undefined || b === null || b === undefined) return false;
96
+ if (a.trim() === '' || b.trim() === '') return false;
97
+ return normalizeRegistry(a) === normalizeRegistry(b);
98
+ }
99
+
100
+ export function registryFailure(change: { application?: string; packageResult?: { kind?: string; output?: string }; errorDetail?: string }): boolean {
101
+ if (change.application !== 'failed') return false;
102
+ if (change.packageResult?.kind === 'not-found') return true;
103
+ const output = change.packageResult?.output ?? change.errorDetail ?? '';
104
+ return /ERR_PNPM_[A-Z_]*FETCH/.test(output);
105
+ }
@@ -0,0 +1,50 @@
1
+ import type { DshHostContext } from '../api/dsh.js';
2
+ import type { DataReader } from '../contract/data.js';
3
+ import type { LibReader } from '../contract/lib.js';
4
+ import type { GitReader } from '../contract/git.js';
5
+ import type { CommandReader } from '../contract/command.js';
6
+ import type {
7
+ EnvSupplyPort,
8
+ HostActions,
9
+ ModulePort,
10
+ PluginSurface,
11
+ ProbePort,
12
+ ProfilePort,
13
+ RuntimePort,
14
+ } from './port-types.js';
15
+
16
+ export const WORKBENCH_ADAPTERS = 'workbenchAdapters';
17
+
18
+ export const ADAPTER_SET_API_VERSION = 1;
19
+
20
+ export const SUPPORTED_ADAPTER_API_VERSIONS: readonly number[] = [ADAPTER_SET_API_VERSION];
21
+
22
+ export interface AdapterSet {
23
+ readonly apiVersion: number;
24
+ readonly id: string;
25
+ readonly version: string;
26
+ readonly data: DataReader;
27
+ readonly libs?: LibReader | undefined;
28
+ readonly git?: GitReader | undefined;
29
+ readonly commands?: CommandReader | undefined;
30
+ readonly supply: EnvSupplyPort;
31
+ readonly modules: ModulePort;
32
+ readonly profile: ProfilePort;
33
+ readonly probe: ProbePort;
34
+ readonly runtime: RuntimePort;
35
+ readonly plugins: PluginSurface;
36
+ readonly actions: HostActions;
37
+ }
38
+
39
+ export type CoreAdapterSet = Pick<
40
+ AdapterSet,
41
+ 'apiVersion' | 'id' | 'version' | 'data' | 'supply' | 'profile' | 'probe'
42
+ >;
43
+
44
+ export type WorkbenchAdapters = AdapterSet;
45
+
46
+ export function adaptersOf(ctx: DshHostContext): AdapterSet {
47
+ const adapters = ctx.get<AdapterSet>(WORKBENCH_ADAPTERS, false);
48
+ if (adapters !== undefined) return adapters;
49
+ throw new Error('workbenchAdapters 未提供');
50
+ }
@@ -0,0 +1,3 @@
1
+ export const BROWSER_SCREEN_SESSION = 'workbench';
2
+ export const BROWSER_READ_SESSION = 'workbench-read';
3
+ export const BROWSER_RECORD_SESSION = 'workbench-rec';
@@ -0,0 +1,212 @@
1
+ import dayjs from 'dayjs';
2
+
3
+ export const CHANGELOG_FILE = 'CHANGELOG.md';
4
+
5
+ export const CHANGELOG_KINDS = ['added', 'changed', 'fixed', 'removed', 'security', 'deprecated'] as const;
6
+
7
+ export type ChangelogKind = (typeof CHANGELOG_KINDS)[number];
8
+
9
+ export interface ChangelogSection {
10
+ readonly version: string;
11
+ readonly body: string;
12
+ }
13
+
14
+ export interface ChangelogEntryOptions {
15
+ readonly version: string;
16
+ readonly kind?: ChangelogKind | undefined;
17
+ readonly note: string;
18
+ readonly date?: string | undefined;
19
+ }
20
+
21
+ const KIND_LABELS: Readonly<Record<ChangelogKind, string>> = {
22
+ added: '✨ 新增',
23
+ changed: '⚡ 改进',
24
+ fixed: '🐛 修复',
25
+ removed: '🗑️ 移除',
26
+ security: '🔒 安全',
27
+ deprecated: '⚠️ 弃用',
28
+ };
29
+
30
+ const VERSION_HEADING = /^##[ \t]+\[?v?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)\]?[ \t]*(?:-[ \t]*.*)?$/;
31
+ const ANY_SECTION = /^##[ \t]/;
32
+ const SUBSECTION = /^###[ \t]/;
33
+
34
+ export function changelogVersion(value: string): string {
35
+ return String(value ?? '')
36
+ .trim()
37
+ .replace(/^v/, '');
38
+ }
39
+
40
+ export function changelogDate(now: Date = new Date()): string {
41
+ return dayjs(now).format('YYYY-MM-DD');
42
+ }
43
+
44
+ export function normalizeChangelogText(text: string): string {
45
+ return String(text ?? '').replace(/\r\n?/g, '\n');
46
+ }
47
+
48
+ function linesOf(text: string): string[] {
49
+ return normalizeChangelogText(text).split('\n');
50
+ }
51
+
52
+ function bodyOf(rows: readonly string[]): string {
53
+ return rows.join('\n').trim();
54
+ }
55
+
56
+ function sectionRange(
57
+ rows: readonly string[],
58
+ version: string,
59
+ ): { readonly start: number; readonly end: number } | undefined {
60
+ const wanted = changelogVersion(version);
61
+ for (let index = 0; index < rows.length; index += 1) {
62
+ const row = rows[index] ?? '';
63
+ if (!ANY_SECTION.test(row)) continue;
64
+ const match = VERSION_HEADING.exec(row);
65
+ if (match === null || changelogVersion(match[1] ?? '') !== wanted) continue;
66
+ let end = rows.length;
67
+ for (let next = index + 1; next < rows.length; next += 1) {
68
+ if (ANY_SECTION.test(rows[next] ?? '')) {
69
+ end = next;
70
+ break;
71
+ }
72
+ }
73
+ return { start: index, end };
74
+ }
75
+ return undefined;
76
+ }
77
+
78
+ export function changelogSkeleton(): string {
79
+ return '# 更新日志\n';
80
+ }
81
+
82
+ export function parseChangelog(text: string): readonly ChangelogSection[] {
83
+ const rows = linesOf(text);
84
+ const sections: ChangelogSection[] = [];
85
+ let current: { readonly version: string; readonly from: number } | undefined;
86
+ for (let index = 0; index < rows.length; index += 1) {
87
+ const row = rows[index] ?? '';
88
+ if (!ANY_SECTION.test(row)) continue;
89
+ if (current !== undefined) {
90
+ sections.push({ version: current.version, body: bodyOf(rows.slice(current.from, index)) });
91
+ }
92
+ const match = VERSION_HEADING.exec(row);
93
+ current =
94
+ match === null
95
+ ? undefined
96
+ : { version: changelogVersion(match[1] ?? ''), from: index + 1 };
97
+ }
98
+ if (current !== undefined) {
99
+ sections.push({ version: current.version, body: bodyOf(rows.slice(current.from)) });
100
+ }
101
+ return sections;
102
+ }
103
+
104
+ export function changelogSectionOf(text: string, version: string): string | undefined {
105
+ const wanted = changelogVersion(version);
106
+ return parseChangelog(text).find((one) => one.version === wanted)?.body;
107
+ }
108
+
109
+ export function changelogHasEntry(text: string, version: string): boolean {
110
+ const body = changelogSectionOf(text, version);
111
+ if (body === undefined) return false;
112
+ return body
113
+ .split('\n')
114
+ .map((one) => one.trim())
115
+ .some((one) => one !== '' && !one.startsWith('#') && !one.startsWith('<!--') && !/^`[^`]*`$/.test(one));
116
+ }
117
+
118
+ function bulletsOf(note: string): string[] {
119
+ return note
120
+ .split('\n')
121
+ .map((one) => one.trim())
122
+ .filter((one) => one !== '')
123
+ .map((one) => `- ${one}`);
124
+ }
125
+
126
+ function blockOf(options: ChangelogEntryOptions, label: string, bullets: readonly string[]): string[] {
127
+ const date = (options.date ?? '').trim();
128
+ return [
129
+ `## v${changelogVersion(options.version)}`,
130
+ ...(date === '' ? [] : ['', `\`${date}\``]),
131
+ '',
132
+ `### ${label}`,
133
+ '',
134
+ ...bullets,
135
+ ];
136
+ }
137
+
138
+ function insertBlock(rows: readonly string[], at: number, block: readonly string[]): string[] {
139
+ const before = rows.slice(0, at);
140
+ while (before.length > 0 && (before[before.length - 1] ?? '').trim() === '') before.pop();
141
+ const after = rows.slice(at);
142
+ while (after.length > 0 && (after[0] ?? '').trim() === '') after.shift();
143
+ const out = [...before];
144
+ if (out.length > 0) out.push('');
145
+ out.push(...block);
146
+ if (after.length > 0) {
147
+ out.push('');
148
+ out.push(...after);
149
+ }
150
+ return out;
151
+ }
152
+
153
+ function newSectionAt(rows: readonly string[]): number {
154
+ for (let index = 0; index < rows.length; index += 1) {
155
+ if (/^##[ \t]+\[?unreleased\]?[ \t]*$/i.test(rows[index] ?? '')) {
156
+ for (let next = index + 1; next < rows.length; next += 1) {
157
+ if (ANY_SECTION.test(rows[next] ?? '')) return next;
158
+ }
159
+ return rows.length;
160
+ }
161
+ }
162
+ for (let index = 0; index < rows.length; index += 1) {
163
+ if (ANY_SECTION.test(rows[index] ?? '')) return index;
164
+ }
165
+ return rows.length;
166
+ }
167
+
168
+ export function appendChangelogEntry(text: string, options: ChangelogEntryOptions): string {
169
+ const kind: ChangelogKind = options.kind ?? 'changed';
170
+ const label = KIND_LABELS[kind];
171
+ const bullets = bulletsOf(options.note);
172
+ if (bullets.length === 0) return normalizeChangelogText(text);
173
+ const blocks = blockOf(options, label, bullets);
174
+ const source = normalizeChangelogText(text).trim() === '' ? changelogSkeleton() : normalizeChangelogText(text);
175
+ const rows = linesOf(source);
176
+ const range = sectionRange(rows, options.version);
177
+ if (range === undefined) return `${insertBlock(rows, newSectionAt(rows), blocks).join('\n').replace(/\n+$/, '')}\n`;
178
+ let subsection = -1;
179
+ for (let index = range.start + 1; index < range.end; index += 1) {
180
+ const heading = (rows[index] ?? '').trim();
181
+ if (SUBSECTION.test(heading) && heading.includes(label)) {
182
+ subsection = index;
183
+ break;
184
+ }
185
+ }
186
+ if (subsection === -1) {
187
+ const out = [...rows];
188
+ out.splice(range.end, 0, '', `### ${label}`, '', ...bullets);
189
+ return `${normalizeWhitespace(out)}\n`;
190
+ }
191
+ let end = range.end;
192
+ for (let index = subsection + 1; index < range.end; index += 1) {
193
+ if (SUBSECTION.test(rows[index] ?? '')) {
194
+ end = index;
195
+ break;
196
+ }
197
+ }
198
+ const out = [...rows];
199
+ let at = end;
200
+ while (at > subsection + 1 && (out[at - 1] ?? '').trim() === '') at -= 1;
201
+ out.splice(at, 0, ...bullets);
202
+ return `${normalizeWhitespace(out)}\n`;
203
+ }
204
+
205
+ function normalizeWhitespace(rows: readonly string[]): string {
206
+ const out: string[] = [];
207
+ for (const row of rows) {
208
+ if (row.trim() === '' && out.length > 0 && (out[out.length - 1] ?? '').trim() === '') continue;
209
+ out.push(row);
210
+ }
211
+ return out.join('\n').replace(/\n+$/, '');
212
+ }