@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,363 @@
1
+ /**
2
+ * 本地源插件打包(T1):让 `link:` / `file:` 来源的插件能真正跨机器迁移。
3
+ *
4
+ * 背景(真实缺陷):`src/adapters/plugins.ts` 会把 profile package.json 的依赖 spec
5
+ * 原样记进 `PluginEntry.spec`,其中 `link:D:/path`、`file:./x.tgz` 指向**本机路径**——
6
+ * 目标机器上该路径根本不存在,导入时 `pnpm add link:/...` 必然失败,插件被静默丢失。
7
+ * 本机实例:`web` profile 的 `"dsh-config-manager": "link:D:/Projects/personal/dsh-config-manager"`。
8
+ *
9
+ * 解法:导出时对本地源插件执行 `npm pack`,把产出的 tarball 一并放进备份;
10
+ * 导入时用「解包后的 tarball 绝对路径」重写 spec,走 `file:<abs>` 安装(pnpm 原生支持)。
11
+ *
12
+ * 本模块前半部分是**纯函数**(零 IO、零依赖,只用于 spec 分类与路径推导,node 可直测);
13
+ * 后半部分是**打包编排**(`exec` 注入,测试用假 exec 即可离线驱动,不绑真实 npm)。
14
+ *
15
+ * 安全与资源约束:
16
+ * - 只打包 `npm pack` 的产物,**绝不**递归整个插件目录(node_modules 由 npm pack 自身排除);
17
+ * - tarball 体积超上限即跳过并告警(防单个插件把备份撑爆);
18
+ * - 归档内相对路径一律 `local-plugins/<name>.tgz`,**不含任何路径分隔符**(防穿越);
19
+ * - 单个插件打包失败**不中断导出**,只记 warning(与「单分区失败不拖垮整体」同语义)。
20
+ */
21
+ import path from 'node:path';
22
+
23
+ /* ---------------- 常量 ---------------- */
24
+
25
+ /** 本地源插件 tarball 在 ZIP 内的存放目录(相对分区前缀之下,如 plugin-files/ 之下) */
26
+ export const LOCAL_PLUGIN_DIR = 'local-plugins';
27
+
28
+ /** 单插件 tarball 体积上限(超过则跳过并告警,防异常巨大的插件撑爆备份) */
29
+ export const MAX_LOCAL_TARBALL_BYTES = 100 * 1024 * 1024;
30
+
31
+ /** 本地源 spec 前缀(大小写不敏感匹配后使用小写前缀做判定) */
32
+ const LINK_PREFIX = 'link:';
33
+ const FILE_PREFIX = 'file:';
34
+
35
+ /** 非 registry 来源前缀(与 core/plugin-cli.ts 的 NON_REGISTRY_SPEC 语义对齐) */
36
+ const GIT_PREFIXES = ['github:', 'gitlab:', 'bitbucket:', 'git+', 'http:', 'https:'];
37
+
38
+ /* ---------------- 纯函数:spec 分类 ---------------- */
39
+
40
+ /** 依赖 spec 的来源类别 */
41
+ export type LocalPluginSourceKind = 'link' | 'file' | 'registry' | 'git' | 'other';
42
+
43
+ /** spec 前缀判定:大小写不敏感 + 首尾空白容忍(`Link:` / ` link:` 都能识别)。 */
44
+ function startsWithIgnoreCase(spec: string, prefix: string): boolean {
45
+ return spec.slice(0, prefix.length).toLowerCase() === prefix;
46
+ }
47
+
48
+ /**
49
+ * 分类依赖 spec。
50
+ * - `link:` → 'link'(本地目录)
51
+ * - `file:` → 'file'(本地 tarball / 目录)
52
+ * - `github:` / `gitlab:` / `bitbucket:` / `git+` / `http(s):` → 'git'
53
+ * - 空 / `workspace:` / 版本区间(`^1.2.3`、`~1.2`、`1.2.3`、`latest`、`*`)→ 'registry'
54
+ * - 其余 → 'other'
55
+ */
56
+ export function classifyPluginSpec(spec: string | undefined): LocalPluginSourceKind {
57
+ if (typeof spec !== 'string') return 'registry';
58
+ const s = spec.trim();
59
+ if (s === '') return 'registry';
60
+ if (startsWithIgnoreCase(s, LINK_PREFIX)) return 'link';
61
+ if (startsWithIgnoreCase(s, FILE_PREFIX)) return 'file';
62
+ for (const p of GIT_PREFIXES) {
63
+ if (startsWithIgnoreCase(s, p)) return 'git';
64
+ }
65
+ if (startsWithIgnoreCase(s, 'workspace:')) return 'registry';
66
+ return 'registry';
67
+ }
68
+
69
+ /** 是否本地源(换机后必然不可达,需打包) */
70
+ export function isLocalPluginSpec(spec: string | undefined): boolean {
71
+ const kind = classifyPluginSpec(spec);
72
+ return kind === 'link' || kind === 'file';
73
+ }
74
+
75
+ /**
76
+ * spec 是否带显式前缀(`link:` / `file:` 等)。
77
+ *
78
+ * 注意:**Windows 盘符不是 scheme**。`C:\dev\x` 含 `:` 但那是单字母盘符后跟路径分隔符,
79
+ * 必须排除,否则裸盘符路径会被误判为「已有 scheme」而绕过 `file:` 补全。
80
+ */
81
+ function hasSchemePrefix(spec: string): boolean {
82
+ if (/^[a-zA-Z]:[\\/]/.test(spec)) return false; // 盘符路径,不是 scheme
83
+ return /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(spec); // RFC 3986 scheme
84
+ }
85
+
86
+ /* ---------------- 纯函数:路径与命名 ---------------- */
87
+
88
+ /**
89
+ * 包名 → 归档内安全的文件名片段。
90
+ * `@scope/name` → `@scope__name`(斜杠被替换,**结果绝不含 `/` 或 `\`**)。
91
+ * 其余非法字符(路径分隔符、`:`、空白)一并折叠为 `-`。
92
+ */
93
+ export function safePackageFileFragment(pkgName: string): string {
94
+ const replaced = pkgName.replace(/[/\\]/g, '__');
95
+ return replaced.replace(/[^A-Za-z0-9@._-]/g, '-');
96
+ }
97
+
98
+ /** tarball 在归档内的相对路径(一律 `local-plugins/<fragment>.tgz`,pkgName 已脱离 version) */
99
+ export function tarballNameFor(pkgName: string, version: string): string {
100
+ const safeVersion = version.replace(/[^A-Za-z0-9._-]/g, '-');
101
+ const fragment = safePackageFileFragment(pkgName);
102
+ const withVersion = safeVersion === '' ? fragment : `${fragment}-${safeVersion}`;
103
+ return `${LOCAL_PLUGIN_DIR}/${withVersion}.tgz`;
104
+ }
105
+
106
+ /**
107
+ * 本地 spec → 绝对路径。
108
+ * - `link:D:/x` / `link:/abs/x` → 绝对路径原样(规范化分隔符);
109
+ * - `link:./x` / `link:../x` → 相对 profileDir 解析;
110
+ * - `~` 开头 → 相对 homeDir 展开;
111
+ * - 裸路径(无前缀,如 `./plugin`)→ 同样按相对 profileDir 处理。
112
+ *
113
+ * 平台无关:不读 `process.platform`,交给 `path.resolve` / `path.isAbsolute`。
114
+ */
115
+ export function resolveLocalPluginPath(
116
+ spec: string,
117
+ opts: { homeDir: string; profileDir: string },
118
+ ): string {
119
+ let raw = spec.trim();
120
+ if (startsWithIgnoreCase(raw, LINK_PREFIX)) raw = raw.slice(LINK_PREFIX.length);
121
+ else if (startsWithIgnoreCase(raw, FILE_PREFIX)) raw = raw.slice(FILE_PREFIX.length);
122
+ raw = raw.trim();
123
+
124
+ // `~` 展开(`~/x` 或 `~`)
125
+ if (raw === '~') return opts.homeDir;
126
+ if (raw.startsWith('~/') || raw.startsWith('~\\')) {
127
+ return path.resolve(opts.homeDir, raw.slice(2));
128
+ }
129
+ // 绝对路径(含 Windows 盘符 / UNC)
130
+ if (path.isAbsolute(raw)) return path.normalize(raw);
131
+ // 相对路径 → 以 profileDir 为基准
132
+ return path.resolve(opts.profileDir, raw);
133
+ }
134
+
135
+ /**
136
+ * 把不可移植的本地 spec 重写成可移植形式。
137
+ * 幂等:对已是 `file:` 前缀的输入再次调用不改变语义(前缀保留、路径部分原样)。
138
+ */
139
+ export function rewriteLocalSpec(spec: string, opts: { tarballRel: string }): string {
140
+ void spec;
141
+ const rel = opts.tarballRel.replace(/\\/g, '/');
142
+ return `${FILE_PREFIX}${rel}`;
143
+ }
144
+
145
+ /**
146
+ * 判断一个 spec 是否已是「重写后的可移植形式」(`file:local-plugins/...`)。
147
+ * 导入端据此避免二次重写。
148
+ */
149
+ export function isPackedLocalSpec(spec: string | undefined): boolean {
150
+ if (typeof spec !== 'string') return false;
151
+ const s = spec.trim();
152
+ if (!startsWithIgnoreCase(s, FILE_PREFIX)) return false;
153
+ const rest = s.slice(FILE_PREFIX.length).replace(/\\/g, '/');
154
+ return rest.startsWith(`${LOCAL_PLUGIN_DIR}/`);
155
+ }
156
+
157
+ /* ---------------- 打包编排(exec 注入) ---------------- */
158
+
159
+ /** 打包所需的插件最小信息(只依赖 core/types.ts 的 PluginInfo 子集,便于测试构造) */
160
+ export interface LocalPluginCandidate {
161
+ name: string;
162
+ version: string;
163
+ spec?: string;
164
+ }
165
+
166
+ /** 命令执行结果(与 child_process 的 execFile 回调对齐;不 import node:child_process) */
167
+ export interface PackExecResult {
168
+ stdout: string;
169
+ stderr: string;
170
+ code: number | null;
171
+ }
172
+
173
+ /** 命令执行器签名:`exec(file, args, opts)` → Promise */
174
+ export type PackExec = (
175
+ file: string,
176
+ args: string[],
177
+ opts: { cwd: string; timeoutMs?: number },
178
+ ) => Promise<PackExecResult>;
179
+
180
+ export interface PackLocalPluginsOptions {
181
+ plugins: readonly LocalPluginCandidate[];
182
+ /** $DSH_HOME 绝对路径 */
183
+ homeDir: string;
184
+ /** profile 目录绝对路径(profiles/<name>) */
185
+ profileDir: string;
186
+ /** tarball 临时产物目录(调用方保证存在或由本函数创建) */
187
+ packDir: string;
188
+ /** 命令执行器(注入;生产传 npm pack 的 execFile 包装) */
189
+ exec: PackExec;
190
+ /** 读文件(注入,便于测试)——只在打包成功后调用 */
191
+ readFile: (absPath: string) => Promise<Uint8Array>;
192
+ /** 确保目录存在(注入) */
193
+ mkdir: (absDir: string) => Promise<void>;
194
+ /** 目标平台校验(可选;'win32' 时 npm 是 .cmd 垫片,调用方负责 exec 形态) */
195
+ timeoutMs?: number;
196
+ /** tarball 体积上限(缺省 MAX_LOCAL_TARBALL_BYTES) */
197
+ maxTarballBytes?: number;
198
+ /** 是否把 `npm pack` 的 stderr 作为告警捕获(缺省 true) */
199
+ collectStderr?: boolean;
200
+ }
201
+
202
+ /** 单个本地插件的打包结果 */
203
+ export interface PackedLocalPlugin {
204
+ packageName: string;
205
+ version: string;
206
+ /** 归档内相对路径(`local-plugins/<x>.tgz`,正斜杠) */
207
+ relativePath: string;
208
+ /** 重写后的 spec(`file:local-plugins/<x>.tgz`) */
209
+ rewrittenSpec: string;
210
+ data: Uint8Array;
211
+ }
212
+
213
+ export interface PackLocalPluginsResult {
214
+ packed: PackedLocalPlugin[];
215
+ /** packageName → 重写后的 spec */
216
+ rewritten: Record<string, string>;
217
+ /** 非致命告警(跳过原因等;调用方并入分区 warnings) */
218
+ warnings: string[];
219
+ }
220
+
221
+ /**
222
+ * 从 `npm pack --json` 的 stdout 解析出 tarball 文件名。
223
+ * 兼容三种形态:
224
+ * 1. `--json` 数组:`[{"filename":"x.tgz", ...}]` → 取 filename
225
+ * 2. 旧版单对象:`{"filename":"x.tgz"}` → 取 filename
226
+ * 3. 纯文本:末行非空即文件名(兜底)
227
+ * 解析失败返回 null(调用方按文件名约定回退)。
228
+ */
229
+ export function parsePackOutput(stdout: string, expectedPkg: string, version: string): string | null {
230
+ const text = stdout.trim();
231
+ if (text === '') return null;
232
+ // 形态 1/2:JSON(可能与 npm 的进度日志混排,故从首个 '[' / '{' 起截取)
233
+ const jsonStart = Math.min(
234
+ ...[text.indexOf('['), text.indexOf('{')].filter((i) => i >= 0),
235
+ );
236
+ if (Number.isFinite(jsonStart)) {
237
+ try {
238
+ const parsed: unknown = JSON.parse(text.slice(jsonStart));
239
+ const first = Array.isArray(parsed) ? parsed[0] : parsed;
240
+ if (first !== null && typeof first === 'object') {
241
+ const filename = (first as { filename?: unknown }).filename;
242
+ if (typeof filename === 'string' && filename !== '') return filename;
243
+ }
244
+ } catch {
245
+ /* 落到文本兜底 */
246
+ }
247
+ }
248
+ // 形态 3:纯文本,取最后一行非空
249
+ const lines = text.split(/\r?\n/).map((l) => l.trim()).filter((l) => l !== '');
250
+ const last = lines[lines.length - 1];
251
+ if (last !== undefined && last.endsWith('.tgz')) return last;
252
+ // 最终兜底:按 npm pack 的命名约定推导
253
+ const fragment = safePackageFileFragment(expectedPkg).replace(/^@/, '').replace(/__/g, '-');
254
+ return `${fragment}-${version}.tgz`;
255
+ }
256
+
257
+ /**
258
+ * 对全部本地源插件执行打包。
259
+ *
260
+ * 行为要点:
261
+ * - 只处理 `isLocalPluginSpec(spec)` 的插件;非本地源直接跳过(**不产生告警**,属正常);
262
+ * - 每个插件独立 try/catch:任一步失败 → 该插件进 warnings,**其余继续**;
263
+ * - 目录不存在 / 非绝对可达 → 告警跳过(不抛错);
264
+ * - 成功产出 `packed`(含字节)与 `rewritten` 映射。
265
+ */
266
+ export async function packLocalPlugins(
267
+ opts: PackLocalPluginsOptions,
268
+ ): Promise<PackLocalPluginsResult> {
269
+ const packed: PackedLocalPlugin[] = [];
270
+ const rewritten: Record<string, string> = {};
271
+ const warnings: string[] = [];
272
+ const maxBytes = opts.maxTarballBytes ?? MAX_LOCAL_TARBALL_BYTES;
273
+ const timeoutMs = opts.timeoutMs ?? 5 * 60 * 1000;
274
+
275
+ const candidates = opts.plugins.filter((p) => isLocalPluginSpec(p.spec));
276
+ if (candidates.length === 0) return { packed, rewritten, warnings };
277
+
278
+ try {
279
+ await opts.mkdir(opts.packDir);
280
+ } catch (err) {
281
+ warnings.push(`本地插件打包目录创建失败,已跳过全部本地插件: ${reasonOf(err)}`);
282
+ return { packed, rewritten, warnings };
283
+ }
284
+
285
+ for (const plugin of candidates) {
286
+ const spec = plugin.spec as string;
287
+ try {
288
+ const sourcePath = resolveLocalPluginPath(spec, {
289
+ homeDir: opts.homeDir,
290
+ profileDir: opts.profileDir,
291
+ });
292
+
293
+ // cwd 设为插件目录 + `npm pack . --pack-destination <abs>`:跨 npm 版本最稳的形态
294
+ const result = await opts.exec('npm', ['pack', '.', '--pack-destination', opts.packDir], {
295
+ cwd: sourcePath,
296
+ timeoutMs,
297
+ });
298
+ if (result.code !== 0) {
299
+ warnings.push(
300
+ `本地插件 ${plugin.name} 打包失败(npm pack 退出码 ${result.code ?? 'null'}):${firstLine(result.stderr) || firstLine(result.stdout) || '无输出'}`,
301
+ );
302
+ continue;
303
+ }
304
+
305
+ const filename = parsePackOutput(result.stdout, plugin.name, plugin.version);
306
+ if (filename === null) {
307
+ warnings.push(`本地插件 ${plugin.name} 打包失败:无法从 npm pack 输出解析文件名`);
308
+ continue;
309
+ }
310
+
311
+ const tgzPath = path.join(opts.packDir, filename);
312
+ const data = await opts.readFile(tgzPath);
313
+ if (data.byteLength > maxBytes) {
314
+ warnings.push(
315
+ `本地插件 ${plugin.name} 的 tarball 超过上限(${data.byteLength} > ${maxBytes} 字节),已跳过打包`,
316
+ );
317
+ continue;
318
+ }
319
+
320
+ const relativePath = tarballNameFor(plugin.name, plugin.version);
321
+ const rewrittenSpec = rewriteLocalSpec(spec, { tarballRel: relativePath });
322
+ packed.push({
323
+ packageName: plugin.name,
324
+ version: plugin.version,
325
+ relativePath,
326
+ rewrittenSpec,
327
+ data,
328
+ });
329
+ rewritten[plugin.name] = rewrittenSpec;
330
+ } catch (err) {
331
+ warnings.push(`本地插件 ${plugin.name} 打包失败:${reasonOf(err)}`);
332
+ }
333
+ }
334
+
335
+ return { packed, rewritten, warnings };
336
+ }
337
+
338
+ function reasonOf(err: unknown): string {
339
+ return err instanceof Error ? err.message : String(err);
340
+ }
341
+
342
+ function firstLine(s: string): string {
343
+ const l = s.split(/\r?\n/).find((x) => x.trim() !== '');
344
+ return l === undefined ? '' : l.trim();
345
+ }
346
+
347
+ /**
348
+ * 判断 spec 是否是「裸本地路径」(无任何前缀,如 `./plugin`、`D:\dev\x`)。
349
+ * 这类 spec 在 DSH profile 中不常见(pnpm 侧通常要求 `link:` / `file:` 前缀),
350
+ * 但一旦出现,同样指向本机路径、换机后不可达。
351
+ *
352
+ * **当前无生产调用点**(`packLocalPlugins` 目前只处理带前缀的 spec)——这是**有意的**
353
+ * 预留扩展点,不是死代码:保留它可让「无前缀裸路径」这一已识别的边界有明确表达,
354
+ * 后续若要在 `packLocalPlugins` 中一并处理,直接复用即可。
355
+ * (若哪天决定不支持该形态,请连同本注释与对应测试一起删除。)
356
+ */
357
+ export function isBareLocalPath(spec: string | undefined): boolean {
358
+ if (typeof spec !== 'string') return false;
359
+ const s = spec.trim();
360
+ if (s === '') return false;
361
+ if (hasSchemePrefix(s)) return false;
362
+ return s.startsWith('./') || s.startsWith('../') || s.startsWith('/') || /^[a-zA-Z]:[\\/]/.test(s);
363
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * 双语消息目录测试(core/messages.ts + ui/i18n.ts):
3
+ * - en 镜像必须覆盖 zh 全部键(编译期已有 Record 类型约束,此处做运行期断言);
4
+ * - {param} 插值行为(命中/缺参保留);
5
+ * - 未知键回退 zh → 键名(绝不抛错);
6
+ * - makeUiT 同款行为(客户端展示层目录)。
7
+ */
8
+ import test from 'node:test';
9
+ import assert from 'node:assert/strict';
10
+ import { en, makeMsg, zh, zhMsg } from '../core/messages.ts';
11
+ import { uiEn, uiZh, makeUiT, zhUiT } from '../ui/i18n.ts';
12
+
13
+ test('messages: en 镜像覆盖 zh 全部键(无缺键/多键)', () => {
14
+ const zhKeys = Object.keys(zh);
15
+ const enKeys = Object.keys(en);
16
+ assert.deepEqual(
17
+ [...enKeys].sort(),
18
+ [...zhKeys].sort(),
19
+ 'en 键集合必须与 zh 完全一致(编译期 Record<keyof typeof zh> 已强制,运行期再验一道)',
20
+ );
21
+ });
22
+
23
+ test('messages: 插值替换 {param},缺参原样保留', () => {
24
+ const zhMsg1 = makeMsg('zh');
25
+ assert.equal(zhMsg1('export.sectionFailed', { adapter: 'settings', reason: 'boom' }), '分区 settings 导出失败: boom');
26
+ assert.equal(zhMsg1('export.sectionFailed', { adapter: 'settings' }), '分区 settings 导出失败: {reason}', '缺参保留占位符');
27
+ const enMsg1 = makeMsg('en');
28
+ assert.equal(enMsg1('export.sectionFailed', { adapter: 'settings', reason: 'boom' }), 'Section settings export failed: boom');
29
+ });
30
+
31
+ test('messages: zhMsg 缺省行为与改造前一致(中文)', () => {
32
+ assert.equal(zhMsg('import.secretNotProvided'), '凭据未提供,需补录');
33
+ assert.equal(zhMsg('rollback.cred.noReadback'), 'DSH 不回读凭据值,无法自动恢复');
34
+ assert.equal(makeMsg(undefined)('restore.pluginRemoveFailed'), '卸载失败', '未指定语言 → zh');
35
+ });
36
+
37
+ test('messages: 未知键回退 zh → 键名,绝不抛错', () => {
38
+ assert.equal(zhMsg('no.such.key'), 'no.such.key');
39
+ assert.equal(makeMsg('en')('no.such.key'), 'no.such.key');
40
+ });
41
+
42
+ test('i18n(ui): en 镜像覆盖 zh 全部键', () => {
43
+ assert.deepEqual([...Object.keys(uiEn)].sort(), [...Object.keys(uiZh)].sort());
44
+ });
45
+
46
+ test('i18n(ui): zhUiT 缺省 + makeUiT 双语插值', () => {
47
+ assert.equal(zhUiT('error.notMounted'), 'config-manager 服务未挂载(插件未加载):请确认 profile 中已安装 dsh-config-manager 并重启 DSH');
48
+ const enT = makeUiT('en');
49
+ assert.equal(enT('error.notMounted'), 'config-manager service is not mounted (plugin not loaded): make sure dsh-config-manager is installed in the profile and restart DSH');
50
+ assert.equal(enT('report.credentialsNeedEntry', { count: '3' }), '3 credential(s) need to be entered');
51
+ // 未知键回退 zh → 键名(类型层不允许字面量,这里用宽松断言验证运行期回退)
52
+ const loose = makeUiT('en') as (key: string) => string;
53
+ assert.equal(loose('no.such.key'), 'no.such.key');
54
+ });