matterbridge 3.0.7-dev-20250618-fb768ee → 3.0.7

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 (188) hide show
  1. package/CHANGELOG.md +3 -2
  2. package/README-DEV.md +4 -4
  3. package/dist/cli.d.ts +29 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +62 -2
  6. package/dist/cli.js.map +1 -0
  7. package/dist/clusters/export.d.ts +2 -0
  8. package/dist/clusters/export.d.ts.map +1 -0
  9. package/dist/clusters/export.js +2 -0
  10. package/dist/clusters/export.js.map +1 -0
  11. package/dist/defaultConfigSchema.d.ts +27 -0
  12. package/dist/defaultConfigSchema.d.ts.map +1 -0
  13. package/dist/defaultConfigSchema.js +23 -0
  14. package/dist/defaultConfigSchema.js.map +1 -0
  15. package/dist/deviceManager.d.ts +114 -0
  16. package/dist/deviceManager.d.ts.map +1 -0
  17. package/dist/deviceManager.js +94 -1
  18. package/dist/deviceManager.js.map +1 -0
  19. package/dist/devices/export.d.ts +5 -0
  20. package/dist/devices/export.d.ts.map +1 -0
  21. package/dist/devices/export.js +2 -0
  22. package/dist/devices/export.js.map +1 -0
  23. package/dist/evse.d.ts +67 -0
  24. package/dist/evse.d.ts.map +1 -0
  25. package/dist/evse.js +65 -9
  26. package/dist/evse.js.map +1 -0
  27. package/dist/frontend.d.ts +256 -0
  28. package/dist/frontend.d.ts.map +1 -0
  29. package/dist/frontend.js +374 -16
  30. package/dist/frontend.js.map +1 -0
  31. package/dist/globalMatterbridge.d.ts +32 -0
  32. package/dist/globalMatterbridge.d.ts.map +1 -0
  33. package/dist/globalMatterbridge.js +20 -0
  34. package/dist/globalMatterbridge.js.map +1 -0
  35. package/dist/helpers.d.ts +47 -0
  36. package/dist/helpers.d.ts.map +1 -0
  37. package/dist/helpers.js +51 -0
  38. package/dist/helpers.js.map +1 -0
  39. package/dist/index.d.ts +37 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +28 -1
  42. package/dist/index.js.map +1 -0
  43. package/dist/laundryWasher.d.ts +243 -0
  44. package/dist/laundryWasher.d.ts.map +1 -0
  45. package/dist/laundryWasher.js +92 -7
  46. package/dist/laundryWasher.js.map +1 -0
  47. package/dist/logger/export.d.ts +2 -0
  48. package/dist/logger/export.d.ts.map +1 -0
  49. package/dist/logger/export.js +1 -0
  50. package/dist/logger/export.js.map +1 -0
  51. package/dist/matter/behaviors.d.ts +2 -0
  52. package/dist/matter/behaviors.d.ts.map +1 -0
  53. package/dist/matter/behaviors.js +2 -0
  54. package/dist/matter/behaviors.js.map +1 -0
  55. package/dist/matter/clusters.d.ts +2 -0
  56. package/dist/matter/clusters.d.ts.map +1 -0
  57. package/dist/matter/clusters.js +2 -0
  58. package/dist/matter/clusters.js.map +1 -0
  59. package/dist/matter/devices.d.ts +2 -0
  60. package/dist/matter/devices.d.ts.map +1 -0
  61. package/dist/matter/devices.js +2 -0
  62. package/dist/matter/devices.js.map +1 -0
  63. package/dist/matter/endpoints.d.ts +2 -0
  64. package/dist/matter/endpoints.d.ts.map +1 -0
  65. package/dist/matter/endpoints.js +2 -0
  66. package/dist/matter/endpoints.js.map +1 -0
  67. package/dist/matter/export.d.ts +5 -0
  68. package/dist/matter/export.d.ts.map +1 -0
  69. package/dist/matter/export.js +2 -0
  70. package/dist/matter/export.js.map +1 -0
  71. package/dist/matter/types.d.ts +3 -0
  72. package/dist/matter/types.d.ts.map +1 -0
  73. package/dist/matter/types.js +2 -0
  74. package/dist/matter/types.js.map +1 -0
  75. package/dist/matterbridge.d.ts +445 -0
  76. package/dist/matterbridge.d.ts.map +1 -0
  77. package/dist/matterbridge.js +748 -46
  78. package/dist/matterbridge.js.map +1 -0
  79. package/dist/matterbridgeAccessoryPlatform.d.ts +40 -0
  80. package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
  81. package/dist/matterbridgeAccessoryPlatform.js +34 -0
  82. package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
  83. package/dist/matterbridgeBehaviors.d.ts +1333 -0
  84. package/dist/matterbridgeBehaviors.d.ts.map +1 -0
  85. package/dist/matterbridgeBehaviors.js +54 -1
  86. package/dist/matterbridgeBehaviors.js.map +1 -0
  87. package/dist/matterbridgeDeviceTypes.d.ts +644 -0
  88. package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
  89. package/dist/matterbridgeDeviceTypes.js +578 -15
  90. package/dist/matterbridgeDeviceTypes.js.map +1 -0
  91. package/dist/matterbridgeDynamicPlatform.d.ts +40 -0
  92. package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
  93. package/dist/matterbridgeDynamicPlatform.js +34 -0
  94. package/dist/matterbridgeDynamicPlatform.js.map +1 -0
  95. package/dist/matterbridgeEndpoint.d.ts +1145 -0
  96. package/dist/matterbridgeEndpoint.d.ts.map +1 -0
  97. package/dist/matterbridgeEndpoint.js +995 -40
  98. package/dist/matterbridgeEndpoint.js.map +1 -0
  99. package/dist/matterbridgeEndpointHelpers.d.ts +3083 -0
  100. package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
  101. package/dist/matterbridgeEndpointHelpers.js +204 -10
  102. package/dist/matterbridgeEndpointHelpers.js.map +1 -0
  103. package/dist/matterbridgePlatform.d.ts +290 -0
  104. package/dist/matterbridgePlatform.d.ts.map +1 -0
  105. package/dist/matterbridgePlatform.js +221 -6
  106. package/dist/matterbridgePlatform.js.map +1 -0
  107. package/dist/matterbridgeTypes.d.ts +196 -0
  108. package/dist/matterbridgeTypes.d.ts.map +1 -0
  109. package/dist/matterbridgeTypes.js +24 -0
  110. package/dist/matterbridgeTypes.js.map +1 -0
  111. package/dist/pluginManager.d.ts +273 -0
  112. package/dist/pluginManager.d.ts.map +1 -0
  113. package/dist/pluginManager.js +269 -3
  114. package/dist/pluginManager.js.map +1 -0
  115. package/dist/roboticVacuumCleaner.d.ts +102 -0
  116. package/dist/roboticVacuumCleaner.d.ts.map +1 -0
  117. package/dist/roboticVacuumCleaner.js +81 -6
  118. package/dist/roboticVacuumCleaner.js.map +1 -0
  119. package/dist/shelly.d.ts +161 -0
  120. package/dist/shelly.d.ts.map +1 -0
  121. package/dist/shelly.js +155 -7
  122. package/dist/shelly.js.map +1 -0
  123. package/dist/storage/export.d.ts +2 -0
  124. package/dist/storage/export.d.ts.map +1 -0
  125. package/dist/storage/export.js +1 -0
  126. package/dist/storage/export.js.map +1 -0
  127. package/dist/update.d.ts +58 -0
  128. package/dist/update.d.ts.map +1 -0
  129. package/dist/update.js +53 -0
  130. package/dist/update.js.map +1 -0
  131. package/dist/utils/colorUtils.d.ts +61 -0
  132. package/dist/utils/colorUtils.d.ts.map +1 -0
  133. package/dist/utils/colorUtils.js +205 -2
  134. package/dist/utils/colorUtils.js.map +1 -0
  135. package/dist/utils/commandLine.d.ts +58 -0
  136. package/dist/utils/commandLine.d.ts.map +1 -0
  137. package/dist/utils/commandLine.js +53 -0
  138. package/dist/utils/commandLine.js.map +1 -0
  139. package/dist/utils/copyDirectory.d.ts +32 -0
  140. package/dist/utils/copyDirectory.d.ts.map +1 -0
  141. package/dist/utils/copyDirectory.js +37 -1
  142. package/dist/utils/copyDirectory.js.map +1 -0
  143. package/dist/utils/createDirectory.d.ts +32 -0
  144. package/dist/utils/createDirectory.d.ts.map +1 -0
  145. package/dist/utils/createDirectory.js +31 -0
  146. package/dist/utils/createDirectory.js.map +1 -0
  147. package/dist/utils/createZip.d.ts +38 -0
  148. package/dist/utils/createZip.d.ts.map +1 -0
  149. package/dist/utils/createZip.js +42 -2
  150. package/dist/utils/createZip.js.map +1 -0
  151. package/dist/utils/deepCopy.d.ts +31 -0
  152. package/dist/utils/deepCopy.d.ts.map +1 -0
  153. package/dist/utils/deepCopy.js +38 -0
  154. package/dist/utils/deepCopy.js.map +1 -0
  155. package/dist/utils/deepEqual.d.ts +53 -0
  156. package/dist/utils/deepEqual.d.ts.map +1 -0
  157. package/dist/utils/deepEqual.js +71 -1
  158. package/dist/utils/deepEqual.js.map +1 -0
  159. package/dist/utils/export.d.ts +12 -0
  160. package/dist/utils/export.d.ts.map +1 -0
  161. package/dist/utils/export.js +1 -0
  162. package/dist/utils/export.js.map +1 -0
  163. package/dist/utils/hex.d.ts +48 -0
  164. package/dist/utils/hex.d.ts.map +1 -0
  165. package/dist/utils/hex.js +57 -0
  166. package/dist/utils/hex.js.map +1 -0
  167. package/dist/utils/isvalid.d.ts +102 -0
  168. package/dist/utils/isvalid.d.ts.map +1 -0
  169. package/dist/utils/isvalid.js +100 -0
  170. package/dist/utils/isvalid.js.map +1 -0
  171. package/dist/utils/network.d.ts +69 -0
  172. package/dist/utils/network.d.ts.map +1 -0
  173. package/dist/utils/network.js +76 -5
  174. package/dist/utils/network.js.map +1 -0
  175. package/dist/utils/spawn.d.ts +12 -0
  176. package/dist/utils/spawn.d.ts.map +1 -0
  177. package/dist/utils/spawn.js +16 -0
  178. package/dist/utils/spawn.js.map +1 -0
  179. package/dist/utils/wait.d.ts +52 -0
  180. package/dist/utils/wait.d.ts.map +1 -0
  181. package/dist/utils/wait.js +58 -9
  182. package/dist/utils/wait.js.map +1 -0
  183. package/dist/waterHeater.d.ts +90 -0
  184. package/dist/waterHeater.d.ts.map +1 -0
  185. package/dist/waterHeater.js +62 -2
  186. package/dist/waterHeater.js.map +1 -0
  187. package/npm-shrinkwrap.json +2 -2
  188. package/package.json +2 -1
