@iobroker/js-controller-cli 4.0.23 → 4.1.0-alpha.0-20220823-bd606e5c

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 (77) hide show
  1. package/build/index.d.ts +87 -0
  2. package/{index.js → build/index.js} +8 -2
  3. package/build/lib/cli/cliCert.d.ts +16 -0
  4. package/build/lib/cli/cliCert.d.ts.map +1 -0
  5. package/{lib → build/lib}/cli/cliCert.js +6 -10
  6. package/build/lib/cli/cliCert.js.map +1 -0
  7. package/build/lib/cli/cliCommand.d.ts +41 -0
  8. package/build/lib/cli/cliCommand.d.ts.map +1 -0
  9. package/build/lib/cli/cliCommand.js +27 -0
  10. package/build/lib/cli/cliCommand.js.map +1 -0
  11. package/build/lib/cli/cliCompact.d.ts +26 -0
  12. package/build/lib/cli/cliCompact.d.ts.map +1 -0
  13. package/{lib → build/lib}/cli/cliCompact.js +24 -44
  14. package/build/lib/cli/cliCompact.js.map +1 -0
  15. package/build/lib/cli/cliDebug.d.ts +19 -0
  16. package/build/lib/cli/cliDebug.d.ts.map +1 -0
  17. package/{lib → build/lib}/cli/cliDebug.js +7 -14
  18. package/build/lib/cli/cliDebug.js.map +1 -0
  19. package/build/lib/cli/cliHost.d.ts +36 -0
  20. package/build/lib/cli/cliHost.d.ts.map +1 -0
  21. package/{lib → build/lib}/cli/cliHost.js +23 -48
  22. package/build/lib/cli/cliHost.js.map +1 -0
  23. package/build/lib/cli/cliLogs.d.ts +58 -0
  24. package/build/lib/cli/cliLogs.d.ts.map +1 -0
  25. package/{lib → build/lib}/cli/cliLogs.js +17 -21
  26. package/build/lib/cli/cliLogs.js.map +1 -0
  27. package/build/lib/cli/cliMessage.d.ts +10 -0
  28. package/build/lib/cli/cliMessage.d.ts.map +1 -0
  29. package/{lib → build/lib}/cli/cliMessage.js +6 -9
  30. package/build/lib/cli/cliMessage.js.map +1 -0
  31. package/build/lib/cli/cliObjects.d.ts +105 -0
  32. package/build/lib/cli/cliObjects.d.ts.map +1 -0
  33. package/{lib → build/lib}/cli/cliObjects.js +70 -111
  34. package/build/lib/cli/cliObjects.js.map +1 -0
  35. package/build/lib/cli/cliPlugin.d.ts +23 -0
  36. package/build/lib/cli/cliPlugin.d.ts.map +1 -0
  37. package/{lib → build/lib}/cli/cliPlugin.js +25 -51
  38. package/build/lib/cli/cliPlugin.js.map +1 -0
  39. package/build/lib/cli/cliProcess.d.ts +50 -0
  40. package/build/lib/cli/cliProcess.d.ts.map +1 -0
  41. package/{lib → build/lib}/cli/cliProcess.js +51 -63
  42. package/build/lib/cli/cliProcess.js.map +1 -0
  43. package/build/lib/cli/cliStates.d.ts +49 -0
  44. package/build/lib/cli/cliStates.d.ts.map +1 -0
  45. package/{lib → build/lib}/cli/cliStates.js +127 -122
  46. package/build/lib/cli/cliStates.js.map +1 -0
  47. package/build/lib/cli/cliTools.d.ts +64 -0
  48. package/build/lib/cli/cliTools.d.ts.map +1 -0
  49. package/{lib → build/lib}/cli/cliTools.js +37 -49
  50. package/build/lib/cli/cliTools.js.map +1 -0
  51. package/build/lib/cli/messages.d.ts +53 -0
  52. package/build/lib/cli/messages.d.ts.map +1 -0
  53. package/{lib → build/lib}/cli/messages.js +27 -66
  54. package/build/lib/cli/messages.js.map +1 -0
  55. package/build/lib/setup/setupBackup.d.ts +126 -0
  56. package/build/lib/setup/setupBackup.d.ts.map +1 -0
  57. package/build/lib/setup/setupBackup.js +1031 -0
  58. package/build/lib/setup/setupBackup.js.map +1 -0
  59. package/build/lib/setup/setupInstall.d.ts +23 -0
  60. package/build/lib/setup/setupInstall.d.ts.map +1 -0
  61. package/build/lib/setup/setupInstall.js +1620 -0
  62. package/build/lib/setup/setupInstall.js.map +1 -0
  63. package/build/lib/setup/setupList.d.ts +49 -0
  64. package/build/lib/setup/setupList.d.ts.map +1 -0
  65. package/build/lib/setup/setupList.js +722 -0
  66. package/build/lib/setup/setupList.js.map +1 -0
  67. package/build/lib/setup/setupPacketManager.d.ts +65 -0
  68. package/build/lib/setup/setupPacketManager.d.ts.map +1 -0
  69. package/build/lib/setup/setupPacketManager.js +216 -0
  70. package/build/lib/setup/setupPacketManager.js.map +1 -0
  71. package/build/lib/setup/setupUpload.d.ts +72 -0
  72. package/build/lib/setup/setupUpload.d.ts.map +1 -0
  73. package/build/lib/setup/setupUpload.js +784 -0
  74. package/build/lib/setup/setupUpload.js.map +1 -0
  75. package/package.json +18 -11
  76. package/lib/cli/cliCommand.js +0 -58
  77. package/lib/setup/setupList.js +0 -840
