@dsh-workbench/shared 0.3.10 → 1.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (391) 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 +19 -1
  6. package/dist/api/host-write.d.ts +0 -15
  7. package/dist/api/host-write.js +1 -35
  8. package/dist/api/workbench-client.js +2 -1
  9. package/dist/components/Activity.css +0 -1
  10. package/dist/components/ApprovalDetail.css +1 -2
  11. package/dist/components/ApprovalDetail.d.ts +1 -1
  12. package/dist/components/ApprovalPlan.css +0 -1
  13. package/dist/components/Card.css +0 -1
  14. package/dist/components/Card.d.ts +1 -1
  15. package/dist/components/CardBoundary.d.ts +1 -1
  16. package/dist/components/CardHead.css +0 -1
  17. package/dist/components/CardHead.d.ts +1 -1
  18. package/dist/components/ChoiceGroup.css +1 -2
  19. package/dist/components/ConfirmBar.d.ts +1 -1
  20. package/dist/components/ConfirmBar.js +1 -1
  21. package/dist/components/Form.css +1 -1
  22. package/dist/components/FormActions.js +1 -1
  23. package/dist/components/LongTaskRow.css +1 -2
  24. package/dist/components/LongTaskRow.d.ts +1 -1
  25. package/dist/components/LongTaskRow.js +1 -1
  26. package/dist/components/Note.css +0 -1
  27. package/dist/components/ResourceStates.css +0 -1
  28. package/dist/components/SettingsSection.css +0 -1
  29. package/dist/components/Spinner.css +0 -1
  30. package/dist/components/Stack.css +0 -1
  31. package/dist/components/StateBlock.css +0 -1
  32. package/dist/components/board-module.d.ts +3 -3
  33. package/dist/components/icons.d.ts +1 -7
  34. package/dist/components/icons.js +11 -16
  35. package/dist/config/constants.d.ts +2 -2
  36. package/dist/config/constants.js +2 -2
  37. package/dist/config/storage.d.ts +0 -4
  38. package/dist/config/storage.js +0 -11
  39. package/dist/contract/adapters.d.ts +10 -0
  40. package/dist/contract/cli.d.ts +3 -0
  41. package/dist/contract/data.d.ts +126 -0
  42. package/dist/{api → contract}/facts.d.ts +12 -3
  43. package/dist/{api → contract}/facts.js +19 -1
  44. package/dist/contract/needs.d.ts +132 -0
  45. package/dist/contract/needs.js +232 -0
  46. package/dist/contract/plugin-source.d.ts +1 -0
  47. package/dist/contract/plugin-source.js +1 -0
  48. package/dist/contract/protocol/bundle-install.d.ts +136 -0
  49. package/dist/contract/protocol/bundle-install.js +12 -0
  50. package/dist/contract/protocol/config-sync.d.ts +55 -0
  51. package/dist/contract/protocol/config-sync.js +2 -0
  52. package/dist/contract/protocol/diy.d.ts +4 -0
  53. package/dist/contract/protocol/diy.js +4 -0
  54. package/dist/contract/protocol/dsh-plugins.d.ts +11 -0
  55. package/dist/contract/protocol/dsh-plugins.js +1 -0
  56. package/dist/contract/protocol/long-tasks.d.ts +13 -0
  57. package/dist/contract/protocol/long-tasks.js +1 -0
  58. package/dist/contract/protocol/market-index.d.ts +195 -0
  59. package/dist/contract/protocol/market-index.js +7 -0
  60. package/dist/contract/protocol/platform.d.ts +44 -0
  61. package/dist/contract/protocol/platform.js +1 -0
  62. package/dist/contract/protocol/preflight-wake.d.ts +7 -0
  63. package/dist/contract/protocol/preflight-wake.js +1 -0
  64. package/dist/contract/protocol/profile-create.d.ts +30 -0
  65. package/dist/contract/protocol/profile-create.js +2 -0
  66. package/dist/contract/protocol/sidebar-edition.d.ts +14 -0
  67. package/dist/contract/protocol/sidebar-edition.js +2 -0
  68. package/dist/contract/protocol/unit-manifests.d.ts +6 -0
  69. package/dist/contract/protocol/unit-manifests.js +1 -0
  70. package/dist/contract/unit-board.d.ts +18 -0
  71. package/dist/contract/unit-board.js +1 -0
  72. package/dist/contract/unit-capability.d.ts +34 -0
  73. package/dist/contract/unit-capability.js +1 -0
  74. package/dist/{api → contract}/unit-declaration.d.ts +7 -5
  75. package/dist/{api → contract}/unit-declaration.js +19 -13
  76. package/dist/contract/unit.d.ts +57 -0
  77. package/dist/contract/unit.js +24 -0
  78. package/dist/contract/workbench-contract.d.ts +15 -0
  79. package/dist/contract/workbench-contract.js +1 -0
  80. package/dist/host/adapter-set.d.ts +26 -0
  81. package/dist/host/adapter-set.js +243 -0
  82. package/dist/host/adapter-types.d.ts +79 -0
  83. package/dist/host/adapter-types.js +23 -0
  84. package/dist/host/adapters.d.ts +22 -0
  85. package/dist/host/adapters.js +9 -0
  86. package/dist/host/browser.d.ts +3 -0
  87. package/dist/host/browser.js +3 -0
  88. package/dist/host/changelog.d.ts +21 -0
  89. package/dist/host/changelog.js +188 -0
  90. package/dist/host/channel-plugin.d.ts +15 -0
  91. package/dist/host/channel-plugin.js +42 -0
  92. package/dist/host/cli.d.ts +4 -0
  93. package/dist/host/cli.js +7 -0
  94. package/dist/host/command-name.d.ts +1 -0
  95. package/dist/host/command-name.js +14 -0
  96. package/dist/host/data-error.d.ts +13 -0
  97. package/dist/host/data-error.js +20 -0
  98. package/dist/host/data-root.js +3 -0
  99. package/dist/host/diy-debug.js +2 -1
  100. package/dist/host/diy-record.js +2 -1
  101. package/dist/host/facts.d.ts +1 -1
  102. package/dist/host/facts.js +3 -2
  103. package/dist/host/git.js +2 -2
  104. package/dist/host/http.js +1 -1
  105. package/dist/host/lib-capabilities.d.ts +22 -0
  106. package/dist/host/lib-capabilities.js +37 -0
  107. package/dist/host/lib-install.d.ts +9 -0
  108. package/dist/host/lib-install.js +10 -0
  109. package/dist/host/long-task.d.ts +1 -1
  110. package/dist/host/long-task.js +4 -3
  111. package/dist/host/market-index.d.ts +22 -0
  112. package/dist/host/market-index.js +420 -0
  113. package/dist/host/needs.d.ts +4 -0
  114. package/dist/host/needs.js +2 -0
  115. package/dist/host/patch.d.ts +9 -0
  116. package/dist/host/patch.js +78 -0
  117. package/dist/host/paths.d.ts +12 -0
  118. package/dist/host/paths.js +28 -0
  119. package/dist/host/platform.d.ts +14 -0
  120. package/dist/host/platform.js +40 -0
  121. package/dist/host/plugin-source.d.ts +8 -6
  122. package/dist/host/plugin-source.js +43 -33
  123. package/dist/host/plugin.d.ts +4 -0
  124. package/dist/host/plugin.js +39 -0
  125. package/dist/host/port-types.d.ts +191 -0
  126. package/dist/host/port-types.js +1 -0
  127. package/dist/host/preflight.d.ts +28 -0
  128. package/dist/host/preflight.js +1 -0
  129. package/dist/host/profile-port.d.ts +4 -0
  130. package/dist/host/profile-port.js +4 -0
  131. package/dist/host/provision.d.ts +2 -2
  132. package/dist/host/provision.js +5 -3
  133. package/dist/host/recipe-rules.d.ts +5 -0
  134. package/dist/host/recipe-rules.js +382 -0
  135. package/dist/host/recipe.d.ts +14 -0
  136. package/dist/host/recipe.js +283 -0
  137. package/dist/host/shell-text.d.ts +9 -2
  138. package/dist/host/shell-text.js +15 -3
  139. package/dist/host/shell.d.ts +2 -0
  140. package/dist/host/shell.js +7 -0
  141. package/dist/host/spec.d.ts +8 -0
  142. package/dist/host/spec.js +24 -0
  143. package/dist/host/supply-index.d.ts +34 -0
  144. package/dist/host/supply-index.js +202 -0
  145. package/dist/host/supply-port.d.ts +5 -0
  146. package/dist/host/supply-port.js +39 -0
  147. package/dist/host/time.d.ts +12 -0
  148. package/dist/host/time.js +19 -0
  149. package/dist/host/tokens.d.ts +26 -0
  150. package/dist/host/tokens.js +154 -0
  151. package/dist/host/tool-approval.d.ts +12 -0
  152. package/dist/host/tool-approval.js +4 -0
  153. package/dist/host/tool-kit.js +1 -1
  154. package/dist/host/unit-face.d.ts +5 -4
  155. package/dist/host/version-range.d.ts +2 -0
  156. package/dist/host/version-range.js +73 -0
  157. package/dist/host/version.d.ts +3 -0
  158. package/dist/host/version.js +29 -0
  159. package/dist/host.d.ts +47 -16
  160. package/dist/host.js +47 -16
  161. package/dist/i18n/en.js +259 -86
  162. package/dist/i18n/index.d.ts +333 -88
  163. package/dist/i18n/merge.js +1 -1
  164. package/dist/i18n/need-text.d.ts +26 -0
  165. package/dist/i18n/need-text.js +26 -0
  166. package/dist/i18n/settings.d.ts +70 -0
  167. package/dist/i18n/settings.js +136 -0
  168. package/dist/i18n/zh.d.ts +332 -87
  169. package/dist/i18n/zh.js +260 -87
  170. package/dist/index.d.ts +24 -8
  171. package/dist/index.js +24 -8
  172. package/dist/lib/blocker-action.d.ts +0 -15
  173. package/dist/lib/blocker-action.js +1 -25
  174. package/dist/{api → lib}/dsh-client.d.ts +12 -1
  175. package/dist/lib/dsh-client.js +1 -0
  176. package/dist/lib/market-index.d.ts +7 -0
  177. package/dist/lib/market-index.js +25 -0
  178. package/dist/lib/react-types.d.ts +5 -0
  179. package/dist/lib/react-types.js +1 -0
  180. package/dist/lib/react.d.ts +1 -40
  181. package/dist/lib/react.js +0 -36
  182. package/dist/lib/ui.js +3 -9
  183. package/dist/testing/data.d.ts +13 -0
  184. package/dist/testing/data.js +46 -0
  185. package/dist/testing/declaration.d.ts +1 -1
  186. package/dist/testing/declaration.js +1 -1
  187. package/dist/testing/host-fakes.d.ts +0 -13
  188. package/dist/testing/host-fakes.js +0 -24
  189. package/dist/testing/needs.d.ts +16 -0
  190. package/dist/testing/needs.js +46 -0
  191. package/dist/testing.d.ts +2 -0
  192. package/dist/testing.js +2 -0
  193. package/package.json +18 -2
  194. package/src/api/__tests__/blocker-table.test.ts +135 -86
  195. package/src/api/blocker-table.ts +52 -26
  196. package/src/api/dsh.ts +17 -1
  197. package/src/api/host-write.ts +0 -43
  198. package/src/api/workbench-client.test.ts +2 -2
  199. package/src/api/workbench-client.ts +2 -1
  200. package/src/components/Activity.css +0 -1
  201. package/src/components/ApprovalDetail.css +1 -2
  202. package/src/components/ApprovalDetail.tsx +1 -1
  203. package/src/components/ApprovalPlan.css +0 -1
  204. package/src/components/Card.css +0 -1
  205. package/src/components/Card.tsx +1 -1
  206. package/src/components/CardAnchor.test.tsx +1 -1
  207. package/src/components/CardBoundary.tsx +1 -1
  208. package/src/components/CardHead.css +0 -1
  209. package/src/components/CardHead.tsx +1 -1
  210. package/src/components/ChoiceGroup.css +1 -2
  211. package/src/components/ConfirmBar.tsx +1 -1
  212. package/src/components/Form.css +1 -1
  213. package/src/components/Form.tsx +1 -1
  214. package/src/components/FormActions.tsx +1 -1
  215. package/src/components/LongTaskRow.css +1 -2
  216. package/src/components/LongTaskRow.tsx +1 -1
  217. package/src/components/Note.css +0 -1
  218. package/src/components/ResourceStates.css +0 -1
  219. package/src/components/SelectFieldNodes.tsx +0 -1
  220. package/src/components/SettingsSection.css +0 -1
  221. package/src/components/Spinner.css +0 -1
  222. package/src/components/Stack.css +0 -1
  223. package/src/components/StateBlock.css +0 -1
  224. package/src/components/__tests__/long-task-row.test.ts +1 -1
  225. package/src/components/board-module.ts +3 -3
  226. package/src/components/icons.tsx +16 -24
  227. package/src/config/constants.ts +2 -2
  228. package/src/config/storage.test.ts +1 -25
  229. package/src/config/storage.ts +0 -12
  230. package/src/{api → contract}/__tests__/facts.test.ts +73 -25
  231. package/src/{api → contract}/__tests__/long-task.test.ts +3 -3
  232. package/src/contract/__tests__/needs.test.ts +74 -0
  233. package/src/{host → contract}/__tests__/unit-declaration.test.ts +1 -1
  234. package/src/contract/__tests__/unit.test.ts +43 -0
  235. package/src/contract/adapters.ts +9 -0
  236. package/src/contract/cli.ts +3 -0
  237. package/src/contract/data.ts +145 -0
  238. package/src/{api → contract}/facts.ts +34 -4
  239. package/src/contract/needs.ts +331 -0
  240. package/src/contract/plugin-source.ts +1 -0
  241. package/src/contract/protocol/bundle-install.ts +198 -0
  242. package/src/contract/protocol/config-sync.ts +65 -0
  243. package/src/contract/protocol/diy.ts +4 -0
  244. package/src/contract/protocol/dsh-plugins.ts +13 -0
  245. package/src/contract/protocol/long-tasks.ts +16 -0
  246. package/src/contract/protocol/market-index.ts +246 -0
  247. package/src/contract/protocol/platform.ts +47 -0
  248. package/src/contract/protocol/preflight-wake.ts +17 -0
  249. package/src/contract/protocol/profile-create.ts +36 -0
  250. package/src/contract/protocol/sidebar-edition.ts +19 -0
  251. package/src/contract/protocol/unit-manifests.ts +8 -0
  252. package/src/contract/unit-board.ts +23 -0
  253. package/src/contract/unit-capability.ts +37 -0
  254. package/src/{host → contract}/unit-declaration.ts +22 -18
  255. package/src/contract/unit.ts +76 -0
  256. package/src/contract/workbench-contract.ts +30 -0
  257. package/src/host/__tests__/adapter-set.test.ts +228 -0
  258. package/src/host/__tests__/adapters.test.ts +27 -0
  259. package/src/host/__tests__/changelog.test.ts +102 -0
  260. package/src/host/__tests__/command-name.test.ts +16 -0
  261. package/src/host/__tests__/diy-record.test.ts +3 -3
  262. package/src/host/__tests__/facts.test.ts +5 -5
  263. package/src/host/__tests__/long-task.test.ts +1 -1
  264. package/src/host/__tests__/market-index.test.ts +179 -0
  265. package/src/host/__tests__/plugin-ledger.test.ts +10 -10
  266. package/src/host/__tests__/provision.test.ts +7 -6
  267. package/src/host/__tests__/shell-text.test.ts +16 -1
  268. package/src/host/__tests__/supply-index.test.ts +137 -0
  269. package/src/host/__tests__/tokens.test.ts +110 -0
  270. package/src/host/__tests__/version-range.test.ts +21 -0
  271. package/src/host/adapter-set.ts +298 -0
  272. package/src/host/adapter-types.ts +105 -0
  273. package/src/host/adapters.ts +44 -0
  274. package/src/host/browser.ts +3 -0
  275. package/src/host/changelog.ts +212 -0
  276. package/src/host/channel-plugin.ts +55 -0
  277. package/src/host/cli.ts +11 -0
  278. package/src/host/command-name.ts +15 -0
  279. package/src/host/data-error.ts +21 -0
  280. package/src/host/data-root.ts +2 -0
  281. package/src/host/diy-debug.ts +2 -1
  282. package/src/host/diy-record.ts +2 -1
  283. package/src/host/facts.ts +4 -3
  284. package/src/host/git.ts +2 -2
  285. package/src/host/http.ts +1 -1
  286. package/src/host/lib-capabilities.ts +52 -0
  287. package/src/host/lib-install.ts +18 -0
  288. package/src/host/long-task.ts +4 -3
  289. package/src/host/market-index.ts +413 -0
  290. package/src/host/needs.ts +6 -0
  291. package/src/host/patch.ts +93 -0
  292. package/src/host/paths.ts +37 -0
  293. package/src/host/platform.ts +48 -0
  294. package/src/host/plugin-source.ts +48 -56
  295. package/src/host/plugin.ts +39 -0
  296. package/src/host/port-types.ts +222 -0
  297. package/src/host/preflight.ts +31 -0
  298. package/src/host/profile-port.ts +9 -0
  299. package/src/host/provision.ts +7 -4
  300. package/src/host/recipe-rules.ts +330 -0
  301. package/src/host/recipe.test.ts +334 -0
  302. package/src/host/recipe.ts +323 -0
  303. package/src/host/shell-text.ts +15 -3
  304. package/src/host/shell.ts +9 -0
  305. package/src/host/spec.ts +32 -0
  306. package/src/host/supply-index.ts +256 -0
  307. package/src/host/supply-port.ts +45 -0
  308. package/src/host/time.ts +22 -0
  309. package/src/host/tokens.ts +186 -0
  310. package/src/host/tool-approval.ts +17 -0
  311. package/src/host/tool-kit.ts +1 -1
  312. package/src/host/unit-face.ts +5 -4
  313. package/src/host/version-range.ts +71 -0
  314. package/src/host/version.ts +30 -0
  315. package/src/host.ts +47 -16
  316. package/src/i18n/en.ts +261 -86
  317. package/src/i18n/merge.test.ts +6 -6
  318. package/src/i18n/merge.ts +1 -1
  319. package/src/i18n/need-text.ts +27 -0
  320. package/src/i18n/settings.ts +141 -0
  321. package/src/i18n/zh.ts +263 -87
  322. package/src/index.ts +24 -8
  323. package/src/lib/blocker-action.ts +0 -28
  324. package/src/{api → lib}/dsh-client.ts +16 -2
  325. package/src/lib/market-index.ts +36 -0
  326. package/src/lib/react-types.ts +5 -0
  327. package/src/lib/react.ts +1 -40
  328. package/src/lib/text.test.ts +2 -2
  329. package/src/lib/ui.test.ts +6 -5
  330. package/src/lib/ui.ts +3 -9
  331. package/src/testing/__tests__/declaration.test.ts +1 -1
  332. package/src/testing/data.ts +66 -0
  333. package/src/testing/declaration.ts +2 -2
  334. package/src/testing/host-fakes.ts +0 -38
  335. package/src/testing/needs.ts +80 -0
  336. package/src/testing.ts +2 -0
  337. package/dist/api/unit-board.d.ts +0 -39
  338. package/dist/api/unit-board.js +0 -45
  339. package/dist/api/unit.d.ts +0 -85
  340. package/dist/api/unit.js +0 -8
  341. package/dist/api/workbench-contract.d.ts +0 -44
  342. package/dist/components/MockStrip.css +0 -39
  343. package/dist/components/MockStrip.d.ts +0 -15
  344. package/dist/components/MockStrip.js +0 -8
  345. package/dist/components/SaveBar.css +0 -7
  346. package/dist/host/browser-find.d.ts +0 -38
  347. package/dist/host/browser-find.js +0 -242
  348. package/dist/host/browser-read.d.ts +0 -28
  349. package/dist/host/browser-read.js +0 -243
  350. package/dist/host/opencli-runtime.d.ts +0 -25
  351. package/dist/host/opencli-runtime.js +0 -56
  352. package/dist/host/opencli-window.d.ts +0 -2
  353. package/dist/host/opencli-window.js +0 -8
  354. package/dist/host/opencli.d.ts +0 -37
  355. package/dist/host/opencli.js +0 -155
  356. package/dist/host/unit-declaration.d.ts +0 -65
  357. package/dist/host/unit-declaration.js +0 -213
  358. package/dist/host/unit-libs.d.ts +0 -37
  359. package/dist/lib/diy-debug.d.ts +0 -80
  360. package/dist/lib/diy-debug.js +0 -88
  361. package/dist/lib/diy-record.d.ts +0 -135
  362. package/dist/lib/diy-record.js +0 -162
  363. package/dist/lib/lint-probe.d.ts +0 -1
  364. package/dist/lib/lint-probe.js +0 -6
  365. package/dist/lib/panel-state.d.ts +0 -120
  366. package/dist/lib/panel-state.js +0 -176
  367. package/src/api/__tests__/host-write.test.ts +0 -75
  368. package/src/api/__tests__/unit-board.test.ts +0 -76
  369. package/src/api/__tests__/unit.test.ts +0 -38
  370. package/src/api/unit-board.ts +0 -69
  371. package/src/api/unit.ts +0 -93
  372. package/src/api/workbench-contract.ts +0 -59
  373. package/src/host/__tests__/browser-find.test.ts +0 -330
  374. package/src/host/__tests__/browser-read.test.ts +0 -228
  375. package/src/host/__tests__/opencli-runtime.test.ts +0 -46
  376. package/src/host/browser-find.ts +0 -311
  377. package/src/host/browser-read.ts +0 -311
  378. package/src/host/opencli-runtime.ts +0 -79
  379. package/src/host/opencli-window.ts +0 -11
  380. package/src/host/opencli.ts +0 -213
  381. package/src/host/unit-libs.ts +0 -41
  382. package/src/lib/blocker-action.test.ts +0 -40
  383. /package/dist/{api/blocker.js → contract/adapters.js} +0 -0
  384. /package/dist/{api → contract}/blocker.d.ts +0 -0
  385. /package/dist/{api/dsh-client.js → contract/blocker.js} +0 -0
  386. /package/dist/{api/workbench-contract.js → contract/cli.js} +0 -0
  387. /package/dist/{host/unit-libs.js → contract/data.js} +0 -0
  388. /package/dist/{api → contract}/long-task.d.ts +0 -0
  389. /package/dist/{api → contract}/long-task.js +0 -0
  390. /package/src/{api → contract}/blocker.ts +0 -0
  391. /package/src/{api → contract}/long-task.ts +0 -0
