@iobroker/js-controller-cli 8.0.0-alpha.1-20260921-e5941ca8a → 8.0.0-alpha.2-20260923-99da17fcc

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.
@@ -211,6 +211,21 @@ export declare class Install {
211
211
  * @param instance e.g. 1, if undefined deletes all instances
212
212
  */
213
213
  deleteInstance(adapter: string, instance?: number): Promise<void | EXIT_CODES.CANNOT_DELETE_DEPENDENCY>;
214
+ /**
215
+ * Free all exclusive resources (serial ports, TCP/UDP ports, ...) the given instance(s) had registered.
216
+ *
217
+ * This is needed because an instance can be deleted via the CLI while a js-controller is not running: it
218
+ * cannot notice that the instance object disappeared, so its `system.host.<host>.usedResources.<type>`
219
+ * states would keep listing the resources of an instance that no longer exists.
220
+ *
221
+ * Hosts that are running are deliberately skipped. Such a host cleans its own registry up when it sees the
222
+ * instance object being deleted, and it holds the registry in memory: it rewrites the state from that copy
223
+ * on its next change, which would silently undo a write made here.
224
+ *
225
+ * @param adapter adapter name, e.g. "mqtt"
226
+ * @param instance instance number; if undefined, all instances of the adapter are freed
227
+ */
228
+ private _freeUsedResources;
214
229
  /**
215
230
  * Remove all node modules that has been installed by this instance
216
231
  *
@@ -1143,6 +1143,7 @@ class Install {
1143
1143
  await this._deleteAdapterFiles(adapter, metaFilesToDelete);
1144
1144
  await this._deleteAdapterObjects(knownObjectIDs);
1145
1145
  await this._deleteAdapterStates(knownStateIDs);
1146
+ await this._freeUsedResources(adapter);
1146
1147
  if (this.params.custom) {
1147
1148
  await this._removeCustomFromObjects([...instances, adapter]);
1148
1149
  }
@@ -1176,10 +1177,79 @@ class Install {
1176
1177
  await this._enumerateAdapterDocs(knownObjectIDs, adapter, instance);
1177
1178
  await this._deleteAdapterObjects(knownObjectIDs);
1178
1179
  await this._deleteAdapterStates(knownStateIDs);
1180
+ await this._freeUsedResources(adapter, instance);
1179
1181
  if (this.params.custom) {
1180
1182
  await this._removeCustomFromObjects([`${adapter}.${instance}`]);
1181
1183
  }
1182
1184
  }
1185
+ /**
1186
+ * Free all exclusive resources (serial ports, TCP/UDP ports, ...) the given instance(s) had registered.
1187
+ *
1188
+ * This is needed because an instance can be deleted via the CLI while a js-controller is not running: it
1189
+ * cannot notice that the instance object disappeared, so its `system.host.<host>.usedResources.<type>`
1190
+ * states would keep listing the resources of an instance that no longer exists.
1191
+ *
1192
+ * Hosts that are running are deliberately skipped. Such a host cleans its own registry up when it sees the
1193
+ * instance object being deleted, and it holds the registry in memory: it rewrites the state from that copy
1194
+ * on its next change, which would silently undo a write made here.
1195
+ *
1196
+ * @param adapter adapter name, e.g. "mqtt"
1197
+ * @param instance instance number; if undefined, all instances of the adapter are freed
1198
+ */
1199
+ async _freeUsedResources(adapter, instance) {
1200
+ const matches = /* @__PURE__ */ __name((namespace) => instance !== void 0 ? namespace === `${adapter}.${instance}` : namespace.startsWith(`${adapter}.`), "matches");
1201
+ let keys;
1202
+ try {
1203
+ keys = await this.states.getKeys("system.host.*.usedResources.*");
1204
+ } catch (e) {
1205
+ console.warn(`Cannot read the used resources registry: ${e.message}`);
1206
+ return;
1207
+ }
1208
+ if (!keys?.length) {
1209
+ return;
1210
+ }
1211
+ const hostPrefix = "system.host.";
1212
+ const registryMarker = ".usedResources.";
1213
+ const hostIsRunning = /* @__PURE__ */ new Map();
1214
+ for (const id of keys) {
1215
+ const markerIndex = id.lastIndexOf(registryMarker);
1216
+ if (!id.startsWith(hostPrefix) || markerIndex <= hostPrefix.length) {
1217
+ continue;
1218
+ }
1219
+ const host = id.substring(hostPrefix.length, markerIndex);
1220
+ let isRunning = hostIsRunning.get(host);
1221
+ if (isRunning === void 0) {
1222
+ try {
1223
+ const aliveState = await this.states.getState(`${hostPrefix}${host}.alive`);
1224
+ isRunning = aliveState?.val === true;
1225
+ } catch (e) {
1226
+ console.warn(`Cannot check whether host "${host}" is running: ${e.message}`);
1227
+ isRunning = true;
1228
+ }
1229
+ hostIsRunning.set(host, isRunning);
1230
+ }
1231
+ if (isRunning) {
1232
+ continue;
1233
+ }
1234
+ try {
1235
+ const state = await this.states.getState(id);
1236
+ if (!state || typeof state.val !== "string" || !state.val) {
1237
+ continue;
1238
+ }
1239
+ const parsed = JSON.parse(state.val);
1240
+ if (!Array.isArray(parsed)) {
1241
+ continue;
1242
+ }
1243
+ const original = parsed;
1244
+ const filtered = original.filter((entry) => typeof entry?.instance !== "string" || !matches(entry.instance));
1245
+ if (filtered.length !== original.length) {
1246
+ await this.states.setStateAsync(id, { val: JSON.stringify(filtered), ack: true });
1247
+ }
1248
+ } catch (e) {
1249
+ console.warn(`Cannot free the used resources in "${id}": ${e.message}`);
1250
+ }
1251
+ }
1252
+ }
1183
1253
  /**
1184
1254
  * Remove all node modules that has been installed by this instance
1185
1255
  *