@logictan/dsh-config-manager 0.1.60

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 (429) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +105 -0
  3. package/cordis.patch.yml +21 -0
  4. package/lib/adapters/agent-instructions.d.ts +13 -0
  5. package/lib/adapters/agent-instructions.js +46 -0
  6. package/lib/adapters/agent-presets.d.ts +13 -0
  7. package/lib/adapters/agent-presets.js +14 -0
  8. package/lib/adapters/credentials.d.ts +37 -0
  9. package/lib/adapters/credentials.js +122 -0
  10. package/lib/adapters/file-collection.d.ts +15 -0
  11. package/lib/adapters/file-collection.js +127 -0
  12. package/lib/adapters/index.d.ts +57 -0
  13. package/lib/adapters/index.js +55 -0
  14. package/lib/adapters/link-report.d.ts +16 -0
  15. package/lib/adapters/link-report.js +47 -0
  16. package/lib/adapters/mcp.d.ts +28 -0
  17. package/lib/adapters/mcp.js +178 -0
  18. package/lib/adapters/plugin-files.d.ts +18 -0
  19. package/lib/adapters/plugin-files.js +142 -0
  20. package/lib/adapters/plugins.d.ts +41 -0
  21. package/lib/adapters/plugins.js +603 -0
  22. package/lib/adapters/pnpm-workspace.d.ts +26 -0
  23. package/lib/adapters/pnpm-workspace.js +147 -0
  24. package/lib/adapters/prompts.d.ts +30 -0
  25. package/lib/adapters/prompts.js +231 -0
  26. package/lib/adapters/providers.d.ts +34 -0
  27. package/lib/adapters/providers.js +155 -0
  28. package/lib/adapters/self.d.ts +17 -0
  29. package/lib/adapters/self.js +71 -0
  30. package/lib/adapters/sessions.d.ts +14 -0
  31. package/lib/adapters/sessions.js +15 -0
  32. package/lib/adapters/settings.d.ts +28 -0
  33. package/lib/adapters/settings.js +165 -0
  34. package/lib/adapters/skills.d.ts +13 -0
  35. package/lib/adapters/skills.js +14 -0
  36. package/lib/adapters/test-helpers.d.ts +125 -0
  37. package/lib/adapters/test-helpers.js +236 -0
  38. package/lib/adapters/ui.d.ts +27 -0
  39. package/lib/adapters/ui.js +89 -0
  40. package/lib/adapters/workspaces.d.ts +13 -0
  41. package/lib/adapters/workspaces.js +106 -0
  42. package/lib/client.d.ts +1026 -0
  43. package/lib/client.js +10047 -0
  44. package/lib/core/analyzer.d.ts +99 -0
  45. package/lib/core/analyzer.js +934 -0
  46. package/lib/core/backup.d.ts +138 -0
  47. package/lib/core/backup.js +513 -0
  48. package/lib/core/boot-rescue.d.ts +146 -0
  49. package/lib/core/boot-rescue.js +618 -0
  50. package/lib/core/cache-cleaner.d.ts +31 -0
  51. package/lib/core/cache-cleaner.js +95 -0
  52. package/lib/core/config-lifecycle.d.ts +180 -0
  53. package/lib/core/config-lifecycle.js +481 -0
  54. package/lib/core/config-snapshot.d.ts +135 -0
  55. package/lib/core/config-snapshot.js +424 -0
  56. package/lib/core/config-state.d.ts +70 -0
  57. package/lib/core/config-state.js +177 -0
  58. package/lib/core/consult-source.d.ts +58 -0
  59. package/lib/core/consult-source.js +260 -0
  60. package/lib/core/crash-report.d.ts +84 -0
  61. package/lib/core/crash-report.js +317 -0
  62. package/lib/core/exporter.d.ts +68 -0
  63. package/lib/core/exporter.js +375 -0
  64. package/lib/core/ghost-sweep.d.ts +19 -0
  65. package/lib/core/ghost-sweep.js +52 -0
  66. package/lib/core/importer.d.ts +60 -0
  67. package/lib/core/importer.js +29 -0
  68. package/lib/core/index.d.ts +14 -0
  69. package/lib/core/index.js +14 -0
  70. package/lib/core/journal.d.ts +197 -0
  71. package/lib/core/journal.js +418 -0
  72. package/lib/core/local-plugin-host.d.ts +35 -0
  73. package/lib/core/local-plugin-host.js +120 -0
  74. package/lib/core/local-plugin-pack.d.ts +135 -0
  75. package/lib/core/local-plugin-pack.js +278 -0
  76. package/lib/core/messages.d.ts +310 -0
  77. package/lib/core/messages.js +621 -0
  78. package/lib/core/migration-consult.d.ts +117 -0
  79. package/lib/core/migration-consult.js +363 -0
  80. package/lib/core/migration-history.d.ts +153 -0
  81. package/lib/core/migration-history.js +411 -0
  82. package/lib/core/model-tools.d.ts +50 -0
  83. package/lib/core/model-tools.js +148 -0
  84. package/lib/core/msg-types.d.ts +8 -0
  85. package/lib/core/msg-types.js +6 -0
  86. package/lib/core/phase3-child-crash.d.ts +1 -0
  87. package/lib/core/phase3-child-crash.js +87 -0
  88. package/lib/core/phase3-host.d.ts +138 -0
  89. package/lib/core/phase3-host.js +359 -0
  90. package/lib/core/phase3-prod-child.d.ts +1 -0
  91. package/lib/core/phase3-prod-child.js +35 -0
  92. package/lib/core/phase4-crash-child.d.ts +1 -0
  93. package/lib/core/phase4-crash-child.js +103 -0
  94. package/lib/core/plugin-cli.d.ts +150 -0
  95. package/lib/core/plugin-cli.js +497 -0
  96. package/lib/core/reconcile.d.ts +120 -0
  97. package/lib/core/reconcile.js +389 -0
  98. package/lib/core/recovery-orchestrator.d.ts +94 -0
  99. package/lib/core/recovery-orchestrator.js +327 -0
  100. package/lib/core/restore.d.ts +162 -0
  101. package/lib/core/restore.js +703 -0
  102. package/lib/core/rollback.d.ts +12 -0
  103. package/lib/core/rollback.js +145 -0
  104. package/lib/core/run-registry.d.ts +85 -0
  105. package/lib/core/run-registry.js +157 -0
  106. package/lib/core/startup-barrier.d.ts +53 -0
  107. package/lib/core/startup-barrier.js +77 -0
  108. package/lib/core/types.d.ts +515 -0
  109. package/lib/core/types.js +27 -0
  110. package/lib/core/undo.d.ts +73 -0
  111. package/lib/core/undo.js +87 -0
  112. package/lib/core/validator.d.ts +18 -0
  113. package/lib/core/validator.js +77 -0
  114. package/lib/core/verify-recovery.d.ts +21 -0
  115. package/lib/core/verify-recovery.js +256 -0
  116. package/lib/core/watcher.d.ts +101 -0
  117. package/lib/core/watcher.js +219 -0
  118. package/lib/index.d.ts +238 -0
  119. package/lib/index.js +2764 -0
  120. package/lib/migrations/index.d.ts +29 -0
  121. package/lib/migrations/index.js +56 -0
  122. package/lib/migrations/v1-to-v2.d.ts +9 -0
  123. package/lib/migrations/v1-to-v2.js +10 -0
  124. package/lib/profiles/index.d.ts +4 -0
  125. package/lib/profiles/index.js +5 -0
  126. package/lib/profiles/profile-manager.d.ts +106 -0
  127. package/lib/profiles/profile-manager.js +543 -0
  128. package/lib/schema/config.d.ts +28 -0
  129. package/lib/schema/config.js +165 -0
  130. package/lib/schema/index.d.ts +24 -0
  131. package/lib/schema/index.js +26 -0
  132. package/lib/schema/manifest.d.ts +28 -0
  133. package/lib/schema/manifest.js +109 -0
  134. package/lib/schema/tombstones.d.ts +46 -0
  135. package/lib/schema/tombstones.js +64 -0
  136. package/lib/schema/types.d.ts +232 -0
  137. package/lib/schema/types.js +8 -0
  138. package/lib/schema/versions.d.ts +27 -0
  139. package/lib/schema/versions.js +50 -0
  140. package/lib/security/encryption.d.ts +85 -0
  141. package/lib/security/encryption.js +279 -0
  142. package/lib/security/index.d.ts +16 -0
  143. package/lib/security/index.js +17 -0
  144. package/lib/security/integrity.d.ts +30 -0
  145. package/lib/security/integrity.js +78 -0
  146. package/lib/security/redaction.d.ts +26 -0
  147. package/lib/security/redaction.js +95 -0
  148. package/lib/security/secret-scanner.d.ts +86 -0
  149. package/lib/security/secret-scanner.js +404 -0
  150. package/lib/security/vault.d.ts +59 -0
  151. package/lib/security/vault.js +151 -0
  152. package/lib/security/zip-security.d.ts +27 -0
  153. package/lib/security/zip-security.js +154 -0
  154. package/lib/sync/autosync-config.d.ts +39 -0
  155. package/lib/sync/autosync-config.js +151 -0
  156. package/lib/sync/autosync-scheduler.d.ts +139 -0
  157. package/lib/sync/autosync-scheduler.js +564 -0
  158. package/lib/sync/fs.d.ts +18 -0
  159. package/lib/sync/fs.js +49 -0
  160. package/lib/sync/git/git-transport.d.ts +100 -0
  161. package/lib/sync/git/git-transport.js +437 -0
  162. package/lib/sync/github-auth.d.ts +131 -0
  163. package/lib/sync/github-auth.js +264 -0
  164. package/lib/sync/layout.d.ts +43 -0
  165. package/lib/sync/layout.js +203 -0
  166. package/lib/sync/snapshot-json.d.ts +9 -0
  167. package/lib/sync/snapshot-json.js +107 -0
  168. package/lib/sync/sync-config.d.ts +81 -0
  169. package/lib/sync/sync-config.js +281 -0
  170. package/lib/sync/sync-engine.d.ts +269 -0
  171. package/lib/sync/sync-engine.js +591 -0
  172. package/lib/sync/sync-history.d.ts +42 -0
  173. package/lib/sync/sync-history.js +78 -0
  174. package/lib/sync/sync-selection.d.ts +29 -0
  175. package/lib/sync/sync-selection.js +127 -0
  176. package/lib/sync/sync-session.d.ts +40 -0
  177. package/lib/sync/sync-session.js +54 -0
  178. package/lib/sync/sync-state.d.ts +37 -0
  179. package/lib/sync/sync-state.js +108 -0
  180. package/lib/sync/transport.d.ts +75 -0
  181. package/lib/sync/transport.js +55 -0
  182. package/lib/sync/ui-prefs.d.ts +34 -0
  183. package/lib/sync/ui-prefs.js +109 -0
  184. package/lib/sync/webdav/webdav-transport.d.ts +77 -0
  185. package/lib/sync/webdav/webdav-transport.js +451 -0
  186. package/lib/ui/errors.d.ts +19 -0
  187. package/lib/ui/errors.js +81 -0
  188. package/lib/ui/export-flow.d.ts +82 -0
  189. package/lib/ui/export-flow.js +98 -0
  190. package/lib/ui/i18n.d.ts +289 -0
  191. package/lib/ui/i18n.js +581 -0
  192. package/lib/ui/migration-consult-view.d.ts +91 -0
  193. package/lib/ui/migration-consult-view.js +115 -0
  194. package/lib/ui/progress.d.ts +38 -0
  195. package/lib/ui/progress.js +87 -0
  196. package/lib/ui/report.d.ts +23 -0
  197. package/lib/ui/report.js +153 -0
  198. package/lib/ui/test-helpers.d.ts +69 -0
  199. package/lib/ui/test-helpers.js +154 -0
  200. package/lib/ui/types.d.ts +257 -0
  201. package/lib/ui/types.js +21 -0
  202. package/lib/utils/atomic-write.d.ts +88 -0
  203. package/lib/utils/atomic-write.js +399 -0
  204. package/lib/utils/env-lock.d.ts +393 -0
  205. package/lib/utils/env-lock.js +930 -0
  206. package/lib/utils/hashing.d.ts +17 -0
  207. package/lib/utils/hashing.js +41 -0
  208. package/lib/utils/json.d.ts +24 -0
  209. package/lib/utils/json.js +76 -0
  210. package/lib/utils/logger.d.ts +39 -0
  211. package/lib/utils/logger.js +82 -0
  212. package/lib/utils/paths.d.ts +42 -0
  213. package/lib/utils/paths.js +189 -0
  214. package/lib/utils/proxy.d.ts +69 -0
  215. package/lib/utils/proxy.js +455 -0
  216. package/lib/utils/recursive-walk.d.ts +24 -0
  217. package/lib/utils/recursive-walk.js +134 -0
  218. package/lib/utils/zip.d.ts +54 -0
  219. package/lib/utils/zip.js +285 -0
  220. package/package.json +188 -0
  221. package/src/adapters/agent-instructions.ts +48 -0
  222. package/src/adapters/agent-presets.ts +14 -0
  223. package/src/adapters/credentials.test.ts +90 -0
  224. package/src/adapters/credentials.ts +143 -0
  225. package/src/adapters/file-collection.ts +137 -0
  226. package/src/adapters/files.test.ts +353 -0
  227. package/src/adapters/index.ts +94 -0
  228. package/src/adapters/link-report.test.ts +65 -0
  229. package/src/adapters/link-report.ts +61 -0
  230. package/src/adapters/mcp.test.ts +96 -0
  231. package/src/adapters/mcp.ts +185 -0
  232. package/src/adapters/plugin-files.ts +146 -0
  233. package/src/adapters/plugins.test.ts +326 -0
  234. package/src/adapters/plugins.ts +615 -0
  235. package/src/adapters/pnpm-workspace.test.ts +87 -0
  236. package/src/adapters/pnpm-workspace.ts +150 -0
  237. package/src/adapters/prompts.test.ts +110 -0
  238. package/src/adapters/prompts.ts +230 -0
  239. package/src/adapters/providers.test.ts +126 -0
  240. package/src/adapters/providers.ts +173 -0
  241. package/src/adapters/roundtrip.test.ts +190 -0
  242. package/src/adapters/self.test.ts +92 -0
  243. package/src/adapters/self.ts +75 -0
  244. package/src/adapters/sessions.ts +15 -0
  245. package/src/adapters/settings.test.ts +100 -0
  246. package/src/adapters/settings.ts +187 -0
  247. package/src/adapters/skills.ts +14 -0
  248. package/src/adapters/test-helpers.ts +248 -0
  249. package/src/adapters/ui.test.ts +57 -0
  250. package/src/adapters/ui.ts +108 -0
  251. package/src/adapters/workspaces.test.ts +106 -0
  252. package/src/adapters/workspaces.ts +109 -0
  253. package/src/client/ConfigManagerSection.tsx +90 -0
  254. package/src/client/api.ts +66 -0
  255. package/src/client/client-types.ts +18 -0
  256. package/src/client/common/ErrorBanner.tsx +73 -0
  257. package/src/client/common/Icon.tsx +129 -0
  258. package/src/client/common/Modal.tsx +173 -0
  259. package/src/client/common/ToastViewport.tsx +73 -0
  260. package/src/client/common/toast-store.test.ts +192 -0
  261. package/src/client/common/toast-store.ts +181 -0
  262. package/src/client/common/toast-types.ts +7 -0
  263. package/src/client/common/ui.tsx +342 -0
  264. package/src/client/config-manager.module.css +2971 -0
  265. package/src/client/css-modules.d.ts +8 -0
  266. package/src/client/index.ts +65 -0
  267. package/src/client/locales.ts +56 -0
  268. package/src/client/lucide-icons.d.ts +38 -0
  269. package/src/client/run-store.test.ts +223 -0
  270. package/src/client/run-store.ts +372 -0
  271. package/src/client/sync/SyncConfirmView.tsx +379 -0
  272. package/src/client/sync/SyncHistoryView.test.ts +40 -0
  273. package/src/client/sync/SyncHistoryView.tsx +220 -0
  274. package/src/client/sync/SyncSettingsView.tsx +1535 -0
  275. package/src/client/sync/history-model.test.ts +194 -0
  276. package/src/client/sync/history-model.ts +195 -0
  277. package/src/client/sync/sync-api.test.ts +585 -0
  278. package/src/client/sync/sync-api.ts +543 -0
  279. package/src/client/sync/sync-locales.ts +416 -0
  280. package/src/client/sync/sync-push-preview.test.ts +52 -0
  281. package/src/client/sync/sync-view-v2.test.ts +213 -0
  282. package/src/client/sync/sync-view.test.ts +487 -0
  283. package/src/client/sync/sync-view.ts +759 -0
  284. package/src/core/analyzer.ts +1068 -0
  285. package/src/core/backup.ts +605 -0
  286. package/src/core/boot-rescue.test.ts +847 -0
  287. package/src/core/boot-rescue.ts +737 -0
  288. package/src/core/cache-cleaner.test.ts +169 -0
  289. package/src/core/cache-cleaner.ts +121 -0
  290. package/src/core/config-lifecycle.test.ts +727 -0
  291. package/src/core/config-lifecycle.ts +573 -0
  292. package/src/core/config-snapshot.test.ts +369 -0
  293. package/src/core/config-snapshot.ts +522 -0
  294. package/src/core/config-state.test.ts +381 -0
  295. package/src/core/config-state.ts +207 -0
  296. package/src/core/consult-source.test.ts +199 -0
  297. package/src/core/consult-source.ts +293 -0
  298. package/src/core/crash-report.test.ts +380 -0
  299. package/src/core/crash-report.ts +369 -0
  300. package/src/core/exporter.ts +417 -0
  301. package/src/core/ghost-sweep.test.ts +94 -0
  302. package/src/core/ghost-sweep.ts +67 -0
  303. package/src/core/importer.ts +83 -0
  304. package/src/core/index.ts +56 -0
  305. package/src/core/journal.test.ts +257 -0
  306. package/src/core/journal.ts +518 -0
  307. package/src/core/local-plugin-host.ts +152 -0
  308. package/src/core/local-plugin-pack.test.ts +359 -0
  309. package/src/core/local-plugin-pack.ts +363 -0
  310. package/src/core/messages.test.ts +54 -0
  311. package/src/core/messages.ts +664 -0
  312. package/src/core/migration-consult.test.ts +315 -0
  313. package/src/core/migration-consult.ts +494 -0
  314. package/src/core/migration-history.test.ts +319 -0
  315. package/src/core/migration-history.ts +484 -0
  316. package/src/core/model-tools.test.ts +190 -0
  317. package/src/core/model-tools.ts +208 -0
  318. package/src/core/msg-types.ts +10 -0
  319. package/src/core/phase3-child-crash.ts +95 -0
  320. package/src/core/phase3-host.ts +411 -0
  321. package/src/core/phase3-p1.test.ts +212 -0
  322. package/src/core/phase3-p2b-fingerprint.test.ts +189 -0
  323. package/src/core/phase3-prod-child.ts +36 -0
  324. package/src/core/phase3-production-integration.test.ts +286 -0
  325. package/src/core/phase4-crash-child.ts +101 -0
  326. package/src/core/phase4-crash-injection.test.ts +166 -0
  327. package/src/core/plugin-cli.fs.test.ts +184 -0
  328. package/src/core/plugin-cli.test.ts +137 -0
  329. package/src/core/plugin-cli.ts +563 -0
  330. package/src/core/reconcile.test.ts +392 -0
  331. package/src/core/reconcile.ts +498 -0
  332. package/src/core/recovery-orchestrator.test.ts +145 -0
  333. package/src/core/recovery-orchestrator.ts +366 -0
  334. package/src/core/restore-manage.test.ts +94 -0
  335. package/src/core/restore.ts +849 -0
  336. package/src/core/rollback.ts +151 -0
  337. package/src/core/run-progress.test.ts +237 -0
  338. package/src/core/run-registry.test.ts +232 -0
  339. package/src/core/run-registry.ts +209 -0
  340. package/src/core/smoke.test.ts +842 -0
  341. package/src/core/startup-barrier.ts +99 -0
  342. package/src/core/types.ts +542 -0
  343. package/src/core/undo.ts +116 -0
  344. package/src/core/validator.ts +84 -0
  345. package/src/core/verify-recovery.ts +265 -0
  346. package/src/core/watcher.test.ts +326 -0
  347. package/src/core/watcher.ts +263 -0
  348. package/src/index.facade.test.ts +265 -0
  349. package/src/index.sync.test.ts +217 -0
  350. package/src/index.ts +2975 -0
  351. package/src/migrations/index.ts +71 -0
  352. package/src/migrations/v1-to-v2.ts +17 -0
  353. package/src/profiles/index.ts +8 -0
  354. package/src/profiles/profile-manager.test.ts +282 -0
  355. package/src/profiles/profile-manager.ts +630 -0
  356. package/src/schema/config.ts +165 -0
  357. package/src/schema/index.ts +71 -0
  358. package/src/schema/manifest-schema.test.ts +519 -0
  359. package/src/schema/manifest.ts +124 -0
  360. package/src/schema/tombstones.test.ts +131 -0
  361. package/src/schema/tombstones.ts +89 -0
  362. package/src/schema/types.ts +245 -0
  363. package/src/schema/versions.ts +55 -0
  364. package/src/security/encryption.ts +335 -0
  365. package/src/security/index.ts +16 -0
  366. package/src/security/integrity.ts +102 -0
  367. package/src/security/redaction.ts +101 -0
  368. package/src/security/secret-scanner.ts +445 -0
  369. package/src/security/security.test.ts +1215 -0
  370. package/src/security/vault.test.ts +164 -0
  371. package/src/security/vault.ts +198 -0
  372. package/src/security/zip-security.ts +180 -0
  373. package/src/sync/autosync-config.test.ts +147 -0
  374. package/src/sync/autosync-config.ts +179 -0
  375. package/src/sync/autosync-scheduler.test.ts +490 -0
  376. package/src/sync/autosync-scheduler.ts +646 -0
  377. package/src/sync/fs.ts +48 -0
  378. package/src/sync/git/git-transport.test.ts +673 -0
  379. package/src/sync/git/git-transport.ts +491 -0
  380. package/src/sync/github-auth.test.ts +280 -0
  381. package/src/sync/github-auth.ts +356 -0
  382. package/src/sync/layout.test.ts +270 -0
  383. package/src/sync/layout.ts +222 -0
  384. package/src/sync/snapshot-json.test.ts +119 -0
  385. package/src/sync/snapshot-json.ts +128 -0
  386. package/src/sync/sync-config.test.ts +310 -0
  387. package/src/sync/sync-config.ts +299 -0
  388. package/src/sync/sync-engine.test.ts +1074 -0
  389. package/src/sync/sync-engine.ts +754 -0
  390. package/src/sync/sync-history.test.ts +116 -0
  391. package/src/sync/sync-history.ts +120 -0
  392. package/src/sync/sync-selection.test.ts +186 -0
  393. package/src/sync/sync-selection.ts +143 -0
  394. package/src/sync/sync-session.test.ts +143 -0
  395. package/src/sync/sync-session.ts +77 -0
  396. package/src/sync/sync-state.test.ts +164 -0
  397. package/src/sync/sync-state.ts +131 -0
  398. package/src/sync/transport.test.ts +111 -0
  399. package/src/sync/transport.ts +118 -0
  400. package/src/sync/ui-prefs.test.ts +152 -0
  401. package/src/sync/ui-prefs.ts +135 -0
  402. package/src/sync/webdav/webdav-transport.test.ts +871 -0
  403. package/src/sync/webdav/webdav-transport.ts +535 -0
  404. package/src/ui/errors.test.ts +47 -0
  405. package/src/ui/errors.ts +100 -0
  406. package/src/ui/export-flow.test.ts +87 -0
  407. package/src/ui/export-flow.ts +156 -0
  408. package/src/ui/i18n.ts +590 -0
  409. package/src/ui/migration-consult-view.test.ts +151 -0
  410. package/src/ui/migration-consult-view.ts +178 -0
  411. package/src/ui/progress.test.ts +47 -0
  412. package/src/ui/progress.ts +102 -0
  413. package/src/ui/report.test.ts +101 -0
  414. package/src/ui/report.ts +146 -0
  415. package/src/ui/test-helpers.ts +184 -0
  416. package/src/ui/types.ts +325 -0
  417. package/src/utils/atomic-write.test.ts +312 -0
  418. package/src/utils/atomic-write.ts +422 -0
  419. package/src/utils/env-lock.test.ts +999 -0
  420. package/src/utils/env-lock.ts +1086 -0
  421. package/src/utils/hashing.ts +53 -0
  422. package/src/utils/json.ts +77 -0
  423. package/src/utils/logger.ts +114 -0
  424. package/src/utils/paths.ts +176 -0
  425. package/src/utils/proxy.test.ts +465 -0
  426. package/src/utils/proxy.ts +493 -0
  427. package/src/utils/recursive-walk.test.ts +148 -0
  428. package/src/utils/recursive-walk.ts +145 -0
  429. package/src/utils/zip.ts +332 -0