@@ -1,3 +1,26 @@
1
+ /**
2
+ * This file contains the Plugins class.
3
+ *
4
+ * @file plugins.ts
5
+ * @author Luca Liguori
6
+ * @date 2024-07-14
7
+ * @version 1.1.2
8
+ *
9
+ * Copyright 2024, 2025, 2026 Luca Liguori.
10
+ *
11
+ * Licensed under the Apache License, Version 2.0 (the "License");
12
+ * you may not use this file except in compliance with the License.
13
+ * You may obtain a copy of the License at
14
+ *
15
+ * http://www.apache.org/licenses/LICENSE-2.0
16
+ *
17
+ * Unless required by applicable law or agreed to in writing, software
18
+ * distributed under the License is distributed on an "AS IS" BASIS,
19
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
+ * See the License for the specific language governing permissions and
21
+ * limitations under the License. *
22
+ */
23
+ // AnsiLogger module
1
24
  import { AnsiLogger, UNDERLINE, UNDERLINEOFF, BLUE, db, er, nf, nt, rs, wr } from './logger/export.js';
2
25
  import { plg, typ } from './matterbridgeTypes.js';
3
26
  export class PluginManager {
@@ -7,8 +30,9 @@ export class PluginManager {
7
30
  log;
8
31
  constructor(matterbridge) {
9
32
  this.matterbridge = matterbridge;
33
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
34
  this.nodeContext = matterbridge.nodeContext;
11
- this.log = new AnsiLogger({ logName: 'PluginManager', logTimestampFormat: 4, logLevel: matterbridge.log.logLevel });
35
+ this.log = new AnsiLogger({ logName: 'PluginManager', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: matterbridge.log.logLevel });
12
36
  this.log.debug('Matterbridge plugin manager starting...');
13
37
  }
14
38
  get length() {
@@ -45,6 +69,7 @@ export class PluginManager {
45
69
  }
46
70
  catch (err) {
47
71
  this.log.error(`Error processing forEach plugin ${plg}${plugin.name}${er}: ${err instanceof Error ? err.message + '\n' + err.stack : err}`);
72
+ // throw error;
48
73
  }
49
74
  });
50
75
  await Promise.all(tasks);
@@ -52,13 +77,31 @@ export class PluginManager {
52
77
  set logLevel(logLevel) {
53
78
  this.log.logLevel = logLevel;
54
79
  }
80
+ /**
81
+ * Loads registered plugins from storage.
82
+ *
83
+ * This method retrieves an array of registered plugins from the storage and converts it
84
+ * into a map where the plugin names are the keys and the plugin objects are the values.
85
+ *
86
+ * @returns {Promise<RegisteredPlugin[]>} A promise that resolves to an array of registered plugins.
87
+ */
55
88
  async loadFromStorage() {
89
+ // Load the array from storage and convert it to a map
56
90
  const pluginsArray = await this.nodeContext.get('plugins', []);
57
91
  for (const plugin of pluginsArray)
58
92
  this._plugins.set(plugin.name, plugin);
59
93
  return pluginsArray;
60
94
  }
95
+ /**
96
+ * Loads registered plugins from storage.
97
+ *
98
+ * This method retrieves an array of registered plugins from the storage and converts it
99
+ * into a map where the plugin names are the keys and the plugin objects are the values.
100
+ *
101
+ * @returns {Promise<RegisteredPlugin[]>} A promise that resolves to an array of registered plugins.
102
+ */
61
103
  async saveToStorage() {
104
+ // Convert the map to an array
62
105
  const plugins = [];
63
106
  const pluginArrayFromMap = Array.from(this._plugins.values());
64
107
  for (const plugin of pluginArrayFromMap) {
@@ -78,13 +121,20 @@ export class PluginManager {
78
121
  this.log.debug(`Saved ${BLUE}${plugins.length}${db} plugins to storage`);
79
122
  return plugins.length;
80
123
  }
124
+ /**
125
+ * Resolves the name of a plugin by loading and parsing its package.json file.
126
+ * @param {string} pluginPath - The path to the plugin or the path to the plugin's package.json file.
127
+ * @returns The path to the resolved package.json file, or null if the package.json file is not found or does not contain a name.
128
+ */
81
129
  async resolve(pluginPath) {
82
130
  const { default: path } = await import('node:path');
83
131
  const { promises } = await import('node:fs');
84
132
  if (!pluginPath.endsWith('package.json'))
85
133
  pluginPath = path.join(pluginPath, 'package.json');
134
+ // Resolve the package.json of the plugin
86
135
  let packageJsonPath = path.resolve(pluginPath);
87
136
  this.log.debug(`Resolving plugin path ${plg}${packageJsonPath}${db}`);
137
+ // Check if the package.json file exists
88
138
  try {
89
139
  await promises.access(packageJsonPath);
90
140
  }
@@ -94,7 +144,9 @@ export class PluginManager {
94
144
  this.log.debug(`Trying at ${plg}${packageJsonPath}${db}`);
95
145
  }
96
146
  try {
147
+ // Load the package.json of the plugin
97
148
  const packageJson = JSON.parse(await promises.readFile(packageJsonPath, 'utf8'));
149
+ // Check for main issues
98
150
  if (!packageJson.name) {
99
151
  this.log.error(`Package.json name not found at ${packageJsonPath}`);
100
152
  return null;
@@ -107,6 +159,7 @@ export class PluginManager {
107
159
  this.log.error(`Plugin at ${packageJsonPath} has no main entrypoint in package.json`);
108
160
  return null;
109
161
  }
162
+ // Check for @project-chip and @matter packages in dependencies and devDependencies
110
163
  const checkForProjectChipPackages = (dependencies) => {
111
164
  return Object.keys(dependencies).filter((pkg) => pkg.startsWith('@project-chip') || pkg.startsWith('@matter'));
112
165
  };
@@ -128,6 +181,7 @@ export class PluginManager {
128
181
  this.log.error(`Please open an issue on the plugin repository to remove them.`);
129
182
  return null;
130
183
  }
184
+ // Check for matterbridge package in dependencies and devDependencies
131
185
  const checkForMatterbridgePackage = (dependencies) => {
132
186
  return Object.keys(dependencies).filter((pkg) => pkg === 'matterbridge');
133
187
  };
@@ -157,6 +211,12 @@ export class PluginManager {
157
211
  return null;
158
212
  }
159
213
  }
214
+ /**
215
+ * Get the author of a plugin from its package.json.
216
+ *
217
+ * @param {Record<string, string | number | Record<string, string | number | object>>} packageJson - The package.json object of the plugin.
218
+ * @returns {string} The author of the plugin, or 'Unknown author' if not found.
219
+ */
160
220
  getAuthor(packageJson) {
161
221
  if (packageJson.author && typeof packageJson.author === 'string')
162
222
  return packageJson.author;
@@ -164,6 +224,12 @@ export class PluginManager {
164
224
  return packageJson.author.name;
165
225
  return 'Unknown author';
166
226
  }
227
+ /**
228
+ * Get the homepage of a plugin from its package.json.
229
+ *
230
+ * @param {Record<string, string | number | Record<string, string | number | object>>} packageJson - The package.json object of the plugin.
231
+ * @returns {string | undefined} The homepage of the plugin, or undefined if not found.
232
+ */
167
233
  getHomepage(packageJson) {
168
234
  if (packageJson.homepage && typeof packageJson.homepage === 'string' && packageJson.homepage.includes('http')) {
169
235
  return packageJson.homepage.replace('git+', '').replace('.git', '');
@@ -172,7 +238,14 @@ export class PluginManager {
172
238
  return packageJson.repository.url.replace('git+', '').replace('.git', '');
173
239
  }
174
240
  }
241
+ /**
242
+ * Get the help URL of a plugin from its package.json.
243
+ *
244
+ * @param {Record<string, string | number | Record<string, string | number | object>>} packageJson - The package.json object of the plugin.
245
+ * @returns {string | undefined} The URL to the help page or to the README file, or undefined if not found.
246
+ */
175
247
  getHelp(packageJson) {
248
+ // If there's a help field that looks like a URL, return it.
176
249
  if (packageJson.help && typeof packageJson.help === 'string' && packageJson.help.startsWith('http')) {
177
250
  return packageJson.help;
178
251
  }
@@ -183,7 +256,14 @@ export class PluginManager {
183
256
  return packageJson.homepage.replace('git+', '').replace('.git', '');
184
257
  }
185
258
  }
259
+ /**
260
+ * Get the changelog URL of a plugin from its package.json.
261
+ *
262
+ * @param {Record<string, string | number | Record<string, string | number | object>>} packageJson - The package.json object of the plugin.
263
+ * @returns {string | undefined} The URL to the CHANGELOG file, or undefined if not found.
264
+ */
186
265
  getChangelog(packageJson) {
266
+ // If there's a changelog field that looks like a URL, return it.
187
267
  if (packageJson.changelog && typeof packageJson.changelog === 'string' && packageJson.changelog.startsWith('http')) {
188
268
  return packageJson.changelog;
189
269
  }
@@ -194,6 +274,13 @@ export class PluginManager {
194
274
  return packageJson.homepage.replace('git+', '').replace('.git', '');
195
275
  }
196
276
  }
277
+ /**
278
+ * Get the first funding URL(s) of a plugin from its package.json.
279
+ *
280
+ * @param {Record<string, any>} packageJson - The package.json object of the plugin.
281
+ * @returns {string | undefined} The first funding URLs, or undefined if not found.
282
+ */
283
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
197
284
  getFunding(packageJson) {
198
285
  const funding = packageJson.funding;
199
286
  if (!funding)
@@ -202,16 +289,24 @@ export class PluginManager {
202
289
  return;
203
290
  if (typeof funding === 'string' && funding.startsWith('http'))
204
291
  return funding;
292
+ // Normalize funding into an array.
205
293
  const fundingEntries = Array.isArray(funding) ? funding : [funding];
206
294
  for (const entry of fundingEntries) {
207
295
  if (entry && typeof entry === 'string' && entry.startsWith('http')) {
296
+ // If the funding entry is a string, assume it is a URL.
208
297
  return entry;
209
298
  }
210
299
  else if (entry && typeof entry === 'object' && typeof entry.url === 'string' && entry.url.startsWith('http')) {
300
+ // If it's an object with a 'url' property, use that.
211
301
  return entry.url;
212
302
  }
213
303
  }
214
304
  }
305
+ /**
306
+ * Loads and parse the plugin package.json and returns it.
307
+ * @param {RegisteredPlugin} plugin - The plugin to load the package from.
308
+ * @returns A Promise that resolves to the package.json object or null if the package.json could not be loaded.
309
+ */
215
310
  async parse(plugin) {
216
311
  const { promises } = await import('node:fs');
217
312
  try {
@@ -243,6 +338,7 @@ export class PluginManager {
243
338
  this.log.warn(`Plugin ${plg}${plugin.name}${wr} has no path`);
244
339
  if (!plugin.type)
245
340
  this.log.warn(`Plugin ${plg}${plugin.name}${wr} has no type`);
341
+ // Check for @project-chip and @matter packages in dependencies and devDependencies
246
342
  const checkForProjectChipPackages = (dependencies) => {
247
343
  return Object.keys(dependencies).filter((pkg) => pkg.startsWith('@project-chip') || pkg.startsWith('@matter'));
248
344
  };
@@ -264,6 +360,7 @@ export class PluginManager {
264
360
  this.log.error(`Please open an issue on the plugin repository to remove them.`);
265
361
  return null;
266
362
  }
363
+ // Check for matterbridge package in dependencies and devDependencies
267
364
  const checkForMatterbridgePackage = (dependencies) => {
268
365
  return Object.keys(dependencies).filter((pkg) => pkg === 'matterbridge');
269
366
  };
@@ -293,6 +390,16 @@ export class PluginManager {
293
390
  return null;
294
391
  }
295
392
  }
393
+ /**
394
+ * Enables a plugin by its name or path.
395
+ *
396
+ * This method enables a plugin by setting its `enabled` property to `true` and saving the updated
397
+ * plugin information to storage. It first checks if the plugin is already registered in the `_plugins` map.
398
+ * If not, it attempts to resolve the plugin's `package.json` file to retrieve its name and enable it.
399
+ *
400
+ * @param {string} nameOrPath - The name or path of the plugin to enable.
401
+ * @returns {Promise<RegisteredPlugin | null>} A promise that resolves to the enabled plugin object, or null if the plugin could not be enabled.
402
+ */
296
403
  async enable(nameOrPath) {
297
404
  const { promises } = await import('node:fs');
298
405
  if (!nameOrPath || nameOrPath === '')
@@ -326,6 +433,16 @@ export class PluginManager {
326
433
  return null;
327
434
  }
328
435
  }
436
+ /**
437
+ * Enables a plugin by its name or path.
438
+ *
439
+ * This method enables a plugin by setting its `enabled` property to `true` and saving the updated
440
+ * plugin information to storage. It first checks if the plugin is already registered in the `_plugins` map.
441
+ * If not, it attempts to resolve the plugin's `package.json` file to retrieve its name and enable it.
442
+ *
443
+ * @param {string} nameOrPath - The name or path of the plugin to enable.
444
+ * @returns {Promise<RegisteredPlugin | null>} A promise that resolves to the enabled plugin object, or null if the plugin could not be enabled.
445
+ */
329
446
  async disable(nameOrPath) {
330
447
  const { promises } = await import('node:fs');
331
448
  if (!nameOrPath || nameOrPath === '')
@@ -359,6 +476,16 @@ export class PluginManager {
359
476
  return null;
360
477
  }
361
478
  }
479
+ /**
480
+ * Removes a plugin by its name or path.
481
+ *
482
+ * This method removes a plugin from the `_plugins` map and saves the updated plugin information to storage.
483
+ * It first checks if the plugin is already registered in the `_plugins` map. If not, it attempts to resolve
484
+ * the plugin's `package.json` file to retrieve its name and remove it.
485
+ *
486
+ * @param {string} nameOrPath - The name or path of the plugin to remove.
487
+ * @returns {Promise<RegisteredPlugin | null>} A promise that resolves to the removed plugin object, or null if the plugin could not be removed.
488
+ */
362
489
  async remove(nameOrPath) {
363
490
  const { promises } = await import('node:fs');
364
491
  if (!nameOrPath || nameOrPath === '')
@@ -392,6 +519,17 @@ export class PluginManager {
392
519
  return null;
393
520
  }
394
521
  }
522
+ /**
523
+ * Adds a plugin by its name or path.
524
+ *
525
+ * This method adds a plugin to the plugins map and saves the updated plugin information to storage.
526
+ * It first resolves the plugin's `package.json` file to retrieve its details. If the plugin is already
527
+ * registered, it logs an info message and returns null. Otherwise, it registers the plugin, enables it,
528
+ * and saves the updated plugin information to storage.
529
+ *
530
+ * @param {string} nameOrPath - The name or path of the plugin to add.
531
+ * @returns {Promise<RegisteredPlugin | null>} A promise that resolves to the added plugin object, or null if the plugin could not be added.
532
+ */
395
533
  async add(nameOrPath) {
396
534
  const { promises } = await import('node:fs');
397
535
  if (!nameOrPath || nameOrPath === '')
@@ -426,6 +564,15 @@ export class PluginManager {
426
564
  return null;
427
565
  }
428
566
  }
567
+ /**
568
+ * Installs a plugin by its name.
569
+ *
570
+ * This method first uninstalls any existing version of the plugin, then installs the plugin globally using npm.
571
+ * It logs the installation process and retrieves the installed version of the plugin.
572
+ *
573
+ * @param {string} name - The name of the plugin to install.
574
+ * @returns {Promise<string | undefined>} A promise that resolves to the installed version of the plugin, or undefined if the installation failed.
575
+ */
429
576
  async install(name) {
430
577
  const { exec } = await import('node:child_process');
431
578
  await this.uninstall(name);
@@ -440,11 +587,14 @@ export class PluginManager {
440
587
  else {
441
588
  this.log.info(`Installed plugin ${plg}${name}${nf}`);
442
589
  this.log.debug(`Installed plugin ${plg}${name}${db}: ${stdout}`);
590
+ // Get the installed version
591
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
443
592
  exec(`npm list -g ${name} --depth=0`, (listError, listStdout, listStderr) => {
444
593
  if (listError) {
445
594
  this.log.error(`List error: ${listError}`);
446
595
  resolve(undefined);
447
596
  }
597
+ // Clean the output to get only the package name and version
448
598
  const lines = listStdout.split('\n');
449
599
  const versionLine = lines.find((line) => line.includes(`${name}@`));
450
600
  if (versionLine) {
@@ -460,6 +610,15 @@ export class PluginManager {
460
610
  });
461
611
  });
462
612
  }
613
+ /**
614
+ * Uninstalls a plugin by its name.
615
+ *
616
+ * This method uninstalls a globally installed plugin using npm. It logs the uninstallation process
617
+ * and returns the name of the uninstalled plugin if successful, or undefined if the uninstallation failed.
618
+ *
619
+ * @param {string} name - The name of the plugin to uninstall.
620
+ * @returns {Promise<string | undefined>} A promise that resolves to the name of the uninstalled plugin, or undefined if the uninstallation failed.
621
+ */
463
622
  async uninstall(name) {
464
623
  const { exec } = await import('node:child_process');
465
624
  this.log.info(`Uninstalling plugin ${plg}${name}${nf}`);
@@ -478,6 +637,14 @@ export class PluginManager {
478
637
  });
479
638
  });
480
639
  }
640
+ /**
641
+ * Loads a plugin and returns the corresponding MatterbridgePlatform instance.
642
+ * @param {RegisteredPlugin} plugin - The plugin to load.
643
+ * @param {boolean} start - Optional flag indicating whether to start the plugin after loading. Default is false.
644
+ * @param {string} message - Optional message to pass to the plugin when starting.
645
+ * @param {boolean} configure - Optional flag indicating whether to configure the plugin after loading. Default is false.
646
+ * @returns {Promise<MatterbridgePlatform | undefined>} A Promise that resolves to the loaded MatterbridgePlatform instance or undefined.
647
+ */
481
648
  async load(plugin, start = false, message = '', configure = false) {
482
649
  const { promises } = await import('node:fs');
483
650
  const { default: path } = await import('node:path');
@@ -491,15 +658,20 @@ export class PluginManager {
491
658
  }
492
659
  this.log.info(`Loading plugin ${plg}${plugin.name}${nf} type ${typ}${plugin.type}${nf}`);
493
660
  try {
661
+ // Load the package.json of the plugin
494
662
  const packageJson = JSON.parse(await promises.readFile(plugin.path, 'utf8'));
663
+ // Resolve the main module path relative to package.json
495
664
  const pluginEntry = path.resolve(path.dirname(plugin.path), packageJson.main);
665
+ // Dynamically import the plugin
496
666
  const { pathToFileURL } = await import('node:url');
497
667
  const pluginUrl = pathToFileURL(pluginEntry);
498
668
  this.log.debug(`Importing plugin ${plg}${plugin.name}${db} from ${pluginUrl.href}`);
499
669
  const pluginInstance = await import(pluginUrl.href);
500
670
  this.log.debug(`Imported plugin ${plg}${plugin.name}${db} from ${pluginUrl.href}`);
671
+ // Call the default export function of the plugin, passing this MatterBridge instance, the log and the config
501
672
  if (pluginInstance.default) {
502
673
  const config = await this.loadConfig(plugin);
674
+ // Preset the plugin properties here in case the plugin throws an error during loading. In this case the user can change the config and restart the plugin.
503
675
  plugin.name = packageJson.name;
504
676
  plugin.description = packageJson.description ?? 'No description';
505
677
  plugin.version = packageJson.version;
@@ -508,7 +680,7 @@ export class PluginManager {
508
680
  plugin.schemaJson = await this.loadSchema(plugin);
509
681
  config.name = plugin.name;
510
682
  config.version = packageJson.version;
511
- const log = new AnsiLogger({ logName: plugin.description ?? 'No description', logTimestampFormat: 4, logLevel: config.debug ? "debug" : this.matterbridge.log.logLevel });
683
+ const log = new AnsiLogger({ logName: plugin.description ?? 'No description', logTimestampFormat: 4 /* TimestampFormat.TIME_MILLIS */, logLevel: config.debug ? "debug" /* LogLevel.DEBUG */ : this.matterbridge.log.logLevel });
512
684
  const platform = pluginInstance.default(this.matterbridge, log, config);
513
685
  config.type = platform.type;
514
686
  platform.name = packageJson.name;
@@ -527,7 +699,7 @@ export class PluginManager {
527
699
  plugin.loaded = true;
528
700
  plugin.registeredDevices = 0;
529
701
  plugin.addedDevices = 0;
530
- await this.saveToStorage();
702
+ await this.saveToStorage(); // Save the plugin to storage
531
703
  this.log.notice(`Loaded plugin ${plg}${plugin.name}${nt} type ${typ}${platform.type}${nt} (entrypoint ${UNDERLINE}${pluginEntry}${UNDERLINEOFF})`);
532
704
  if (start)
533
705
  await this.start(plugin, message, false);
@@ -546,6 +718,14 @@ export class PluginManager {
546
718
  }
547
719
  return undefined;
548
720
  }
721
+ /**
722
+ * Starts a plugin.
723
+ *
724
+ * @param {RegisteredPlugin} plugin - The plugin to start.
725
+ * @param {string} [message] - Optional message to pass to the plugin's onStart method.
726
+ * @param {boolean} [configure] - Indicates whether to configure the plugin after starting (default false).
727
+ * @returns {Promise<RegisteredPlugin | undefined>} A promise that resolves when the plugin is started successfully, or rejects with an error if starting the plugin fails.
728
+ */
549
729
  async start(plugin, message, configure = false) {
550
730
  if (!plugin.loaded) {
551
731
  this.log.error(`Plugin ${plg}${plugin.name}${er} not loaded`);
@@ -575,6 +755,12 @@ export class PluginManager {
575
755
  }
576
756
  return undefined;
577
757
  }
758
+ /**
759
+ * Configures a plugin.
760
+ *
761
+ * @param {RegisteredPlugin} plugin - The plugin to configure.
762
+ * @returns {Promise<RegisteredPlugin | undefined>} A promise that resolves when the plugin is configured successfully, or rejects with an error if configuration fails.
763
+ */
578
764
  async configure(plugin) {
579
765
  if (!plugin.loaded) {
580
766
  this.log.error(`Plugin ${plg}${plugin.name}${er} not loaded`);
@@ -605,6 +791,18 @@ export class PluginManager {
605
791
  }
606
792
  return undefined;
607
793
  }
794
+ /**
795
+ * Shuts down a plugin.
796
+ *
797
+ * This method shuts down a plugin by calling its `onShutdown` method and resetting its state.
798
+ * It logs the shutdown process and optionally removes all devices associated with the plugin.
799
+ *
800
+ * @param {RegisteredPlugin} plugin - The plugin to shut down.
801
+ * @param {string} [reason] - The reason for shutting down the plugin.
802
+ * @param {boolean} [removeAllDevices=false] - Whether to remove all devices associated with the plugin.
803
+ * @param {boolean} [force=false] - Whether to force the shutdown even if the plugin is not loaded or started.
804
+ * @returns {Promise<RegisteredPlugin | undefined>} A promise that resolves to the shut down plugin object, or undefined if the shutdown failed.
805
+ */
608
806
  async shutdown(plugin, reason, removeAllDevices = false, force = false) {
609
807
  this.log.debug(`Shutting down plugin ${plg}${plugin.name}${db}`);
610
808
  if (!plugin.loaded) {
@@ -647,6 +845,15 @@ export class PluginManager {
647
845
  }
648
846
  return undefined;
649
847
  }
848
+ /**
849
+ * Loads the configuration for a plugin.
850
+ * If the configuration file exists, it reads the file and returns the parsed JSON data.
851
+ * If the configuration file does not exist, it creates a new file with default configuration and returns it.
852
+ * If any error occurs during file access or creation, it logs an error and return un empty config.
853
+ *
854
+ * @param {RegisteredPlugin} plugin - The plugin for which to load the configuration.
855
+ * @returns {Promise<PlatformConfig>} A promise that resolves to the loaded or created configuration.
856
+ */
650
857
  async loadConfig(plugin) {
651
858
  const { default: path } = await import('node:path');
652
859
  const { promises } = await import('node:fs');
@@ -657,6 +864,8 @@ export class PluginManager {
657
864
  const data = await promises.readFile(configFile, 'utf8');
658
865
  const config = JSON.parse(data);
659
866
  this.log.debug(`Loaded config file ${configFile} for plugin ${plg}${plugin.name}${db}.`);
867
+ // this.log.debug(`Loaded config file ${configFile} for plugin ${plg}${plugin.name}${db}.\nConfig:${rs}\n`, config);
868
+ // The first time a plugin is added to the system, the config file is created with the plugin name and type "AnyPlatform".
660
869
  config.name = plugin.name;
661
870
  config.type = plugin.type;
662
871
  if (config.debug === undefined)
@@ -680,6 +889,7 @@ export class PluginManager {
680
889
  try {
681
890
  await promises.writeFile(configFile, JSON.stringify(config, null, 2), 'utf8');
682
891
  this.log.debug(`Created config file ${configFile} for plugin ${plg}${plugin.name}${db}.`);
892
+ // this.log.debug(`Created config file ${configFile} for plugin ${plg}${plugin.name}${db}.\nConfig:${rs}\n`, config);
683
893
  return config;
684
894
  }
685
895
  catch (err) {
@@ -693,6 +903,19 @@ export class PluginManager {
693
903
  }
694
904
  }
695
905
  }
906
+ /**
907
+ * Saves the configuration of a plugin to a file.
908
+ *
909
+ * This method saves the configuration of the specified plugin to a JSON file in the matterbridge directory.
910
+ * If the plugin's configuration is not found, it logs an error and rejects the promise. If the configuration
911
+ * is successfully saved, it logs a debug message. If an error occurs during the file write operation, it logs
912
+ * the error and rejects the promise.
913
+ *
914
+ * @param {RegisteredPlugin} plugin - The plugin whose configuration is to be saved.
915
+ * @param {boolean} [restartRequired=false] - Indicates whether a restart is required after saving the configuration.
916
+ * @returns {Promise<void>} A promise that resolves when the configuration is successfully saved, or rejects if an error occurs.
917
+ * @throws {Error} If the plugin's configuration is not found.
918
+ */
696
919
  async saveConfigFromPlugin(plugin, restartRequired = false) {
697
920
  const { default: path } = await import('node:path');
698
921
  const { promises } = await import('node:fs');
@@ -707,6 +930,7 @@ export class PluginManager {
707
930
  if (restartRequired)
708
931
  plugin.restartRequired = true;
709
932
  this.log.debug(`Saved config file ${configFile} for plugin ${plg}${plugin.name}${db}`);
933
+ // this.log.debug(`Saved config file ${configFile} for plugin ${plg}${plugin.name}${db}.\nConfig:${rs}\n`, plugin.platform.config);
710
934
  return Promise.resolve();
711
935
  }
712
936
  catch (err) {
@@ -714,6 +938,20 @@ export class PluginManager {
714
938
  return Promise.reject(err);
715
939
  }
716
940
  }
941
+ /**
942
+ * Saves the configuration of a plugin from a JSON object to a file.
943
+ *
944
+ * This method saves the provided configuration of the specified plugin to a JSON file in the matterbridge directory.
945
+ * It first checks if the configuration data is valid by ensuring it contains the correct name and type, and matches
946
+ * the plugin's name. If the configuration data is invalid, it logs an error and returns. If the configuration is
947
+ * successfully saved, it updates the plugin's `configJson` property and logs a debug message. If an error occurs
948
+ * during the file write operation, it logs the error and returns.
949
+ *
950
+ * @param {RegisteredPlugin} plugin - The plugin whose configuration is to be saved.
951
+ * @param {PlatformConfig} config - The configuration data to be saved.
952
+ * @param {boolean} [restartRequired=false] - Indicates whether a restart is required after saving the configuration.
953
+ * @returns {Promise<void>} A promise that resolves when the configuration is successfully saved, or returns if an error occurs.
954
+ */
717
955
  async saveConfigFromJson(plugin, config, restartRequired = false) {
718
956
  const { default: path } = await import('node:path');
719
957
  const { promises } = await import('node:fs');
@@ -732,12 +970,23 @@ export class PluginManager {
732
970
  plugin.platform.onConfigChanged(config).catch((err) => this.log.error(`Error calling onConfigChanged for plugin ${plg}${plugin.name}${er}: ${err}`));
733
971
  }
734
972
  this.log.debug(`Saved config file ${configFile} for plugin ${plg}${plugin.name}${db}`);
973
+ // this.log.debug(`Saved config file ${configFile} for plugin ${plg}${plugin.name}${db}.\nConfig:${rs}\n`, config);
735
974
  }
736
975
  catch (err) {
737
976
  this.log.error(`Error saving config file ${configFile} for plugin ${plg}${plugin.name}${er}: ${err instanceof Error ? err.message + '\n' + err.stack : err}`);
738
977
  return;
739
978
  }
740
979
  }
980
+ /**
981
+ * Loads the schema for a plugin.
982
+ *
983
+ * This method attempts to load the schema file for the specified plugin. If the schema file is found,
984
+ * it reads and parses the file, updates the schema's title and description, and logs the process.
985
+ * If the schema file is not found, it logs the event and loads a default schema for the plugin.
986
+ *
987
+ * @param {RegisteredPlugin} plugin - The plugin whose schema is to be loaded.
988
+ * @returns {Promise<PlatformSchema>} A promise that resolves to the loaded schema object, or the default schema if the schema file is not found.
989
+ */
741
990
  async loadSchema(plugin) {
742
991
  const { promises } = await import('node:fs');
743
992
  const schemaFile = plugin.path.replace('package.json', `${plugin.name}.schema.json`);
@@ -748,13 +997,29 @@ export class PluginManager {
748
997
  schema.title = plugin.description;
749
998
  schema.description = plugin.name + ' v. ' + plugin.version + ' by ' + plugin.author;
750
999
  this.log.debug(`Loaded schema file ${schemaFile} for plugin ${plg}${plugin.name}${db}.`);
1000
+ // this.log.debug(`Loaded schema file ${schemaFile} for plugin ${plg}${plugin.name}${db}.\nSchema:${rs}\n`, schema);
751
1001
  return schema;
752
1002
  }
753
1003
  catch (_err) {
1004
+ // const nodeErr = err as NodeJS.ErrnoException;
1005
+ // if (nodeErr.code === 'ENOENT') {
754
1006
  this.log.debug(`Schema file ${schemaFile} for plugin ${plg}${plugin.name}${db} not found. Loading default schema.`);
1007
+ // } else {
1008
+ // this.log.debug(`Schema file ${schemaFile} for plugin ${plg}${plugin.name}${db} not found. Loading default schema. Error: ${err instanceof Error ? err.message + '\n' + err.stack : err}`);
1009
+ // }
755
1010
  return this.getDefaultSchema(plugin);
756
1011
  }
757
1012
  }
1013
+ /**
1014
+ * Returns the default schema for a plugin.
1015
+ *
1016
+ * This method generates a default schema object for the specified plugin. The schema includes
1017
+ * metadata such as the plugin's title, description, version, and author. It also defines the
1018
+ * properties of the schema, including the plugin's name, type, debug flag, and unregisterOnShutdown flag.
1019
+ *
1020
+ * @param {RegisteredPlugin} plugin - The plugin for which the default schema is to be generated.
1021
+ * @returns {PlatformSchema} The default schema object for the plugin.
1022
+ */
758
1023
  getDefaultSchema(plugin) {
759
1024
  return {
760
1025
  title: plugin.description,
@@ -785,3 +1050,4 @@ export class PluginManager {
785
1050
  };
786
1051
  }
787
1052
  }
1053
+ //# sourceMappingURL=pluginManager.js.map