homebridge-config-ui-x 5.0.0-beta.11 → 5.0.0-beta.111

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 (325) hide show
  1. package/CHANGELOG.md +526 -31
  2. package/CONTRIBUTING.md +5 -4
  3. package/LICENSE +1 -1
  4. package/config.schema.json +55 -252
  5. package/dist/bin/hb-service.d.ts +2 -2
  6. package/dist/bin/hb-service.js +52 -51
  7. package/dist/bin/hb-service.js.map +1 -1
  8. package/dist/bin/platforms/darwin.js +3 -11
  9. package/dist/bin/platforms/darwin.js.map +1 -1
  10. package/dist/bin/platforms/win32.js +4 -2
  11. package/dist/bin/platforms/win32.js.map +1 -1
  12. package/dist/bin/standalone.js +1 -0
  13. package/dist/bin/standalone.js.map +1 -1
  14. package/dist/core/auth/auth.controller.d.ts +35 -3
  15. package/dist/core/auth/auth.controller.js.map +1 -1
  16. package/dist/core/auth/auth.module.js +1 -1
  17. package/dist/core/auth/auth.module.js.map +1 -1
  18. package/dist/core/auth/auth.service.js +10 -10
  19. package/dist/core/auth/auth.service.js.map +1 -1
  20. package/dist/core/auth/jwt.strategy.d.ts +3 -1
  21. package/dist/core/config/config.service.d.ts +50 -11
  22. package/dist/core/config/config.service.js +59 -20
  23. package/dist/core/config/config.service.js.map +1 -1
  24. package/dist/core/config/config.startup.js +2 -2
  25. package/dist/core/config/config.startup.js.map +1 -1
  26. package/dist/core/homebridge-ipc/homebridge-ipc.service.js +3 -3
  27. package/dist/core/homebridge-ipc/homebridge-ipc.service.js.map +1 -1
  28. package/dist/index.js +6 -77
  29. package/dist/index.js.map +1 -1
  30. package/dist/main.js +3 -1
  31. package/dist/main.js.map +1 -1
  32. package/dist/modules/accessories/accessories.controller.js +5 -5
  33. package/dist/modules/accessories/accessories.controller.js.map +1 -1
  34. package/dist/modules/accessories/accessories.service.js +8 -7
  35. package/dist/modules/accessories/accessories.service.js.map +1 -1
  36. package/dist/modules/backup/backup.controller.d.ts +1 -0
  37. package/dist/modules/backup/backup.controller.js +18 -7
  38. package/dist/modules/backup/backup.controller.js.map +1 -1
  39. package/dist/modules/backup/backup.service.d.ts +1 -0
  40. package/dist/modules/backup/backup.service.js +37 -83
  41. package/dist/modules/backup/backup.service.js.map +1 -1
  42. package/dist/modules/child-bridges/child-bridges.service.js +0 -7
  43. package/dist/modules/child-bridges/child-bridges.service.js.map +1 -1
  44. package/dist/modules/config-editor/config-editor.controller.d.ts +7 -1
  45. package/dist/modules/config-editor/config-editor.controller.js +36 -8
  46. package/dist/modules/config-editor/config-editor.controller.js.map +1 -1
  47. package/dist/modules/config-editor/config-editor.service.d.ts +5 -1
  48. package/dist/modules/config-editor/config-editor.service.js +133 -69
  49. package/dist/modules/config-editor/config-editor.service.js.map +1 -1
  50. package/dist/modules/custom-plugins/plugins-settings-ui/plugins-settings-ui.service.d.ts +1 -1
  51. package/dist/modules/custom-plugins/plugins-settings-ui/plugins-settings-ui.service.js +13 -11
  52. package/dist/modules/custom-plugins/plugins-settings-ui/plugins-settings-ui.service.js.map +1 -1
  53. package/dist/modules/log/log.gateway.d.ts +2 -1
  54. package/dist/modules/log/log.gateway.js.map +1 -1
  55. package/dist/modules/log/log.service.js +3 -3
  56. package/dist/modules/log/log.service.js.map +1 -1
  57. package/dist/modules/platform-tools/docker/docker.controller.js +3 -3
  58. package/dist/modules/platform-tools/docker/docker.controller.js.map +1 -1
  59. package/dist/modules/platform-tools/docker/docker.service.js +1 -1
  60. package/dist/modules/platform-tools/docker/docker.service.js.map +1 -1
  61. package/dist/modules/platform-tools/hb-service/hb-service.controller.js +3 -3
  62. package/dist/modules/platform-tools/hb-service/hb-service.controller.js.map +1 -1
  63. package/dist/modules/platform-tools/hb-service/hb-service.service.js +6 -6
  64. package/dist/modules/platform-tools/hb-service/hb-service.service.js.map +1 -1
  65. package/dist/modules/platform-tools/linux/linux.controller.js +2 -2
  66. package/dist/modules/platform-tools/linux/linux.controller.js.map +1 -1
  67. package/dist/modules/platform-tools/linux/linux.service.js +2 -2
  68. package/dist/modules/platform-tools/linux/linux.service.js.map +1 -1
  69. package/dist/modules/platform-tools/terminal/terminal.gateway.d.ts +2 -1
  70. package/dist/modules/platform-tools/terminal/terminal.gateway.js.map +1 -1
  71. package/dist/modules/platform-tools/terminal/terminal.service.js +2 -2
  72. package/dist/modules/platform-tools/terminal/terminal.service.js.map +1 -1
  73. package/dist/modules/plugins/plugins.controller.d.ts +2 -0
  74. package/dist/modules/plugins/plugins.controller.js +5 -5
  75. package/dist/modules/plugins/plugins.controller.js.map +1 -1
  76. package/dist/modules/plugins/plugins.service.d.ts +10 -1
  77. package/dist/modules/plugins/plugins.service.js +287 -168
  78. package/dist/modules/plugins/plugins.service.js.map +1 -1
  79. package/dist/modules/server/server.controller.d.ts +33 -3
  80. package/dist/modules/server/server.controller.js +162 -20
  81. package/dist/modules/server/server.controller.js.map +1 -1
  82. package/dist/modules/server/server.service.d.ts +35 -6
  83. package/dist/modules/server/server.service.js +288 -66
  84. package/dist/modules/server/server.service.js.map +1 -1
  85. package/dist/modules/status/status.controller.d.ts +0 -1
  86. package/dist/modules/status/status.controller.js +3 -4
  87. package/dist/modules/status/status.controller.js.map +1 -1
  88. package/dist/modules/status/status.gateway.d.ts +1 -1
  89. package/dist/modules/status/status.service.d.ts +1 -1
  90. package/dist/modules/status/status.service.js +34 -41
  91. package/dist/modules/status/status.service.js.map +1 -1
  92. package/dist/modules/users/users.controller.js +7 -7
  93. package/dist/modules/users/users.controller.js.map +1 -1
  94. package/dist/self-check.js +6 -6
  95. package/dist/self-check.js.map +1 -1
  96. package/package.json +50 -43
  97. package/public/3rdpartylicenses.txt +246 -136
  98. package/public/assets/hap-icons/air-purifier.svg +50 -0
  99. package/public/assets/hap-icons/air-quality-sensor.svg +25 -0
  100. package/public/assets/hap-icons/carbon-dioxide-sensor.svg +72 -0
  101. package/public/assets/hap-icons/carbon-monoxide-sensor.svg +72 -0
  102. package/public/assets/hap-icons/contact-sensor-closed.svg +36 -0
  103. package/public/assets/hap-icons/contact-sensor-open.svg +81 -0
  104. package/public/assets/hap-icons/door-closed.svg +32 -2
  105. package/public/assets/hap-icons/door-open.svg +48 -2
  106. package/public/assets/hap-icons/fan-off.svg +24 -13
  107. package/public/assets/hap-icons/fan-on.svg +24 -13
  108. package/public/assets/hap-icons/filter-maintenance.svg +26 -0
  109. package/public/assets/hap-icons/garage-door.svg +25 -0
  110. package/public/assets/hap-icons/humidity-sensor.svg +25 -0
  111. package/public/assets/hap-icons/irrigation-system.svg +47 -18
  112. package/public/assets/hap-icons/leak-sensor.svg +53 -0
  113. package/public/assets/hap-icons/light-bulb.svg +25 -0
  114. package/public/assets/hap-icons/light-sensor.svg +50 -0
  115. package/public/assets/hap-icons/lock-locked.svg +24 -13
  116. package/public/assets/hap-icons/lock-unlocked.svg +24 -13
  117. package/public/assets/hap-icons/motion-sensor.svg +101 -0
  118. package/public/assets/hap-icons/occupancy-sensor.svg +98 -0
  119. package/public/assets/hap-icons/outlet.svg +24 -13
  120. package/public/assets/hap-icons/security-system-active.svg +103 -0
  121. package/public/assets/hap-icons/security-system-off.svg +69 -0
  122. package/public/assets/hap-icons/security-system-triggered.svg +103 -0
  123. package/public/assets/hap-icons/smoke-sensor.svg +59 -0
  124. package/public/assets/hap-icons/speaker.svg +29 -13
  125. package/public/assets/hap-icons/stateless-programmable-switch.svg +52 -0
  126. package/public/assets/hap-icons/switch.svg +24 -13
  127. package/public/assets/hap-icons/television.svg +15 -4
  128. package/public/assets/hap-icons/temperature.svg +24 -13
  129. package/public/assets/hap-icons/unknown.svg +24 -13
  130. package/public/assets/hap-icons/valve-faucet.svg +21 -0
  131. package/public/assets/hap-icons/valve-generic.svg +27 -16
  132. package/public/assets/hap-icons/valve-irrigation.svg +37 -21
  133. package/public/assets/hap-icons/valve-shower-head.svg +52 -0
  134. package/public/assets/hap-icons/window-closed.svg +85 -2
  135. package/public/assets/hap-icons/window-covering-closed.svg +49 -0
  136. package/public/assets/hap-icons/window-covering-open.svg +44 -0
  137. package/public/assets/hap-icons/window-open.svg +136 -2
  138. package/public/assets/homebridge-color-round.svg +36 -1
  139. package/public/assets/homebridge-logo.svg +11 -1
  140. package/public/assets/mask-icon.svg +5 -1
  141. package/public/assets/plugin-ui-utils/ui.js +3 -0
  142. package/public/assets/plugin-ui-utils/ui.js.map +1 -1
  143. package/public/chunk-2B6Z42D5.js +2 -0
  144. package/public/chunk-3KEVK4CU.js +1 -0
  145. package/public/{chunk-T5R3UG6Z.js → chunk-3TXUNOXT.js} +1 -1
  146. package/public/chunk-4G7W6MRL.js +1 -0
  147. package/public/chunk-4N7ZDOUO.js +1 -0
  148. package/public/chunk-5CIDMZGS.js +1 -0
  149. package/public/chunk-66VSEU25.js +5 -0
  150. package/public/chunk-7DSQ7XWE.js +1 -0
  151. package/public/{chunk-QE7DO6J3.js → chunk-7EQ4BJZB.js} +2 -2
  152. package/public/chunk-7GGXZ7OU.js +1 -0
  153. package/public/chunk-7QRMEOTO.js +8 -0
  154. package/public/chunk-7RKBTRXY.js +1 -0
  155. package/public/chunk-ABBL3RO7.js +1 -0
  156. package/public/chunk-BCMP25TC.js +1 -0
  157. package/public/chunk-BP5LQRNS.js +4 -0
  158. package/public/chunk-CF2ZN7LY.js +5 -0
  159. package/public/chunk-DSKSIOUV.js +1 -0
  160. package/public/chunk-EGCUYKRH.js +1 -0
  161. package/public/chunk-EL5XLLBH.js +1 -0
  162. package/public/chunk-EPA4YSLT.js +1 -0
  163. package/public/chunk-F3ERQJVG.js +1 -0
  164. package/public/{chunk-LM6S4A72.js → chunk-FFFA4Z4P.js} +1 -1
  165. package/public/chunk-FNE4UWIK.js +1 -0
  166. package/public/chunk-FXYQSGFB.js +1 -0
  167. package/public/{chunk-7EUQWCP5.js → chunk-GDFV4BMO.js} +2 -2
  168. package/public/chunk-GW2DVJRQ.js +1 -0
  169. package/public/chunk-HTQJEJW6.js +23 -0
  170. package/public/chunk-IN7NZAFJ.js +1 -0
  171. package/public/chunk-J6YDFLQQ.js +1 -0
  172. package/public/chunk-K6X6GPJI.js +1 -0
  173. package/public/chunk-KKNYWOLQ.js +20 -0
  174. package/public/chunk-L3KYADWL.js +1 -0
  175. package/public/chunk-LUNR4YXI.js +1 -0
  176. package/public/chunk-LXXELGX4.js +1 -0
  177. package/public/chunk-LZTR2JMZ.js +1 -0
  178. package/public/{chunk-JZZQRLNW.js → chunk-NGNQPAAK.js} +1 -1
  179. package/public/chunk-NH3ELE5E.js +4 -0
  180. package/public/chunk-OKTBHUEL.js +1 -0
  181. package/public/{chunk-5M6KOARX.js → chunk-ORZLF5HZ.js} +1 -1
  182. package/public/chunk-Q3UFAHWJ.js +1 -0
  183. package/public/{chunk-WNWWUCCZ.js → chunk-QDCKP2LU.js} +3 -3
  184. package/public/chunk-QEBH3NJ4.js +1 -0
  185. package/public/chunk-QTCNRXCY.js +29 -0
  186. package/public/chunk-R33U7ULB.js +1 -0
  187. package/public/chunk-RMY3PS3V.js +1 -0
  188. package/public/chunk-S5TTYLQD.js +1 -0
  189. package/public/chunk-SKZUTSHE.js +7 -0
  190. package/public/chunk-SRP6QX5M.js +2 -0
  191. package/public/chunk-SSMWAG33.js +1 -0
  192. package/public/chunk-T252YJXA.js +1 -0
  193. package/public/chunk-TFSYGCLN.js +1 -0
  194. package/public/chunk-TKTM3Z2T.js +3 -0
  195. package/public/chunk-TQNKRJOX.js +1 -0
  196. package/public/chunk-U3DAE6KH.js +1 -0
  197. package/public/chunk-UD6SVFW2.js +7 -0
  198. package/public/chunk-UO2HQVMO.js +1 -0
  199. package/public/{chunk-3RNRIJ74.js → chunk-UQYCFN3T.js} +11 -11
  200. package/public/chunk-V6SZXPWT.js +1 -0
  201. package/public/chunk-VGEV4FQW.js +1 -0
  202. package/public/{chunk-EA5J2VEJ.js → chunk-X2SHRXXY.js} +1 -1
  203. package/public/chunk-X7PVYZAE.js +1 -0
  204. package/public/{chunk-3TUBDO76.js → chunk-XBWIDIPO.js} +1 -1
  205. package/public/chunk-ZDJGHFIU.js +12 -0
  206. package/public/chunk-ZNACRWKA.js +1 -0
  207. package/public/chunk-ZVUGVJDR.js +1 -0
  208. package/public/index.html +2 -2
  209. package/public/main-YVALKUM3.js +1 -0
  210. package/public/media/fa-brands-400-Q3XCMWHQ.woff2 +0 -0
  211. package/public/media/{fa-brands-400-KOKGDU7E.ttf → fa-brands-400-R2XQZCET.ttf} +0 -0
  212. package/public/media/fa-regular-400-QSNYFYRT.woff2 +0 -0
  213. package/public/media/{fa-regular-400-IPMAEX5Y.ttf → fa-regular-400-XUOPSR7E.ttf} +0 -0
  214. package/public/media/fa-solid-900-5ZUYHGA7.woff2 +0 -0
  215. package/public/media/{fa-solid-900-SRFFQLRM.ttf → fa-solid-900-PJNKLK6W.ttf} +0 -0
  216. package/public/polyfills-WE4HA5DL.js +2 -0
  217. package/public/styles-YDIK2EDN.css +1 -0
  218. package/scripts/upgrade-install-plugin.sh +1 -1
  219. package/public/assets/hap-icons/airpurifier.svg +0 -17
  220. package/public/assets/hap-icons/airquality.svg +0 -14
  221. package/public/assets/hap-icons/contactsensor-closed.svg +0 -3
  222. package/public/assets/hap-icons/contactsensor-open.svg +0 -3
  223. package/public/assets/hap-icons/garagedoor.svg +0 -14
  224. package/public/assets/hap-icons/humidity.svg +0 -14
  225. package/public/assets/hap-icons/leaksensor.svg +0 -3
  226. package/public/assets/hap-icons/light.svg +0 -31
  227. package/public/assets/hap-icons/lightbulb.svg +0 -14
  228. package/public/assets/hap-icons/motionsensor.svg +0 -3
  229. package/public/assets/hap-icons/occupancysensor.svg +0 -3
  230. package/public/assets/hap-icons/securitysystem-active.svg +0 -3
  231. package/public/assets/hap-icons/securitysystem-off.svg +0 -3
  232. package/public/assets/hap-icons/smokesensor.svg +0 -13
  233. package/public/assets/hap-icons/statelessprogrammableswitch.svg +0 -3
  234. package/public/assets/hap-icons/windowcovering-closed.svg +0 -53
  235. package/public/assets/hap-icons/windowcovering-open.svg +0 -48
  236. package/public/chunk-23E7VL2P.js +0 -1
  237. package/public/chunk-33LJ3YIR.js +0 -1
  238. package/public/chunk-34W4KCBV.js +0 -1
  239. package/public/chunk-3IX3CLER.js +0 -1
  240. package/public/chunk-3UBYWHRR.js +0 -1
  241. package/public/chunk-52HTAWOT.js +0 -1
  242. package/public/chunk-535DPMCW.js +0 -1
  243. package/public/chunk-56BOPXKC.js +0 -1
  244. package/public/chunk-6IAVZXBU.js +0 -20
  245. package/public/chunk-6T27YQ2D.js +0 -1
  246. package/public/chunk-6TCHCTXZ.js +0 -1
  247. package/public/chunk-7WLTVXXL.js +0 -1
  248. package/public/chunk-AS2OC4NZ.js +0 -1
  249. package/public/chunk-BKUGARB4.js +0 -7
  250. package/public/chunk-BLUX2UMQ.js +0 -1
  251. package/public/chunk-BPMSJ2VF.js +0 -1
  252. package/public/chunk-BW6NZ6VD.js +0 -6
  253. package/public/chunk-C4ENUEJD.js +0 -1
  254. package/public/chunk-C536FAQ7.js +0 -5
  255. package/public/chunk-CCUID66K.js +0 -1
  256. package/public/chunk-CL4YMJZW.js +0 -1
  257. package/public/chunk-D7HF5OGR.js +0 -1
  258. package/public/chunk-DD3R2DFS.js +0 -1
  259. package/public/chunk-DHAYGFW2.js +0 -1
  260. package/public/chunk-DVTSZQXS.js +0 -1
  261. package/public/chunk-EIBIFGLP.js +0 -1
  262. package/public/chunk-F4CW25FV.js +0 -1
  263. package/public/chunk-HL7XXKHI.js +0 -1
  264. package/public/chunk-HSJSWZHD.js +0 -1
  265. package/public/chunk-HUV3VJSE.js +0 -1
  266. package/public/chunk-INJABMER.js +0 -23
  267. package/public/chunk-IU27XSWW.js +0 -1
  268. package/public/chunk-JVAHZDVJ.js +0 -1
  269. package/public/chunk-KQJ7ONUG.js +0 -1
  270. package/public/chunk-KS3FIR4R.js +0 -1
  271. package/public/chunk-KXWJIWYD.js +0 -1
  272. package/public/chunk-LDGB6S2N.js +0 -1
  273. package/public/chunk-M45D5CCT.js +0 -1
  274. package/public/chunk-MGBLPFXT.js +0 -1
  275. package/public/chunk-MIMQGXTO.js +0 -1
  276. package/public/chunk-NKN62APE.js +0 -1
  277. package/public/chunk-NW6AFAD7.js +0 -1
  278. package/public/chunk-NZNNTHFQ.js +0 -1
  279. package/public/chunk-ORPWYWCL.js +0 -5
  280. package/public/chunk-Q2YQ4J5L.js +0 -1
  281. package/public/chunk-QHPDGSZ6.js +0 -1
  282. package/public/chunk-QXHB5Q7H.js +0 -1
  283. package/public/chunk-R2QZPN2M.js +0 -1
  284. package/public/chunk-RDOXIKK6.js +0 -1
  285. package/public/chunk-RHBPQJLQ.js +0 -1
  286. package/public/chunk-RSGTDKO6.js +0 -1
  287. package/public/chunk-RTVXC3Y7.js +0 -1
  288. package/public/chunk-SFK7OSIU.js +0 -1
  289. package/public/chunk-SFLEKDGL.js +0 -1
  290. package/public/chunk-TB7FJ5XX.js +0 -1
  291. package/public/chunk-TCSXGQNF.js +0 -1
  292. package/public/chunk-TRTZHJGG.js +0 -1
  293. package/public/chunk-TWWDGSRW.js +0 -1
  294. package/public/chunk-TXOB7R5K.js +0 -7
  295. package/public/chunk-U2WFZQTC.js +0 -32
  296. package/public/chunk-UG5DK2RQ.js +0 -2
  297. package/public/chunk-UPYKPJUD.js +0 -4
  298. package/public/chunk-UVNEKL77.js +0 -1
  299. package/public/chunk-VHLIKZYD.js +0 -1
  300. package/public/chunk-WHJOLAED.js +0 -1
  301. package/public/chunk-WHJSVGC7.js +0 -1
  302. package/public/chunk-Y62756RF.js +0 -1
  303. package/public/chunk-YA4WNIBX.js +0 -1
  304. package/public/chunk-ZGUIFA2B.js +0 -1
  305. package/public/chunk-ZLOETFRA.js +0 -8
  306. package/public/chunk-ZT23DWNL.js +0 -1
  307. package/public/main-TLKQDE7H.js +0 -1
  308. package/public/media/01-RQ3S2L53.png +0 -0
  309. package/public/media/02-VNCG2I2A.png +0 -0
  310. package/public/media/03-HI42L4ZG.png +0 -0
  311. package/public/media/04-FJLL55LZ.png +0 -0
  312. package/public/media/05-V3EO6SPT.png +0 -0
  313. package/public/media/06-EOJZCQZN.png +0 -0
  314. package/public/media/07-KMKB5PBD.png +0 -0
  315. package/public/media/08-UQJRF6B2.png +0 -0
  316. package/public/media/09-2DJQFRHH.png +0 -0
  317. package/public/media/arrow_left-CQT7FZM7.svg +0 -4
  318. package/public/media/arrow_right-SBUDRR2G.svg +0 -4
  319. package/public/media/fa-brands-400-6PJPV6JM.woff2 +0 -0
  320. package/public/media/fa-regular-400-OHB6J4OK.woff2 +0 -0
  321. package/public/media/fa-solid-900-ABTK6BNK.woff2 +0 -0
  322. package/public/polyfills-C6JHVXJJ.js +0 -2
  323. package/public/scripts-6GVLYD7F.js +0 -62
  324. package/public/styles-EG5MFQEM.css +0 -1
  325. /package/public/assets/{bootstrap-4 → bootstrap-5}/cssframework/assets.json +0 -0