@@ -0,0 +1,326 @@
1
+ /**
2
+ * plugins adapter 测试:插件清单 + 用户 patch 行导出、
3
+ * 已装同版本 Skip / 未装 Install / patch 行 Create,applyItem(install → needsRestart;patch 写回)。
4
+ */
5
+ import test from 'node:test';
6
+ import assert from 'node:assert/strict';
7
+ import { PluginsAdapter, USER_PATCH_FILE } from './plugins.ts';
8
+ import { makeContext, makeImportContext } from './test-helpers.ts';
9
+ import type { PlanItem } from '../core/types.ts';
10
+
11
+ test('issue #35:patch 文件随分区迁移;目标缺失的 patchedDependencies 声明导入时剔除', async () => {
12
+ const ws = [
13
+ 'allowBuilds:',
14
+ ' ssh2: true',
15
+ 'patchedDependencies:',
16
+ ' dsh-approval-gate: patches/dsh-approval-gate.patch',
17
+ ' dsh-whale-galgame: patches/dsh-whale-galgame.patch',
18
+ '',
19
+ ].join('\n');
20
+ const src = makeContext('win32', 'C:\\Users\\alice', 'web');
21
+ await src.fs.writeFile('profiles/web/pnpm-workspace.yaml', new TextEncoder().encode(ws));
22
+ await src.fs.writeFile('profiles/web/patches/dsh-approval-gate.patch', new TextEncoder().encode('diff --git a/x b/x\n'));
23
+ // 源机缺第二个 patch 文件 → 导出必须告警(此前静默只搬声明)
24
+
25
+ const adapter = new PluginsAdapter();
26
+ const out = await adapter.export(src, { includeSecrets: false });
27
+ assert.deepEqual(out.data.patchFiles?.map((p) => p.relativePath), ['patches/dsh-approval-gate.patch']);
28
+ assert.ok(out.warnings.some((w) => w.includes('dsh-whale-galgame')), `源机缺 patch 文件必须告警: ${out.warnings.join(' | ')}`);
29
+
30
+ // 目标机全新:只有携带了文件的那条声明能被满足
31
+ const dst = makeContext('linux', '/home/bob', 'web');
32
+ const items = await adapter.analyzeImport(out.data, makeImportContext(dst, new Map([['plugins', out.data]])));
33
+ const wsItem = items.find((i) => i.id === 'plugins:pnpm-workspace');
34
+ assert.equal(wsItem?.kind, 'Create');
35
+ assert.ok(wsItem?.detail?.includes('dsh-whale-galgame'), '剔除必须在计划项里说清');
36
+ assert.ok(
37
+ items.some((i) => i.id === 'plugins:pnpm-workspace-dropped' && i.kind === 'Warning'),
38
+ '剔除必须是可见的信息项(不静默改配置语义)',
39
+ );
40
+
41
+ // patch 文件必须成为计划项(进导入前快照 → 可回滚;且用户可见)
42
+ const patchItems = items.filter((i) => i.id.startsWith('plugins:patch:'));
43
+ assert.equal(patchItems.length, 1, `每个携带的 patch 文件都应有计划项: ${items.map((i) => i.id).join(',')}`);
44
+ assert.equal(patchItems[0]?.id, 'plugins:patch:patches/dsh-approval-gate.patch');
45
+ assert.equal(patchItems[0]?.kind, 'Create');
46
+ assert.deepEqual(patchItems[0]?.target, { adapter: 'plugins', ref: 'patchFile:patches/dsh-approval-gate.patch' });
47
+ // 顺序不变量:patch 文件必须先于 pnpm-workspace.yaml(否则会留下「声明在、文件未到」的窗口,
48
+ // 中途中断后目标机 pnpm 从此拒绝一切 add),两者都必须先于插件安装项。
49
+ const order = items.map((i) => i.id);
50
+ assert.ok(
51
+ order.indexOf(patchItems[0]!.id) < order.indexOf('plugins:pnpm-workspace'),
52
+ `patch 文件项必须先于 pnpm-workspace 项: ${order.join(' → ')}`,
53
+ );
54
+ const firstInstall = order.findIndex((id) => id.startsWith('plugin:'));
55
+ if (firstInstall >= 0) {
56
+ assert.ok(
57
+ order.indexOf('plugins:pnpm-workspace') < firstInstall,
58
+ `pnpm-workspace 项必须先于插件安装项: ${order.join(' → ')}`,
59
+ );
60
+ }
61
+
62
+ // 按计划顺序执行:先 patch 文件项,再 pnpm-workspace 项
63
+ const patchApply = await adapter.applyItem(patchItems[0]!, makeImportContext(dst, new Map([['plugins', out.data]])));
64
+ assert.equal(patchApply.ok, true);
65
+ assert.equal(
66
+ new TextDecoder().decode(dst.fs.files.get('/home/bob/profiles/web/patches/dsh-approval-gate.patch')!),
67
+ 'diff --git a/x b/x\n',
68
+ 'patch 文件必须与声明同进同出(落到 profile 的 patches/ 目录)',
69
+ );
70
+ const r = await adapter.applyItem(wsItem!, makeImportContext(dst, new Map([['plugins', out.data]])));
71
+ assert.equal(r.ok, true);
72
+ const writtenWs = new TextDecoder().decode(dst.fs.files.get('/home/bob/profiles/web/pnpm-workspace.yaml')!);
73
+ assert.ok(writtenWs.includes('dsh-approval-gate: patches/dsh-approval-gate.patch'), '可满足的声明必须保留');
74
+ assert.ok(!writtenWs.includes('dsh-whale-galgame'), '不可满足的声明绝不能写入(否则目标机 pnpm 拒绝一切安装)');
75
+ assert.ok(writtenWs.includes('allowBuilds:'), '其余配置不受影响');
76
+
77
+ // 内容一致 → Skip(幂等,不重复写)
78
+ const again = await adapter.analyzeImport(out.data, makeImportContext(dst, new Map([['plugins', out.data]])));
79
+ assert.equal(again.find((i) => i.id.startsWith('plugins:patch:'))?.kind, 'Skip');
80
+ // 内容不同 → Conflict(不静默覆盖目标机已有的 patch)
81
+ await dst.fs.writeFile('profiles/web/patches/dsh-approval-gate.patch', new TextEncoder().encode('different\n'));
82
+ const conflicted = await adapter.analyzeImport(out.data, makeImportContext(dst, new Map([['plugins', out.data]])));
83
+ assert.equal(conflicted.find((i) => i.id.startsWith('plugins:patch:'))?.kind, 'Conflict');
84
+
85
+ // 目标机本来就有该 patch 文件 → 声明保留(不误删用户环境里已就位的补丁)
86
+ const dst2 = makeContext('linux', '/home/bob2', 'web');
87
+ await dst2.fs.writeFile('profiles/web/patches/dsh-whale-galgame.patch', new TextEncoder().encode('existing\n'));
88
+ const items2 = await adapter.analyzeImport(out.data, makeImportContext(dst2, new Map([['plugins', out.data]])));
89
+ assert.equal(items2.some((i) => i.id === 'plugins:pnpm-workspace-dropped'), false, '文件已存在 → 无需剔除');
90
+ const wsItem2 = items2.find((i) => i.id === 'plugins:pnpm-workspace');
91
+ const r2 = await adapter.applyItem(wsItem2!, makeImportContext(dst2, new Map([['plugins', out.data]])));
92
+ assert.equal(r2.ok, true);
93
+ const written2 = new TextDecoder().decode(dst2.fs.files.get('/home/bob2/profiles/web/pnpm-workspace.yaml')!);
94
+ assert.ok(written2.includes('dsh-whale-galgame: patches/dsh-whale-galgame.patch'), '已存在的 patch 文件对应声明必须保留');
95
+ });
96
+
97
+ test('issue #35:用户否决 patch 覆盖时,pnpm-workspace 项不得越权替它落盘(也不得留下悬空声明)', async () => {
98
+ const ws = [
99
+ 'patchedDependencies:',
100
+ ' mine: patches/mine.patch',
101
+ '',
102
+ ].join('\n');
103
+ const src = makeContext('win32', 'C:\\Users\\alice', 'web');
104
+ await src.fs.writeFile('profiles/web/pnpm-workspace.yaml', new TextEncoder().encode(ws));
105
+ await src.fs.writeFile('profiles/web/patches/mine.patch', new TextEncoder().encode('BACKUP VERSION\n'));
106
+ const adapter = new PluginsAdapter();
107
+ const out = await adapter.export(src, { includeSecrets: false });
108
+
109
+ // 场景 A:目标机已有**不同**的 patch 文件 → 计划项是 Conflict;
110
+ // 用户 keepCurrent(不应用该项)→ 文件必须保持原样,且声明仍然成立(文件在,pnpm 能读)。
111
+ const dst = makeContext('linux', '/home/bob', 'web');
112
+ await dst.fs.writeFile('profiles/web/patches/mine.patch', new TextEncoder().encode('LOCAL VERSION\n'));
113
+ const items = await adapter.analyzeImport(out.data, makeImportContext(dst, new Map([['plugins', out.data]])));
114
+ const patchItem = items.find((i) => i.id.startsWith('plugins:patch:'));
115
+ assert.equal(patchItem?.kind, 'Conflict');
116
+ const wsItem = items.find((i) => i.id === 'plugins:pnpm-workspace');
117
+ assert.ok(wsItem !== undefined, '配置内容不同 → 仍应有 pnpm-workspace 项');
118
+ const r = await adapter.applyItem(wsItem!, makeImportContext(dst, new Map([['plugins', out.data]])));
119
+ assert.equal(r.ok, true);
120
+ assert.equal(
121
+ new TextDecoder().decode(dst.fs.files.get('/home/bob/profiles/web/patches/mine.patch')!),
122
+ 'LOCAL VERSION\n',
123
+ '配置项绝不能覆盖用户已否决(keepCurrent)的 patch 文件',
124
+ );
125
+ const written = new TextDecoder().decode(dst.fs.files.get('/home/bob/profiles/web/pnpm-workspace.yaml')!);
126
+ assert.ok(written.includes('mine: patches/mine.patch'), '文件仍在 → 声明必须保留(否则白丢用户的补丁配置)');
127
+
128
+ // 场景 B:目标机没有该文件、且 patch 项未被应用(用户跳过 / 被墓碑过滤)→
129
+ // 声明必须被剔除,绝不留下「声明在、文件不在」的致命组合。
130
+ const dst2 = makeContext('linux', '/home/bob2', 'web');
131
+ const items2 = await adapter.analyzeImport(out.data, makeImportContext(dst2, new Map([['plugins', out.data]])));
132
+ const wsItem2 = items2.find((i) => i.id === 'plugins:pnpm-workspace');
133
+ const r2 = await adapter.applyItem(wsItem2!, makeImportContext(dst2, new Map([['plugins', out.data]])));
134
+ assert.equal(r2.ok, true);
135
+ const written2 = new TextDecoder().decode(dst2.fs.files.get('/home/bob2/profiles/web/pnpm-workspace.yaml')!);
136
+ assert.ok(!written2.includes('patchedDependencies'), `悬空声明必须剔除: ${JSON.stringify(written2)}`);
137
+ assert.equal(dst2.fs.files.has('/home/bob2/profiles/web/patches/mine.patch'), false, '配置项不得越权写 patch 文件');
138
+ });
139
+
140
+ test('plugins: 导出剔除插件自身(默认 dsh-config-manager,可配置)', async () => {
141
+ const ctx = makeContext('win32', 'C:\\Users\\alice');
142
+ ctx.plugins.installed.set('dsh-config-manager', { name: 'dsh-config-manager', version: '0.1.28', enabled: true });
143
+ ctx.plugins.installed.set('@linxin666/dsh-ssh', { name: '@linxin666/dsh-ssh', version: '0.1.12', enabled: true });
144
+ ctx.plugins.installed.set('dsh-memory-evolve', { name: 'dsh-memory-evolve', version: '1.0.0', enabled: true });
145
+
146
+ // 默认 selfName = dsh-config-manager → 从导出清单剔除
147
+ const adapter = new PluginsAdapter();
148
+ const out = await adapter.export(ctx, { includeSecrets: false });
149
+ assert.equal(out.data.plugins.length, 2, '自身应被剔除');
150
+ assert.ok(!out.data.plugins.some((p) => p.name === 'dsh-config-manager'), '导出清单不应包含自身');
151
+ assert.ok(out.data.plugins.some((p) => p.name === '@linxin666/dsh-ssh'));
152
+ assert.ok(out.data.plugins.some((p) => p.name === 'dsh-memory-evolve'));
153
+
154
+ // 空 selfName 表示不过滤(保留全部)
155
+ const adapterAll = new PluginsAdapter('');
156
+ const outAll = await adapterAll.export(ctx, { includeSecrets: false });
157
+ assert.equal(outAll.data.plugins.length, 3, 'selfName="" 不过滤');
158
+
159
+ // 可配置成任意包名(如 scope 化安装名)
160
+ const adapterScoped = new PluginsAdapter('@scope/dsh-config-manager');
161
+ const outScoped = await adapterScoped.export(ctx, { includeSecrets: false });
162
+ assert.equal(outScoped.data.plugins.length, 3, '不同包名不匹配 → 不过滤');
163
+ });
164
+
165
+ test('plugins: 导出清单与 patch 行', async () => {
166
+ const ctx = makeContext('win32', 'C:\\Users\\alice');
167
+ ctx.plugins.installed.set('@linxin666/dsh-ssh', { name: '@linxin666/dsh-ssh', version: '0.1.12', enabled: true, isBundle: true, inBundles: ['@linxin666/dsh-web-ui-all'] });
168
+ ctx.plugins.installed.set('dsh-memory-evolve', { name: 'dsh-memory-evolve', version: '1.0.0', enabled: true, spec: 'github:csyangwen/dsh-memory-evolve' });
169
+ ctx.plugins.installed.set('@deepseek-ai/dsh-base', { name: '@deepseek-ai/dsh-base', version: '0.1.0-rc.6', enabled: true });
170
+ ctx.patchFile.lines.set('skill-badge', { lineId: 'skill-badge', raw: { id: 'skill-badge', disabled: true } });
171
+
172
+ const adapter = new PluginsAdapter();
173
+ const out = await adapter.export(ctx, { includeSecrets: false });
174
+ assert.equal(out.data.version, 1);
175
+ assert.equal(out.data.plugins.length, 3);
176
+ assert.equal(out.data.patch.length, 1);
177
+ assert.equal(out.data.patch[0]?.lineId, 'skill-badge');
178
+ const ssh = out.data.plugins.find((p) => p.name === '@linxin666/dsh-ssh');
179
+ assert.equal(ssh?.isBundle, true);
180
+ assert.deepEqual(ssh?.inBundles, ['@linxin666/dsh-web-ui-all']);
181
+ // 非 registry spec 必须随导出保留(导入时按此重装)
182
+ const mem = out.data.plugins.find((p) => p.name === 'dsh-memory-evolve');
183
+ assert.equal(mem?.spec, 'github:csyangwen/dsh-memory-evolve');
184
+ assert.equal(out.data.pnpmWorkspace, null, '目标无 pnpm-workspace.yaml → null');
185
+
186
+ const v = await adapter.validate(out.data);
187
+ assert.equal(v.valid, true);
188
+ });
189
+
190
+ test('plugins: 导出携带 pnpm-workspace.yaml;analyze 目标无文件 → Create', async () => {
191
+ const src = makeContext('win32', 'C:\\Users\\alice', 'web');
192
+ await src.fs.writeFile('profiles/web/pnpm-workspace.yaml', new TextEncoder().encode('allowBuilds:\n ssh2: true\n'));
193
+ const adapter = new PluginsAdapter();
194
+ const out = await adapter.export(src, { includeSecrets: false });
195
+ assert.equal(out.data.pnpmWorkspace, 'allowBuilds:\n ssh2: true\n');
196
+
197
+ // 目标机没有该文件 → Create;有相同内容 → Skip;有不同内容 → Update
198
+ const dst = makeContext('linux', '/home/bob', 'web');
199
+ const items = await adapter.analyzeImport(out.data, makeImportContext(dst, new Map([['plugins', out.data]])));
200
+ const wsItem = items.find((i) => i.id === 'plugins:pnpm-workspace');
201
+ assert.equal(wsItem?.kind, 'Create');
202
+ assert.deepEqual(wsItem?.target, { adapter: 'plugins', ref: 'pnpm-workspace.yaml' });
203
+ assert.equal(items[0]?.id, 'plugins:pnpm-workspace', 'pnpm-workspace 项必须先于插件安装项');
204
+
205
+ // applyItem 写入目标 fs
206
+ const r = await adapter.applyItem(wsItem!, makeImportContext(dst, new Map([['plugins', out.data]])));
207
+ assert.equal(r.ok, true);
208
+ assert.equal(r.needsRestart, true);
209
+ const written = new TextDecoder().decode(dst.fs.files.get('/home/bob/profiles/web/pnpm-workspace.yaml')!);
210
+ assert.equal(written, 'allowBuilds:\n ssh2: true\n');
211
+
212
+ // 内容一致 → Skip;不同 → Update
213
+ const same = await adapter.analyzeImport(out.data, makeImportContext(dst, new Map([['plugins', out.data]])));
214
+ assert.equal(same.some((i) => i.id === 'plugins:pnpm-workspace'), false, '内容一致 → 不生成项');
215
+ const other = makeContext('linux', '/home/bob', 'web');
216
+ await other.fs.writeFile('profiles/web/pnpm-workspace.yaml', new TextEncoder().encode('nodeLinker: isolated\n'));
217
+ const diff = await adapter.analyzeImport(out.data, makeImportContext(other, new Map([['plugins', out.data]])));
218
+ const diffItem = diff.find((i) => i.id === 'plugins:pnpm-workspace');
219
+ assert.equal(diffItem?.kind, 'Update');
220
+ });
221
+
222
+ test('plugins: 已装同版本 Skip / 未装 Install / 版本不同 Conflict / patch Create', async () => {
223
+ const src = makeContext('win32', 'C:\\Users\\alice');
224
+ src.plugins.installed.set('pkg-a', { name: 'pkg-a', version: '1.0.0', enabled: true });
225
+ src.plugins.installed.set('pkg-b', { name: 'pkg-b', version: '2.0.0', enabled: true });
226
+ src.patchFile.lines.set('my-line', { lineId: 'my-line', raw: { id: 'my-line', name: 'pkg-c', config: { x: 1 } } });
227
+ const adapter = new PluginsAdapter();
228
+ const exported = await adapter.export(src, { includeSecrets: false });
229
+ const sections = new Map([['plugins', exported.data]]);
230
+
231
+ const dst = makeContext('linux', '/home/bob');
232
+ dst.plugins.installed.set('pkg-a', { name: 'pkg-a', version: '1.0.0', enabled: true }); // 同版本
233
+ dst.plugins.installed.set('pkg-b', { name: 'pkg-b', version: '1.5.0', enabled: true }); // 不同版本
234
+ const items = await adapter.analyzeImport(exported.data, makeImportContext(dst, sections));
235
+ const byId = new Map(items.map((i) => [i.id, i]));
236
+ assert.equal(byId.get('plugin:pkg-a')?.kind, 'Skip');
237
+ assert.equal(byId.get('plugin:pkg-b')?.kind, 'Conflict');
238
+ assert.equal(byId.get('patch:my-line')?.kind, 'Create');
239
+ assert.equal(byId.get('patch:my-line')?.target?.ref, 'my-line');
240
+
241
+ // patch 行写入(Create)
242
+ const r = await adapter.applyItem(byId.get('patch:my-line')!, makeImportContext(dst, sections));
243
+ assert.equal(r.ok, true);
244
+ assert.equal(r.needsRestart, true);
245
+ assert.deepEqual(dst.patchFile.lines.get('my-line')?.raw, { id: 'my-line', name: 'pkg-c', config: { x: 1 } });
246
+ });
247
+
248
+ test('plugins: applyItem Install → needsRestart(官方机制,不打包二进制)', async () => {
249
+ const src = makeContext('win32', 'C:\\Users\\alice');
250
+ src.plugins.installed.set('need-install', { name: 'need-install', version: '3.0.0', enabled: true });
251
+ const adapter = new PluginsAdapter();
252
+ const exported = await adapter.export(src, { includeSecrets: false });
253
+ const sections = new Map([['plugins', exported.data]]);
254
+
255
+ const dst = makeContext('linux', '/home/bob');
256
+ const items = await adapter.analyzeImport(exported.data, makeImportContext(dst, sections));
257
+ const installItem = items.find((i) => i.id === 'plugin:need-install');
258
+ assert.equal(installItem?.kind, 'Install');
259
+ assert.equal(installItem?.target, undefined, 'Install 项不做快照(不可回滚,如实报告)');
260
+ const r = await adapter.applyItem(installItem!, makeImportContext(dst, sections));
261
+ assert.equal(r.ok, true);
262
+ assert.equal(r.needsRestart, true);
263
+ assert.ok(dst.plugins.installed.has('need-install'), '经 installPlugin 门面写入');
264
+ });
265
+
266
+ test('plugins: 非 registry spec(github:)随导入传给 install,registry 包不传 spec', async () => {
267
+ const src = makeContext('win32', 'C:\\Users\\alice');
268
+ src.plugins.installed.set('dsh-memory-evolve', { name: 'dsh-memory-evolve', version: '1.0.0', enabled: true, spec: 'github:csyangwen/dsh-memory-evolve' });
269
+ src.plugins.installed.set('dshmarket', { name: 'dshmarket', version: '1.0.3', enabled: true, spec: '^1.0.3' });
270
+ const adapter = new PluginsAdapter();
271
+ const exported = await adapter.export(src, { includeSecrets: false });
272
+ const sections = new Map([['plugins', exported.data]]);
273
+
274
+ const dst = makeContext('linux', '/home/bob');
275
+ const items = await adapter.analyzeImport(exported.data, makeImportContext(dst, sections));
276
+ await adapter.applyItem(items.find((i) => i.id === 'plugin:dsh-memory-evolve')!, makeImportContext(dst, sections));
277
+ assert.equal(dst.plugins.lastSpec, 'github:csyangwen/dsh-memory-evolve', 'github: spec 必须原样传给安装通道');
278
+
279
+ await adapter.applyItem(items.find((i) => i.id === 'plugin:dshmarket')!, makeImportContext(dst, sections));
280
+ assert.equal(dst.plugins.lastSpec, '^1.0.3', 'registry 版本区间也透传(由门面决定按裸名装最新)');
281
+ });
282
+
283
+ test('plugins: 安装失败 → 非致命 warning(§34.17,不触发整体回滚)', async () => {
284
+ const src = makeContext('win32', 'C:\\Users\\alice');
285
+ src.plugins.installed.set('broken-pkg', { name: 'broken-pkg', version: '3.0.0', enabled: true });
286
+ const adapter = new PluginsAdapter();
287
+ const exported = await adapter.export(src, { includeSecrets: false });
288
+ const sections = new Map([['plugins', exported.data]]);
289
+
290
+ const dst = makeContext('linux', '/home/bob');
291
+ dst.plugins.failInstall = true; // 模拟 npm ERESOLVE / 网络不可达
292
+ const items = await adapter.analyzeImport(exported.data, makeImportContext(dst, sections));
293
+ const installItem = items.find((i) => i.id === 'plugin:broken-pkg');
294
+ assert.equal(installItem?.kind, 'Install');
295
+ const r = await adapter.applyItem(installItem!, makeImportContext(dst, sections));
296
+ assert.equal(r.ok, false);
297
+ assert.equal(r.warning, true, '安装失败必须记为 warning(不计入失败、不触发回滚)');
298
+ assert.match(r.message ?? '', /ERESOLVE/);
299
+ assert.match(r.message ?? '', /dsh plugin --profile web add broken-pkg/, '失败报告必须给可复制的手动安装命令');
300
+ assert.ok(!dst.plugins.installed.has('broken-pkg'), '安装失败不得假装已写入');
301
+ });
302
+
303
+ test('plugins: 版本冲突 useImported → Update 走安装通道,失败同样为 warning', async () => {
304
+ const src = makeContext('win32', 'C:\\Users\\alice');
305
+ src.plugins.installed.set('pkg-b', { name: 'pkg-b', version: '2.0.0', enabled: true });
306
+ const adapter = new PluginsAdapter();
307
+ const exported = await adapter.export(src, { includeSecrets: false });
308
+ const sections = new Map([['plugins', exported.data]]);
309
+
310
+ const dst = makeContext('linux', '/home/bob');
311
+ dst.plugins.installed.set('pkg-b', { name: 'pkg-b', version: '1.5.0', enabled: true });
312
+ const items = await adapter.analyzeImport(exported.data, makeImportContext(dst, sections));
313
+ const conflict = items.find((i) => i.id === 'plugin:pkg-b');
314
+ assert.equal(conflict?.kind, 'Conflict');
315
+ // 模拟 analyzer 在 createImportPlan 中对 useImported 的解析结果
316
+ const updateItem: PlanItem = { ...conflict!, kind: 'Update', conflict: { itemId: conflict!.id, resolution: 'useImported' } };
317
+
318
+ const rOk = await adapter.applyItem(updateItem, makeImportContext(dst, sections));
319
+ assert.equal(rOk.ok, true, 'Update 应走安装通道,而不是报缺少 target.ref');
320
+ assert.equal(rOk.needsRestart, true);
321
+
322
+ dst.plugins.failInstall = true;
323
+ const rFail = await adapter.applyItem(updateItem, makeImportContext(dst, sections));
324
+ assert.equal(rFail.ok, false);
325
+ assert.equal(rFail.warning, true, '更新失败同样为非致命 warning');
326
+ });