@@ -0,0 +1,1620 @@
1
+ "use strict";
2
+ /**
3
+ * Install adapter
4
+ *
5
+ * Copyright 2013-2022 bluefox <dogafox@gmail.com>
6
+ *
7
+ * MIT License
8
+ *
9
+ */
10
+ var __importDefault = (this && this.__importDefault) || function (mod) {
11
+ return (mod && mod.__esModule) ? mod : { "default": mod };
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ const js_controller_common_1 = require("@iobroker/js-controller-common");
15
+ const fs_extra_1 = __importDefault(require("fs-extra"));
16
+ const path_1 = __importDefault(require("path"));
17
+ const semver_1 = __importDefault(require("semver"));
18
+ const child_process_1 = __importDefault(require("child_process"));
19
+ const axios_1 = __importDefault(require("axios"));
20
+ const os_1 = require("os");
21
+ const deep_clone_1 = __importDefault(require("deep-clone"));
22
+ const url_1 = require("url");
23
+ const setupUpload_1 = require("./setupUpload");
24
+ const setupPacketManager_1 = require("./setupPacketManager");
25
+ const hostname = js_controller_common_1.tools.getHostName();
26
+ const osPlatform = (0, os_1.platform)();
27
+ /** Note: this is duplicated in preinstallCheck */
28
+ const RECOMMENDED_NPM_VERSION = 8;
29
+ class Install {
30
+ constructor(options) {
31
+ // todo solve it somehow
32
+ this.unsafePermAlways = [
33
+ `${js_controller_common_1.tools.appName.toLowerCase()}.zwave`,
34
+ `${js_controller_common_1.tools.appName.toLowerCase()}.amazon-dash`,
35
+ `${js_controller_common_1.tools.appName.toLowerCase()}.xbox`
36
+ ];
37
+ this.isRootOnUnix = typeof process.getuid === 'function' && process.getuid() === 0;
38
+ options = options || {};
39
+ if (!options.states) {
40
+ throw new Error('Invalid arguments: states is missing');
41
+ }
42
+ if (!options.objects) {
43
+ throw new Error('Invalid arguments: objects is missing');
44
+ }
45
+ if (!options.processExit) {
46
+ throw new Error('Invalid arguments: processExit is missing');
47
+ }
48
+ if (!options.getRepository) {
49
+ throw new Error('Invalid arguments: getRepository is missing');
50
+ }
51
+ this.objects = options.objects;
52
+ this.states = options.states;
53
+ this.processExit = options.processExit;
54
+ this.getRepository = options.getRepository;
55
+ this.params = options.params || {};
56
+ this.tarballRegex = /\/tarball\/[^/]+$/;
57
+ this.upload = new setupUpload_1.Upload(options);
58
+ }
59
+ /**
60
+ * Enables or disables given instances
61
+ */
62
+ async enableInstances(instances, enabled) {
63
+ if (instances && instances.length) {
64
+ const ts = Date.now();
65
+ for (const instance of instances) {
66
+ const updatedObj = {
67
+ common: {
68
+ enabled
69
+ },
70
+ from: `system.host.${js_controller_common_1.tools.getHostName()}.cli`,
71
+ ts
72
+ };
73
+ console.log(`host.${hostname} Adapter "${instance._id}" is ${enabled ? 'started' : 'stopped.'}`);
74
+ // @ts-expect-error should be fixed with #1917
75
+ await this.objects.extendObjectAsync(instance._id, updatedObj);
76
+ }
77
+ }
78
+ }
79
+ /**
80
+ * Download given packet
81
+ *
82
+ * @param repoUrl
83
+ * @param packetName
84
+ * @param options, { stopDb: true } - will stop the db before upgrade ONLY use it for controller upgrade -
85
+ * db is gone afterwards, does not work with stoppedList
86
+ * @param stoppedList
87
+ */
88
+ async downloadPacket(repoUrl, packetName, options, stoppedList) {
89
+ let url;
90
+ if (!options || typeof options !== 'object') {
91
+ options = {};
92
+ }
93
+ stoppedList = stoppedList || [];
94
+ let sources;
95
+ if (!repoUrl || typeof repoUrl !== 'object') {
96
+ try {
97
+ sources = await this.getRepository(repoUrl, this.params);
98
+ }
99
+ catch (err) {
100
+ return this.processExit(err);
101
+ }
102
+ }
103
+ else {
104
+ sources = repoUrl;
105
+ }
106
+ if (options.stopDb && stoppedList.length) {
107
+ console.warn('[downloadPacket] stoppedList cannot be used if stopping of databases is requested');
108
+ stoppedList = [];
109
+ }
110
+ const debug = process.argv.includes('--debug');
111
+ let version;
112
+ // check if the adapter has format adapter@1.0.0
113
+ if (packetName.includes('@')) {
114
+ const parts = packetName.split('@');
115
+ packetName = parts[0];
116
+ version = parts[1];
117
+ }
118
+ else {
119
+ // always take version from repository
120
+ if (sources[packetName] && sources[packetName].version) {
121
+ version = sources[packetName].version;
122
+ }
123
+ else {
124
+ version = '';
125
+ }
126
+ }
127
+ options.packetName = packetName;
128
+ options.unsafePerm = sources[packetName] && sources[packetName].unsafePerm;
129
+ // Check if flag stopBeforeUpdate is true or on windows we stop because of issue #1436
130
+ if (((sources[packetName] && sources[packetName].stopBeforeUpdate) || process.platform === 'win32') &&
131
+ !stoppedList.length) {
132
+ stoppedList = await this._getInstancesOfAdapter(packetName);
133
+ await this.enableInstances(stoppedList, false);
134
+ }
135
+ // try to extract the information from local sources-dist.json
136
+ if (!sources[packetName]) {
137
+ try {
138
+ const sourcesDist = fs_extra_1.default.readJsonSync(`${js_controller_common_1.tools.getControllerDir()}/conf/sources-dist.json`);
139
+ sources[packetName] = sourcesDist[packetName];
140
+ }
141
+ catch {
142
+ // OK
143
+ }
144
+ }
145
+ if (sources[packetName]) {
146
+ url = sources[packetName].url;
147
+ if (url &&
148
+ packetName === 'js-controller' &&
149
+ fs_extra_1.default.pathExistsSync(`${js_controller_common_1.tools.getControllerDir()}/../../node_modules/${js_controller_common_1.tools.appName}.js-controller`)) {
150
+ url = null;
151
+ }
152
+ if (!url && packetName !== 'example') {
153
+ if (options.stopDb) {
154
+ if (this.objects.destroy) {
155
+ await this.objects.destroy();
156
+ console.log('Stopped Objects DB');
157
+ }
158
+ if (this.states.destroy) {
159
+ await this.states.destroy();
160
+ console.log('Stopped States DB');
161
+ }
162
+ }
163
+ // Install node modules
164
+ await this._npmInstallWithCheck(`${js_controller_common_1.tools.appName.toLowerCase()}.${packetName}${version ? `@${version}` : ''}`, options, debug);
165
+ return { packetName, stoppedList };
166
+ }
167
+ else if (url && url.match(this.tarballRegex)) {
168
+ if (options.stopDb) {
169
+ if (this.objects.destroy) {
170
+ await this.objects.destroy();
171
+ console.log('Stopped Objects DB');
172
+ }
173
+ if (this.states.destroy) {
174
+ await this.states.destroy();
175
+ console.log('Stopped States DB');
176
+ }
177
+ }
178
+ // Install node modules
179
+ await this._npmInstallWithCheck(url, options, debug);
180
+ return { packetName, stoppedList };
181
+ }
182
+ else if (!url) {
183
+ // Adapter
184
+ console.warn(`host.${hostname} Adapter "${packetName}" can be updated only together with ${js_controller_common_1.tools.appName}.js-controller`);
185
+ return { packetName, stoppedList };
186
+ }
187
+ }
188
+ console.error(`host.${hostname} Unknown packetName ${packetName}. Please install packages from outside the repository using npm!`);
189
+ return this.processExit(js_controller_common_1.EXIT_CODES.UNKNOWN_PACKET_NAME);
190
+ }
191
+ /**
192
+ * Install npm module from url
193
+ */
194
+ async _npmInstallWithCheck(npmUrl, options, debug) {
195
+ // Get npm version
196
+ try {
197
+ let npmVersion;
198
+ try {
199
+ npmVersion = child_process_1.default.execSync('npm -v', { encoding: 'utf8' });
200
+ if (npmVersion) {
201
+ npmVersion = semver_1.default.valid(npmVersion.trim());
202
+ }
203
+ console.log(`NPM version: ${npmVersion}`);
204
+ }
205
+ catch (err) {
206
+ console.error(`Error trying to check npm version: ${err.message}`);
207
+ }
208
+ if (!npmVersion) {
209
+ console.error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
210
+ console.error('Aborting install because the npm version could not be checked!');
211
+ console.error('Please check that npm is installed correctly.');
212
+ console.error(`Use "npm install -g npm@${RECOMMENDED_NPM_VERSION}" or "npm install -g npm@latest" to install a supported version.`);
213
+ console.error('You need to make sure to repeat this step after installing an update to NodeJS and/or npm');
214
+ console.error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
215
+ return this.processExit(js_controller_common_1.EXIT_CODES.INVALID_NPM_VERSION);
216
+ }
217
+ else if (semver_1.default.gte(npmVersion, '5.0.0') && semver_1.default.lt(npmVersion, '5.7.1')) {
218
+ console.error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
219
+ console.error('NPM 5 is only supported starting with version 5.7.1!');
220
+ console.error(`Please use "npm install -g npm@${RECOMMENDED_NPM_VERSION}" to upgrade npm to ${RECOMMENDED_NPM_VERSION}.x or `);
221
+ console.error('use "npm install -g npm@latest" to install a supported version of npm!');
222
+ console.error('You need to make sure to repeat this step after installing an update to NodeJS and/or npm');
223
+ console.error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
224
+ return this.processExit(js_controller_common_1.EXIT_CODES.INVALID_NPM_VERSION);
225
+ }
226
+ }
227
+ catch (err) {
228
+ console.error(`Could not check npm version: ${err.message}`);
229
+ console.error('Assuming that correct version is installed.');
230
+ }
231
+ try {
232
+ return await this._npmInstall(npmUrl, options, debug);
233
+ }
234
+ catch (err) {
235
+ console.error(`Could not install ${npmUrl}: ${err.message}`);
236
+ }
237
+ }
238
+ async _npmInstall(npmUrl, options, debug) {
239
+ if (typeof options !== 'object') {
240
+ options = {};
241
+ }
242
+ // zwave for example requires always unsafe-perm option
243
+ if (this.unsafePermAlways.some(adapter => npmUrl.includes(adapter))) {
244
+ options.unsafePerm = true;
245
+ }
246
+ else if (this.isRootOnUnix) {
247
+ // If ioBroker or the CLI is executed as root on unix platforms,
248
+ // not providing the --unsafe-perm options means that every pre/postinstall
249
+ // script fails when it uses npm commands.
250
+ options.unsafePerm = true;
251
+ }
252
+ console.log(` Installing ${npmUrl}... (System call)`);
253
+ const result = await js_controller_common_1.tools.installNodeModule(npmUrl, {
254
+ debug: !!debug,
255
+ unsafePerm: !!options.unsafePerm
256
+ });
257
+ if (result.success || result.exitCode === 1) {
258
+ // code 1 is strange error that cannot be explained. Everything is installed but error :(
259
+ // Determine where the packet would be installed if npm succeeds
260
+ // TODO: There's probably a better way to figure this out
261
+ let packetDirName;
262
+ if (options.packetName) {
263
+ packetDirName = js_controller_common_1.tools.appName.toLowerCase() + '.' + options.packetName;
264
+ }
265
+ else {
266
+ packetDirName = npmUrl.toLowerCase();
267
+ // If the user installed a git commit-ish, the url contains stuff that doesn't belong in a folder name
268
+ // e.g. iobroker/iobroker.javascript#branch-name
269
+ if (packetDirName.includes('#')) {
270
+ packetDirName = packetDirName.substr(0, packetDirName.indexOf('#'));
271
+ }
272
+ if (packetDirName.includes('/') && !packetDirName.startsWith('@')) {
273
+ // only scoped packages (e.g. @types/node ) may have a slash in their path
274
+ packetDirName = packetDirName.substring(packetDirName.lastIndexOf('/') + 1);
275
+ }
276
+ }
277
+ const installDir = js_controller_common_1.tools.getAdapterDir(packetDirName);
278
+ // inject the installedFrom information in io-package
279
+ if (installDir && fs_extra_1.default.existsSync(installDir)) {
280
+ const ioPackPath = path_1.default.join(installDir, 'io-package.json');
281
+ let iopack;
282
+ try {
283
+ iopack = fs_extra_1.default.readJSONSync(ioPackPath);
284
+ }
285
+ catch {
286
+ iopack = null;
287
+ }
288
+ if (iopack) {
289
+ iopack.common = iopack.common || {};
290
+ iopack.common.installedFrom = npmUrl;
291
+ try {
292
+ fs_extra_1.default.writeFileSync(ioPackPath, JSON.stringify(iopack, null, 2), 'utf8');
293
+ }
294
+ catch {
295
+ // OK
296
+ }
297
+ }
298
+ }
299
+ else {
300
+ // TODO: revisit this - this should not happen
301
+ console.error(`host.${hostname} Cannot install ${npmUrl}: ${result.exitCode}`);
302
+ return this.processExit(js_controller_common_1.EXIT_CODES.CANNOT_INSTALL_NPM_PACKET);
303
+ }
304
+ // create file that indicates, that npm was called there
305
+ fs_extra_1.default.writeFileSync(path_1.default.join(installDir, 'iob_npm.done'), ' ');
306
+ // command succeeded
307
+ return { _url: npmUrl, installDir: path_1.default.dirname(installDir) };
308
+ }
309
+ else {
310
+ console.error(`host.${hostname} Cannot install ${npmUrl}: ${result.exitCode}`);
311
+ return this.processExit(js_controller_common_1.EXIT_CODES.CANNOT_INSTALL_NPM_PACKET);
312
+ }
313
+ }
314
+ async _npmUninstall(packageName, debug) {
315
+ const result = await js_controller_common_1.tools.uninstallNodeModule(packageName, { debug: !!debug });
316
+ if (!result.success) {
317
+ throw new Error(`host.${hostname}: Cannot uninstall ${packageName}: ${result.exitCode}`);
318
+ }
319
+ }
320
+ // this command is executed always on THIS host
321
+ async _checkDependencies(adapter, deps, globalDeps, _options) {
322
+ if (!deps && !globalDeps) {
323
+ return adapter;
324
+ }
325
+ deps = js_controller_common_1.tools.parseDependencies(deps);
326
+ globalDeps = js_controller_common_1.tools.parseDependencies(globalDeps);
327
+ // combine both dependencies
328
+ const allDeps = { ...deps, ...globalDeps };
329
+ // Get all installed adapters
330
+ // @ts-expect-error #1917 should fix it
331
+ const objs = await this.objects.getObjectViewAsync('system', 'instance', {
332
+ startkey: 'system.adapter.',
333
+ endkey: 'system.adapter.\u9999'
334
+ });
335
+ if (objs && objs.rows && objs.rows.length) {
336
+ for (const dName in allDeps) {
337
+ let isFound = false;
338
+ if (dName === 'js-controller') {
339
+ const version = allDeps[dName];
340
+ // Check only if version not *, else we dont have to read io-pack unnecessarily
341
+ if (version !== '*') {
342
+ const packJson = fs_extra_1.default.readJSONSync(`${js_controller_common_1.tools.getControllerDir()}/package.json`);
343
+ if (!semver_1.default.satisfies(packJson.version, version, { includePrerelease: true })) {
344
+ console.error(`host.${hostname} Invalid version of "${dName}". Installed "${packJson.version}", required "${version}"`);
345
+ return this.processExit(js_controller_common_1.EXIT_CODES.INVALID_DEPENDENCY_VERSION);
346
+ }
347
+ else {
348
+ isFound = true;
349
+ }
350
+ }
351
+ else {
352
+ isFound = true;
353
+ }
354
+ }
355
+ if (!isFound) {
356
+ let gInstances = [];
357
+ let locInstances = [];
358
+ // if global dep get all instances of adapter
359
+ if (globalDeps[dName] !== undefined) {
360
+ gInstances = objs.rows.filter(obj => obj && obj.value && obj.value.common && obj.value.common.name === dName);
361
+ }
362
+ if (deps[dName] !== undefined) {
363
+ // local dep get all instances on same host
364
+ locInstances = objs.rows.filter(obj => obj &&
365
+ obj.value &&
366
+ obj.value.common &&
367
+ obj.value.common.name === dName &&
368
+ obj.value.common.host === hostname);
369
+ if (locInstances.length === 0) {
370
+ console.error(`host.${hostname} Required dependency "${dName}" not found on this host.`);
371
+ }
372
+ }
373
+ // we check, that all existing instances match - respect different versions for local and global deps
374
+ for (const instance of locInstances) {
375
+ if (
376
+ // @ts-expect-error InstaceCommon has version: TODO fix tpes
377
+ !semver_1.default.satisfies(instance.value.common.version, deps[dName], {
378
+ includePrerelease: true
379
+ })) {
380
+ console.error(`host.${hostname} Invalid version of "${dName}". Installed "${
381
+ // @ts-expect-error InstaceCommon has version: TODO fix tpes
382
+ instance.value.common.version}", required "${deps[dName]}"`);
383
+ return this.processExit(js_controller_common_1.EXIT_CODES.INVALID_DEPENDENCY_VERSION);
384
+ }
385
+ else {
386
+ isFound = true;
387
+ }
388
+ }
389
+ for (const instance of gInstances) {
390
+ if (
391
+ // @ts-expect-error InstaceCommon has version: TODO fix tpes
392
+ !semver_1.default.satisfies(instance.value.common.version, globalDeps[dName], {
393
+ includePrerelease: true
394
+ })) {
395
+ console.error(`host.${hostname} Invalid version of "${dName}". Installed "${
396
+ // @ts-expect-error InstaceCommon has version: TODO fix tpes
397
+ instance.value.common.version}", required "${globalDeps[dName]}"`);
398
+ return this.processExit(js_controller_common_1.EXIT_CODES.INVALID_DEPENDENCY_VERSION);
399
+ }
400
+ else {
401
+ isFound = true;
402
+ }
403
+ }
404
+ }
405
+ // if required dependency not found => install it
406
+ if (!isFound) {
407
+ await this.createInstance(dName, _options);
408
+ }
409
+ }
410
+ }
411
+ }
412
+ async _uploadStaticObjects(adapter, _adapterConf) {
413
+ let adapterConf;
414
+ if (!_adapterConf) {
415
+ const adapterDir = js_controller_common_1.tools.getAdapterDir(adapter);
416
+ if (!adapterDir || !fs_extra_1.default.existsSync(`${adapterDir}/io-package.json`)) {
417
+ const message = `Adapter directory "${adapterDir}" does not exists`;
418
+ console.error(`host.${hostname} ${message}`);
419
+ throw new Error(message);
420
+ }
421
+ try {
422
+ adapterConf = await fs_extra_1.default.readJSON(path_1.default.join(adapterDir, 'io-package.json'));
423
+ }
424
+ catch (err) {
425
+ const message = `error reading io-package.json: ${err.message}`;
426
+ console.error(`host.${hostname} ${message}`, adapter);
427
+ throw new Error(message);
428
+ }
429
+ }
430
+ else {
431
+ adapterConf = _adapterConf;
432
+ }
433
+ let objs;
434
+ if (adapterConf.objects && adapterConf.objects.length > 0) {
435
+ objs = adapterConf.objects;
436
+ }
437
+ else {
438
+ objs = [];
439
+ }
440
+ // check if some dependencies are missing and install them if some found
441
+ await this._checkDependencies(adapter, adapterConf.common.dependencies, adapterConf.common.globalDependencies, this.params);
442
+ adapterConf.common.installedVersion = adapterConf.common.version;
443
+ if (adapterConf.common.news) {
444
+ delete adapterConf.common.news; // remove this information as it will be taken from repo
445
+ }
446
+ objs.push({
447
+ _id: `system.adapter.${adapterConf.common.name}`,
448
+ type: 'adapter',
449
+ common: adapterConf.common,
450
+ native: adapterConf.native
451
+ });
452
+ if (objs && objs.length) {
453
+ for (let i = 0; i < objs.length; i++) {
454
+ const obj = objs[i];
455
+ obj.from = `system.host.${js_controller_common_1.tools.getHostName()}.cli`;
456
+ obj.ts = Date.now();
457
+ try {
458
+ // @ts-expect-error #1917
459
+ await this.objects.extendObjectAsync(obj._id, obj);
460
+ }
461
+ catch (err) {
462
+ console.error(`host.${hostname} error setObject ${obj._id} ${err.message}`);
463
+ return js_controller_common_1.EXIT_CODES.CANNOT_SET_OBJECT;
464
+ }
465
+ console.log(`host.${hostname} object ${obj._id} created/updated`);
466
+ }
467
+ }
468
+ }
469
+ /**
470
+ * Installs given adapter
471
+ */
472
+ async installAdapter(adapter, repoUrl, _installCount) {
473
+ _installCount = _installCount || 0;
474
+ const fullName = adapter;
475
+ if (adapter.includes('@')) {
476
+ adapter = adapter.split('@')[0];
477
+ }
478
+ const adapterDir = js_controller_common_1.tools.getAdapterDir(adapter);
479
+ console.log(`host.${hostname} install adapter ${fullName}`);
480
+ if (!fs_extra_1.default.existsSync(adapterDir + '/io-package.json')) {
481
+ if (_installCount === 2) {
482
+ console.error(`host.${hostname} Cannot install ${adapter}`);
483
+ return this.processExit(js_controller_common_1.EXIT_CODES.CANNOT_INSTALL_NPM_PACKET);
484
+ }
485
+ _installCount++;
486
+ // @ts-expect-error TODO needs adaption
487
+ const { stoppedList } = await this.downloadPacket(repoUrl, fullName);
488
+ await this.installAdapter(adapter, repoUrl, _installCount);
489
+ await this.enableInstances(stoppedList, true); // even if unlikely make sure to reenable disabled instances
490
+ return adapter;
491
+ }
492
+ let adapterConf;
493
+ try {
494
+ adapterConf = fs_extra_1.default.readJSONSync(`${adapterDir}/io-package.json`);
495
+ }
496
+ catch (err) {
497
+ console.error(`host.${hostname} error: reading io-package.json ${err.message}`);
498
+ return this.processExit(js_controller_common_1.EXIT_CODES.INVALID_IO_PACKAGE_JSON);
499
+ }
500
+ // Check if the operation system is ok
501
+ if (adapterConf.common && adapterConf.common.os) {
502
+ if (typeof adapterConf.common.os === 'string' && adapterConf.common.os !== osPlatform) {
503
+ console.error(`host.${hostname} Adapter does not support current os. Required ${adapterConf.common.os}. Actual platform: ${osPlatform}`);
504
+ return this.processExit(js_controller_common_1.EXIT_CODES.INVALID_OS);
505
+ }
506
+ else {
507
+ if (!adapterConf.common.os.includes(osPlatform)) {
508
+ console.error(`host.${hostname} Adapter does not support current os. Required one of ${adapterConf.common.os.join(', ')}. Actual platform: ${osPlatform}`);
509
+ return this.processExit(js_controller_common_1.EXIT_CODES.INVALID_OS);
510
+ }
511
+ }
512
+ }
513
+ let engineVersion;
514
+ try {
515
+ // read directly from disk and not via require to allow "on the fly" updates of adapters.
516
+ const p = JSON.parse(fs_extra_1.default.readFileSync(adapterDir + '/package.json', 'utf8'));
517
+ engineVersion = p && p.engines && p.engines.node;
518
+ }
519
+ catch {
520
+ console.error(`host.${hostname}: Cannot read and parse "${adapterDir}/package.json"`);
521
+ }
522
+ // check node.js version if defined in package.json
523
+ if (engineVersion) {
524
+ if (!semver_1.default.satisfies(process.version.replace(/^v/, ''), engineVersion)) {
525
+ console.error(`host.${hostname} Adapter does not support current nodejs version. Required ${engineVersion}. Actual version: ${process.version}`);
526
+ return this.processExit(js_controller_common_1.EXIT_CODES.INVALID_NODE_VERSION);
527
+ }
528
+ }
529
+ if (adapterConf.common.osDependencies && adapterConf.common.osDependencies[process.platform]) {
530
+ // install linux/osx libraries
531
+ try {
532
+ this.packetManager = this.packetManager || new setupPacketManager_1.PacketManager();
533
+ await this.packetManager.install(adapterConf.common.osDependencies[process.platform]);
534
+ }
535
+ catch (err) {
536
+ console.error(`host.${hostname} Could not install required OS packages: ${err.message}`);
537
+ }
538
+ }
539
+ await this.upload.uploadAdapter(adapter, true, true);
540
+ await this.upload.uploadAdapter(adapter, false, true);
541
+ await this.callInstallOfAdapter(adapter, adapterConf);
542
+ await this._uploadStaticObjects(adapter);
543
+ await this.upload.upgradeAdapterObjects(adapter);
544
+ return adapter;
545
+ }
546
+ async callInstallOfAdapter(adapter, config) {
547
+ if (config.common.install) {
548
+ // Install node modules
549
+ let cmd = 'node ';
550
+ let fileFullName;
551
+ try {
552
+ fileFullName = await js_controller_common_1.tools.resolveAdapterMainFile(adapter);
553
+ }
554
+ catch {
555
+ return;
556
+ }
557
+ return new Promise(resolve => {
558
+ cmd += `"${fileFullName}" --install`;
559
+ console.log(`host.${hostname} command: ${cmd}`);
560
+ const child = child_process_1.default.exec(cmd, { windowsHide: true });
561
+ if (child.stderr) {
562
+ js_controller_common_1.tools.pipeLinewise(child.stderr, process.stdout);
563
+ }
564
+ child.on('exit', () => resolve(adapter));
565
+ });
566
+ }
567
+ }
568
+ /**
569
+ * Create adapter instance
570
+ */
571
+ async createInstance(adapter, options) {
572
+ let ignoreIfExists = false;
573
+ options = options || {};
574
+ options.host = options.host || js_controller_common_1.tools.getHostName();
575
+ if (options.enabled === 'true') {
576
+ options.enabled = true;
577
+ }
578
+ if (options.enabled === 'false') {
579
+ options.enabled = false;
580
+ }
581
+ if (options.ignoreIfExists !== undefined) {
582
+ ignoreIfExists = !!options.ignoreIfExists;
583
+ delete options.ignoreIfExists;
584
+ }
585
+ let doc;
586
+ let err;
587
+ try {
588
+ doc = await this.objects.getObjectAsync(`system.adapter.${adapter}`);
589
+ }
590
+ catch (_err) {
591
+ err = _err;
592
+ }
593
+ // Adapter is not installed - install it now
594
+ if (err || !doc || !doc.common.installedVersion) {
595
+ await this.installAdapter(adapter);
596
+ doc = await this.objects.getObjectAsync(`system.adapter.${adapter}`);
597
+ }
598
+ if (!doc) {
599
+ console.error('Adapter object not found, cannot create instance');
600
+ return void this.processExit(js_controller_common_1.EXIT_CODES.ADAPTER_NOT_FOUND);
601
+ }
602
+ // Check if some web pages should be uploaded
603
+ await this.upload.uploadAdapter(adapter, true, false);
604
+ await this.upload.uploadAdapter(adapter, false, false);
605
+ // @ts-expect-error #1917
606
+ const res = await this.objects.getObjectViewAsync('system', 'instance', {
607
+ startkey: `system.adapter.${adapter}.`,
608
+ endkey: `system.adapter.${adapter}.\u9999`
609
+ });
610
+ const systemConfig = await this.objects.getObjectAsync('system.config');
611
+ const defaultLogLevel = systemConfig && systemConfig.common && systemConfig.common.defaultLogLevel;
612
+ if (!res) {
613
+ console.error(`host.${hostname} error: view instanceStats`);
614
+ return this.processExit(js_controller_common_1.EXIT_CODES.CANNOT_READ_INSTANCES);
615
+ }
616
+ // Count started instances
617
+ if (doc.common.singleton && res.rows.length) {
618
+ if (ignoreIfExists) {
619
+ return;
620
+ }
621
+ console.error(`host.${hostname} error: this adapter does not allow multiple instances`);
622
+ return this.processExit(js_controller_common_1.EXIT_CODES.NO_MULTIPLE_INSTANCES_ALLOWED);
623
+ }
624
+ // check singletonHost one on host
625
+ if (doc.common.singletonHost) {
626
+ for (const row of res.rows) {
627
+ if (row.value.common.host === hostname) {
628
+ if (ignoreIfExists) {
629
+ return;
630
+ }
631
+ console.error(`host.${hostname} error: this adapter does not allow multiple instances on one host`);
632
+ return this.processExit(js_controller_common_1.EXIT_CODES.NO_MULTIPLE_INSTANCES_ALLOWED_ON_HOST);
633
+ }
634
+ }
635
+ }
636
+ let instance = null;
637
+ if (options.instance !== undefined) {
638
+ instance = options.instance;
639
+ // find max instance
640
+ if (res.rows.find(obj => parseInt(obj.id.split('.').pop(), 10) === instance)) {
641
+ console.error(`host.${hostname} error: instance yet exists`);
642
+ return this.processExit(js_controller_common_1.EXIT_CODES.INSTANCE_ALREADY_EXISTS);
643
+ }
644
+ }
645
+ else {
646
+ // find max instance
647
+ for (const row of res.rows) {
648
+ const iInstance = parseInt(row.id.split('.').pop(), 10);
649
+ if (instance === null || iInstance > instance) {
650
+ instance = iInstance;
651
+ }
652
+ }
653
+ if (instance === null) {
654
+ instance = 0;
655
+ }
656
+ else {
657
+ instance++;
658
+ }
659
+ }
660
+ const instanceObj = (0, deep_clone_1.default)(doc);
661
+ instanceObj._id = `system.adapter.${adapter}.${instance}`;
662
+ // @ts-expect-error we now convert the adapter object to an instance object
663
+ instanceObj.type = 'instance';
664
+ // @ts-expect-error types needed TODO
665
+ if (instanceObj.common.news) {
666
+ // @ts-expect-error types needed TODO
667
+ delete instanceObj.common.news; // remove this information as it could be big, but it will be taken from repo
668
+ }
669
+ instanceObj.common.enabled =
670
+ options.enabled === true || options.enabled === false
671
+ ? options.enabled
672
+ : instanceObj.common.enabled === true || instanceObj.common.enabled === false
673
+ ? instanceObj.common.enabled
674
+ : false;
675
+ // @ts-expect-error we now convert the adapter object to an instance object
676
+ instanceObj.common.host = options.host;
677
+ if (options.port) {
678
+ instanceObj.native = instanceObj.native || {};
679
+ instanceObj.native.port = options.port;
680
+ }
681
+ if (instanceObj.common.dataFolder && instanceObj.common.dataFolder.includes('%INSTANCE%')) {
682
+ instanceObj.common.dataFolder = instanceObj.common.dataFolder.replace(/%INSTANCE%/g, instance.toString());
683
+ }
684
+ if (defaultLogLevel) {
685
+ // @ts-expect-error TODO should it be loglevel or logLevel
686
+ instanceObj.common.loglevel = defaultLogLevel;
687
+ // @ts-expect-error TODO should it be loglevel or logLevel
688
+ }
689
+ else if (!instanceObj.common.loglevel) {
690
+ // @ts-expect-error TODO should it be loglevel or logLevel looked into my objects -> loglevel
691
+ instanceObj.common.loglevel = 'info';
692
+ }
693
+ console.log(`host.${hostname} create instance ${adapter}`);
694
+ let objs;
695
+ if (!instanceObj.common.onlyWWW && instanceObj.common.mode !== 'once') {
696
+ objs = js_controller_common_1.tools.getInstanceIndicatorObjects(`${adapter}.${instance}`, !!instanceObj.common.wakeup);
697
+ }
698
+ else {
699
+ objs = [];
700
+ }
701
+ const adapterDir = js_controller_common_1.tools.getAdapterDir(adapter);
702
+ if (adapterDir && fs_extra_1.default.existsSync(path_1.default.join(adapterDir, 'www'))) {
703
+ objs.push({
704
+ _id: `system.adapter.${adapter}.upload`,
705
+ type: 'state',
706
+ common: {
707
+ name: adapter + '.upload',
708
+ type: 'number',
709
+ read: true,
710
+ write: false,
711
+ role: 'indicator.state',
712
+ unit: '%',
713
+ def: 0,
714
+ desc: 'Upload process indicator'
715
+ },
716
+ native: {}
717
+ });
718
+ }
719
+ let adapterConf;
720
+ try {
721
+ adapterConf = fs_extra_1.default.readJSONSync(`${adapterDir}/io-package.json`);
722
+ }
723
+ catch (err) {
724
+ console.error(`host.${hostname} error: reading io-package.json ${err.message}`);
725
+ return this.processExit(js_controller_common_1.EXIT_CODES.INVALID_IO_PACKAGE_JSON);
726
+ }
727
+ adapterConf.instanceObjects = adapterConf.instanceObjects || [];
728
+ adapterConf.objects = adapterConf.objects || [];
729
+ const defStates = new Map();
730
+ // Create only for this instance the predefined in io-package.json objects
731
+ // It is not necessary to write "system.adapter.name.N." in the object '_id'
732
+ for (const instanceObject of adapterConf.instanceObjects) {
733
+ instanceObject._id = `${adapter}.${instance}${instanceObject._id ? `.${instanceObject._id}` : ''}`;
734
+ if (instanceObject.common) {
735
+ if (instanceObject.common.name) {
736
+ // if name has many languages
737
+ if (typeof instanceObject.common.name === 'object') {
738
+ Object.keys(instanceObject.common.name).forEach(lang => (instanceObject.common.name[lang] = instanceObject.common.name[lang].replace('%INSTANCE%', instance)));
739
+ }
740
+ else {
741
+ instanceObject.common.name = instanceObject.common.name.replace('%INSTANCE%', instance);
742
+ }
743
+ }
744
+ if (instanceObject.common.desc) {
745
+ // if name has many languages
746
+ if (typeof instanceObject.common.desc === 'object') {
747
+ Object.keys(instanceObject.common.desc).forEach(lang => (instanceObject.common.desc[lang] = instanceObject.common.desc[lang].replace('%INSTANCE%', instance)));
748
+ }
749
+ else {
750
+ instanceObject.common.desc = instanceObject.common.desc.replace('%INSTANCE%', instance);
751
+ }
752
+ }
753
+ }
754
+ objs.push(instanceObject);
755
+ if (instanceObject.common && instanceObject.common.def !== undefined) {
756
+ defStates.set(instanceObject._id, {
757
+ val: instanceObject.common.def
758
+ });
759
+ }
760
+ }
761
+ // upload all objects
762
+ for (const obj of objs) {
763
+ try {
764
+ js_controller_common_1.tools.validateGeneralObjectProperties(obj);
765
+ }
766
+ catch (err) {
767
+ // todo: in the future we will not create this object
768
+ console.warn(`host.${hostname} Object ${obj._id} is invalid: ${err.message}`);
769
+ console.warn(`host.${hostname} This object will not be created in future versions. Please report this to the developer.`);
770
+ }
771
+ obj.from = `system.host.${js_controller_common_1.tools.getHostName()}.cli`;
772
+ obj.ts = Date.now();
773
+ try {
774
+ await this.objects.setObjectAsync(obj._id, obj);
775
+ console.log(`host.${hostname} object ${obj._id} created`);
776
+ }
777
+ catch (err) {
778
+ console.error(`host.${hostname} error: ${err.message}`);
779
+ }
780
+ }
781
+ // sets the default states if any given
782
+ for (const [id, defState] of defStates) {
783
+ defState.ack = true;
784
+ defState.from = `system.host.${js_controller_common_1.tools.getHostName()}.cli`;
785
+ try {
786
+ await this.states.setStateAsync(id, defState);
787
+ console.log(`host.${hostname} Set default value of ${id}: ${defState.val}`);
788
+ }
789
+ catch (err) {
790
+ console.error(`host.${hostname} error: ${err.message}`);
791
+ }
792
+ }
793
+ instanceObj.from = `system.host.${js_controller_common_1.tools.getHostName()}.cli`;
794
+ instanceObj.ts = Date.now();
795
+ try {
796
+ await this.objects.setObjectAsync(instanceObj._id, instanceObj);
797
+ console.log(`host.${hostname} object ${instanceObj._id} created`);
798
+ }
799
+ catch (err) {
800
+ console.error(`host.${hostname} error: ${err.message}`);
801
+ }
802
+ }
803
+ /**
804
+ * Enumerate all instances of an adapter
805
+ */
806
+ async _enumerateAdapterInstances(knownObjIDs, notDeleted, adapter, instance) {
807
+ if (!notDeleted) {
808
+ notDeleted = [];
809
+ }
810
+ // We need to filter the instances using RegExp, because the naive approach with startkey/endkey
811
+ // startkey: system.adapter.mqtt
812
+ // endkey: system.adapter.mqtt.\u9999
813
+ // matches system.adapter.mqtt AND system.adapter.mqtt-client
814
+ const instanceRegex = instance !== undefined
815
+ ? new RegExp(`^system\\.adapter\\.${adapter}\\.${instance}$`)
816
+ : new RegExp(`^system\\.adapter\\.${adapter}\\.\\d+$`);
817
+ try {
818
+ const doc = await this.objects.getObjectView('system', 'instance', {
819
+ startkey: `system.adapter.${adapter}${instance !== undefined ? `.${instance}` : ''}`,
820
+ endkey: `system.adapter.${adapter}${instance !== undefined ? `.${instance}` : ''}\u9999`
821
+ });
822
+ // add non-duplicates to the list (if instance not given -> only for this host)
823
+ const newObjIDs = doc.rows
824
+ // only the ones with an ID ...
825
+ .filter((row) => { var _a; return !!((_a = row === null || row === void 0 ? void 0 : row.value) === null || _a === void 0 ? void 0 : _a._id); })
826
+ // ... that matches the pattern
827
+ .filter(row => instanceRegex.test(row.value._id))
828
+ // if instance given also delete from foreign host else only instance on this host
829
+ .filter(row => {
830
+ var _a, _b;
831
+ if (instance !== undefined || !((_a = row.value.common) === null || _a === void 0 ? void 0 : _a.host) || ((_b = row.value.common) === null || _b === void 0 ? void 0 : _b.host) === hostname) {
832
+ return true;
833
+ }
834
+ else {
835
+ if (!notDeleted.includes(row.value._id)) {
836
+ notDeleted.push(row.value._id);
837
+ }
838
+ return false;
839
+ }
840
+ })
841
+ .map(row => row.value._id)
842
+ .filter(id => !knownObjIDs.includes(id));
843
+ knownObjIDs.push(...newObjIDs);
844
+ if (newObjIDs.length > 0) {
845
+ console.log(`host.${hostname} Counted ${newObjIDs.length} instances of ${adapter}${instance !== undefined ? `.${instance}` : ''}`);
846
+ }
847
+ }
848
+ catch (err) {
849
+ err !== js_controller_common_1.tools.ERRORS.ERROR_NOT_FOUND &&
850
+ err.message !== js_controller_common_1.tools.ERRORS.ERROR_NOT_FOUND &&
851
+ console.error(`host.${hostname} error: ${err.message}`);
852
+ }
853
+ }
854
+ /**
855
+ * Enumerate all meta objects of an adapter
856
+ */
857
+ async _enumerateAdapterMeta(knownObjIDs, adapter, metaFilesToDelete) {
858
+ try {
859
+ // @ts-expect-error #1917
860
+ const doc = await this.objects.getObjectViewAsync('system', 'meta', {
861
+ startkey: `${adapter}.`,
862
+ endkey: `${adapter}.\u9999`
863
+ });
864
+ if (doc && doc.rows.length !== 0) {
865
+ const adapterRegex = new RegExp(`^${adapter}\\.`);
866
+ // add non-duplicates to the list
867
+ const newObjs = doc.rows
868
+ .filter(row => row && row.value && row.value._id)
869
+ .map(row => row.value._id)
870
+ .filter(id => adapterRegex.test(id))
871
+ .filter(id => knownObjIDs.indexOf(id) === -1);
872
+ knownObjIDs.push(...newObjs);
873
+ // meta ids can also be present as files
874
+ metaFilesToDelete.push(...newObjs);
875
+ if (newObjs.length) {
876
+ console.log(`host.${hostname} Counted ${newObjs.length} meta of ${adapter}`);
877
+ }
878
+ }
879
+ }
880
+ catch (err) {
881
+ err !== js_controller_common_1.tools.ERRORS.ERROR_NOT_FOUND &&
882
+ err.message !== js_controller_common_1.tools.ERRORS.ERROR_NOT_FOUND &&
883
+ console.error(`host.${hostname} error: ${err.message}`);
884
+ }
885
+ }
886
+ async _enumerateAdapters(knownObjIDs, adapter) {
887
+ // This does not really enumerate the adapters, but finds the adapter object
888
+ // if it exists and adds it to the list
889
+ try {
890
+ const obj = await this.objects.getObjectAsync(`system.adapter.${adapter}`);
891
+ if (obj) {
892
+ if (obj.common && obj.common.nondeletable) {
893
+ // If the adapter is non-deletable, mark it as not installed
894
+ console.log(`host.${hostname} Adapter ${adapter} cannot be deleted completely, because it is marked non-deletable.`);
895
+ obj.common.installedVersion = '';
896
+ obj.from = `system.host.${js_controller_common_1.tools.getHostName()}.cli`;
897
+ obj.ts = Date.now();
898
+ await this.objects.setObjectAsync(obj._id, obj);
899
+ return js_controller_common_1.EXIT_CODES.CANNOT_DELETE_NON_DELETABLE;
900
+ }
901
+ else {
902
+ // The adapter is deletable, remember it for deletion
903
+ knownObjIDs.push(obj._id);
904
+ console.log(`host.${hostname} Counted 1 adapter for ${adapter}`);
905
+ return js_controller_common_1.EXIT_CODES.NO_ERROR;
906
+ }
907
+ }
908
+ }
909
+ catch (err) {
910
+ console.error(`host.${hostname} Cannot enumerate adapters: ${err.message}`);
911
+ }
912
+ }
913
+ /**
914
+ * Enumerates the devices of an adapter (or instance)
915
+ *
916
+ * @param knownObjIDs The already known object ids
917
+ * @param adapter The adapter to enumerate the devices for
918
+ * @param instance The instance to enumerate the devices for (optional)
919
+ */
920
+ async _enumerateAdapterDevices(knownObjIDs, adapter, instance) {
921
+ const adapterRegex = new RegExp(`^${adapter}${instance ? `\\.${instance}` : ''}\\.`);
922
+ try {
923
+ // @ts-expect-error #1917
924
+ const doc = await this.objects.getObjectViewAsync('system', 'device', {
925
+ startkey: `${adapter}${instance !== undefined ? `.${instance}` : ''}`,
926
+ endkey: `${adapter}${instance !== undefined ? `.${instance}` : ''}\u9999`
927
+ });
928
+ if (doc && doc.rows && doc.rows.length) {
929
+ // add non-duplicates to the list
930
+ const newObjs = doc.rows
931
+ .filter(row => row && row.value && row.value._id)
932
+ .map(row => row.value._id)
933
+ .filter(id => adapterRegex.test(id))
934
+ .filter(id => !knownObjIDs.includes(id));
935
+ knownObjIDs.push(...newObjs);
936
+ if (newObjs.length > 0) {
937
+ console.log(`host.${hostname} Counted ${newObjs.length} devices of ${adapter}${instance !== undefined ? `.${instance}` : ''}`);
938
+ }
939
+ }
940
+ }
941
+ catch (err) {
942
+ err !== js_controller_common_1.tools.ERRORS.ERROR_NOT_FOUND &&
943
+ err.message !== js_controller_common_1.tools.ERRORS.ERROR_NOT_FOUND &&
944
+ console.error(`host.${hostname} error: ${err.message}`);
945
+ }
946
+ }
947
+ /**
948
+ * Enumerates the channels of an adapter (or instance)
949
+ * @param knownObjIDs The already known object ids
950
+ * @param adapter The adapter to enumerate the channels for
951
+ * @param instance The instance to enumerate the channels for (optional)
952
+ */
953
+ async _enumerateAdapterChannels(knownObjIDs, adapter, instance) {
954
+ const adapterRegex = new RegExp(`^${adapter}${instance ? `\\.${instance}` : ''}\\.`);
955
+ try {
956
+ // @ts-expect-error #1917
957
+ const doc = await this.objects.getObjectViewAsync('system', 'channel', {
958
+ startkey: `${adapter}${instance !== undefined ? `.${instance}` : ''}`,
959
+ endkey: `${adapter}${instance !== undefined ? `.${instance}` : ''}\u9999`
960
+ });
961
+ if (doc && doc.rows && doc.rows.length) {
962
+ // add non-duplicates to the list
963
+ const newObjs = doc.rows
964
+ .filter(row => row && row.value && row.value._id)
965
+ .map(row => row.value._id)
966
+ .filter(id => adapterRegex.test(id))
967
+ .filter(id => !knownObjIDs.includes(id));
968
+ knownObjIDs.push(...newObjs);
969
+ if (newObjs.length > 0) {
970
+ console.log(`host.${hostname} Counted ${newObjs.length} channels of ${adapter}${instance ? `.${instance}` : ''}`);
971
+ }
972
+ }
973
+ }
974
+ catch (err) {
975
+ err !== js_controller_common_1.tools.ERRORS.ERROR_NOT_FOUND &&
976
+ err.message !== js_controller_common_1.tools.ERRORS.ERROR_NOT_FOUND &&
977
+ console.error(`host.${hostname} error: ${err.message}`);
978
+ }
979
+ }
980
+ /**
981
+ * Enumerates the states of an adapter (or instance)
982
+ * @param knownObjIDs The already known object ids
983
+ * @param adapter The adapter to enumerate the states for
984
+ * @param instance The instance to enumerate the states for (optional)
985
+ */
986
+ async _enumerateAdapterStateObjects(knownObjIDs, adapter, instance) {
987
+ const adapterRegex = new RegExp(`^${adapter}${instance ? `\\.${instance}` : ''}\\.`);
988
+ const sysAdapterRegex = new RegExp(`^system\\.adapter\\.${adapter}${instance ? `\\.${instance}` : ''}\\.`);
989
+ try {
990
+ // @ts-expect-error #1917
991
+ let doc = await this.objects.getObjectViewAsync('system', 'state', {
992
+ startkey: `${adapter}${instance !== undefined ? `.${instance}` : ''}`,
993
+ endkey: `${adapter}${instance !== undefined ? `.${instance}` : ''}\u9999`
994
+ });
995
+ if (doc && doc.rows && doc.rows.length) {
996
+ // add non-duplicates to the list
997
+ const newObjs = doc.rows
998
+ .filter(row => row && row.value && row.value._id)
999
+ .map(row => row.value._id)
1000
+ .filter(id => adapterRegex.test(id))
1001
+ .filter(id => !knownObjIDs.includes(id));
1002
+ knownObjIDs.push(...newObjs);
1003
+ if (newObjs.length > 0) {
1004
+ console.log(`host.${hostname} Counted ${newObjs.length} states of ${adapter}${instance ? `.${instance}` : ''}`);
1005
+ }
1006
+ }
1007
+ // @ts-expect-error #1917
1008
+ doc = await this.objects.getObjectViewAsync('system', 'state', {
1009
+ startkey: `system.adapter.${adapter}${instance !== undefined ? `.${instance}` : ''}`,
1010
+ endkey: `system.adapter.${adapter}${instance !== undefined ? `.${instance}` : ''}\u9999`
1011
+ });
1012
+ if (doc && doc.rows && doc.rows.length) {
1013
+ // add non-duplicates to the list
1014
+ const newObjs = doc.rows
1015
+ .filter(row => row && row.value && row.value._id)
1016
+ .map(row => row.value._id)
1017
+ .filter(id => sysAdapterRegex.test(id))
1018
+ .filter(id => !knownObjIDs.includes(id));
1019
+ knownObjIDs.push(...newObjs);
1020
+ if (newObjs.length > 0) {
1021
+ console.log(`host.${hostname} Counted ${newObjs.length} states of system.adapter.${adapter}${instance ? `.${instance}` : ''}`);
1022
+ }
1023
+ }
1024
+ }
1025
+ catch (err) {
1026
+ err !== js_controller_common_1.tools.ERRORS.ERROR_NOT_FOUND &&
1027
+ err.message !== js_controller_common_1.tools.ERRORS.ERROR_NOT_FOUND &&
1028
+ console.error(`host.${hostname} error: ${err.message}`);
1029
+ }
1030
+ }
1031
+ /**
1032
+ * Enumerates the docs of an adapter (or instance)
1033
+ * @param knownObjIDs The already known object ids
1034
+ * @param adapter The adapter to enumerate the states for
1035
+ * @param instance The instance to enumerate the states for (optional)
1036
+ */
1037
+ async _enumerateAdapterDocs(knownObjIDs, adapter, instance) {
1038
+ const adapterRegex = new RegExp(`^${adapter}${instance ? `\\.${instance}` : ''}\\.`);
1039
+ const sysAdapterRegex = new RegExp(`^system\\.adapter\\.${adapter}${instance ? `\\.${instance}` : ''}\\.`);
1040
+ try {
1041
+ // @ts-expect-error #1917
1042
+ const doc = await this.objects.getObjectListAsync({ include_docs: true });
1043
+ if (doc && doc.rows && doc.rows.length) {
1044
+ // add non-duplicates to the list
1045
+ const newObjs = doc.rows
1046
+ .filter(row => row && row.value && row.value._id)
1047
+ .map(row => row.value._id)
1048
+ .filter(id => adapterRegex.test(id) || sysAdapterRegex.test(id))
1049
+ .filter(id => !knownObjIDs.includes(id));
1050
+ knownObjIDs.push(...newObjs);
1051
+ if (newObjs.length > 0) {
1052
+ console.log(`host.${hostname} Counted ${newObjs.length} objects of ${adapter}${instance ? `.${instance}` : ''}`);
1053
+ }
1054
+ }
1055
+ }
1056
+ catch (err) {
1057
+ err !== js_controller_common_1.tools.ERRORS.ERROR_NOT_FOUND &&
1058
+ err.message !== js_controller_common_1.tools.ERRORS.ERROR_NOT_FOUND &&
1059
+ console.error(`host.${hostname} error: ${err.message}`);
1060
+ }
1061
+ }
1062
+ /**
1063
+ * Enumerate all state IDs of an adapter (or instance)
1064
+ */
1065
+ async _enumerateAdapterStates(knownStateIDs, adapter, instance) {
1066
+ for (const pattern of [
1067
+ `io.${adapter}.${instance ? instance + '.' : ''}*`,
1068
+ `messagebox.${adapter}.${instance ? instance + '.' : ''}*`,
1069
+ `log.${adapter}.${instance ? instance + '.' : ''}*`,
1070
+ `${adapter}.${instance ? instance + '.' : ''}*`,
1071
+ `system.adapter.${adapter}.${instance ? instance + '.' : ''}*`
1072
+ ]) {
1073
+ try {
1074
+ // @ts-expect-error #1917
1075
+ const ids = await this.states.getKeys(pattern);
1076
+ if (ids && ids.length) {
1077
+ // add non-duplicates to the list
1078
+ const newStates = ids.filter(id => !knownStateIDs.includes(id));
1079
+ knownStateIDs.push(...newStates);
1080
+ if (newStates.length) {
1081
+ console.log(`host.${hostname} Counted ${newStates.length} states (${pattern}) from states`);
1082
+ }
1083
+ }
1084
+ }
1085
+ catch (err) {
1086
+ console.error(`host.${hostname} Cannot get keys async: ${err.message}`);
1087
+ }
1088
+ }
1089
+ }
1090
+ /**
1091
+ * delete WWW pages, objects and meta files
1092
+ */
1093
+ async _deleteAdapterFiles(adapter, metaFilesToDelete) {
1094
+ // special files, which are not meta (vis widgets), combined with meta object ids
1095
+ const filesToDelete = [
1096
+ { id: `vis`, name: `widgets/${adapter}` },
1097
+ { id: `vis`, name: `widgets/${adapter}.html` },
1098
+ { id: adapter },
1099
+ { id: `${adapter}.admin` },
1100
+ ...metaFilesToDelete.map(id => ({ id }))
1101
+ ];
1102
+ for (const file of filesToDelete) {
1103
+ const id = typeof file === 'object' ? file.id : file;
1104
+ try {
1105
+ // @ts-expect-error #1917
1106
+ await this.objects.unlinkAsync(id, file.name || '');
1107
+ console.log(`host.${hostname} file ${id + (file.name ? `/${file.name}` : '')} deleted`);
1108
+ }
1109
+ catch (err) {
1110
+ err !== js_controller_common_1.tools.ERRORS.ERROR_NOT_FOUND &&
1111
+ err.message !== js_controller_common_1.tools.ERRORS.ERROR_NOT_FOUND &&
1112
+ console.error(`host.${hostname} Cannot delete ${id} files folder: ${err.message}`);
1113
+ }
1114
+ }
1115
+ for (const objId of [adapter, `${adapter}.admin`]) {
1116
+ try {
1117
+ await this.objects.delObjectAsync(objId);
1118
+ console.log(`host.${hostname} object ${objId} deleted`);
1119
+ }
1120
+ catch (err) {
1121
+ err !== js_controller_common_1.tools.ERRORS.ERROR_NOT_FOUND &&
1122
+ err.message !== js_controller_common_1.tools.ERRORS.ERROR_NOT_FOUND &&
1123
+ console.error(`host.${hostname} cannot delete objects: ${err.message}`);
1124
+ }
1125
+ }
1126
+ }
1127
+ async _deleteAdapterStates(stateIDs) {
1128
+ if (stateIDs.length > 1000) {
1129
+ console.log(`host.${hostname} Deleting ${stateIDs.length} state(s). Be patient...`);
1130
+ }
1131
+ else if (stateIDs.length) {
1132
+ console.log(`host.${hostname} Deleting ${stateIDs.length} state(s).`);
1133
+ }
1134
+ while (stateIDs.length > 0) {
1135
+ if (stateIDs.length % 200 === 0) {
1136
+ // write progress report
1137
+ console.log(`host.${hostname}: Only ${stateIDs.length} states left to be deleted.`);
1138
+ }
1139
+ // try to delete the current state
1140
+ try {
1141
+ // @ts-expect-error #1917
1142
+ await this.states.delState(stateIDs.pop());
1143
+ }
1144
+ catch (err) {
1145
+ // yep that works!
1146
+ err !== js_controller_common_1.tools.ERRORS.ERROR_NOT_FOUND &&
1147
+ err.message !== js_controller_common_1.tools.ERRORS.ERROR_NOT_FOUND &&
1148
+ console.error(`host.${hostname} Cannot delete states: ${err.message}`);
1149
+ }
1150
+ }
1151
+ }
1152
+ async _deleteAdapterObjects(objIDs) {
1153
+ if (objIDs.length > 1000) {
1154
+ console.log(`host.${hostname} Deleting ${objIDs.length} object(s). Be patient...`);
1155
+ }
1156
+ else if (objIDs.length) {
1157
+ console.log(`host.${hostname} Deleting ${objIDs.length} object(s).`);
1158
+ }
1159
+ let allEnums;
1160
+ if (objIDs.length > 1) {
1161
+ try {
1162
+ // cache all enums, else it will be slow to delete many objects
1163
+ allEnums = await js_controller_common_1.tools.getAllEnums(this.objects);
1164
+ }
1165
+ catch (e) {
1166
+ console.error(`host.${hostname}: Could not retrieve all enums: ${e.message}`);
1167
+ }
1168
+ }
1169
+ while (objIDs.length > 0) {
1170
+ if (objIDs.length % 200 === 0) {
1171
+ // write progress report
1172
+ console.log(`host.${hostname}: Only ${objIDs.length} objects left to be deleted.`);
1173
+ }
1174
+ // try to delete the current object
1175
+ try {
1176
+ const id = objIDs.pop();
1177
+ await this.objects.delObjectAsync(id);
1178
+ await js_controller_common_1.tools.removeIdFromAllEnums(this.objects, id, allEnums);
1179
+ }
1180
+ catch (e) {
1181
+ if (e !== js_controller_common_1.tools.ERRORS.ERROR_NOT_FOUND && e.message !== js_controller_common_1.tools.ERRORS.ERROR_NOT_FOUND) {
1182
+ console.error(`host.${hostname} cannot delete objects: ${e.message}`);
1183
+ }
1184
+ }
1185
+ }
1186
+ }
1187
+ /**
1188
+ * Deltes given adapter from filesystem and removes all instances
1189
+ */
1190
+ async deleteAdapter(adapter) {
1191
+ const knownObjectIDs = [];
1192
+ const metaFilesToDelete = [];
1193
+ const notDeletedObjectIDs = [];
1194
+ const knownStateIDs = [];
1195
+ let resultCode = js_controller_common_1.EXIT_CODES.NO_ERROR;
1196
+ const _uninstallNpm = async () => {
1197
+ try {
1198
+ // find the adapter's io-package.json
1199
+ const adapterNpm = `${js_controller_common_1.tools.appName}.${adapter}`;
1200
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
1201
+ const ioPack = require(`${adapterNpm}/io-package.json`); // yep, it's that easy
1202
+ if (!ioPack.common || !ioPack.common.nondeletable) {
1203
+ await this._npmUninstall(adapterNpm, false);
1204
+ // after uninstalling we have to restart the defined adapters
1205
+ if (ioPack.common.restartAdapters) {
1206
+ if (!Array.isArray(ioPack.common.restartAdapters)) {
1207
+ // its not an array, now it can only be a single adapter as string
1208
+ if (typeof ioPack.common.restartAdapters !== 'string') {
1209
+ return;
1210
+ }
1211
+ ioPack.common.restartAdapters = [ioPack.common.restartAdapters];
1212
+ }
1213
+ if (ioPack.common.restartAdapters.length && ioPack.common.restartAdapters[0]) {
1214
+ const instances = await js_controller_common_1.tools.getAllInstances(ioPack.common.restartAdapters, this.objects);
1215
+ if (instances && instances.length) {
1216
+ for (const instance of instances) {
1217
+ const obj = await this.objects.getObjectAsync(instance);
1218
+ // if instance is enabled
1219
+ if (obj && obj.common && obj.common.enabled) {
1220
+ try {
1221
+ obj.common.enabled = false; // disable instance
1222
+ obj.from = `system.host.${js_controller_common_1.tools.getHostName()}.cli`;
1223
+ obj.ts = Date.now();
1224
+ await this.objects.setObjectAsync(obj._id, obj);
1225
+ obj.common.enabled = true; // enable instance
1226
+ obj.from = `system.host.${js_controller_common_1.tools.getHostName()}.cli`;
1227
+ obj.ts = Date.now();
1228
+ await this.objects.setObjectAsync(obj._id, obj);
1229
+ console.log(`Adapter "${obj._id}" restarted.`);
1230
+ }
1231
+ catch (err) {
1232
+ console.error(`Cannot restart adapter "${obj._id}": ${err.message}`);
1233
+ }
1234
+ }
1235
+ }
1236
+ }
1237
+ }
1238
+ }
1239
+ }
1240
+ }
1241
+ catch (err) {
1242
+ console.error(`Error deleting adapter ${adapter} from disk: ${err.message}`);
1243
+ console.error(`You might have to delete it yourself!`);
1244
+ }
1245
+ };
1246
+ try {
1247
+ // detect if all instances on this host, if not so the www and admin must not be deleted
1248
+ await this._enumerateAdapterInstances(knownObjectIDs, notDeletedObjectIDs, adapter);
1249
+ if (notDeletedObjectIDs.length) {
1250
+ // just delete all instances on this host and then delete npm
1251
+ for (const knownObjectID of knownObjectIDs) {
1252
+ await this.deleteInstance(adapter, parseInt(knownObjectID.split('.').pop()));
1253
+ }
1254
+ // remove adapter from custom
1255
+ await this._removeCustomFromObjects([adapter]);
1256
+ await _uninstallNpm();
1257
+ }
1258
+ else {
1259
+ // we are not allowed to delete last instance if another instance depends on us
1260
+ const dependentInstance = await this._hasDependentInstances(adapter);
1261
+ if (dependentInstance) {
1262
+ console.log(`Cannot remove adapter "${adapter}", because instance "${dependentInstance}" depends on it!`);
1263
+ return js_controller_common_1.EXIT_CODES.CANNOT_DELETE_DEPENDENCY;
1264
+ }
1265
+ const instances = knownObjectIDs.map(id => `${adapter}.${id.split('.').pop()}`);
1266
+ await this._enumerateAdapterMeta(knownObjectIDs, adapter, metaFilesToDelete);
1267
+ resultCode = (await this._enumerateAdapters(knownObjectIDs, adapter)) || resultCode;
1268
+ await this._enumerateAdapterDevices(knownObjectIDs, adapter);
1269
+ await this._enumerateAdapterChannels(knownObjectIDs, adapter);
1270
+ await this._enumerateAdapterStateObjects(knownObjectIDs, adapter);
1271
+ await this._enumerateAdapterStates(knownStateIDs, adapter);
1272
+ await this._enumerateAdapterDocs(knownObjectIDs, adapter);
1273
+ await this._deleteAdapterFiles(adapter, metaFilesToDelete);
1274
+ await this._deleteAdapterObjects(knownObjectIDs);
1275
+ await this._deleteAdapterStates(knownStateIDs);
1276
+ if (this.params.custom) {
1277
+ // remove adapter from custom
1278
+ await this._removeCustomFromObjects([...instances, adapter]);
1279
+ }
1280
+ await _uninstallNpm();
1281
+ }
1282
+ }
1283
+ catch (err) {
1284
+ console.error(`There was an error uninstalling ${adapter} on ${hostname}: ${err.message}`);
1285
+ }
1286
+ return resultCode;
1287
+ }
1288
+ /**
1289
+ * Deletes given instance of an adapter
1290
+ *
1291
+ * @param adapter adapter name like hm-rpc
1292
+ * @param instance e.g. 1
1293
+ */
1294
+ async deleteInstance(adapter, instance) {
1295
+ const knownObjectIDs = [];
1296
+ const knownStateIDs = [];
1297
+ // we are not allowed to delete last instance if another instance depends on us
1298
+ const dependentInstance = await this._hasDependentInstances(adapter, instance);
1299
+ if (dependentInstance) {
1300
+ console.log(`Cannot remove instance "${adapter}.${instance}", because instance "${dependentInstance}" depends on it!`);
1301
+ return js_controller_common_1.EXIT_CODES.CANNOT_DELETE_DEPENDENCY;
1302
+ }
1303
+ await this._enumerateAdapterInstances(knownObjectIDs, [], adapter, instance);
1304
+ await this._enumerateAdapterDevices(knownObjectIDs, adapter, instance);
1305
+ await this._enumerateAdapterChannels(knownObjectIDs, adapter, instance);
1306
+ await this._enumerateAdapterStateObjects(knownObjectIDs, adapter, instance);
1307
+ await this._enumerateAdapterStates(knownStateIDs, adapter, instance);
1308
+ await this._enumerateAdapterDocs(knownObjectIDs, adapter, instance);
1309
+ await this._deleteAdapterObjects(knownObjectIDs);
1310
+ await this._deleteAdapterStates(knownStateIDs);
1311
+ if (this.params.custom) {
1312
+ // delete instance from custom
1313
+ await this._removeCustomFromObjects([`${adapter}.${instance}`]);
1314
+ }
1315
+ // TODO delete meta objects - I think a recursive deletion of all child object would be less effort.
1316
+ }
1317
+ /**
1318
+ * Removes the custom attribute of the provided adapter/instance
1319
+ *
1320
+ * @param ids - id of the adapter/instance to check for
1321
+ */
1322
+ async _removeCustomFromObjects(ids) {
1323
+ var _a;
1324
+ // get all objects which have a custom attribute
1325
+ // @ts-expect-error # 1917
1326
+ const res = await this.objects.getObjectViewAsync('system', 'custom', {
1327
+ startkey: '',
1328
+ endkey: '\u9999'
1329
+ });
1330
+ if (res && res.rows) {
1331
+ for (const row of res.rows) {
1332
+ let obj;
1333
+ for (const id of ids) {
1334
+ if (Object.prototype.hasOwnProperty.call(row.value, id)) {
1335
+ if (!obj) {
1336
+ obj = await this.objects.getObjectAsync(row.id);
1337
+ }
1338
+ if ((_a = obj === null || obj === void 0 ? void 0 : obj.common) === null || _a === void 0 ? void 0 : _a.custom) {
1339
+ delete obj.common.custom[id];
1340
+ }
1341
+ }
1342
+ }
1343
+ if (obj) {
1344
+ // if we have removed a custom attribute, set it to db
1345
+ await this.objects.setObjectAsync(row.id, obj);
1346
+ }
1347
+ }
1348
+ }
1349
+ }
1350
+ /**
1351
+ * Installs an adapter from given url
1352
+ */
1353
+ async installAdapterFromUrl(url, name) {
1354
+ // If the user provided an URL, try to parse it into known ways to represent a Github URL
1355
+ let parsedUrl;
1356
+ try {
1357
+ parsedUrl = new url_1.URL(url);
1358
+ }
1359
+ catch {
1360
+ /* ignore, not a valid URL */
1361
+ }
1362
+ const debug = process.argv.includes('--debug');
1363
+ if (parsedUrl && parsedUrl.hostname === 'github.com') {
1364
+ if (!js_controller_common_1.tools.isGithubPathname(parsedUrl.pathname)) {
1365
+ return console.error(`Cannot install from GitHub. Invalid URL ${url}`);
1366
+ }
1367
+ // This is a URL we can parse
1368
+ // @ts-expect-error check if type check above is enough
1369
+ const { repo, user, commit } = js_controller_common_1.tools.parseGithubPathname(parsedUrl.pathname);
1370
+ if (!commit) {
1371
+ // No commit given, try to get it from the API
1372
+ try {
1373
+ const result = await (0, axios_1.default)(`http://api.github.com/repos/${user}/${repo}/commits`, {
1374
+ headers: {
1375
+ 'User-Agent': 'ioBroker Adapter install',
1376
+ // @ts-expect-error should be okay..
1377
+ validateStatus: status => status === 200
1378
+ }
1379
+ });
1380
+ if (result.data && Array.isArray(result.data) && result.data.length >= 1 && result.data[0].sha) {
1381
+ url = `${user}/${repo}#${result.data[0].sha}`;
1382
+ }
1383
+ else {
1384
+ console.log(`Info: Can not get current GitHub commit, only remember that we installed from GitHub.`);
1385
+ url = `${user}/${repo}`;
1386
+ }
1387
+ }
1388
+ catch (err) {
1389
+ console.log(`Info: Can not get current GitHub commit, only remember that we installed from GitHub: ${err.message}`);
1390
+ // Install using the npm Github URL syntax `npm i user/repo_name`:
1391
+ url = `${user}/${repo}`;
1392
+ }
1393
+ }
1394
+ else {
1395
+ // We've extracted all we need from the URL
1396
+ url = `${user}/${repo}#${commit}`;
1397
+ }
1398
+ }
1399
+ console.log(`install ${url}`);
1400
+ // Try to extract name from URL
1401
+ if (!name) {
1402
+ const reNpmPacket = new RegExp('^' + js_controller_common_1.tools.appName + '\\.([-_\\w\\d]+)(@.*)?$', 'i');
1403
+ const match = reNpmPacket.exec(url); // we have iobroker.adaptername@1.2.3
1404
+ if (match) {
1405
+ name = match[1];
1406
+ }
1407
+ else if (url.match(/\.(tgz|gz|zip|tar\.gz)$/)) {
1408
+ const parts = url.split('/');
1409
+ const last = parts.pop();
1410
+ const mm = last.match(/\.([-_\w\d]+)-[.\d]+/);
1411
+ if (mm) {
1412
+ name = mm[1];
1413
+ }
1414
+ }
1415
+ else {
1416
+ const githubUrlParts = js_controller_common_1.tools.parseShortGithubUrl(url);
1417
+ // Try to extract the adapter name from the github url if possible
1418
+ // Otherwise fall back to the complete URL
1419
+ if (githubUrlParts) {
1420
+ name = githubUrlParts.repo;
1421
+ }
1422
+ else {
1423
+ name = url;
1424
+ }
1425
+ // Remove the leading `iobroker.` from the name
1426
+ const reG = new RegExp(js_controller_common_1.tools.appName + '\\.([-_\\w\\d]+)$', 'i');
1427
+ const match = reG.exec(name);
1428
+ if (match) {
1429
+ name = match[1];
1430
+ }
1431
+ }
1432
+ }
1433
+ if (name === 'js-controller') {
1434
+ console.error(`Cannot install "js-controller" from url, use "${js_controller_common_1.tools.appName.toLowerCase()} upgrade self"`);
1435
+ return;
1436
+ }
1437
+ const options = {
1438
+ packetName: name
1439
+ };
1440
+ /** list of stopped instances for windows */
1441
+ let stoppedList = [];
1442
+ if (process.platform === 'win32') {
1443
+ stoppedList = await this._getInstancesOfAdapter(name);
1444
+ await this.enableInstances(stoppedList, false);
1445
+ }
1446
+ const res = await this._npmInstallWithCheck(url, options, debug);
1447
+ // if we have no installDir, the method has called processExit itself
1448
+ if (!res || !res.installDir) {
1449
+ return;
1450
+ }
1451
+ const { installDir } = res;
1452
+ if (name) {
1453
+ await this.upload.uploadAdapter(name, true, true);
1454
+ await this.upload.uploadAdapter(name, false, true);
1455
+ await this.upload.upgradeAdapterObjects(name);
1456
+ }
1457
+ else {
1458
+ // Try to find io-package.json with newest date
1459
+ const dirs = fs_extra_1.default.readdirSync(installDir);
1460
+ let date = null;
1461
+ let dir = null;
1462
+ for (const _dir of dirs) {
1463
+ if (fs_extra_1.default.existsSync(`${installDir}/${_dir}/io-package.json`)) {
1464
+ const stat = fs_extra_1.default.statSync(`${installDir}/${_dir}/io-package.json`);
1465
+ if (!date || stat.mtime.getTime() > date.getTime()) {
1466
+ dir = _dir;
1467
+ date = stat.mtime;
1468
+ }
1469
+ }
1470
+ }
1471
+ // if modify time is not older than one hour
1472
+ if (dir && date && Date.now() - date.getTime() < 3600000) {
1473
+ name = dir.substring(js_controller_common_1.tools.appName.length + 1);
1474
+ await this.upload.uploadAdapter(name, true, true);
1475
+ await this.upload.uploadAdapter(name, false, true);
1476
+ await this.upload.upgradeAdapterObjects(name);
1477
+ }
1478
+ }
1479
+ // re-enable stopped instances
1480
+ await this.enableInstances(stoppedList, true);
1481
+ }
1482
+ /**
1483
+ * Checks if other adapters depend on this adapter
1484
+ *
1485
+ * @param adapter adapter name
1486
+ * @param instance instance, like 1
1487
+ * @return if dependent exists returns adapter name
1488
+ */
1489
+ async _hasDependentInstances(adapter, instance) {
1490
+ try {
1491
+ // lets get all instances
1492
+ // @ts-expect-error #1917
1493
+ const doc = await this.objects.getObjectViewAsync('system', 'instance', {
1494
+ startkey: 'system.adapter.',
1495
+ endkey: 'system.adapter.\u9999'
1496
+ });
1497
+ if (!doc) {
1498
+ console.error(`Could not check dependent instances for "${adapter}", because instances could not be read`);
1499
+ return;
1500
+ }
1501
+ let scopedHostname;
1502
+ if (instance) {
1503
+ // we need to respect host relative to the instance
1504
+ [scopedHostname] = doc.rows
1505
+ .filter(row => row.id === `system.adapter.${adapter}.${instance}`)
1506
+ .map(row => row.value.common.host);
1507
+ }
1508
+ // fallback is this host
1509
+ scopedHostname = scopedHostname || hostname;
1510
+ for (const row of doc.rows) {
1511
+ if (!row.value.common) {
1512
+ // this object seems to be corrupted so it will not need our adapter
1513
+ continue;
1514
+ }
1515
+ const localDeps = js_controller_common_1.tools.parseDependencies(row.value.common.dependencies);
1516
+ for (const localDep of Object.keys(localDeps)) {
1517
+ if (row.value.common.host === scopedHostname && localDep === adapter) {
1518
+ if (!instance) {
1519
+ // this adapter needs us locally and all instances should be deleted
1520
+ return `${row.value.common.name}.${row.id.split('.').pop()}`;
1521
+ }
1522
+ else {
1523
+ // check if other instance of us exists on this host
1524
+ if (this._checkDependencyFulfilledThisHost(adapter, instance, doc.rows, scopedHostname)) {
1525
+ // there are other instances of our adapter - ok
1526
+ break;
1527
+ }
1528
+ else {
1529
+ return `${row.value.common.name}.${row.id.split('.').pop()}`;
1530
+ }
1531
+ }
1532
+ }
1533
+ }
1534
+ const globalDeps = js_controller_common_1.tools.parseDependencies(row.value.common.globalDependencies);
1535
+ for (const globalDep of Object.keys(globalDeps)) {
1536
+ if (globalDep === adapter) {
1537
+ if (!instance) {
1538
+ // all instances on this host should be removed so check if there are some on other hosts
1539
+ if (this._checkDependencyFulfilledForeignHosts(adapter, doc.rows, scopedHostname)) {
1540
+ break;
1541
+ }
1542
+ else {
1543
+ return row.value.common.name;
1544
+ }
1545
+ }
1546
+ else if (this._checkDependencyFulfilledForeignHosts(adapter, doc.rows, scopedHostname) ||
1547
+ this._checkDependencyFulfilledThisHost(adapter, instance, doc.rows, scopedHostname)) {
1548
+ // another instance of our adapter is on another host or on ours, no need to search further
1549
+ break;
1550
+ }
1551
+ else {
1552
+ return row.value.common.name;
1553
+ }
1554
+ }
1555
+ }
1556
+ }
1557
+ }
1558
+ catch (e) {
1559
+ console.error(`Could not check dependent instances for "${adapter}": ${e.message}`);
1560
+ }
1561
+ }
1562
+ /**
1563
+ * Checks if adapter can also be found on another host than this
1564
+ *
1565
+ * @param adapter adapter name
1566
+ * @param instancesRows all instances objects view rows
1567
+ * @param scopedHostname hostname which should be assumed as local
1568
+ * @return true if an instance is present on other host
1569
+ */
1570
+ _checkDependencyFulfilledForeignHosts(adapter, instancesRows, scopedHostname) {
1571
+ for (const row of instancesRows) {
1572
+ if (row.value && row.value.common.name === adapter && row.value.common.host !== scopedHostname) {
1573
+ return true;
1574
+ }
1575
+ }
1576
+ return false;
1577
+ }
1578
+ /**
1579
+ * Checks if another instance then the given is present on this host
1580
+ *
1581
+ * @param adapter adapter name
1582
+ * @param instance instance number like 1
1583
+ * @param instancesRows all instances objects view rows
1584
+ * @param scopedHostname hostname which should be assumed as local
1585
+ * @return true if another instance is present on this host
1586
+ */
1587
+ _checkDependencyFulfilledThisHost(adapter, instance, instancesRows, scopedHostname) {
1588
+ for (const row of instancesRows) {
1589
+ if (row.value &&
1590
+ row.value.common.name === adapter &&
1591
+ row.value.common.host === scopedHostname &&
1592
+ parseInt(row.value._id.split('.').pop()) !== instance) {
1593
+ return true;
1594
+ }
1595
+ }
1596
+ return false;
1597
+ }
1598
+ /**
1599
+ * Get all instances of an adapter which are on the current host
1600
+ */
1601
+ async _getInstancesOfAdapter(adapter) {
1602
+ const instances = [];
1603
+ // @ts-expect-error fixed with #1917
1604
+ const doc = await this.objects.getObjectListAsync({
1605
+ startkey: `system.adapter.${adapter}.`,
1606
+ endkey: `system.adapter.${adapter}.\u9999`
1607
+ });
1608
+ if (doc) {
1609
+ for (const row of doc.rows) {
1610
+ // stop only started instances on this host
1611
+ if (row.value.common.enabled && hostname === row.value.common.host) {
1612
+ instances.push(row.value);
1613
+ }
1614
+ }
1615
+ }
1616
+ return instances;
1617
+ }
1618
+ }
1619
+ module.exports = { Install };
1620
+ //# sourceMappingURL=setupInstall.js.map