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

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 (320) hide show
  1. package/CHANGELOG.md +479 -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 +31 -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 +46 -11
  22. package/dist/core/config/config.service.js +57 -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 +2 -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.js +7 -7
  37. package/dist/modules/backup/backup.controller.js.map +1 -1
  38. package/dist/modules/backup/backup.service.js +19 -79
  39. package/dist/modules/backup/backup.service.js.map +1 -1
  40. package/dist/modules/child-bridges/child-bridges.service.js +0 -7
  41. package/dist/modules/child-bridges/child-bridges.service.js.map +1 -1
  42. package/dist/modules/config-editor/config-editor.controller.d.ts +7 -1
  43. package/dist/modules/config-editor/config-editor.controller.js +36 -8
  44. package/dist/modules/config-editor/config-editor.controller.js.map +1 -1
  45. package/dist/modules/config-editor/config-editor.service.d.ts +5 -1
  46. package/dist/modules/config-editor/config-editor.service.js +133 -69
  47. package/dist/modules/config-editor/config-editor.service.js.map +1 -1
  48. package/dist/modules/custom-plugins/plugins-settings-ui/plugins-settings-ui.service.d.ts +1 -1
  49. package/dist/modules/custom-plugins/plugins-settings-ui/plugins-settings-ui.service.js +13 -11
  50. package/dist/modules/custom-plugins/plugins-settings-ui/plugins-settings-ui.service.js.map +1 -1
  51. package/dist/modules/log/log.gateway.d.ts +2 -1
  52. package/dist/modules/log/log.gateway.js.map +1 -1
  53. package/dist/modules/log/log.service.js +3 -3
  54. package/dist/modules/log/log.service.js.map +1 -1
  55. package/dist/modules/platform-tools/docker/docker.controller.js +3 -3
  56. package/dist/modules/platform-tools/docker/docker.controller.js.map +1 -1
  57. package/dist/modules/platform-tools/docker/docker.service.js +1 -1
  58. package/dist/modules/platform-tools/docker/docker.service.js.map +1 -1
  59. package/dist/modules/platform-tools/hb-service/hb-service.controller.js +3 -3
  60. package/dist/modules/platform-tools/hb-service/hb-service.controller.js.map +1 -1
  61. package/dist/modules/platform-tools/hb-service/hb-service.service.js +6 -6
  62. package/dist/modules/platform-tools/hb-service/hb-service.service.js.map +1 -1
  63. package/dist/modules/platform-tools/linux/linux.controller.js +2 -2
  64. package/dist/modules/platform-tools/linux/linux.controller.js.map +1 -1
  65. package/dist/modules/platform-tools/linux/linux.service.js +2 -2
  66. package/dist/modules/platform-tools/linux/linux.service.js.map +1 -1
  67. package/dist/modules/platform-tools/terminal/terminal.gateway.d.ts +2 -1
  68. package/dist/modules/platform-tools/terminal/terminal.gateway.js.map +1 -1
  69. package/dist/modules/platform-tools/terminal/terminal.service.js +2 -2
  70. package/dist/modules/platform-tools/terminal/terminal.service.js.map +1 -1
  71. package/dist/modules/plugins/plugins.controller.d.ts +2 -0
  72. package/dist/modules/plugins/plugins.controller.js +5 -5
  73. package/dist/modules/plugins/plugins.controller.js.map +1 -1
  74. package/dist/modules/plugins/plugins.service.d.ts +10 -1
  75. package/dist/modules/plugins/plugins.service.js +285 -168
  76. package/dist/modules/plugins/plugins.service.js.map +1 -1
  77. package/dist/modules/server/server.controller.d.ts +33 -3
  78. package/dist/modules/server/server.controller.js +162 -20
  79. package/dist/modules/server/server.controller.js.map +1 -1
  80. package/dist/modules/server/server.service.d.ts +35 -6
  81. package/dist/modules/server/server.service.js +286 -66
  82. package/dist/modules/server/server.service.js.map +1 -1
  83. package/dist/modules/status/status.controller.d.ts +0 -1
  84. package/dist/modules/status/status.controller.js +3 -4
  85. package/dist/modules/status/status.controller.js.map +1 -1
  86. package/dist/modules/status/status.gateway.d.ts +1 -1
  87. package/dist/modules/status/status.service.d.ts +1 -1
  88. package/dist/modules/status/status.service.js +25 -41
  89. package/dist/modules/status/status.service.js.map +1 -1
  90. package/dist/modules/users/users.controller.js +7 -7
  91. package/dist/modules/users/users.controller.js.map +1 -1
  92. package/dist/self-check.js +6 -6
  93. package/dist/self-check.js.map +1 -1
  94. package/package.json +50 -43
  95. package/public/3rdpartylicenses.txt +218 -108
  96. package/public/assets/hap-icons/airpurifier.svg +49 -16
  97. package/public/assets/hap-icons/airquality.svg +24 -13
  98. package/public/assets/hap-icons/co-sensor.svg +72 -0
  99. package/public/assets/hap-icons/co2-sensor.svg +72 -0
  100. package/public/assets/hap-icons/contactsensor-closed.svg +35 -2
  101. package/public/assets/hap-icons/contactsensor-open.svg +80 -2
  102. package/public/assets/hap-icons/door-closed.svg +32 -2
  103. package/public/assets/hap-icons/door-open.svg +48 -2
  104. package/public/assets/hap-icons/fan-off.svg +24 -13
  105. package/public/assets/hap-icons/fan-on.svg +24 -13
  106. package/public/assets/hap-icons/filtermaintenance.svg +26 -0
  107. package/public/assets/hap-icons/garagedoor.svg +24 -13
  108. package/public/assets/hap-icons/humidity.svg +24 -13
  109. package/public/assets/hap-icons/irrigation-system.svg +47 -18
  110. package/public/assets/hap-icons/leaksensor.svg +52 -2
  111. package/public/assets/hap-icons/light.svg +47 -28
  112. package/public/assets/hap-icons/lightbulb.svg +24 -13
  113. package/public/assets/hap-icons/lock-locked.svg +24 -13
  114. package/public/assets/hap-icons/lock-unlocked.svg +24 -13
  115. package/public/assets/hap-icons/motionsensor.svg +100 -2
  116. package/public/assets/hap-icons/occupancysensor.svg +97 -2
  117. package/public/assets/hap-icons/outlet.svg +24 -13
  118. package/public/assets/hap-icons/securitysystem-active.svg +102 -2
  119. package/public/assets/hap-icons/securitysystem-off.svg +68 -2
  120. package/public/assets/hap-icons/securitysystem-triggered.svg +103 -0
  121. package/public/assets/hap-icons/smokesensor.svg +42 -9
  122. package/public/assets/hap-icons/speaker.svg +29 -13
  123. package/public/assets/hap-icons/statelessprogrammableswitch.svg +51 -2
  124. package/public/assets/hap-icons/switch.svg +24 -13
  125. package/public/assets/hap-icons/television.svg +15 -4
  126. package/public/assets/hap-icons/temperature.svg +24 -13
  127. package/public/assets/hap-icons/unknown.svg +24 -13
  128. package/public/assets/hap-icons/valve-generic.svg +27 -16
  129. package/public/assets/hap-icons/valve-irrigation.svg +37 -21
  130. package/public/assets/hap-icons/valve-showerhead.svg +52 -0
  131. package/public/assets/hap-icons/valve-waterfaucet.svg +21 -0
  132. package/public/assets/hap-icons/window-closed.svg +85 -2
  133. package/public/assets/hap-icons/window-open.svg +136 -2
  134. package/public/assets/hap-icons/windowcovering-closed.svg +45 -49
  135. package/public/assets/hap-icons/windowcovering-open.svg +40 -44
  136. package/public/assets/homebridge-color-round.svg +36 -1
  137. package/public/assets/homebridge-logo.svg +11 -1
  138. package/public/assets/mask-icon.svg +5 -1
  139. package/public/assets/plugin-ui-utils/ui.js +3 -0
  140. package/public/assets/plugin-ui-utils/ui.js.map +1 -1
  141. package/public/chunk-2FVPRLQG.js +1 -0
  142. package/public/{chunk-MGBLPFXT.js → chunk-2GSALGGZ.js} +1 -1
  143. package/public/chunk-2IJPNUW7.js +29 -0
  144. package/public/chunk-2NQ5FYSH.js +5 -0
  145. package/public/chunk-3FQVTGEO.js +1 -0
  146. package/public/chunk-3Z2VETLJ.js +1 -0
  147. package/public/{chunk-3TUBDO76.js → chunk-4G5TRQV5.js} +1 -1
  148. package/public/chunk-4I6LLOMI.js +1 -0
  149. package/public/chunk-4N5XER24.js +1 -0
  150. package/public/chunk-555LSHXI.js +1 -0
  151. package/public/{chunk-CCUID66K.js → chunk-5CEP4QNR.js} +1 -1
  152. package/public/chunk-5NG7YGKN.js +1 -0
  153. package/public/chunk-6TUL34DU.js +1 -0
  154. package/public/{chunk-3RNRIJ74.js → chunk-6XANA56B.js} +11 -11
  155. package/public/chunk-BOWJ3GNE.js +1 -0
  156. package/public/chunk-BY2CUGGW.js +1 -0
  157. package/public/{chunk-LM6S4A72.js → chunk-CI7ES5EB.js} +1 -1
  158. package/public/chunk-CXAHE4UR.js +1 -0
  159. package/public/chunk-CYTL54HA.js +1 -0
  160. package/public/chunk-D35WUZO6.js +1 -0
  161. package/public/chunk-DM5SJAXR.js +1 -0
  162. package/public/chunk-DX3J7WR4.js +20 -0
  163. package/public/chunk-ELHQENNT.js +1 -0
  164. package/public/chunk-EM4PY2IW.js +2 -0
  165. package/public/chunk-FGR2Q3QC.js +1 -0
  166. package/public/chunk-FQM6JWF4.js +1 -0
  167. package/public/chunk-GD7WE5NM.js +1 -0
  168. package/public/{chunk-6TCHCTXZ.js → chunk-GMQEHLXV.js} +1 -1
  169. package/public/chunk-GSH6XPH5.js +1 -0
  170. package/public/chunk-HTBBDMIM.js +1 -0
  171. package/public/chunk-IMPGSXGE.js +1 -0
  172. package/public/chunk-IZCQX62P.js +1 -0
  173. package/public/chunk-IZENZ6C7.js +1 -0
  174. package/public/{chunk-QE7DO6J3.js → chunk-J764EFYE.js} +2 -2
  175. package/public/chunk-JBQX4JBH.js +1 -0
  176. package/public/chunk-JROQHF7T.js +1 -0
  177. package/public/chunk-JVERWDTV.js +8 -0
  178. package/public/chunk-KAHHKQN4.js +1 -0
  179. package/public/chunk-KFUUWA7X.js +1 -0
  180. package/public/chunk-LSWNZUER.js +1 -0
  181. package/public/chunk-M5VWLNY3.js +7 -0
  182. package/public/chunk-MDSOQRIW.js +1 -0
  183. package/public/chunk-MFBVZAEQ.js +2 -0
  184. package/public/chunk-MGITCDRJ.js +1 -0
  185. package/public/chunk-NQ5I37UV.js +1 -0
  186. package/public/chunk-NQE2RXH6.js +1 -0
  187. package/public/chunk-O3VYSP26.js +1 -0
  188. package/public/chunk-O7GTAWDZ.js +1 -0
  189. package/public/chunk-OR2P7NW7.js +6 -0
  190. package/public/chunk-OT72EUBX.js +1 -0
  191. package/public/chunk-OUNNY3LQ.js +1 -0
  192. package/public/chunk-P5VEHFPQ.js +1 -0
  193. package/public/chunk-QHLEZFHN.js +1 -0
  194. package/public/{chunk-WNWWUCCZ.js → chunk-QJQSHTGR.js} +3 -3
  195. package/public/chunk-QZ7YTQCT.js +1 -0
  196. package/public/chunk-R4C7YVNE.js +1 -0
  197. package/public/chunk-RPX42C27.js +1 -0
  198. package/public/{chunk-JZZQRLNW.js → chunk-S6UXXPGF.js} +1 -1
  199. package/public/chunk-SCRWY2OT.js +1 -0
  200. package/public/chunk-SVBSGSWA.js +1 -0
  201. package/public/chunk-SWUH5RAT.js +1 -0
  202. package/public/chunk-SYDH2EUJ.js +1 -0
  203. package/public/{chunk-EA5J2VEJ.js → chunk-TJJCM4OW.js} +1 -1
  204. package/public/chunk-TNXIENZS.js +1 -0
  205. package/public/{chunk-UPYKPJUD.js → chunk-TY327ZWV.js} +2 -2
  206. package/public/chunk-U3GYVCZ5.js +1 -0
  207. package/public/chunk-UC3VLQ36.js +23 -0
  208. package/public/chunk-UTDF2KPP.js +1 -0
  209. package/public/chunk-UXMF7TL3.js +1 -0
  210. package/public/chunk-VFFTVCSV.js +1 -0
  211. package/public/chunk-VP6YRYDX.js +1 -0
  212. package/public/chunk-W6X46MTW.js +7 -0
  213. package/public/chunk-WP6HJUZ6.js +1 -0
  214. package/public/{chunk-7EUQWCP5.js → chunk-WTTWP6KF.js} +2 -2
  215. package/public/chunk-WUXDJZ6X.js +1 -0
  216. package/public/chunk-WXHERXH5.js +1 -0
  217. package/public/chunk-YLZEJ2HL.js +5 -0
  218. package/public/chunk-YRRMQRGZ.js +1 -0
  219. package/public/chunk-YSWBG5VY.js +8 -0
  220. package/public/chunk-Z7NIAJEB.js +1 -0
  221. package/public/chunk-ZI5BUHBS.js +1 -0
  222. package/public/index.html +2 -2
  223. package/public/main-EWBCXJ6H.js +1 -0
  224. package/public/media/fa-brands-400-Q3XCMWHQ.woff2 +0 -0
  225. package/public/media/{fa-brands-400-KOKGDU7E.ttf → fa-brands-400-R2XQZCET.ttf} +0 -0
  226. package/public/media/fa-regular-400-QSNYFYRT.woff2 +0 -0
  227. package/public/media/{fa-regular-400-IPMAEX5Y.ttf → fa-regular-400-XUOPSR7E.ttf} +0 -0
  228. package/public/media/fa-solid-900-5ZUYHGA7.woff2 +0 -0
  229. package/public/media/{fa-solid-900-SRFFQLRM.ttf → fa-solid-900-PJNKLK6W.ttf} +0 -0
  230. package/public/polyfills-WE4HA5DL.js +2 -0
  231. package/public/styles-SK4HZ2RX.css +1 -0
  232. package/scripts/upgrade-install-plugin.sh +1 -1
  233. package/public/chunk-23E7VL2P.js +0 -1
  234. package/public/chunk-33LJ3YIR.js +0 -1
  235. package/public/chunk-34W4KCBV.js +0 -1
  236. package/public/chunk-3IX3CLER.js +0 -1
  237. package/public/chunk-3UBYWHRR.js +0 -1
  238. package/public/chunk-52HTAWOT.js +0 -1
  239. package/public/chunk-535DPMCW.js +0 -1
  240. package/public/chunk-56BOPXKC.js +0 -1
  241. package/public/chunk-5M6KOARX.js +0 -1
  242. package/public/chunk-6IAVZXBU.js +0 -20
  243. package/public/chunk-6T27YQ2D.js +0 -1
  244. package/public/chunk-7WLTVXXL.js +0 -1
  245. package/public/chunk-AS2OC4NZ.js +0 -1
  246. package/public/chunk-BKUGARB4.js +0 -7
  247. package/public/chunk-BLUX2UMQ.js +0 -1
  248. package/public/chunk-BPMSJ2VF.js +0 -1
  249. package/public/chunk-BW6NZ6VD.js +0 -6
  250. package/public/chunk-C4ENUEJD.js +0 -1
  251. package/public/chunk-C536FAQ7.js +0 -5
  252. package/public/chunk-CL4YMJZW.js +0 -1
  253. package/public/chunk-D7HF5OGR.js +0 -1
  254. package/public/chunk-DD3R2DFS.js +0 -1
  255. package/public/chunk-DHAYGFW2.js +0 -1
  256. package/public/chunk-DVTSZQXS.js +0 -1
  257. package/public/chunk-EIBIFGLP.js +0 -1
  258. package/public/chunk-F4CW25FV.js +0 -1
  259. package/public/chunk-HL7XXKHI.js +0 -1
  260. package/public/chunk-HSJSWZHD.js +0 -1
  261. package/public/chunk-HUV3VJSE.js +0 -1
  262. package/public/chunk-INJABMER.js +0 -23
  263. package/public/chunk-IU27XSWW.js +0 -1
  264. package/public/chunk-JVAHZDVJ.js +0 -1
  265. package/public/chunk-KQJ7ONUG.js +0 -1
  266. package/public/chunk-KS3FIR4R.js +0 -1
  267. package/public/chunk-KXWJIWYD.js +0 -1
  268. package/public/chunk-LDGB6S2N.js +0 -1
  269. package/public/chunk-M45D5CCT.js +0 -1
  270. package/public/chunk-MIMQGXTO.js +0 -1
  271. package/public/chunk-NKN62APE.js +0 -1
  272. package/public/chunk-NW6AFAD7.js +0 -1
  273. package/public/chunk-NZNNTHFQ.js +0 -1
  274. package/public/chunk-ORPWYWCL.js +0 -5
  275. package/public/chunk-Q2YQ4J5L.js +0 -1
  276. package/public/chunk-QHPDGSZ6.js +0 -1
  277. package/public/chunk-QXHB5Q7H.js +0 -1
  278. package/public/chunk-R2QZPN2M.js +0 -1
  279. package/public/chunk-RDOXIKK6.js +0 -1
  280. package/public/chunk-RHBPQJLQ.js +0 -1
  281. package/public/chunk-RSGTDKO6.js +0 -1
  282. package/public/chunk-RTVXC3Y7.js +0 -1
  283. package/public/chunk-SFK7OSIU.js +0 -1
  284. package/public/chunk-SFLEKDGL.js +0 -1
  285. package/public/chunk-T5R3UG6Z.js +0 -1
  286. package/public/chunk-TB7FJ5XX.js +0 -1
  287. package/public/chunk-TCSXGQNF.js +0 -1
  288. package/public/chunk-TRTZHJGG.js +0 -1
  289. package/public/chunk-TWWDGSRW.js +0 -1
  290. package/public/chunk-TXOB7R5K.js +0 -7
  291. package/public/chunk-U2WFZQTC.js +0 -32
  292. package/public/chunk-UG5DK2RQ.js +0 -2
  293. package/public/chunk-UVNEKL77.js +0 -1
  294. package/public/chunk-VHLIKZYD.js +0 -1
  295. package/public/chunk-WHJOLAED.js +0 -1
  296. package/public/chunk-WHJSVGC7.js +0 -1
  297. package/public/chunk-Y62756RF.js +0 -1
  298. package/public/chunk-YA4WNIBX.js +0 -1
  299. package/public/chunk-ZGUIFA2B.js +0 -1
  300. package/public/chunk-ZLOETFRA.js +0 -8
  301. package/public/chunk-ZT23DWNL.js +0 -1
  302. package/public/main-TLKQDE7H.js +0 -1
  303. package/public/media/01-RQ3S2L53.png +0 -0
  304. package/public/media/02-VNCG2I2A.png +0 -0
  305. package/public/media/03-HI42L4ZG.png +0 -0
  306. package/public/media/04-FJLL55LZ.png +0 -0
  307. package/public/media/05-V3EO6SPT.png +0 -0
  308. package/public/media/06-EOJZCQZN.png +0 -0
  309. package/public/media/07-KMKB5PBD.png +0 -0
  310. package/public/media/08-UQJRF6B2.png +0 -0
  311. package/public/media/09-2DJQFRHH.png +0 -0
  312. package/public/media/arrow_left-CQT7FZM7.svg +0 -4
  313. package/public/media/arrow_right-SBUDRR2G.svg +0 -4
  314. package/public/media/fa-brands-400-6PJPV6JM.woff2 +0 -0
  315. package/public/media/fa-regular-400-OHB6J4OK.woff2 +0 -0
  316. package/public/media/fa-solid-900-ABTK6BNK.woff2 +0 -0
  317. package/public/polyfills-C6JHVXJJ.js +0 -2
  318. package/public/scripts-6GVLYD7F.js +0 -62
  319. package/public/styles-EG5MFQEM.css +0 -1
  320. /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,81 @@ 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 changelog = await (0, rxjs_1.firstValueFrom)(this.httpService.get(`https://raw.githubusercontent.com/homebridge/${pluginName}/refs/tags/${release.data.tag_name}/CHANGELOG.md`));
