@matterbridge/core 3.6.1-dev-20260311-8b3e31c → 3.6.1-dev-20260313-a699c0e

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.
package/dist/frontend.js CHANGED
@@ -727,6 +727,7 @@ export class Frontend extends EventEmitter {
727
727
  });
728
728
  });
729
729
  this.expressApp.post('/api/uploadpackage', upload.single('file'), async (req, res) => {
730
+ this.log.debug('The frontend sent /api/uploadpackage');
730
731
  if (!this.validateReq(req, res))
731
732
  return;
732
733
  const { filename } = req.body;
@@ -736,35 +737,34 @@ export class Frontend extends EventEmitter {
736
737
  res.status(400).send('Invalid request: file and filename are required');
737
738
  return;
738
739
  }
739
- this.wssSendSnackbarMessage(`Installing package ${filename}. Please wait...`, 0);
740
+ this.wssSendSnackbarMessage(`Installing package ${filename}...`, 0);
740
741
  const filePath = path.join(this.matterbridge.matterbridgeDirectory, 'uploads', filename);
741
742
  try {
742
743
  const fs = await import('node:fs');
743
744
  await fs.promises.rename(file.path, filePath);
744
745
  this.log.info(`File ${plg}${filename}${nf} uploaded successfully`);
745
746
  if (filename.endsWith('.tgz')) {
746
- const { spawnCommand } = await import('./spawn.js');
747
- if (await spawnCommand('npm', ['install', '-g', filePath, '--omit=dev', '--verbose'], 'install', filename)) {
748
- this.log.info(`Plugin package ${plg}${filename}${nf} installed successfully. Full restart required.`);
749
- this.wssSendCloseSnackbarMessage(`Installing package ${filename}. Please wait...`);
750
- this.wssSendSnackbarMessage(`Installed package ${filename}`, 10, 'success');
751
- this.wssSendRestartRequired();
752
- res.send(`Plugin package ${filename} uploaded and installed successfully`);
753
- }
754
- else {
755
- this.log.error(`Error uploading or installing plugin package file ${plg}${filename}${er}`);
756
- this.wssSendCloseSnackbarMessage(`Installing package ${filename}. Please wait...`);
757
- this.wssSendSnackbarMessage(`Error uploading or installing plugin package ${filename}`, 10, 'error');
758
- res.status(500).send(`Error uploading or installing plugin package ${filename}`);
759
- }
760
- }
761
- else {
762
- res.send(`File ${filename} uploaded successfully`);
747
+ this.server.request({
748
+ type: 'manager_run',
749
+ src: 'frontend',
750
+ dst: 'manager',
751
+ params: {
752
+ name: 'SpawnCommand',
753
+ workerData: {
754
+ threadName: 'SpawnCommand',
755
+ command: 'npm',
756
+ args: ['install', '-g', filePath, '--omit=dev', '--verbose'],
757
+ packageCommand: 'install',
758
+ packageName: filename,
759
+ },
760
+ },
761
+ });
763
762
  }
763
+ res.send(`File ${filename} uploaded successfully`);
764
764
  }
765
765
  catch (err) {
766
766
  this.log.error(`Error uploading or installing plugin package file ${plg}${filename}${er}:`, err);
767
- this.wssSendCloseSnackbarMessage(`Installing package ${filename}. Please wait...`);
767
+ this.wssSendCloseSnackbarMessage(`Installing package ${filename}...`);
768
768
  this.wssSendSnackbarMessage(`Error uploading or installing plugin package ${filename}`, 10, 'error');
769
769
  res.status(500).send(`Error uploading or installing plugin package ${filename}`);
770
770
  }
@@ -16,7 +16,7 @@ import { DeviceTypeId, VendorId } from '@matter/types/datatype';
16
16
  import { BroadcastServer } from '@matterbridge/thread/server';
17
17
  import { dev, MATTER_LOGGER_FILE, MATTER_STORAGE_NAME, MATTERBRIDGE_LOGGER_FILE, NODE_STORAGE_DIR, plg, typ } from '@matterbridge/types';
18
18
  import { wait } from '@matterbridge/utils';
19
- import { getIntParameter, getParameter, hasParameter } from '@matterbridge/utils/cli';
19
+ import { getIntParameter, getParameter, hasAnyParameter, hasParameter } from '@matterbridge/utils/cli';
20
20
  import { copyDirectory } from '@matterbridge/utils/copy-dir';
21
21
  import { createDirectory } from '@matterbridge/utils/create-dir';
22
22
  import { formatBytes, formatPercent, formatUptime } from '@matterbridge/utils/format';
@@ -566,17 +566,11 @@ export class Matterbridge extends EventEmitter {
566
566
  this.devices.logLevel = this.log.logLevel;
567
567
  for (const plugin of this.plugins) {
568
568
  this.log.debug(`Parsing plugin ${plg}${plugin.name}${db} from path ${CYAN}${plugin.path}${db} with version ${CYAN}${plugin.version}${db} and type ${CYAN}${plugin.type}${db}.`);
569
- if (!fs.existsSync(plugin.path) &&
570
- !hasParameter('add') &&
571
- !hasParameter('remove') &&
572
- !hasParameter('enable') &&
573
- !hasParameter('disable') &&
574
- !hasParameter('reset') &&
575
- !hasParameter('factoryreset') &&
576
- !hasParameter('systemcheck')) {
569
+ if (!fs.existsSync(plugin.path) && !hasAnyParameter('add', 'remove', 'enable', 'disable', 'reset', 'factoryreset', 'systemcheck')) {
577
570
  this.log.info(`Error parsing plugin ${plg}${plugin.name}${nf}. Trying to reinstall it from npm...`);
578
- const { spawnCommand } = await import('./spawn.js');
579
- if (await spawnCommand('npm', ['install', '-g', `${plugin.name}${plugin.version.includes('-dev-') ? '@dev' : ''}`, '--omit=dev', '--verbose'], 'install', plugin.name)) {
571
+ const { execSync } = await import('node:child_process');
572
+ const sudo = hasParameter('sudo') || (process.platform !== 'win32' && !hasParameter('docker') && !hasParameter('nosudo') && !process.env.PATH?.includes('/.nvm/versions/node/'));
573
+ if (execSync(`${sudo ? 'sudo ' : ''}npm install -g ${plugin.name}${plugin.version.includes('-dev-') ? '@dev' : ''} --omit=dev`)) {
580
574
  this.log.info(`Plugin ${plg}${plugin.name}${nf} reinstalled.`);
581
575
  plugin.error = false;
582
576
  }
@@ -1615,7 +1615,6 @@ declare const MatterbridgeThermostatServer_base: import("@matter/node").ClusterB
1615
1615
  }];
1616
1616
  }>, readonly [Thermostat.Feature.Cooling, Thermostat.Feature.Heating, Thermostat.Feature.AutoMode]>, typeof ThermostatServer, import("@matter/node/behaviors/thermostat").ThermostatInterface>;
1617
1617
  export declare class MatterbridgeThermostatServer extends MatterbridgeThermostatServer_base {
1618
- initialize(): Promise<void>;
1619
1618
  setpointRaiseLower(request: Thermostat.SetpointRaiseLowerRequest): MaybePromise;
1620
1619
  }
1621
1620
  declare const MatterbridgePresetThermostatServer_base: import("@matter/node").ClusterBehavior.Type<import("@matter/types").ClusterComposer.WithFeatures<import("@matter/types").ClusterType.Of<{
@@ -321,17 +321,6 @@ export class MatterbridgeFanControlServer extends FanControlServer.with(FanContr
321
321
  }
322
322
  }
323
323
  export class MatterbridgeThermostatServer extends ThermostatServer.with(Thermostat.Feature.Cooling, Thermostat.Feature.Heating, Thermostat.Feature.AutoMode) {
324
- async initialize() {
325
- await super.initialize();
326
- this.endpoint.construction.onSuccess(async () => {
327
- const device = this.endpoint.stateOf(MatterbridgeServer);
328
- device.log.debug(`Removing atomic commands (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
329
- await this.endpoint.setStateOf(ThermostatServer, {
330
- acceptedCommandList: [0],
331
- generatedCommandList: [],
332
- });
333
- });
334
- }
335
324
  setpointRaiseLower(request) {
336
325
  const device = this.endpoint.stateOf(MatterbridgeServer);
337
326
  device.log.info(`Setting setpoint by ${request.amount} in mode ${request.mode} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
@@ -29,6 +29,16 @@ interface PluginManagerEvents {
29
29
  configured: [name: string];
30
30
  shutdown: [name: string];
31
31
  }
32
+ type PackageJsonDependencies = Record<string, string> | string[];
33
+ type PackageJsonLike = Record<string, unknown> & {
34
+ name?: string;
35
+ dependencies?: PackageJsonDependencies;
36
+ devDependencies?: PackageJsonDependencies;
37
+ peerDependencies?: PackageJsonDependencies;
38
+ optionalDependencies?: PackageJsonDependencies;
39
+ bundledDependencies?: PackageJsonDependencies;
40
+ bundleDependencies?: PackageJsonDependencies;
41
+ };
32
42
  export declare class PluginManager extends EventEmitter<PluginManagerEvents> {
33
43
  private readonly matterbridge;
34
44
  private readonly _plugins;
@@ -36,8 +46,13 @@ export declare class PluginManager extends EventEmitter<PluginManagerEvents> {
36
46
  private readonly server;
37
47
  private readonly debug;
38
48
  private readonly verbose;
49
+ private readonly dependencyTypes;
39
50
  constructor(matterbridge: Matterbridge);
40
51
  destroy(): void;
52
+ private getDependencyNames;
53
+ private findInvalidDependencies;
54
+ private logInvalidDependencies;
55
+ checkDependencies(packageJson: PackageJsonLike): boolean;
41
56
  private msgHandler;
42
57
  get length(): number;
43
58
  get size(): number;
@@ -16,6 +16,7 @@ export class PluginManager extends EventEmitter {
16
16
  server;
17
17
  debug = hasParameter('debug') || hasParameter('verbose');
18
18
  verbose = hasParameter('verbose');
19
+ dependencyTypes = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies', 'bundledDependencies', 'bundleDependencies'];
19
20
  constructor(matterbridge) {
20
21
  super();
21
22
  this.matterbridge = matterbridge;
@@ -29,6 +30,43 @@ export class PluginManager extends EventEmitter {
29
30
  this.server.off('broadcast_message', this.msgHandler.bind(this));
30
31
  this.server.close();
31
32
  }
33
+ getDependencyNames(dependencies) {
34
+ if (!dependencies)
35
+ return [];
36
+ return Array.isArray(dependencies) ? dependencies : Object.keys(dependencies);
37
+ }
38
+ findInvalidDependencies(packageJson) {
39
+ for (const dependencyType of this.dependencyTypes) {
40
+ const packages = this.getDependencyNames(packageJson[dependencyType]);
41
+ const matterbridgePackages = packages.filter((pkg) => pkg.startsWith('matterbridge'));
42
+ if (matterbridgePackages.length > 0) {
43
+ return { dependencyType, packages: matterbridgePackages, isMatterbridgePackage: true };
44
+ }
45
+ const scopedPackages = packages.filter((pkg) => pkg.startsWith('@project-chip') || pkg.startsWith('@matterbridge') || pkg.startsWith('@matter'));
46
+ if (scopedPackages.length > 0) {
47
+ return { dependencyType, packages: scopedPackages, isMatterbridgePackage: false };
48
+ }
49
+ }
50
+ return null;
51
+ }
52
+ logInvalidDependencies(packageJson, invalidDependencies, pluginName) {
53
+ if (invalidDependencies.isMatterbridgePackage) {
54
+ this.log.error(`Found matterbridge package in the plugin${pluginName ? ` ${plg}${pluginName}${er}` : ''} ${invalidDependencies.dependencyType}.`);
55
+ }
56
+ else {
57
+ this.log.error(`Found invalid packages "${invalidDependencies.packages.join(', ')}" in plugin${pluginName ? ` ${plg}${pluginName}${er}` : ''} ${invalidDependencies.dependencyType}.`);
58
+ }
59
+ this.log.error(`Please open an issue on the plugin repository to remove them.`);
60
+ this.server.request({
61
+ type: 'frontend_snackbarmessage',
62
+ src: 'plugins',
63
+ dst: 'frontend',
64
+ params: { message: `Found not allowed package in plugin ${packageJson.name} package.json`, timeout: 30, severity: 'error' },
65
+ });
66
+ }
67
+ checkDependencies(packageJson) {
68
+ return this.findInvalidDependencies(packageJson) === null;
69
+ }
32
70
  async msgHandler(msg) {
33
71
  if (this.server.isWorkerRequest(msg)) {
34
72
  if (this.verbose)
@@ -237,6 +275,8 @@ export class PluginManager extends EventEmitter {
237
275
  switch (msg.type) {
238
276
  case 'manager_spawn_response':
239
277
  if (msg.result && msg.result.packageCommand === 'install') {
278
+ if (msg.result.packageName.endsWith('.tgz'))
279
+ return;
240
280
  if (msg.result.success) {
241
281
  const packageName = msg.result.packageName.replace(/@.*$/, '');
242
282
  if (packageName !== 'matterbridge') {
@@ -435,82 +475,9 @@ export class PluginManager extends EventEmitter {
435
475
  this.log.error(`Plugin at ${packageJsonPath} has no main entrypoint in package.json`);
436
476
  return null;
437
477
  }
438
- const checkForProjectChipPackages = (dependencies) => {
439
- return Object.keys(dependencies).filter((pkg) => pkg.startsWith('@project-chip') || pkg.startsWith('@matter'));
440
- };
441
- const projectChipDependencies = checkForProjectChipPackages(packageJson.dependencies || {});
442
- if (projectChipDependencies.length > 0) {
443
- this.log.error(`Found @project-chip packages "${projectChipDependencies.join(', ')}" in plugin dependencies.`);
444
- this.log.error(`Please open an issue on the plugin repository to remove them.`);
445
- this.server.request({
446
- type: 'frontend_snackbarmessage',
447
- src: 'plugins',
448
- dst: 'frontend',
449
- params: { message: `Found not allowed package in plugin ${packageJson.name} package.json`, timeout: 30, severity: 'error' },
450
- });
451
- return null;
452
- }
453
- const projectChipDevDependencies = checkForProjectChipPackages(packageJson.devDependencies || {});
454
- if (projectChipDevDependencies.length > 0) {
455
- this.log.error(`Found @project-chip packages "${projectChipDevDependencies.join(', ')}" in plugin devDependencies.`);
456
- this.log.error(`Please open an issue on the plugin repository to remove them.`);
457
- this.server.request({
458
- type: 'frontend_snackbarmessage',
459
- src: 'plugins',
460
- dst: 'frontend',
461
- params: { message: `Found not allowed package in plugin ${packageJson.name} package.json`, timeout: 30, severity: 'error' },
462
- });
463
- return null;
464
- }
465
- const projectChipPeerDependencies = checkForProjectChipPackages(packageJson.peerDependencies || {});
466
- if (projectChipPeerDependencies.length > 0) {
467
- this.log.error(`Found @project-chip packages "${projectChipPeerDependencies.join(', ')}" in plugin peerDependencies.`);
468
- this.log.error(`Please open an issue on the plugin repository to remove them.`);
469
- this.server.request({
470
- type: 'frontend_snackbarmessage',
471
- src: 'plugins',
472
- dst: 'frontend',
473
- params: { message: `Found not allowed package in plugin ${packageJson.name} package.json`, timeout: 30, severity: 'error' },
474
- });
475
- return null;
476
- }
477
- const checkForMatterbridgePackage = (dependencies) => {
478
- return Object.keys(dependencies).filter((pkg) => pkg === 'matterbridge');
479
- };
480
- const matterbridgeDependencies = checkForMatterbridgePackage(packageJson.dependencies || {});
481
- if (matterbridgeDependencies.length > 0) {
482
- this.log.error(`Found matterbridge package in the plugin dependencies.`);
483
- this.log.error(`Please open an issue on the plugin repository to remove them.`);
484
- this.server.request({
485
- type: 'frontend_snackbarmessage',
486
- src: 'plugins',
487
- dst: 'frontend',
488
- params: { message: `Found not allowed package in plugin ${packageJson.name} package.json`, timeout: 30, severity: 'error' },
489
- });
490
- return null;
491
- }
492
- const matterbridgeDevDependencies = checkForMatterbridgePackage(packageJson.devDependencies || {});
493
- if (matterbridgeDevDependencies.length > 0) {
494
- this.log.error(`Found matterbridge package in the plugin devDependencies.`);
495
- this.log.error(`Please open an issue on the plugin repository to remove them.`);
496
- this.server.request({
497
- type: 'frontend_snackbarmessage',
498
- src: 'plugins',
499
- dst: 'frontend',
500
- params: { message: `Found not allowed package in plugin ${packageJson.name} package.json`, timeout: 30, severity: 'error' },
501
- });
502
- return null;
503
- }
504
- const matterbridgePeerDependencies = checkForMatterbridgePackage(packageJson.peerDependencies || {});
505
- if (matterbridgePeerDependencies.length > 0) {
506
- this.log.error(`Found matterbridge package in the plugin peerDependencies.`);
507
- this.log.error(`Please open an issue on the plugin repository to remove them.`);
508
- this.server.request({
509
- type: 'frontend_snackbarmessage',
510
- src: 'plugins',
511
- dst: 'frontend',
512
- params: { message: `Found not allowed package in plugin ${packageJson.name} package.json`, timeout: 30, severity: 'error' },
513
- });
478
+ const invalidDependencies = this.findInvalidDependencies(packageJson);
479
+ if (invalidDependencies) {
480
+ this.logInvalidDependencies(packageJson, invalidDependencies);
514
481
  return null;
515
482
  }
516
483
  this.log.debug(`Resolved plugin path ${plg}${nameOrPath}${db}: ${packageJsonPath}`);
@@ -667,82 +634,9 @@ export class PluginManager extends EventEmitter {
667
634
  plugin.funding = this.getFunding(packageJson);
668
635
  if (!plugin.type)
669
636
  this.log.warn(`Plugin ${plg}${plugin.name}${wr} has no type`);
670
- const checkForProjectChipPackages = (dependencies) => {
671
- return Object.keys(dependencies).filter((pkg) => pkg.startsWith('@project-chip') || pkg.startsWith('@matter'));
672
- };
673
- const projectChipDependencies = checkForProjectChipPackages(packageJson.dependencies || {});
674
- if (projectChipDependencies.length > 0) {
675
- this.log.error(`Found @project-chip packages "${projectChipDependencies.join(', ')}" in plugin ${plg}${plugin.name}${er} dependencies.`);
676
- this.log.error(`Please open an issue on the plugin repository to remove them.`);
677
- this.server.request({
678
- type: 'frontend_snackbarmessage',
679
- src: 'plugins',
680
- dst: 'frontend',
681
- params: { message: `Found not allowed package in plugin ${packageJson.name} package.json`, timeout: 30, severity: 'error' },
682
- });
683
- return null;
684
- }
685
- const projectChipDevDependencies = checkForProjectChipPackages(packageJson.devDependencies || {});
686
- if (projectChipDevDependencies.length > 0) {
687
- this.log.error(`Found @project-chip packages "${projectChipDevDependencies.join(', ')}" in plugin ${plg}${plugin.name}${er} devDependencies.`);
688
- this.log.error(`Please open an issue on the plugin repository to remove them.`);
689
- this.server.request({
690
- type: 'frontend_snackbarmessage',
691
- src: 'plugins',
692
- dst: 'frontend',
693
- params: { message: `Found not allowed package in plugin ${packageJson.name} package.json`, timeout: 30, severity: 'error' },
694
- });
695
- return null;
696
- }
697
- const projectChipPeerDependencies = checkForProjectChipPackages(packageJson.peerDependencies || {});
698
- if (projectChipPeerDependencies.length > 0) {
699
- this.log.error(`Found @project-chip packages "${projectChipPeerDependencies.join(', ')}" in plugin ${plg}${plugin.name}${er} peerDependencies.`);
700
- this.log.error(`Please open an issue on the plugin repository to remove them.`);
701
- this.server.request({
702
- type: 'frontend_snackbarmessage',
703
- src: 'plugins',
704
- dst: 'frontend',
705
- params: { message: `Found not allowed package in plugin ${packageJson.name} package.json`, timeout: 30, severity: 'error' },
706
- });
707
- return null;
708
- }
709
- const checkForMatterbridgePackage = (dependencies) => {
710
- return Object.keys(dependencies).filter((pkg) => pkg === 'matterbridge');
711
- };
712
- const matterbridgeDependencies = checkForMatterbridgePackage(packageJson.dependencies || {});
713
- if (matterbridgeDependencies.length > 0) {
714
- this.log.error(`Found matterbridge package in the plugin ${plg}${plugin.name}${er} dependencies.`);
715
- this.log.error(`Please open an issue on the plugin repository to remove them.`);
716
- this.server.request({
717
- type: 'frontend_snackbarmessage',
718
- src: 'plugins',
719
- dst: 'frontend',
720
- params: { message: `Found not allowed package in plugin ${packageJson.name} package.json`, timeout: 30, severity: 'error' },
721
- });
722
- return null;
723
- }
724
- const matterbridgeDevDependencies = checkForMatterbridgePackage(packageJson.devDependencies || {});
725
- if (matterbridgeDevDependencies.length > 0) {
726
- this.log.error(`Found matterbridge package in the plugin ${plg}${plugin.name}${er} devDependencies.`);
727
- this.log.error(`Please open an issue on the plugin repository to remove them.`);
728
- this.server.request({
729
- type: 'frontend_snackbarmessage',
730
- src: 'plugins',
731
- dst: 'frontend',
732
- params: { message: `Found not allowed package in plugin ${packageJson.name} package.json`, timeout: 30, severity: 'error' },
733
- });
734
- return null;
735
- }
736
- const matterbridgePeerDependencies = checkForMatterbridgePackage(packageJson.peerDependencies || {});
737
- if (matterbridgePeerDependencies.length > 0) {
738
- this.log.error(`Found matterbridge package in the plugin ${plg}${plugin.name}${er} peerDependencies.`);
739
- this.log.error(`Please open an issue on the plugin repository to remove them.`);
740
- this.server.request({
741
- type: 'frontend_snackbarmessage',
742
- src: 'plugins',
743
- dst: 'frontend',
744
- params: { message: `Found not allowed package in plugin ${packageJson.name} package.json`, timeout: 30, severity: 'error' },
745
- });
637
+ const invalidDependencies = this.findInvalidDependencies(packageJson);
638
+ if (invalidDependencies) {
639
+ this.logInvalidDependencies(packageJson, invalidDependencies, plugin.name);
746
640
  return null;
747
641
  }
748
642
  return packageJson;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matterbridge/core",
3
- "version": "3.6.1-dev-20260311-8b3e31c",
3
+ "version": "3.6.1-dev-20260313-a699c0e",
4
4
  "description": "Matterbridge core library",
5
5
  "author": "https://github.com/Luligu",
6
6
  "homepage": "https://matterbridge.io/",
@@ -121,11 +121,11 @@
121
121
  "CHANGELOG.md"
122
122
  ],
123
123
  "dependencies": {
124
- "@matter/main": "0.17.0-alpha.0-20260309-33bcd9eab",
125
- "@matterbridge/dgram": "3.6.1-dev-20260311-8b3e31c",
126
- "@matterbridge/thread": "3.6.1-dev-20260311-8b3e31c",
127
- "@matterbridge/types": "3.6.1-dev-20260311-8b3e31c",
128
- "@matterbridge/utils": "3.6.1-dev-20260311-8b3e31c",
124
+ "@matter/main": "0.17.0-alpha.0-20260311-3dbb8a732",
125
+ "@matterbridge/dgram": "3.6.1-dev-20260313-a699c0e",
126
+ "@matterbridge/thread": "3.6.1-dev-20260313-a699c0e",
127
+ "@matterbridge/types": "3.6.1-dev-20260313-a699c0e",
128
+ "@matterbridge/utils": "3.6.1-dev-20260313-a699c0e",
129
129
  "archiver": "7.0.1",
130
130
  "express": "5.2.1",
131
131
  "glob": "13.0.6",
package/dist/spawn.d.ts DELETED
@@ -1 +0,0 @@
1
- export declare function spawnCommand(command: string, args: string[], packageCommand?: 'install' | 'uninstall', packageName?: string): Promise<boolean>;
package/dist/spawn.js DELETED
@@ -1,96 +0,0 @@
1
- import { BroadcastServer } from '@matterbridge/thread/server';
2
- import { hasParameter } from '@matterbridge/utils/cli';
3
- import { AnsiLogger } from 'node-ansi-logger';
4
- export async function spawnCommand(command, args, packageCommand, packageName) {
5
- const { spawn } = await import('node:child_process');
6
- const debug = hasParameter('debug') || hasParameter('verbose') || hasParameter('debug-spawn') || hasParameter('verbose-spawn');
7
- const verbose = hasParameter('verbose') || hasParameter('verbose-spawn');
8
- const log = new AnsiLogger({ logName: 'Spawn', logTimestampFormat: 4, logLevel: debug ? "debug" : "info" });
9
- const server = new BroadcastServer('spawn', log);
10
- const sendLog = (name, message) => {
11
- try {
12
- server.request({ type: 'frontend_logmessage', src: 'spawn', dst: 'frontend', params: { level: 'spawn', time: log.now(), name, message } });
13
- }
14
- catch (err) {
15
- log.debug(`Failed to send log message to frontend: ${err instanceof Error ? err.message : String(err)}`);
16
- }
17
- };
18
- if (verbose)
19
- log.debug(`Spawning command: ${command} with ${args.join(' ')} ${packageCommand} ${packageName}`);
20
- const cmdLine = command + ' ' + args.join(' ');
21
- if (process.platform === 'win32' && command === 'npm') {
22
- const argstring = 'npm ' + args.join(' ');
23
- args.splice(0, args.length, '/c', argstring);
24
- command = 'cmd.exe';
25
- }
26
- if (hasParameter('sudo') ||
27
- (process.platform !== 'win32' && command === 'npm' && !hasParameter('docker') && !hasParameter('nosudo') && !process.env.PATH?.includes('/.nvm/versions/node/'))) {
28
- args.unshift(command);
29
- command = 'sudo';
30
- }
31
- log.debug(`Spawn command ${command} with ${args.join(' ')}`);
32
- const success = await new Promise((resolve) => {
33
- if (packageCommand === 'install')
34
- sendLog('Matterbridge:spawn-init', `Installing ${packageName}`);
35
- else if (packageCommand === 'uninstall')
36
- sendLog('Matterbridge:spawn-init', `Uninstalling ${packageName}`);
37
- const childProcess = spawn(command, args, {
38
- stdio: ['inherit', 'pipe', 'pipe'],
39
- });
40
- childProcess.on('error', (err) => {
41
- log.error(`Failed to start child process "${cmdLine}": ${err.message}`);
42
- sendLog('Matterbridge:spawn-exit-error', 'Spawn process error');
43
- resolve(false);
44
- });
45
- childProcess.on('close', (code, signal) => {
46
- if (code === 0) {
47
- log.debug(`Child process "${cmdLine}" closed with code ${code} and signal ${signal}`);
48
- sendLog('Matterbridge:spawn-exit-success', 'Child process closed');
49
- resolve(true);
50
- }
51
- else {
52
- log.error(`Child process "${cmdLine}" closed with code ${code} and signal ${signal}`);
53
- sendLog('Matterbridge:spawn-exit-error', 'Child process closed');
54
- resolve(false);
55
- }
56
- });
57
- childProcess.on('exit', (code, signal) => {
58
- if (code === 0) {
59
- log.debug(`Child process "${cmdLine}" exited with code ${code} and signal ${signal}`);
60
- sendLog('Matterbridge:spawn-exit-success', 'Child process exited');
61
- resolve(true);
62
- }
63
- else {
64
- log.error(`Child process "${cmdLine}" exited with code ${code} and signal ${signal}`);
65
- sendLog('Matterbridge:spawn-exit-error', 'Child process exited');
66
- resolve(false);
67
- }
68
- });
69
- childProcess.on('disconnect', () => {
70
- log.debug(`Child process "${cmdLine}" has been disconnected from the parent`);
71
- resolve(true);
72
- });
73
- if (childProcess.stdout) {
74
- childProcess.stdout.on('data', (data) => {
75
- const message = data.toString().trim();
76
- const lines = message.split('\n');
77
- for (const line of lines) {
78
- log.debug(`Spawn output (stdout): ${line}`);
79
- sendLog('Matterbridge:spawn', line);
80
- }
81
- });
82
- }
83
- if (childProcess.stderr) {
84
- childProcess.stderr.on('data', (data) => {
85
- const message = data.toString().trim();
86
- const lines = message.split('\n');
87
- for (const line of lines) {
88
- log.debug(`Spawn verbose (stderr): ${line}`);
89
- sendLog('Matterbridge:spawn', line);
90
- }
91
- });
92
- }
93
- });
94
- server.close();
95
- return success;
96
- }