@@ -40,11 +40,13 @@ let PluginsService = PluginsService_1 = class PluginsService {
40
40
  this.npm = this.getNpmPath();
41
41
  this.paths = this.getBasePaths();
42
42
  this.pluginListUrl = 'https://raw.githubusercontent.com/homebridge/plugins/latest/';
43
- this.pluginListFile = `${this.pluginListUrl}assets/plugins.min.json`;
43
+ this.pluginListFile = `${this.pluginListUrl}assets/plugins-v2.min.json`;
44
44
  this.hiddenPlugins = [];
45
45
  this.maintainedPlugins = [];
46
46
  this.pluginIcons = {};
47
- this.scopedPlugins = {};
47
+ this.pluginAuthors = {};
48
+ this.pluginNames = {};
49
+ this.pluginChangelogs = {};
48
50
  this.newScopePlugins = {};
49
51
  this.verifiedPlugins = [];
50
52
  this.verifiedPlusPlugins = [];
@@ -67,6 +69,12 @@ let PluginsService = PluginsService_1 = class PluginsService {
67
69
  this.loadPluginList();
68
70
  setInterval(this.loadPluginList.bind(this), 60000 * 60 * 12);
69
71
  }
72
+ fixDisplayName(plugin) {
73
+ plugin.displayName = plugin.displayName || (plugin.name.charAt(0) === '@' ? plugin.name.split('/')[1] : plugin.name)
74
+ .replace(/-/g, ' ')
75
+ .replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substring(1).toLowerCase());
76
+ return plugin;
77
+ }
70
78
  async getInstalledPlugins() {
71
79
  const plugins = [];
72
80
  const modules = await this.getInstalledModules();
@@ -92,12 +100,12 @@ let PluginsService = PluginsService_1 = class PluginsService {
92
100
  }
93
101
  }