691
+ return {
692
+ name: release.data.name,
693
+ notes: release.data.body,
694
+ changelog: changelog.data,
695
+ latestVersion,
696
+ };
697
+ }
698
+ catch {
699
+ return {
700
+ name: null,
701
+ notes: null,
702
+ changelog: null,
703
+ latestVersion,
704
+ };
705
+ }
614
706
  }
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)) {
707
+ default: {
708
+ await this.getInstalledPlugins();
709
+ const plugin = this.installedPlugins.find(x => x.name === pluginName);
710
+ if (!plugin) {
711
+ throw new common_1.NotFoundException();
712
+ }
713
+ if (!plugin.links.homepage && !plugin.links.bugs) {
714
+ throw new common_1.NotFoundException();
715
+ }
716
+ const repoMatch = plugin.links.homepage?.match(/https:\/\/github.com\/([^/]+)\/([^/#]+)/);
717
+ const bugsMatch = plugin.links.bugs?.match(/https:\/\/github.com\/([^/]+)\/([^/#]+)/);
718
+ let match = repoMatch;
719
+ if (!repoMatch) {
720
+ if (!bugsMatch) {
721
+ throw new common_1.NotFoundException();
722
+ }
723
+ match = bugsMatch;
724
+ }
622
725
  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;
726
+ const release = await (0, rxjs_1.firstValueFrom)(this.httpService.get(`https://api.github.com/repos/${match[1]}/${match[2]}/releases/latest`));
727
+ const latestTag = release.data.tag_name;
728
+ const changelogPath = this.pluginChangelogs[pluginName] || '';
729
+ let changelogData = null;
730
+ try {
731
+ const changelog = await (0, rxjs_1.firstValueFrom)(this.httpService.get(`https://raw.githubusercontent.com/${match[1]}/${match[2]}/refs/tags/${latestTag}/${changelogPath}CHANGELOG.md`));
732
+ changelogData = changelog.data;
733
+ }
734
+ catch {
735
+ try {
736
+ 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;
737
+ changelogData = changelog.data;
738
+ }
739
+ catch { }
740
+ }
741
+ return {
742
+ name: release.data.name || null,
743
+ notes: release.data.body || null,
744
+ changelog: changelogData,
745
+ latestVersion,
746
+ };
627
747
  }
628
748
  catch (e) {
629
- this.logger.error(`Failed to get list of branches from GitHub: ${e.message}`);
749
+ throw new common_1.NotFoundException();
630
750
  }
631
751
  }
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
752
  }
650
753
  }
651
754
  async getPluginAlias(pluginName) {
@@ -696,7 +799,7 @@ let PluginsService = PluginsService_1 = class PluginsService {
696
799
  });
697
800
  }
698
801
  catch (e) {
699
- this.logger.debug('Failed to extract plugin alias:', e);
802
+ this.logger.debug(`Failed to extract ${pluginName} plugin alias as ${e.message}.`);
700
803
  if (this.pluginAliasHints[pluginName]) {
701
804
  output.pluginAlias = this.pluginAliasHints[pluginName].pluginAlias;
702
805
  output.pluginType = this.pluginAliasHints[pluginName].pluginType;
@@ -788,7 +891,7 @@ let PluginsService = PluginsService_1 = class PluginsService {
788
891
  }
789
892
  }
790
893
  catch (e) {
791
- this.logger.log(`Failed to parse item "${module}" in ${requiredPath}: ${e.message}`);
894
+ this.logger.log(`Failed to parse ${module} in ${requiredPath} as ${e.message}.`);
792
895
  }
793
896
  }
794
897
  }
@@ -824,8 +927,8 @@ let PluginsService = PluginsService_1 = class PluginsService {
824
927
  return [windowsNpmPath[0]];
825
928
  }
826
929
  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');
930
+ 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:');
931
+ this.logger.error('npm install -g npm');
829
932
  }
830
933
  }