@@ -1,330 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { BROWSER_SCREEN_SESSION } from '../browser-read.js';
3
- import {
4
- browserFind,
5
- cellRows,
6
- cellRowsOf,
7
- entriesOf,
8
- reasonOf,
9
- sameOrigin,
10
- urlOf,
11
- } from '../browser-find.js';
12
-
13
- interface Step {
14
- readonly stdout?: string;
15
- readonly stderr?: string;
16
- readonly exitCode?: number;
17
- readonly timedOut?: boolean;
18
- readonly throws?: boolean;
19
- }
20
-
21
- interface Plan {
22
- readonly url?: string | readonly string[];
23
- readonly find?: readonly Step[];
24
- readonly open?: readonly Step[];
25
- }
26
-
27
- function fakeShell(plan: Plan) {
28
- const commands: string[] = [];
29
- const counters = { url: 0, find: 0, open: 0 };
30
- const pick = <T>(list: readonly T[] | undefined, index: number, fallback: T): T =>
31
- (list ?? [])[Math.min(index, Math.max(0, (list ?? []).length - 1))] ?? fallback;
32
-
33
- const shell = {
34
- resolve(request: { command: string }) {
35
- commands.push(request.command);
36
- return request;
37
- },
38
- async execute(spec: { command: string }) {
39
- const command = String(spec.command);
40
- let step: Step = {};
41
- let stdout = '';
42
- if (command.includes("'get'") && command.includes("'url'")) {
43
- const urls = typeof plan.url === 'string' ? [plan.url] : (plan.url ?? []);
44
- stdout = pick(urls, counters.url, '') as string;
45
- counters.url += 1;
46
- } else if (command.includes("'find'")) {
47
- step = pick(plan.find, counters.find, {});
48
- stdout = step.stdout ?? '';
49
- counters.find += 1;
50
- } else if (command.includes("'open'")) {
51
- step = pick(plan.open, counters.open, {});
52
- stdout = step.stdout ?? JSON.stringify({ page: 'AE4021' });
53
- counters.open += 1;
54
- }
55
- if (step.throws === true) throw new Error('spawn opencli ENOENT');
56
- return {
57
- result: async () => ({
58
- exitCode: step.exitCode ?? 0,
59
- timedOut: step.timedOut ?? false,
60
- aborted: false,
61
- stdout: { text: stdout, truncated: false },
62
- stderr: { text: step.stderr ?? '', truncated: false },
63
- }),
64
- };
65
- },
66
- };
67
- return { shell: shell as never, commands };
68
- }
69
-
70
- const FAST = { settleMs: 1, timeoutMs: 50 } as const;
71
- const SITE = 'https://example.test/rates';
72
- const OTHER = 'https://example.test/elsewhere';
73
- const CSS = 'table tr';
74
- const MATCH = JSON.stringify({ entries: [{ text: '美元\n673.30', tag: 'tr', nth: 1 }] });
75
- const ZERO_MATCH = JSON.stringify({
76
- error: {
77
- code: 'selector_not_found',
78
- hint: 'try a less specific selector',
79
- message: 'CSS selector table tr matched 0 elements',
80
- },
81
- });
82
-
83
- describe('browserFind · 停在哪一页', () => {
84
- it('已经在目标站点上:只问一次 get url,然后一次 find', async () => {
85
- const { shell, commands } = fakeShell({ url: SITE, find: [{ stdout: MATCH }] });
86
- const out = await browserFind({ shell, css: CSS, url: SITE, ...FAST });
87
-
88
- expect(out.ok).toBe(true);
89
- expect(out.count).toBe(1);
90
- expect(out.entries[0]?.text).toBe('美元\n673.30');
91
- expect(commands.filter((one) => one.includes("'find'"))).toHaveLength(1);
92
- expect(commands.some((one) => one.includes("'open'"))).toBe(false);
93
- expect(commands[0]).toContain("'get' 'url'");
94
- });
95
-
96
- it('新会话还停在 about:blank:先 open 再读,命令走 find --css', async () => {
97
- const { shell, commands } = fakeShell({
98
- url: ['about:blank', SITE],
99
- find: [{ stdout: MATCH }],
100
- });
101
- const out = await browserFind({ shell, css: CSS, url: SITE, ...FAST });
102
-
103
- expect(out.ok).toBe(true);
104
- expect(out.count).toBe(1);
105
- expect(commands.some((one) => one.includes("'open'"))).toBe(true);
106
- expect(commands.filter((one) => one.includes("'find'"))).toHaveLength(1);
107
- expect(commands[1]).toContain("'open'");
108
- expect(commands[1]).toContain(SITE);
109
- });
110
-
111
- it('问不出来在哪一页(get url 空)时不乱开,直接找', async () => {
112
- const { shell, commands } = fakeShell({ url: '', find: [{ stdout: MATCH }] });
113
- const out = await browserFind({ shell, css: CSS, url: SITE, ...FAST });
114
-
115
- expect(out.ok).toBe(true);
116
- expect(commands.some((one) => one.includes("'open'"))).toBe(false);
117
- });
118
-
119
- it('open 失败 → cli-exec-failed,带上 helpUrl 与原因', async () => {
120
- const { shell } = fakeShell({
121
- url: 'about:blank',
122
- open: [{ stdout: JSON.stringify({ error: { message: 'browser session is not bound' } }), exitCode: 1 }],
123
- });
124
- const out = await browserFind({ shell, css: CSS, url: SITE, ...FAST });
125
-
126
- expect(out.ok).toBe(false);
127
- expect(out.code).toBe('cli-exec-failed');
128
- expect(out.message).toContain('打不开');
129
- expect(out.detail).toContain('not bound');
130
- expect(out.helpUrl).toBe(SITE);
131
- });
132
-
133
- it('open 那条命令直接抛(opencli 不在)也要报出来,不许把异常漏出去', async () => {
134
- const { shell } = fakeShell({ url: 'about:blank', open: [{ throws: true }] });
135
- const out = await browserFind({ shell, css: CSS, url: SITE, ...FAST });
136
-
137
- expect(out.ok).toBe(false);
138
- expect(out.code).toBe('cli-exec-failed');
139
- expect(out.detail).toContain('ENOENT');
140
- });
141
- });
142
-
143
- describe('browserFind · 找到与找不到', () => {
144
- it('页面上就是没有匹配元素 → ok,但 count 是 0(空不撒谎)', async () => {
145
- const { shell } = fakeShell({ url: SITE, find: [{ stdout: JSON.stringify({ entries: [] }) }] });
146
- const out = await browserFind({ shell, css: CSS, url: SITE, ...FAST });
147
-
148
- expect(out.ok).toBe(true);
149
- expect(out.count).toBe(0);
150
- expect(out.message).toContain('没有匹配');
151
- });
152
-
153
- it('在站点上但 find 失败(会话刚重启那类):安全网再 open 一次并重读', async () => {
154
- const { shell, commands } = fakeShell({
155
- url: SITE,
156
- find: [{ stdout: ZERO_MATCH, exitCode: 1 }, { stdout: MATCH }],
157
- });
158
- const out = await browserFind({ shell, css: CSS, url: SITE, ...FAST });
159
-
160
- expect(out.ok).toBe(true);
161
- expect(out.count).toBe(1);
162
- expect(commands.filter((one) => one.includes("'find'"))).toHaveLength(2);
163
- expect(commands.some((one) => one.includes("'open'"))).toBe(true);
164
- });
165
-
166
- it('一直失败:把 opencli 自己在 stdout JSON 里说的原因当 message(不是 stderr)', async () => {
167
- const { shell } = fakeShell({
168
- url: SITE,
169
- find: [{ stdout: ZERO_MATCH, exitCode: 1 }, { stdout: ZERO_MATCH, exitCode: 1 }],
170
- });
171
- const out = await browserFind({ shell, css: CSS, url: SITE, ...FAST });
172
-
173
- expect(out.ok).toBe(false);
174
- expect(out.code).toBe('cli-exec-failed');
175
- expect(out.message).toContain('matched 0 elements');
176
- expect(out.hint).toBe('try a less specific selector');
177
- expect(out.helpUrl).toBe(SITE);
178
- });
179
-
180
- it('超时归成 cli-timeout,且不再折腾页面', async () => {
181
- const { shell, commands } = fakeShell({
182
- url: SITE,
183
- find: [{ timedOut: true, exitCode: 124 }],
184
- });
185
- const out = await browserFind({ shell, css: CSS, url: SITE, ...FAST });
186
-
187
- expect(out.ok).toBe(false);
188
- expect(out.code).toBe('cli-timeout');
189
- expect(out.detail).toContain('秒内返回');
190
- expect(commands.some((one) => one.includes("'open'"))).toBe(false);
191
- });
192
-
193
- it('url 没给:只 find,不问页面也不开', async () => {
194
- const { shell, commands } = fakeShell({ find: [{ stdout: MATCH }] });
195
- const out = await browserFind({ shell, css: CSS, ...FAST });
196
-
197
- expect(out.ok).toBe(true);
198
- expect(commands).toHaveLength(1);
199
- expect(commands[0]).toContain("'find'");
200
- });
201
-
202
- it('open:false:只 find(不说自己在哪一页,也不开)', async () => {
203
- const { shell, commands } = fakeShell({
204
- url: SITE,
205
- find: [{ stdout: ZERO_MATCH, exitCode: 1 }, { stdout: MATCH }],
206
- });
207
- const out = await browserFind({ shell, css: CSS, url: SITE, open: false, ...FAST });
208
-
209
- expect(out.ok).toBe(false);
210
- expect(commands).toHaveLength(1);
211
- });
212
-
213
- it('opencli 根本没起来(find 那条抛)→ cli-exec-failed,带照做的话', async () => {
214
- const { shell } = fakeShell({ url: SITE, find: [{ throws: true }] });
215
- const out = await browserFind({ shell, css: CSS, url: SITE, ...FAST });
216
-
217
- expect(out.ok).toBe(false);
218
- expect(out.code).toBe('cli-exec-failed');
219
- expect(out.detail).toContain('ENOENT');
220
- expect(out.hint).toContain('opencli');
221
- });
222
-
223
- it('session / command / limit / textMax 都能覆盖(命令行每个 token 都加引号)', async () => {
224
- const { shell, commands } = fakeShell({ url: SITE, find: [{ stdout: MATCH }] });
225
- await browserFind({
226
- shell,
227
- css: CSS,
228
- url: SITE,
229
- session: 'dsh-fx-rate',
230
- command: '/usr/local/bin/opencli',
231
- limit: 60,
232
- textMax: 400,
233
- ...FAST,
234
- });
235
-
236
- const findLine = commands.find((one) => one.includes("'find'")) ?? '';
237
- expect(commands[0]).toContain("'/usr/local/bin/opencli'");
238
- expect(commands[0]).toContain("'dsh-fx-rate'");
239
- expect(findLine).toContain("'--limit' '60'");
240
- expect(findLine).toContain("'--text-max' '400'");
241
- });
242
- });
243
-
244
- describe('cellRowsOf / reasonOf / sameOrigin', () => {
245
- it('按换行切格、丢掉空行、按 minCells 过滤', () => {
246
- const rows = cellRowsOf([
247
- { text: '货币名称\n现汇买入价\n\n现钞买入价\n现汇卖出价' },
248
- { text: '只有一个' },
249
- { text: undefined },
250
- ]);
251
-
252
- expect(rows).toHaveLength(1);
253
- expect(rows[0]).toEqual(['货币名称', '现汇买入价', '现钞买入价', '现汇卖出价']);
254
- expect(cellRowsOf([{ text: '一\n二' }], 3)).toEqual([]);
255
- expect(cellRows('a\n b \n\n')).toEqual(['a', 'b']);
256
- });
257
-
258
- it('reasonOf 优先取 error.message,没有才退回到最后一行', () => {
259
- expect(reasonOf(JSON.stringify({ error: { message: 'matched 0 elements' } }), '')).toBe(
260
- 'matched 0 elements',
261
- );
262
- expect(reasonOf('', 'opencli: daemon not ready\nboom')).toBe('boom');
263
- expect(reasonOf('not json', '')).toBe('not json');
264
- });
265
-
266
- it('entriesOf 只认有 text 的项,缺 nth 用序号补', () => {
267
- const entries = entriesOf({
268
- entries: [{ text: 'a', tag: 'tr' }, { tag: 'tr' }, 'nope', { text: 'b', tag: 'tr', nth: 9 }],
269
- });
270
-
271
- expect(entries).toHaveLength(3);
272
- expect(entries[0]).toEqual({ text: 'a', tag: 'tr', nth: 0 });
273
- expect(entries[2]).toEqual({ text: 'b', tag: 'tr', nth: 9 });
274
- expect(entriesOf(undefined)).toEqual([]);
275
- expect(entriesOf({ entries: 'nope' })).toEqual([]);
276
- });
277
-
278
- it('urlOf 认裸 URL、认带引号的、也认 {url} 的 JSON(判错的代价是每次多开一次页面)', () => {
279
- expect(urlOf('https://a.test/x\n')).toBe('https://a.test/x');
280
- expect(urlOf('URL: https://a.test/x')).toBe('https://a.test/x');
281
- expect(urlOf('"https://a.test/x"')).toBe('https://a.test/x');
282
- expect(urlOf('{"url":"https://a.test/x","title":"t"}')).toBe('https://a.test/x');
283
- expect(urlOf('')).toBe('');
284
- });
285
-
286
- it('sameOrigin 只看同源;空串或不是 URL 一律不算', () => {
287
- expect(sameOrigin('https://a.test/x', 'https://a.test/y')).toBe(true);
288
- expect(sameOrigin('https://a.test/x', 'https://b.test/y')).toBe(false);
289
- expect(sameOrigin('about:blank', SITE)).toBe(false);
290
- expect(sameOrigin('', SITE)).toBe(false);
291
- expect(sameOrigin('not a url', SITE)).toBe(false);
292
- expect(OTHER).toContain('elsewhere');
293
- });
294
- });
295
-
296
- function backgroundless(commands: readonly string[]): readonly string[] {
297
- return commands
298
- .filter((one) => one.includes("'browser'"))
299
- .filter(
300
- (one) => !one.includes("'--window' 'background'") || !one.includes("'OPENCLI_WINDOW=background'"),
301
- );
302
- }
303
-
304
- describe('browserFind · 后台静默(问页 + 读 + 打开都不许抢焦点)', () => {
305
- it('已经在目标站点上:get url + find 两条全带', async () => {
306
- const { shell, commands } = fakeShell({ url: SITE, find: [{ stdout: MATCH }] });
307
- await browserFind({ shell, css: CSS, url: SITE, ...FAST });
308
-
309
- expect(commands).toHaveLength(2);
310
- expect(backgroundless(commands)).toEqual([]);
311
- });
312
-
313
- it('不在目标站点上:get url + open + find 三条全带', async () => {
314
- const { shell, commands } = fakeShell({ url: ['about:blank', SITE], find: [{ stdout: MATCH }] });
315
- await browserFind({ shell, css: CSS, url: SITE, ...FAST });
316
-
317
- expect(commands.filter((one) => one.includes("'open'"))).toHaveLength(1);
318
- expect(backgroundless(commands)).toEqual([]);
319
- });
320
- });
321
-
322
- describe('browserFind · 读有它自己那条会话(不许碰屏那条)', () => {
323
- it('默认那条命令里一条都不带屏那条会话名', async () => {
324
- const { shell, commands } = fakeShell({ url: ['about:blank', SITE], find: [{ stdout: MATCH }] });
325
- await browserFind({ shell, css: CSS, url: SITE, ...FAST });
326
-
327
- expect(commands.length).toBeGreaterThan(0);
328
- expect(commands.some((one) => one.includes(`'${BROWSER_SCREEN_SESSION}'`))).toBe(false);
329
- });
330
- });
@@ -1,228 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { BROWSER_READ_SESSION, BROWSER_SCREEN_SESSION, browserRead } from '../browser-read.js';
3
-
4
- interface Step {
5
- readonly stdout?: string;
6
- readonly stderr?: string;
7
- readonly exitCode?: number;
8
- readonly timedOut?: boolean;
9
- }
10
-
11
- function fakeShell(plan: readonly Step[]) {
12
- const commands: string[] = [];
13
- let index = 0;
14
- const shell = {
15
- resolve(request: { command: string }) {
16
- commands.push(request.command);
17
- return request;
18
- },
19
- async execute() {
20
- const step = plan[Math.min(index, plan.length - 1)] ?? {};
21
- index += 1;
22
- return {
23
- result: async () => ({
24
- exitCode: step.exitCode ?? 0,
25
- timedOut: step.timedOut ?? false,
26
- aborted: false,
27
- stdout: { text: step.stdout ?? '', truncated: false },
28
- stderr: { text: step.stderr ?? '', truncated: false },
29
- }),
30
- };
31
- },
32
- };
33
- return { shell: shell as never, commands };
34
- }
35
-
36
- const FAST = { retryMs: 1, settleMs: 1, timeoutMs: 50 } as const;
37
- const script = 'return 1';
38
- const URL = 'https://example.test/wallet';
39
-
40
- describe('browserRead · 读成功', () => {
41
- it('页面回 JSON → ok,命令走 `browser <session> eval`', async () => {
42
- const { shell, commands } = fakeShell([{ stdout: JSON.stringify({ ok: true, n: 3 }) }]);
43
- const out = await browserRead({ shell, script, url: URL, ...FAST });
44
-
45
- expect(out.ok).toBe(true);
46
- expect(out.code).toBe('ok');
47
- expect(out.value).toEqual({ ok: true, n: 3 });
48
- expect(commands[0]).toContain(`'browser' '${BROWSER_READ_SESSION}' 'eval'`);
49
- });
50
-
51
- it('脚本没回 ok 字段(例如直接回一个数)也算读到了', async () => {
52
- const { shell } = fakeShell([{ stdout: '42' }]);
53
- const out = await browserRead({ shell, script, ...FAST });
54
- expect(out.ok).toBe(true);
55
- expect(out.value).toBe(42);
56
- });
57
- });
58
-
59
- describe('browserRead · 平台码归一(不许透传)', () => {
60
- it('AUTH_UNAUTHORIZED → auth-required + helpUrl', async () => {
61
- const { shell } = fakeShell([
62
- { stdout: JSON.stringify({ ok: false, code: 'AUTH_UNAUTHORIZED', message: '登录已过期' }) },
63
- ]);
64
- const out = await browserRead({ shell, script, url: URL, loginUrl: 'https://sso.example.com', ...FAST });
65
-
66
- expect(out.ok).toBe(false);
67
- expect(out.code).toBe('auth-required');
68
- expect(out.pageCode).toBe('AUTH_UNAUTHORIZED');
69
- expect(out.helpUrl).toBe('https://sso.example.com');
70
- expect(out.message).toBe('登录已过期');
71
- });
72
-
73
- it('PERMISSION_DENIED → permission-denied', async () => {
74
- const { shell } = fakeShell([{ stdout: JSON.stringify({ ok: false, code: 'permission_denied' }) }]);
75
- const out = await browserRead({ shell, script, url: URL, ...FAST });
76
- expect(out.code).toBe('permission-denied');
77
- });
78
-
79
- it('业务码原样回(落到阻断屏的"其它"那一支)', async () => {
80
- const { shell } = fakeShell([
81
- { stdout: JSON.stringify({ ok: false, code: 'network', message: '连不上', hint: '稍后重试' }) },
82
- ]);
83
- const out = await browserRead({ shell, script, url: URL, ...FAST });
84
- expect(out.code).toBe('network');
85
- expect(out.pageCode).toBe('network');
86
- expect(out.message).toBe('连不上');
87
- expect(out.hint).toBe('稍后重试');
88
- });
89
- });
90
-
91
- describe('browserRead · 两拍(不在站点上就先打开)', () => {
92
- it('not-on-site → open(后台窗口)→ 再读一遍 → ok', async () => {
93
- const { shell, commands } = fakeShell([
94
- { stdout: JSON.stringify({ ok: false, code: 'not-on-site' }) },
95
- { stdout: 'opened' },
96
- { stdout: JSON.stringify({ ok: true }) },
97
- ]);
98
- const out = await browserRead({ shell, script, url: URL, ...FAST });
99
-
100
- expect(out.ok).toBe(true);
101
- expect(commands.some((one) => one.includes("'open'") && one.includes('--window'))).toBe(true);
102
- expect(commands.filter((one) => one.includes("'eval'"))).toHaveLength(2);
103
- });
104
-
105
- it('open 关了:直接说清"没停在那套系统上"', async () => {
106
- const { shell, commands } = fakeShell([{ stdout: JSON.stringify({ ok: false, code: 'not-on-site' }) }]);
107
- const out = await browserRead({ shell, script, url: URL, open: false, ...FAST });
108
-
109
- expect(out.ok).toBe(false);
110
- expect(out.message).toContain('没停在那套系统上');
111
- expect(commands.some((one) => one.includes("'open'"))).toBe(false);
112
- });
113
-
114
- it('打开之后还是不在站点上:报 cli-exec-failed(不是静默成功)', async () => {
115
- const { shell } = fakeShell([
116
- { stdout: JSON.stringify({ ok: false, code: 'not-on-site' }) },
117
- { stdout: 'opened' },
118
- { stdout: JSON.stringify({ ok: false, code: 'not-on-site' }) },
119
- ]);
120
- const out = await browserRead({ shell, script, url: URL, ...FAST });
121
- expect(out.code).toBe('cli-exec-failed');
122
- expect(out.message).toContain('仍然不在那套系统上');
123
- });
124
- });
125
-
126
- describe('browserRead · 读有它自己那条会话(不许碰屏那条)', () => {
127
- it('默认那条不是工坊绑用户当前页用的那条', () => {
128
- expect(BROWSER_READ_SESSION).not.toBe(BROWSER_SCREEN_SESSION);
129
- });
130
-
131
- it('两拍三条命令里一条都不带屏那条会话名', async () => {
132
- const { shell, commands } = fakeShell([
133
- { stdout: JSON.stringify({ ok: false, code: 'not-on-site' }) },
134
- { stdout: 'opened' },
135
- { stdout: JSON.stringify({ ok: true, n: 1 }) },
136
- ]);
137
- const out = await browserRead({ shell, script, url: URL, ...FAST });
138
-
139
- expect(out.ok).toBe(true);
140
- expect(commands.some((one) => one.includes(`'${BROWSER_SCREEN_SESSION}'`))).toBe(false);
141
- });
142
-
143
- it('调用点自己给的 session 说了算(模块可以自己开一条)', async () => {
144
- const { shell, commands } = fakeShell([
145
- { stdout: JSON.stringify({ ok: false, code: 'not-on-site' }) },
146
- { stdout: 'opened' },
147
- { stdout: JSON.stringify({ ok: true }) },
148
- ]);
149
- const out = await browserRead({ shell, script, url: URL, session: 'llm-wallet', ...FAST });
150
-
151
- expect(out.ok).toBe(true);
152
- expect(commands.every((one) => one.includes("'llm-wallet'"))).toBe(true);
153
- expect(commands.some((one) => one.includes(`'${BROWSER_SCREEN_SESSION}'`))).toBe(false);
154
- });
155
- });
156
-
157
- describe('browserRead · 命令这一层的问题', () => {
158
- it('刚重启时第一次失败:等一下再读一次就成功(实测的那个坑)', async () => {
159
- const { shell, commands } = fakeShell([
160
- { exitCode: 1, stderr: 'opencli: daemon not ready' },
161
- { stdout: JSON.stringify({ ok: true }) },
162
- ]);
163
- const out = await browserRead({ shell, script, ...FAST });
164
- expect(out.ok).toBe(true);
165
- expect(commands.filter((one) => one.includes("'eval'"))).toHaveLength(2);
166
- });
167
-
168
- it('一直失败 → cli-exec-failed + 原始输出尾巴', async () => {
169
- const { shell } = fakeShell([{ exitCode: 1, stderr: 'boom\nline2' }]);
170
- const out = await browserRead({ shell, script, ...FAST });
171
- expect(out.code).toBe('cli-exec-failed');
172
- expect(out.detail).toContain('boom');
173
- });
174
-
175
- it('stderr 里是权限问题 → permission-denied(不当成普通失败)', async () => {
176
- const { shell } = fakeShell([{ exitCode: 1, stderr: 'PERMISSION_DENIED: nope' }]);
177
- const out = await browserRead({ shell, script, ...FAST });
178
- expect(out.code).toBe('permission-denied');
179
- });
180
-
181
- it('超时 → cli-timeout,而且**不**重试(已经等满了)', async () => {
182
- const { shell, commands } = fakeShell([{ timedOut: true }]);
183
- const out = await browserRead({ shell, script, ...FAST });
184
- expect(out.code).toBe('cli-timeout');
185
- expect(commands.filter((one) => one.includes("'eval'"))).toHaveLength(1);
186
- });
187
-
188
- it('输出不是 JSON → cli-exec-failed 且说清是"不是 JSON"', async () => {
189
- const { shell } = fakeShell([{ stdout: 'not json at all' }]);
190
- const out = await browserRead({ shell, script, ...FAST });
191
- expect(out.code).toBe('cli-exec-failed');
192
- expect(out.message).toContain('不是 JSON');
193
- });
194
- });
195
-
196
- function backgroundless(commands: readonly string[]): readonly string[] {
197
- return commands
198
- .filter((one) => one.includes("'browser'"))
199
- .filter(
200
- (one) => !one.includes("'--window' 'background'") || !one.includes("'OPENCLI_WINDOW=background'"),
201
- );
202
- }
203
-
204
- describe('browserRead · 后台静默(每一拍都不许抢焦点)', () => {
205
- it('不在站点上那一趟:eval → open → eval 三条全带', async () => {
206
- const { shell, commands } = fakeShell([
207
- { stdout: JSON.stringify({ ok: false, code: 'not-on-site' }) },
208
- {},
209
- { stdout: JSON.stringify({ ok: true, n: 1 }) },
210
- ]);
211
- const out = await browserRead({ shell, script, url: URL, ...FAST });
212
-
213
- expect(out.ok).toBe(true);
214
- expect(commands.filter((one) => one.includes("'browser'"))).toHaveLength(3);
215
- expect(backgroundless(commands)).toEqual([]);
216
- });
217
-
218
- it('命令失败重试那一拍也带', async () => {
219
- const { shell, commands } = fakeShell([
220
- { exitCode: 1, stderr: 'boom' },
221
- { stdout: JSON.stringify({ ok: true }) },
222
- ]);
223
- await browserRead({ shell, script, url: URL, ...FAST });
224
-
225
- expect(commands.filter((one) => one.includes("'eval'"))).toHaveLength(2);
226
- expect(backgroundless(commands)).toEqual([]);
227
- });
228
- });
@@ -1,46 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from 'vitest';
2
- import { OPENCLI_PIN } from '../../config/constants.js';
3
-
4
- async function load(dshHome: string, home: string) {
5
- vi.resetModules();
6
- vi.stubEnv('DSH_HOME', dshHome);
7
- vi.stubEnv('HOME', home);
8
- return import('../opencli-runtime');
9
- }
10
-
11
- afterEach(() => {
12
- vi.unstubAllEnvs();
13
- vi.resetModules();
14
- });
15
-
16
- describe('opencli 隔离运行时', () => {
17
- it('三条路径都从 $DSH_HOME 推(与 wrapper 必须一致)', async () => {
18
- const runtime = await load('/tmp/dshhome', '/tmp/realhome');
19
-
20
- expect(runtime.isolatedRoot()).toBe('/tmp/dshhome/workbench/opencli');
21
- expect(runtime.isolatedHome()).toBe('/tmp/dshhome/workbench/opencli/home');
22
- expect(runtime.toolchainDir()).toBe('/tmp/dshhome/workbench/opencli/toolchain');
23
- });
24
-
25
- it('DSH_HOME 为空 → 退回 $HOME/.dsh(与 wrapper 的 `${DSH_HOME:-…}` 同义)', async () => {
26
- const runtime = await load('', '/tmp/realhome');
27
-
28
- expect(runtime.isolatedRoot()).toBe('/tmp/realhome/.dsh/workbench/opencli');
29
- });
30
-
31
- it('★ 安装命令装进**隔离前缀**且钉住版本 —— 不是 npm i -g', async () => {
32
- const runtime = await load('/tmp/dshhome', '/tmp/realhome');
33
- const command = runtime.isolatedInstallCommand();
34
-
35
- expect(command).toContain("--prefix '/tmp/dshhome/workbench/opencli/toolchain'");
36
- expect(command).toContain(`@jackwener/opencli@${OPENCLI_PIN}`);
37
- expect(command).not.toContain(' -g');
38
- expect(command).toContain("--cache '/tmp/dshhome/workbench/opencli/npm-cache'");
39
- });
40
-
41
- it('★ 对外给的入口是**稳定路径**(软链),不依赖本仓库在哪', async () => {
42
- const runtime = await load('/tmp/dshhome', '/tmp/realhome');
43
-
44
- expect(runtime.isolatedCommand()).toBe('/tmp/dshhome/workbench/opencli/bin/opencli');
45
- });
46
- });