94
102
  catch (e) {
95
- this.logger.error(`Failed to parse plugin "${pkg.name}": ${e.message}`);
103
+ this.logger.error(`Failed to parse plugin ${pkg.name} as ${e.message}.`);
96
104
  }
97
105
  });
98
106
  }));
99
- this.installedPlugins = plugins;
100
- return plugins;
107
+ this.installedPlugins = plugins.map(plugin => this.fixDisplayName(plugin));
108
+ return this.installedPlugins;
101
109
  }
102
110
  async getOutOfDatePlugins() {
103
111
  const plugins = await this.getInstalledPlugins();
@@ -142,58 +150,111 @@ let PluginsService = PluginsService_1 = class PluginsService {
142
150
  throw new common_1.NotFoundException();
143
151
  }
144
152
  }
153
+ extractTerms(query, separator) {
154
+ return query
155
+ .toLowerCase()
156
+ .split(separator)
157
+ .map(term => term.trim())
158
+ .filter(term => term && term !== 'homebridge' && term !== 'plugin');
159
+ }
160
+ getPluginKeywords(plugin) {
161
+ return Array.isArray(plugin.keywords)
162
+ ? plugin.keywords.map(k => k.toLowerCase())
163
+ : [];
164
+ }
165
+ matchesPlugin(plugin, searchTerms) {
166
+ const pluginName = plugin.name.toLowerCase();
167
+ const pluginKeywords = this.getPluginKeywords(plugin);
168
+ const pluginDescription = (plugin.description || '').toLowerCase();
169
+ const nameTerms = this.extractTerms(pluginName.substring(pluginName.lastIndexOf('/') + 1), /-/);
170
+ if (nameTerms.every(term => searchTerms.includes(term))) {
171
+ return 'exactName';
172
+ }
173
+ if (searchTerms.every(term => pluginKeywords.includes(term))
174
+ || searchTerms.every(term => nameTerms.includes(term))) {
175
+ return 'exactKeyword';
176
+ }
177
+ if (searchTerms.some(term => pluginName.includes(term))
178
+ || searchTerms.some(term => pluginKeywords.some(k => k.includes(term)))
179
+ || searchTerms.some(term => pluginDescription.includes(term))) {
180
+ return 'partial';
181
+ }
182
+ return null;
183
+ }
145
184
  async searchNpmRegistry(query) {
146
185
  if (!this.installedPlugins) {
147
186
  await this.getInstalledPlugins();
148
187
  }
149
- const q = `${(!query || !query.length) ? '' : `${query}+`}keywords:homebridge-plugin+not:deprecated&size=30`;
188
+ const searchTerms = this.extractTerms(query, /\s+/);
189
+ const normalizedQuery = searchTerms.length > 0 ? searchTerms.join(' ') : 'homebridge';
190
+ if ((normalizedQuery.startsWith('homebridge-') || this.isScopedPlugin(normalizedQuery))
191
+ && !this.hiddenPlugins.includes(normalizedQuery)) {
192
+ if (!this.installedPlugins.find(x => x.name === normalizedQuery)
193
+ && Object.keys(this.newScopePlugins).includes(normalizedQuery)) {
194
+ return await this.searchNpmRegistrySingle(`@homebridge-plugins/${normalizedQuery}`);
195
+ }
196
+ return await this.searchNpmRegistrySingle(normalizedQuery);
197
+ }
198
+ const q = `${normalizedQuery.substring(0, 15)}+keywords:homebridge-plugin+not:deprecated&size=99`;
150
199
  let searchResults;
151
200
  try {
152
201
  searchResults = (await (0, rxjs_1.firstValueFrom)(this.httpService.get(`https://registry.npmjs.org/-/v1/search?text=${q}`))).data;
153
202
  }
154
203
  catch (e) {
155
- this.logger.error(`Failed to search the npm registry - "${e.message}" - see https://homebridge.io/w/JJSz6 for help.`);
156
- throw new common_1.InternalServerErrorException(`Failed to search the npm registry - "${e.message}" - see logs.`);
204
+ this.logger.error(`Failed to search the npm registry (see https://homebridge.io/w/JJSz6 for help) as ${e.message}.`);
205
+ throw new common_1.InternalServerErrorException(`Failed to search the npm registry as ${e.message}, see logs.`);
157
206
  }
158
- const result = searchResults.objects
159
- .filter(x => x.package.name.indexOf('homebridge-') === 0 || this.isScopedPlugin(x.package.name))
207
+ const plugins = searchResults.objects
208
+ .filter(x => x.package.name.startsWith('homebridge-') || this.isScopedPlugin(x.package.name))
160
209
  .filter(x => !this.hiddenPlugins.includes(x.package.name))
161
210
  .map((pkg) => {
162
- let plugin = {
211
+ const isInstalled = this.installedPlugins.find(x => x.name === pkg.package.name);
212
+ if (isInstalled) {
213
+ return {
214
+ ...isInstalled,
215
+ lastUpdated: pkg.package.date,
216
+ keywords: pkg.package.keywords || [],
217
+ };
218
+ }
219
+ return {
163
220
  name: pkg.package.name,
221
+ displayName: this.pluginNames[pkg.package.name],
164
222
  private: false,
223
+ publicPackage: true,
224
+ installedVersion: null,
225
+ latestVersion: pkg.package.version,
226
+ lastUpdated: pkg.package.date,
227
+ description: (pkg.package.description || pkg.package.name).replace(/\(?(?:https?|ftp):\/\/[\n\S]+/g, '').trim(),
228
+ keywords: pkg.package.keywords || [],
229
+ links: pkg.package.links,
230
+ author: this.pluginAuthors[pkg.package.name] || (pkg.package.publisher ? pkg.package.publisher.username : null),
231
+ verifiedPlugin: this.verifiedPlugins.includes(pkg.package.name),
232
+ verifiedPlusPlugin: this.verifiedPlusPlugins.includes(pkg.package.name),
233
+ icon: this.pluginIcons[pkg.package.name] ? `${this.pluginListUrl}${this.pluginIcons[pkg.package.name]}` : null,
234
+ isHbScoped: pkg.package.name.startsWith('@homebridge-plugins/'),
235
+ newHbScope: this.newScopePlugins[pkg.package.name],
236
+ isHbMaintained: this.maintainedPlugins.includes(pkg.package.name),
165
237
  };
166
- const isInstalled = this.installedPlugins.find(x => x.name === plugin.name);
167
- if (isInstalled) {
168
- plugin = isInstalled;
169
- plugin.lastUpdated = pkg.package.date;
170
- return plugin;
171
- }
172
- plugin.publicPackage = true;
173
- plugin.installedVersion = null;
174
- plugin.latestVersion = pkg.package.version;
175
- plugin.lastUpdated = pkg.package.date;
176
- plugin.description = (pkg.package.description)
177
- ? pkg.package.description.replace(/\(?(?:https?|ftp):\/\/[\n\S]+/g, '').trim()
178
- : pkg.package.name;
179
- plugin.links = pkg.package.links;
180
- plugin.author = this.scopedPlugins[pkg.package.name] || ((pkg.package.publisher) ? pkg.package.publisher.username : null);
181
- plugin.verifiedPlugin = this.verifiedPlugins.includes(pkg.package.name);
182
- plugin.verifiedPlusPlugin = this.verifiedPlusPlugins.includes(pkg.package.name);
183
- plugin.icon = this.pluginIcons[pkg.package.name]
184
- ? `${this.pluginListUrl}${this.pluginIcons[pkg.package.name]}`
185
- : null;
186
- plugin.isHbScoped = !!this.scopedPlugins[pkg.package.name];
187
- plugin.newHbScope = this.newScopePlugins[pkg.package.name];
188
- plugin.isHbMaintained = this.maintainedPlugins.includes(pkg.package.name);
189
- return plugin;
190
238
  });
191
- if (!result.length
192
- && (query.indexOf('homebridge-') === 0 || this.isScopedPlugin(query))
193
- && !this.hiddenPlugins.includes(query.toLowerCase())) {
194
- return await this.searchNpmRegistrySingle(query.toLowerCase());
239
+ const matchGroups = {
240
+ exactName: [],
241
+ exactKeyword: [],
242
+ partial: [],
243
+ };
244
+ for (const plugin of plugins) {
245
+ const matchType = this.matchesPlugin(plugin, searchTerms);
246
+ if (matchType) {
247
+ matchGroups[matchType].push(plugin);
248
+ }
195
249
  }
196
- return (0, lodash_1.orderBy)(result, ['verifiedPlusPlugin', 'verifiedPlugin'], ['desc', 'desc']);
250
+ const orderPlugins = (arr) => (0, lodash_1.orderBy)(arr, ['isHbScoped', 'verifiedPlusPlugin', 'verifiedPlugin', 'lastUpdated'], ['desc', 'desc', 'desc']);
251
+ return [
252
+ ...orderPlugins(matchGroups.exactName),
253
+ ...orderPlugins(matchGroups.exactKeyword),
254
+ ...orderPlugins(matchGroups.partial),
255
+ ]
256
+ .slice(0, 30)
257
+ .map(plugin => this.fixDisplayName(plugin));
197
258
  }
198
259
  async searchNpmRegistrySingle(query) {
199
260
  try {
@@ -224,10 +285,11 @@ let PluginsService = PluginsService_1 = class PluginsService {
224
285
  verifiedPlugin: this.verifiedPlugins.includes(pkg.name),
225
286
  verifiedPlusPlugin: this.verifiedPlusPlugins.includes(pkg.name),
226
287
  icon: this.pluginIcons[pkg.name],
227
- isHbScoped: !!this.scopedPlugins[pkg.name],
288
+ isHbScoped: pkg.name.startsWith('@homebridge-plugins/'),
228
289
  newHbScope: this.newScopePlugins[pkg.name],
229
290
  isHbMaintained: this.maintainedPlugins.includes(pkg.name),
230
291
  };
292
+ plugin.displayName = this.pluginNames[pkg.name];
231
293
  plugin.publicPackage = true;
232
294
  plugin.latestVersion = pkg['dist-tags'] ? pkg['dist-tags'].latest : undefined;
233
295
  plugin.lastUpdated = pkg.time.modified;
@@ -238,38 +300,38 @@ let PluginsService = PluginsService_1 = class PluginsService {
238
300
  homepage: pkg.homepage,
239
301
  bugs: typeof pkg.bugs === 'object' && pkg.bugs?.url ? pkg.bugs.url : null,
240
302
  };
241
- plugin.author = this.scopedPlugins[pkg.name]
303
+ plugin.author = this.pluginAuthors[pkg.name]
242
304
  || ((pkg.maintainers && pkg.maintainers.length) ? pkg.maintainers[0].name : null);
243
305
  plugin.verifiedPlugin = this.verifiedPlugins.includes(pkg.name);
244
306
  plugin.verifiedPlusPlugin = this.verifiedPlusPlugins.includes(pkg.name);
245
307
  plugin.icon = this.pluginIcons[pkg.name]
246
308
  ? `${this.pluginListUrl}${this.pluginIcons[pkg.name]}`
247
309
  : null;
248
- plugin.isHbScoped = !!this.scopedPlugins[pkg.name];
310
+ plugin.isHbScoped = pkg.name.startsWith('@homebridge-plugins/');
249
311
  plugin.newHbScope = this.newScopePlugins[pkg.name];
250
312
  plugin.isHbMaintained = this.maintainedPlugins.includes(pkg.name);
251
- return [plugin];
313
+ return [this.fixDisplayName(plugin)];
252
314
  }
253
315
  catch (e) {
254
316
  if (e.response?.status !== 404) {
255
- this.logger.error(`Failed to search the npm registry - "${e.message}" - see https://homebridge.io/w/JJSz6 for help.`);
317
+ this.logger.error(`Failed to search the npm registry (see https://homebridge.io/w/JJSz6 for help) as ${e.message}.`);
256
318
  }
257
319
  return [];
258
320
  }
259
321
  }
260
- async managePlugin(action, pluginAction, client) {
261
- pluginAction.version = pluginAction.version || 'latest';
262
- if (action === 'uninstall' && pluginAction.name === this.configService.name) {
263
- throw new Error(`Cannot uninstall ${pluginAction.name} from ${this.configService.name}.`);
322
+ async manageUi(action, pluginAction, client) {
323
+ if (action === 'uninstall') {
324
+ throw new Error('Cannot uninstall the Homebridge UI.');
264
325
  }
265
- if (pluginAction.name === this.configService.name && this.configService.dockerOfflineUpdate && pluginAction.version === 'latest') {
326
+ if (this.configService.dockerOfflineUpdate && pluginAction.version === 'latest') {
266
327
  await this.updateSelfOffline(client);
267
328
  return true;
268
329
  }
269
330
  if (action === 'install' && pluginAction.version === 'latest') {
270
331
  pluginAction.version = await this.getNpmModuleLatestVersion(pluginAction.name);
271
332
  }
272
- let installPath = (this.configService.customPluginPath)
333
+ const userPlatform = (0, node_os_1.platform)();
334
+ let installPath = this.configService.customPluginPath
273
335
  ? this.configService.customPluginPath
274
336
  : this.installedPlugins.find(x => x.name === this.configService.name).installPath;
275
337
  await this.getInstalledPlugins();
@@ -277,24 +339,53 @@ let PluginsService = PluginsService_1 = class PluginsService {
277
339
  if (existingPlugin) {
278
340
  installPath = existingPlugin.installPath;
279
341
  }
280
- if (action === 'install' && pluginAction.name === this.configService.name) {
281
- const githubReleaseName = await this.isUiUpdateBundleAvailable(pluginAction);
282
- if (githubReleaseName) {
283
- try {
284
- await this.doUiBundleUpdate(pluginAction, client, githubReleaseName);
285
- return true;
286
- }
287
- catch (e) {
288
- client.emit('stdout', (0, bash_color_1.yellow)('\r\nBundled update failed. Trying regular update using npm.\r\n\r\n'));
289
- }
342
+ const githubReleaseName = await this.isUiUpdateBundleAvailable(pluginAction);
343
+ if (githubReleaseName) {
344
+ try {
345
+ await this.doUiBundleUpdate(pluginAction, client, githubReleaseName);
346
+ return true;
290
347
  }
291
- if ((0, node_os_1.cpus)().length === 1 && (0, node_os_1.arch)() === 'arm') {
292
- client.emit('stdout', (0, bash_color_1.yellow)('***************************************************************\r\n'));
293
- client.emit('stdout', (0, bash_color_1.yellow)(`Please be patient while ${this.configService.name} updates.\r\n`));
294
- client.emit('stdout', (0, bash_color_1.yellow)('This process may take 5-15 minutes to complete on your device.\r\n'));
295
- client.emit('stdout', (0, bash_color_1.yellow)('***************************************************************\r\n\r\n'));
348
+ catch (e) {
349
+ client.emit('stdout', (0, bash_color_1.yellow)('\r\nBundled update failed. Trying regular update using npm.\r\n\r\n'));
296
350
  }
297
351
  }
352
+ if ((0, node_os_1.cpus)().length === 1 && (0, node_os_1.arch)() === 'arm') {
353
+ client.emit('stdout', (0, bash_color_1.yellow)('***************************************************************\r\n'));
354
+ client.emit('stdout', (0, bash_color_1.yellow)(`Please be patient while ${this.configService.name} updates.\r\n`));
355
+ client.emit('stdout', (0, bash_color_1.yellow)('This process may take 5-15 minutes to complete on your device.\r\n'));
356
+ client.emit('stdout', (0, bash_color_1.yellow)('***************************************************************\r\n\r\n'));
357
+ }
358
+ const installOptions = [];
359
+ if (installPath === this.configService.customPluginPath && await (0, fs_extra_1.pathExists)((0, node_path_1.resolve)(installPath, '../package.json'))) {
360
+ installOptions.push('--save');
361
+ }
362
+ installPath = (0, node_path_1.resolve)(installPath, '../');
363
+ if (!this.configService.customPluginPath || userPlatform === 'win32' || existingPlugin?.globalInstall === true) {
364
+ installOptions.push('-g');
365
+ }
366
+ installOptions.push('--omit=dev');
367
+ const npmPluginLabel = `${pluginAction.name}@${pluginAction.version}`;
368
+ await this.cleanNpmCache();
369
+ await this.runNpmCommand([...this.npm, action, ...installOptions, npmPluginLabel], installPath, client, pluginAction.termCols, pluginAction.termRows);
370
+ await this.ensureCustomPluginDirExists();
371
+ return true;
372
+ }
373
+ async managePlugin(action, pluginAction, client) {
374
+ pluginAction.version = pluginAction.version || 'latest';
375
+ if (pluginAction.name === this.configService.name) {
376
+ return await this.manageUi(action, pluginAction, client);
377
+ }
378
+ if (action === 'install' && pluginAction.version === 'latest') {
379
+ pluginAction.version = await this.getNpmModuleLatestVersion(pluginAction.name);
380
+ }
381
+ let installPath = this.configService.customPluginPath
382
+ ? this.configService.customPluginPath
383
+ : this.installedPlugins.find(x => x.name === this.configService.name).installPath;
384
+ await this.getInstalledPlugins();
385
+ const existingPlugin = this.installedPlugins.find(x => x.name === pluginAction.name);
386
+ if (existingPlugin) {
387
+ installPath = existingPlugin.installPath;
388
+ }
298
389
  if (action === 'install' && await this.isPluginBundleAvailable(pluginAction)) {
299
390
  try {
300
391
  await this.doPluginBundleUpdate(pluginAction, client);
@@ -305,6 +396,7 @@ let PluginsService = PluginsService_1 = class PluginsService {
305
396
  }
306
397
  }
307
398
  const installOptions = [];
399
+ let npmPluginLabel = pluginAction.name;
308
400
  if (installPath === this.configService.customPluginPath && await (0, fs_extra_1.pathExists)((0, node_path_1.resolve)(installPath, '../package.json'))) {
309
401
  installOptions.push('--save');
310
402
  }
@@ -312,20 +404,14 @@ let PluginsService = PluginsService_1 = class PluginsService {
312
404
  if (!this.configService.customPluginPath || (0, node_os_1.platform)() === 'win32' || existingPlugin?.globalInstall === true) {
313
405
  installOptions.push('-g');
314
406
  }
315
- const npmPluginLabel = action === 'uninstall' ? pluginAction.name : `${pluginAction.name}@${pluginAction.version}`;
316
- try {
317
- await this.runNpmCommand([...this.npm, action, ...installOptions, npmPluginLabel], installPath, client, pluginAction.termCols, pluginAction.termRows);
318
- await this.ensureCustomPluginDirExists();
319
- return true;
320
- }
321
- catch (e) {
322
- if (pluginAction.name === this.configService.name) {
323
- client.emit('stdout', (0, bash_color_1.yellow)('\r\nCleaning up npm cache, please wait...\r\n'));
324
- await this.cleanNpmCache();
325
- client.emit('stdout', (0, bash_color_1.yellow)(`npm cache cleared, please try updating ${this.configService.name} again.\r\n`));
326
- }
327
- throw e;
407
+ if (action === 'install') {
408
+ installOptions.push('--omit=dev');
409
+ npmPluginLabel = `${pluginAction.name}@${pluginAction.version}`;
328
410
  }
411
+ await this.cleanNpmCache();
412
+ await this.runNpmCommand([...this.npm, action, ...installOptions, npmPluginLabel], installPath, client, pluginAction.termCols, pluginAction.termRows);
413
+ await this.ensureCustomPluginDirExists();
414
+ return true;
329
415
  }
330
416
  async getHomebridgePackage() {
331
417
  if (this.configService.ui.homebridgePackagePath) {
@@ -334,21 +420,21 @@ let PluginsService = PluginsService_1 = class PluginsService {
334
420
  return await this.parsePackageJson(await (0, fs_extra_1.readJson)(pkgJsonPath), this.configService.ui.homebridgePackagePath);
335
421
  }
336
422
  else {
337
- this.logger.error(`"homebridgePath" (${this.configService.ui.homebridgePackagePath}) does not exist`);
423
+ this.logger.error(`The Homebridge path ${this.configService.ui.homebridgePackagePath} does not exist.`);
338
424
  }
339
425
  }
340
426
  const modules = await this.getInstalledModules();
341
427
  const homebridgeInstalls = modules.filter(x => x.name === 'homebridge');
342
428
  if (homebridgeInstalls.length > 1) {
343
- this.logger.warn('Multiple Instances Of Homebridge Found Installed - see https://homebridge.io/w/JJSgm for help.');
429
+ this.logger.warn('Multiple instances of Homebridge were found, see https://homebridge.io/w/JJSgm for help.');
344
430
  homebridgeInstalls.forEach((instance) => {
345
431
  this.logger.warn(instance.installPath);
346
432
  });
347
433
  }
348
434
  if (!homebridgeInstalls.length) {
349
435
  this.configService.hbServiceUiRestartRequired = true;
350
- this.logger.error('Unable To Find Homebridge Installation - see https://homebridge.io/w/JJSgZ for help.');
351
- throw new Error('Unable To Find Homebridge Installation');
436
+ this.logger.error('Unable to find Homebridge installation, see https://homebridge.io/w/JJSgZ for help.');
437
+ throw new Error('Unable To Find Homebridge Installation.');
352
438
  }
353
439
  const homebridgeModule = homebridgeInstalls[0];
354
440
  const pkgJson = await (0, fs_extra_1.readJson)((0, node_path_1.join)(homebridgeModule.installPath, 'package.json'));
@@ -378,6 +464,7 @@ let PluginsService = PluginsService_1 = class PluginsService {
378
464
  }
379
465
  let installPath = homebridge.installPath;
380
466
  const installOptions = [];
467
+ installOptions.push('--omit=dev');
381
468
  if (installPath === this.configService.customPluginPath && await (0, fs_extra_1.pathExists)((0, node_path_1.resolve)(installPath, '../package.json'))) {
382
469
  installOptions.push('--save');
383
470
  }
@@ -416,7 +503,7 @@ let PluginsService = PluginsService_1 = class PluginsService {
416
503
  && pluginAction.name !== this.configService.name
417
504
  && pluginAction.version !== 'latest') {
418
505
  try {
419
- await (0, rxjs_1.firstValueFrom)(this.httpService.head(`https://github.com/homebridge/verified/releases/download/v1.0.0/${pluginAction.name.replace('/', '@')}-${pluginAction.version}.sha256`));
506
+ await (0, rxjs_1.firstValueFrom)(this.httpService.head(`https://github.com/homebridge/plugins/releases/download/v1.0.0/${pluginAction.name.replace('/', '@')}-${pluginAction.version}.sha256`));
420
507
  return true;
421
508
  }
422
509
  catch (e) {
@@ -440,7 +527,7 @@ let PluginsService = PluginsService_1 = class PluginsService {
440
527
  '/var/packages/homebridge/target/app/lib/node_modules',
441
528
  ].includes((0, node_path_1.dirname)(node_process_1.default.env.UIX_BASE_PATH))
442
529
  && pluginAction.name === this.configService.name
443
- && pluginAction.version !== 'latest') {
530
+ && !['latest', 'alpha', 'beta'].includes(pluginAction.version)) {
444
531
  try {
445
532
  try {
446
533
  const withV = `v${pluginAction.version}`;
@@ -454,7 +541,7 @@ let PluginsService = PluginsService_1 = class PluginsService {
454
541
  }
455
542
  }
456
543
  catch (e) {
457
- this.logger.error(`Failed to check for bundled update: ${e.message}`);
544
+ this.logger.error(`Failed to check for bundled update: ${e.message}.`);
458
545
  return '';
459
546
  }
460
547
  }
@@ -494,27 +581,19 @@ let PluginsService = PluginsService_1 = class PluginsService {
494
581
  let configSchema = await (0, fs_extra_1.readJson)(schemaPath);
495
582
  if (configSchema.dynamicSchemaVersion) {
496
583
  const dynamicSchemaPath = (0, node_path_1.resolve)(this.configService.storagePath, `.${pluginName}-v${configSchema.dynamicSchemaVersion}.schema.json`);
497
- this.logger.log(`[${pluginName}] dynamic schema path: ${dynamicSchemaPath}`);
584
+ this.logger.log(`[${pluginName}] dynamic schema path: ${dynamicSchemaPath}.`);
498
585
  if ((0, fs_extra_1.existsSync)(dynamicSchemaPath)) {
499
586
  try {
500
587
  configSchema = await (0, fs_extra_1.readJson)(dynamicSchemaPath);
501
- this.logger.log(`[${pluginName}] dynamic schema loaded from: ${dynamicSchemaPath}`);
588
+ this.logger.log(`[${pluginName}] dynamic schema loaded from ${dynamicSchemaPath}.`);
502
589
  }
503
590
  catch (e) {
504
- this.logger.error(`[${pluginName}] Failed to load dynamic schema at ${dynamicSchemaPath}: ${e.message}`);
591
+ this.logger.error(`[${pluginName}] failed to load dynamic schema from ${dynamicSchemaPath} as ${e.message}.`);
505
592
  }
506
593
  }
507
594
  }
508
595
  if (pluginName === this.configService.name) {
509
596
  configSchema.schema.properties.port.default = this.configService.ui.port;
510
- if (this.configService.serviceMode) {
511
- configSchema.layout = configSchema.layout.filter((x) => {
512
- return x.ref !== 'log';
513
- });
514
- configSchema.layout = configSchema.layout.filter((x) => {
515
- return !(x === 'sudo' || x.key === 'restart');
516
- });
517
- }
518
597
  }
519
598
  if (pluginName === 'homebridge-alexa') {
520
599
  configSchema.schema.properties.pin.default = this.configService.homebridgeConfig.bridge.pin;
@@ -595,57 +674,83 @@ let PluginsService = PluginsService_1 = class PluginsService {
595
674
  }
596
675
  }
597
676
  async getPluginRelease(pluginName) {
598
- if (!this.installedPlugins) {
599
- await this.getInstalledPlugins();
600
- }
601
- const plugin = pluginName === 'homebridge' ? await this.getHomebridgePackage() : this.installedPlugins.find(x => x.name === pluginName);
602
- if (!plugin) {
603
- throw new common_1.NotFoundException();
677
+ let latestVersion = null;
678
+ try {
679
+ const pkg = (await (0, rxjs_1.firstValueFrom)((this.httpService.get(`https://registry.npmjs.org/${encodeURIComponent(pluginName).replace(/%40/g, '@')}`)))).data;
680
+ latestVersion = pkg['dist-tags'] ? pkg['dist-tags'].latest : null;
604
681
  }
605
- if (!plugin.links.homepage && !plugin.links.bugs) {
682
+ catch (e) {
606
683
  throw new common_1.NotFoundException();
607
684
  }
608
- const repoMatch = plugin.links.homepage?.match(/https:\/\/github.com\/([^/]+)\/([^/#]+)/);
609
- const bugsMatch = plugin.links.bugs?.match(/https:\/\/github.com\/([^/]+)\/([^/#]+)/);
610
- let match = repoMatch;
611
- if (!repoMatch) {
612
- if (!bugsMatch) {
613
- throw new common_1.NotFoundException();
685
+ switch (pluginName) {
686
+ case 'homebridge':
687
+ case 'homebridge-config-ui-x': {
688
+ try {
689
+ const release = await (0, rxjs_1.firstValueFrom)(this.httpService.get(`https://api.github.com/repos/homebridge/${pluginName}/releases/latest`));
690
+ const tags = await (0, rxjs_1.firstValueFrom)(this.httpService.get(`https://api.github.com/repos/homebridge/${pluginName}/tags`));
691
+ const changelog = await (0, rxjs_1.firstValueFrom)(this.httpService.get(`https://raw.githubusercontent.com/homebridge/${pluginName}/refs/tags/${tags.data[0].name}/CHANGELOG.md`));
692
+ return {
693
+ name: release.data.name,
694
+ notes: release.data.body,
695
+ changelog: changelog.data,
696
+ latestVersion,
697
+ };
698
+ }
699
+ catch {
700
+ return {
701
+ name: null,
702
+ notes: null,
703
+ changelog: null,
704
+ latestVersion,
705
+ };
706
+ }
614
707
  }
615
- match = bugsMatch;
616
- }
617
- const version = (0, semver_1.parse)(plugin.latestVersion);
618
- const tag = version.prerelease[0]?.toString();
619
- if (tag) {
620
- let branch;
621
- if (['homebridge-config-ui-x', 'homebridge'].includes(plugin.name)) {
708
+ default: {
709
+ await this.getInstalledPlugins();
710
+ const plugin = this.installedPlugins.find(x => x.name === pluginName);
711
+ if (!plugin) {
712
+ throw new common_1.NotFoundException();
713
+ }
714
+ if (!plugin.links.homepage && !plugin.links.bugs) {
715
+ throw new common_1.NotFoundException();
716
+ }
717
+ const repoMatch = plugin.links.homepage?.match(/https:\/\/github.com\/([^/]+)\/([^/#]+)/);
718
+ const bugsMatch = plugin.links.bugs?.match(/https:\/\/github.com\/([^/]+)\/([^/#]+)/);
719
+ let match = repoMatch;
720
+ if (!repoMatch) {
721
+ if (!bugsMatch) {
722
+ throw new common_1.NotFoundException();
723
+ }
724
+ match = bugsMatch;
725
+ }
622
726
  try {
623
- branch = (await (0, rxjs_1.firstValueFrom)(this.httpService.get(`https://api.github.com/repos/homebridge/${plugin.name}/branches`)))
624
- .data
625
- .find((b) => b.name.startsWith(`${tag}-`))
626
- ?.name;
727
+ const release = await (0, rxjs_1.firstValueFrom)(this.httpService.get(`https://api.github.com/repos/${match[1]}/${match[2]}/releases/latest`));
728
+ const latestTag = release.data.tag_name;
729
+ const isReleaseMatch = latestVersion?.replace(/[^0-9.]/g, '').includes(release.data.tag_name?.replace(/[^0-9.]/g, ''));
730
+ const changelogPath = this.pluginChangelogs[pluginName] || '';
731
+ let changelogData = null;
732
+ try {
733
+ const changelog = await (0, rxjs_1.firstValueFrom)(this.httpService.get(`https://raw.githubusercontent.com/${match[1]}/${match[2]}/refs/tags/${latestTag}/${changelogPath}CHANGELOG.md`));
734
+ changelogData = changelog.data;
735
+ }
736
+ catch {
737
+ try {
738
+ const changelog = (await (0, rxjs_1.firstValueFrom)(this.httpService.get(`https://raw.githubusercontent.com/${match[1]}/${match[2]}/refs/tags/${latestTag}/${changelogPath}changelog.md`))).data;
739
+ changelogData = changelog.data;
740
+ }
741
+ catch { }
742
+ }
743
+ return {
744
+ name: isReleaseMatch && release.data.tag_name ? release.data.tag_name : null,
745
+ notes: isReleaseMatch && release.data.body ? release.data.body : null,
746
+ changelog: changelogData,
747
+ latestVersion,
748
+ };
627
749
  }
628
750
  catch (e) {
629
- this.logger.error(`Failed to get list of branches from GitHub: ${e.message}`);
751
+ throw new common_1.NotFoundException();
630
752
  }
631
753
  }
632
- return {
633
- name: `v${plugin.latestVersion}`,
634
- changelog: `Thank you for helping improve ${plugin.displayName || `\`${plugin.name}\``} by testing a beta version.\n\n`
635
- + 'You can use the Homebridge UI at any time to revert back to the stable version.\n\n'
636
- + `Please remember this **${tag}** version is a pre-release, and report any issues to the GitHub repository page:\n`
637
- + `- https://github.com/${repoMatch[1]}/${repoMatch[2]}/issues${branch ? `\n\nSee the commit history for recent changes:\n- https://github.com/${repoMatch[1]}/${repoMatch[2]}/commits/${branch}` : ''}`,
638
- };
639
- }
640
- try {
641
- const release = (await (0, rxjs_1.firstValueFrom)(this.httpService.get(`https://api.github.com/repos/${match[1]}/${match[2]}/releases/latest`))).data;
642
- return {
643
- name: release.name,
644
- changelog: release.body,
645
- };
646
- }
647
- catch (e) {
648
- throw new common_1.NotFoundException();
649
754
  }
650
755
  }
651
756
  async getPluginAlias(pluginName) {
@@ -696,7 +801,7 @@ let PluginsService = PluginsService_1 = class PluginsService {
696
801
  });
697
802
  }
698
803
  catch (e) {
699
- this.logger.debug('Failed to extract plugin alias:', e);
804
+ this.logger.debug(`Failed to extract ${pluginName} plugin alias as ${e.message}.`);
700
805
  if (this.pluginAliasHints[pluginName]) {
701
806
  output.pluginAlias = this.pluginAliasHints[pluginName].pluginAlias;
702
807
  output.pluginType = this.pluginAliasHints[pluginName].pluginType;
@@ -788,7 +893,7 @@ let PluginsService = PluginsService_1 = class PluginsService {
788
893
  }
789
894
  }
790
895
  catch (e) {
791
- this.logger.log(`Failed to parse item "${module}" in ${requiredPath}: ${e.message}`);
896
+ this.logger.log(`Failed to parse ${module} in ${requiredPath} as ${e.message}.`);
792
897
  }
793
898
  }
794
899
  }
@@ -824,8 +929,8 @@ let PluginsService = PluginsService_1 = class PluginsService {
824
929
  return [windowsNpmPath[0]];
825
930
  }
826
931
  else {
827
- this.logger.error('ERROR: Cannot find npm binary. You will not be able to manage plugins or update homebridge.');
828
- this.logger.error('ERROR: You might be able to fix this problem by running: npm install -g npm');
932
+ this.logger.error('Cannot find npm binary, you will not be able to manage plugins or update Homebridge. You might be able to fix this problem by running:');
933
+ this.logger.error('npm install -g npm');
829
934
  }
830
935
  }
831
936
  return ['npm'];
@@ -876,8 +981,8 @@ let PluginsService = PluginsService_1 = class PluginsService {
876
981
  async parsePackageJson(pkgJson, installPath) {
877
982
  const plugin = {
878
983
  name: pkgJson.name,
984
+ displayName: pkgJson.displayName || this.pluginNames[pkgJson.name],
879
985
  private: pkgJson.private || false,
880
- displayName: pkgJson.displayName,
881
986
  description: (pkgJson.description)
882
987
  ? pkgJson.description.replace(/(?:https?|ftp):\/\/[\n\S]+/g, '').trim()
883
988
  : pkgJson.name,
@@ -886,7 +991,7 @@ let PluginsService = PluginsService_1 = class PluginsService {
886
991
  icon: this.pluginIcons[pkgJson.name]
887
992
  ? `${this.pluginListUrl}${this.pluginIcons[pkgJson.name]}`
888
993
  : null,
889
- isHbScoped: !!this.scopedPlugins[pkgJson.name],
994
+ isHbScoped: pkgJson.name.startsWith('@homebridge-plugins/'),
890
995
  newHbScope: this.newScopePlugins[pkgJson.name],
891
996
  isHbMaintained: this.maintainedPlugins.includes(pkgJson.name),
892
997
  installedVersion: installPath ? (pkgJson.version || '0.0.1') : null,
@@ -938,12 +1043,12 @@ let PluginsService = PluginsService_1 = class PluginsService {
938
1043
  homepage: pkg.homepage,
939
1044
  bugs: typeof pkg.bugs === 'object' && pkg.bugs?.url ? pkg.bugs.url : null,
940
1045
  };
941
- plugin.author = this.scopedPlugins[pkg.name]
1046
+ plugin.author = this.pluginAuthors[pkg.name]
942
1047
  || ((pkg.maintainers && pkg.maintainers.length) ? pkg.maintainers[0].name : null);
943
1048
  }
944
1049
  catch (e) {
945
1050
  if (e.response?.status !== 404) {
946
- this.logger.log(`[${plugin.name}] Failed to check registry.npmjs.org for updates: "${e.message}" - see https://homebridge.io/w/JJSz6 for help.`);
1051
+ this.logger.log(`[${plugin.name}] failed to check registry.npmjs.org for updates (see https://homebridge.io/w/JJSz6 for help) as ${e.message}.`);
947
1052
  }
948
1053
  plugin.publicPackage = false;
949
1054
  plugin.latestVersion = null;
@@ -970,18 +1075,25 @@ let PluginsService = PluginsService_1 = class PluginsService {
970
1075
  command.unshift('sudo', '-E', '-n');
971
1076
  }
972
1077
  else {
1078
+ let npmInstallPath;
973
1079
  try {
974
- await (0, fs_extra_1.access)((0, node_path_1.resolve)(cwd, 'node_modules'), fs_extra_1.constants.W_OK);
1080
+ npmInstallPath = (0, node_child_process_1.execSync)('npm root -g').toString().trim();
1081
+ }
1082
+ catch (e) {
1083
+ npmInstallPath = (0, node_path_1.resolve)(cwd, 'node_modules');
1084
+ }
1085
+ try {
1086
+ await (0, fs_extra_1.access)(npmInstallPath, fs_extra_1.constants.W_OK);
975
1087
  }
976
1088
  catch (e) {
977
1089
  client.emit('stdout', (0, bash_color_1.yellow)(`The user "${(0, node_os_1.userInfo)().username}" does not have write access to the target directory:\n\r\n\r`));
978
- client.emit('stdout', `${(0, node_path_1.resolve)(cwd, 'node_modules')}\n\r\n\r`);
1090
+ client.emit('stdout', `${npmInstallPath}\n\r\n\r`);
979
1091
  client.emit('stdout', (0, bash_color_1.yellow)('This may cause the operation to fail.\n\r'));
980
1092
  client.emit('stdout', (0, bash_color_1.yellow)('See the docs for details on how to enable sudo mode:\n\r'));
981
1093
  client.emit('stdout', (0, bash_color_1.yellow)('https://github.com/homebridge/homebridge-config-ui-x/wiki/Manual-Configuration#sudo-mode\n\r\n\r'));
982
1094
  }
983
1095
  }
984
- this.logger.log(`Running Command: ${command.join(' ')}`);
1096
+ this.logger.log(`Running command ${command.join(' ')}.`);
985
1097
  if (!(0, semver_1.satisfies)(node_process_1.default.version, `>=${this.configService.minimumNodeVersion}`)) {
986
1098
  client.emit('stdout', (0, bash_color_1.yellow)(`Node.js v${this.configService.minimumNodeVersion} higher is required for ${this.configService.name}.\n\r`));
987
1099
  client.emit('stdout', (0, bash_color_1.yellow)(`You may experience issues while running on Node.js ${node_process_1.default.version}.\n\r\n\r`));
@@ -1042,13 +1154,12 @@ let PluginsService = PluginsService_1 = class PluginsService {
1042
1154
  return;
1043
1155
  }
1044
1156
  if (!await (0, fs_extra_1.pathExists)(this.configService.customPluginPath)) {
1045
- this.logger.warn(`Custom plugin directory was removed. Re-creating: ${this.configService.customPluginPath}`);
1157
+ this.logger.warn(`Custom plugin directory was removed, re-creating ${this.configService.customPluginPath}.`);
1046
1158
  try {
1047
1159
  await (0, fs_extra_1.ensureDir)(this.configService.customPluginPath);
1048
1160
  }
1049
1161
  catch (e) {
1050
- this.logger.error('Failed to recreate custom plugin directory');
1051
- this.logger.error(e.message);
1162
+ this.logger.error(`Failed to re-create custom plugin directory as ${e.message}.`);
1052
1163
  }
1053
1164
  }
1054
1165
  }
@@ -1063,7 +1174,7 @@ let PluginsService = PluginsService_1 = class PluginsService {
1063
1174
  }
1064
1175
  }
1065
1176
  catch (e) {
1066
- this.logger.error(`Failed to remove ${offendingPath}`, e.message);
1177
+ this.logger.error(`Failed to remove ${offendingPath} as ${e.message}.`);
1067
1178
  }
1068
1179
  }
1069
1180
  async cleanNpmCache() {
@@ -1072,9 +1183,9 @@ let PluginsService = PluginsService_1 = class PluginsService {
1072
1183
  command.unshift('sudo', '-E', '-n');
1073
1184
  }
1074
1185
  return new Promise((res) => {
1075
- const child = (0, node_child_process_1.spawn)(command.shift(), command);
1186
+ const child = (0, node_child_process_1.spawn)(command.shift(), command, { shell: true });
1076
1187
  child.on('exit', (code) => {
1077
- this.logger.log('npm cache clear command executed with exit code', code);
1188
+ this.logger.log(`Executed npm cache clear command with exit code ${code}.`);
1078
1189
  res(null);
1079
1190
  });
1080
1191
  child.on('error', () => {
@@ -1093,36 +1204,44 @@ let PluginsService = PluginsService_1 = class PluginsService {
1093
1204
  this.pluginIcons = {};
1094
1205
  this.hiddenPlugins = [];
1095
1206
  this.maintainedPlugins = [];
1096
- this.scopedPlugins = {};
1207
+ this.pluginAuthors = {};
1208
+ this.pluginNames = {};
1209
+ this.pluginChangelogs = {};
1097
1210
  this.newScopePlugins = {};
1098
1211
  Object.keys(pluginListData).forEach((key) => {
1099
1212
  const plugin = pluginListData[key];
1100
- if (plugin.icon) {
1101
- this.pluginIcons[key] = `icons/${plugin.icon}.png`;
1213
+ if (plugin.i) {
1214
+ this.pluginIcons[key] = `icons/${plugin.i}.png`;
1102
1215
  }
1103
- if (plugin.hidden) {
1216
+ if (plugin.h) {
1104
1217
  this.hiddenPlugins.push(key);
1105
1218
  }
1106
- if (plugin.maintained) {
1219
+ if (plugin.m) {
1107
1220
  this.maintainedPlugins.push(key);
1108
1221
  }
1109
- if (plugin.scoped) {
1110
- this.scopedPlugins[key] = plugin.scoped;
1222
+ if (plugin.a) {
1223
+ this.pluginAuthors[key] = plugin.a;
1224
+ }
1225
+ if (plugin.n) {
1226
+ this.pluginNames[key] = plugin.n;
1111
1227
  }
1112
- if (plugin.newScope) {
1113
- this.newScopePlugins[key] = plugin.newScope;
1228
+ if (plugin.s) {
1229
+ this.newScopePlugins[key] = plugin.s;
1114
1230
  }
1115
- if (plugin.verified) {
1231
+ if (plugin.v) {
1116
1232
  this.verifiedPlugins.push(key);
1117
1233
  }
1118
- if (plugin.verifiedPlus) {
1234
+ if (plugin.p) {
1119
1235
  this.verifiedPlusPlugins.push(key);
1120
1236
  }
1237
+ if (plugin.c) {
1238
+ this.pluginChangelogs[key] = plugin.c;
1239
+ }
1121
1240
  });
1122
1241
  }
1123
1242
  catch (e) {
1124
1243
  this.pluginListRetryTimeout = setTimeout(() => this.loadPluginList(), 60000);
1125
- this.logger.debug('Error when trying to get github plugin list:', e.message);
1244
+ this.logger.debug(`Could not obtain plugin list from plugins repo as ${e.message}.`);
1126
1245
  }
1127
1246
  }
1128
1247
  };