831
934
  return ['npm'];
@@ -876,8 +979,8 @@ let PluginsService = PluginsService_1 = class PluginsService {
876
979
  async parsePackageJson(pkgJson, installPath) {
877
980
  const plugin = {
878
981
  name: pkgJson.name,
982
+ displayName: pkgJson.displayName || this.pluginNames[pkgJson.name],
879
983
  private: pkgJson.private || false,
880
- displayName: pkgJson.displayName,
881
984
  description: (pkgJson.description)
882
985
  ? pkgJson.description.replace(/(?:https?|ftp):\/\/[\n\S]+/g, '').trim()
883
986
  : pkgJson.name,
@@ -886,7 +989,7 @@ let PluginsService = PluginsService_1 = class PluginsService {
886
989
  icon: this.pluginIcons[pkgJson.name]
887
990
  ? `${this.pluginListUrl}${this.pluginIcons[pkgJson.name]}`
888
991
  : null,
889
- isHbScoped: !!this.scopedPlugins[pkgJson.name],
992
+ isHbScoped: pkgJson.name.startsWith('@homebridge-plugins/'),
890
993
  newHbScope: this.newScopePlugins[pkgJson.name],
891
994
  isHbMaintained: this.maintainedPlugins.includes(pkgJson.name),
892
995
  installedVersion: installPath ? (pkgJson.version || '0.0.1') : null,
@@ -938,12 +1041,12 @@ let PluginsService = PluginsService_1 = class PluginsService {
938
1041
  homepage: pkg.homepage,
939
1042
  bugs: typeof pkg.bugs === 'object' && pkg.bugs?.url ? pkg.bugs.url : null,
940
1043
  };
941
- plugin.author = this.scopedPlugins[pkg.name]
1044
+ plugin.author = this.pluginAuthors[pkg.name]
942
1045
  || ((pkg.maintainers && pkg.maintainers.length) ? pkg.maintainers[0].name : null);
943
1046
  }
944
1047
  catch (e) {
945
1048
  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.`);
1049
+ 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
1050
  }
948
1051
  plugin.publicPackage = false;
949
1052
  plugin.latestVersion = null;
@@ -970,18 +1073,25 @@ let PluginsService = PluginsService_1 = class PluginsService {
970
1073
  command.unshift('sudo', '-E', '-n');
971
1074
  }
972
1075
  else {
1076
+ let npmInstallPath;
973
1077
  try {
974
- await (0, fs_extra_1.access)((0, node_path_1.resolve)(cwd, 'node_modules'), fs_extra_1.constants.W_OK);
1078
+ npmInstallPath = (0, node_child_process_1.execSync)('npm root -g').toString().trim();
1079
+ }
1080
+ catch (e) {
1081
+ npmInstallPath = (0, node_path_1.resolve)(cwd, 'node_modules');
1082
+ }
1083
+ try {
1084
+ await (0, fs_extra_1.access)(npmInstallPath, fs_extra_1.constants.W_OK);
975
1085
  }
976
1086
  catch (e) {
977
1087
  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`);
1088
+ client.emit('stdout', `${npmInstallPath}\n\r\n\r`);
979
1089
  client.emit('stdout', (0, bash_color_1.yellow)('This may cause the operation to fail.\n\r'));
980
1090
  client.emit('stdout', (0, bash_color_1.yellow)('See the docs for details on how to enable sudo mode:\n\r'));
981
1091
  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
1092
  }
983
1093
  }
984
- this.logger.log(`Running Command: ${command.join(' ')}`);
1094
+ this.logger.log(`Running command ${command.join(' ')}.`);
985
1095
  if (!(0, semver_1.satisfies)(node_process_1.default.version, `>=${this.configService.minimumNodeVersion}`)) {
986
1096
  client.emit('stdout', (0, bash_color_1.yellow)(`Node.js v${this.configService.minimumNodeVersion} higher is required for ${this.configService.name}.\n\r`));
987
1097
  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 +1152,12 @@ let PluginsService = PluginsService_1 = class PluginsService {
1042
1152
  return;
1043
1153
  }
1044
1154
  if (!await (0, fs_extra_1.pathExists)(this.configService.customPluginPath)) {
1045
- this.logger.warn(`Custom plugin directory was removed. Re-creating: ${this.configService.customPluginPath}`);
1155
+ this.logger.warn(`Custom plugin directory was removed, re-creating ${this.configService.customPluginPath}.`);
1046
1156
  try {
1047
1157
  await (0, fs_extra_1.ensureDir)(this.configService.customPluginPath);
1048
1158
  }
1049
1159
  catch (e) {
1050
- this.logger.error('Failed to recreate custom plugin directory');
1051
- this.logger.error(e.message);
1160
+ this.logger.error(`Failed to re-create custom plugin directory as ${e.message}.`);
1052
1161
  }
1053
1162
  }
1054
1163
  }
@@ -1063,7 +1172,7 @@ let PluginsService = PluginsService_1 = class PluginsService {
1063
1172
  }
1064
1173
  }
1065
1174
  catch (e) {
1066
- this.logger.error(`Failed to remove ${offendingPath}`, e.message);
1175
+ this.logger.error(`Failed to remove ${offendingPath} as ${e.message}.`);
1067
1176
  }
1068
1177
  }
1069
1178
  async cleanNpmCache() {
@@ -1072,9 +1181,9 @@ let PluginsService = PluginsService_1 = class PluginsService {
1072
1181
  command.unshift('sudo', '-E', '-n');
1073
1182
  }
1074
1183
  return new Promise((res) => {
1075
- const child = (0, node_child_process_1.spawn)(command.shift(), command);
1184
+ const child = (0, node_child_process_1.spawn)(command.shift(), command, { shell: true });
1076
1185
  child.on('exit', (code) => {
1077
- this.logger.log('npm cache clear command executed with exit code', code);
1186
+ this.logger.log(`Executed npm cache clear command with exit code ${code}.`);
1078
1187
  res(null);
1079
1188
  });
1080
1189
  child.on('error', () => {
@@ -1093,36 +1202,44 @@ let PluginsService = PluginsService_1 = class PluginsService {
1093
1202
  this.pluginIcons = {};
1094
1203
  this.hiddenPlugins = [];
1095
1204
  this.maintainedPlugins = [];
1096
- this.scopedPlugins = {};
1205
+ this.pluginAuthors = {};
1206
+ this.pluginNames = {};
1207
+ this.pluginChangelogs = {};
1097
1208
  this.newScopePlugins = {};
1098
1209
  Object.keys(pluginListData).forEach((key) => {
1099
1210
  const plugin = pluginListData[key];
1100
- if (plugin.icon) {
1101
- this.pluginIcons[key] = `icons/${plugin.icon}.png`;
1211
+ if (plugin.i) {
1212
+ this.pluginIcons[key] = `icons/${plugin.i}.png`;
1102
1213
  }
1103
- if (plugin.hidden) {
1214
+ if (plugin.h) {
1104
1215
  this.hiddenPlugins.push(key);
1105
1216
  }
1106
- if (plugin.maintained) {
1217
+ if (plugin.m) {
1107
1218
  this.maintainedPlugins.push(key);
1108
1219
  }
1109
- if (plugin.scoped) {
1110
- this.scopedPlugins[key] = plugin.scoped;
1220
+ if (plugin.a) {
1221
+ this.pluginAuthors[key] = plugin.a;
1222
+ }
1223
+ if (plugin.n) {
1224
+ this.pluginNames[key] = plugin.n;
1111
1225
  }
1112
- if (plugin.newScope) {
1113
- this.newScopePlugins[key] = plugin.newScope;
1226
+ if (plugin.s) {
1227
+ this.newScopePlugins[key] = plugin.s;
1114
1228
  }
1115
- if (plugin.verified) {
1229
+ if (plugin.v) {
1116
1230
  this.verifiedPlugins.push(key);
1117
1231
  }
1118
- if (plugin.verifiedPlus) {
1232
+ if (plugin.p) {
1119
1233
  this.verifiedPlusPlugins.push(key);
1120
1234
  }
1235
+ if (plugin.c) {
1236
+ this.pluginChangelogs[key] = plugin.c;
1237
+ }
1121
1238
  });
1122
1239
  }
1123
1240
  catch (e) {
1124
1241
  this.pluginListRetryTimeout = setTimeout(() => this.loadPluginList(), 60000);
1125
- this.logger.debug('Error when trying to get github plugin list:', e.message);
1242
+ this.logger.debug(`Could not obtain plugin list from plugins repo as ${e.message}.`);
1126
1243
  }
1127
1244
  }
1128
1245
  };