iobroker.utility-monitor 1.4.5 → 1.5.0

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 (35) hide show
  1. package/README.md +159 -44
  2. package/admin/custom/.vite/manifest.json +90 -0
  3. package/admin/custom/@mf-types/Components.d.ts +2 -0
  4. package/admin/custom/@mf-types/compiled-types/Components/CSVImporter.d.ts +11 -0
  5. package/admin/custom/@mf-types/compiled-types/Components.d.ts +2 -0
  6. package/admin/custom/@mf-types.d.ts +3 -0
  7. package/admin/custom/@mf-types.zip +0 -0
  8. package/admin/custom/CSVImporter_v15_11.js +4415 -0
  9. package/admin/custom/assets/Components-i0AZ59nl.js +18887 -0
  10. package/admin/custom/assets/UtilityMonitor__loadShare__react__loadShare__-Da99Mak4.js +42 -0
  11. package/admin/custom/assets/UtilityMonitor__mf_v__runtimeInit__mf_v__-BmC4OGk6.js +16 -0
  12. package/admin/custom/assets/_commonjsHelpers-Dj2_voLF.js +30 -0
  13. package/admin/custom/assets/hostInit-DEXfeB0W.js +10 -0
  14. package/admin/custom/assets/index-B3WVNJTz.js +401 -0
  15. package/admin/custom/assets/index-VBwl8x_k.js +64 -0
  16. package/admin/custom/assets/preload-helper-BelkbqnE.js +61 -0
  17. package/admin/custom/assets/virtualExposes-CqCLUNLT.js +19 -0
  18. package/admin/custom/index.html +12 -0
  19. package/admin/custom/mf-manifest.json +1 -0
  20. package/admin/jsonConfig.json +219 -35
  21. package/io-package.json +51 -2
  22. package/lib/billingManager.js +276 -170
  23. package/lib/calculator.js +19 -138
  24. package/lib/consumptionManager.js +48 -331
  25. package/lib/importManager.js +300 -0
  26. package/lib/messagingHandler.js +112 -49
  27. package/lib/meter/MeterRegistry.js +110 -0
  28. package/lib/multiMeterManager.js +410 -181
  29. package/lib/stateManager.js +508 -36
  30. package/lib/utils/billingHelper.js +69 -0
  31. package/lib/utils/consumptionHelper.js +47 -0
  32. package/lib/utils/helpers.js +178 -0
  33. package/lib/utils/typeMapper.js +19 -0
  34. package/main.js +99 -36
  35. package/package.json +10 -4
@@ -0,0 +1,69 @@
1
+ 'use strict';
2
+
3
+ const calculator = require('../calculator');
4
+
5
+ /**
6
+ * Shared billing and cost calculation logic
7
+ */
8
+
9
+ /**
10
+ * Calculates accumulated basic charges over a period
11
+ *
12
+ * @param {number} monthlyFee - Monthly basic charge
13
+ * @param {number} annualFee - Fixed annual fee
14
+ * @param {number} months - Number of months since contract start
15
+ * @returns {object} { basicCharge, annualFee, total }
16
+ */
17
+ function calculateAccumulatedCharges(monthlyFee, annualFee, months) {
18
+ const basicCharge = (monthlyFee || 0) * months;
19
+ const annual = annualFee || 0;
20
+ return {
21
+ basicCharge: calculator.roundToDecimals(basicCharge, 2),
22
+ annualFee: calculator.roundToDecimals(annual, 2),
23
+ total: calculator.roundToDecimals(basicCharge + annual, 2),
24
+ };
25
+ }
26
+
27
+ /**
28
+ * Calculates total paid and balance
29
+ *
30
+ * @param {number} monthlyAbschlag - Monthly installment
31
+ * @param {number} months - Months since start
32
+ * @param {number} totalCosts - Total costs calculated
33
+ * @returns {object} { paid, balance }
34
+ */
35
+ function calculateBalance(monthlyAbschlag, months, totalCosts) {
36
+ const paid = (monthlyAbschlag || 0) * months;
37
+ const balance = totalCosts > 0.01 || paid > 0.01 ? paid - totalCosts : 0;
38
+
39
+ return {
40
+ paid: calculator.roundToDecimals(paid, 2),
41
+ balance: calculator.roundToDecimals(balance, 2),
42
+ };
43
+ }
44
+
45
+ /**
46
+ * Calculates costs for HT/NT split
47
+ *
48
+ * @param {number} htQty - High tariff quantity
49
+ * @param {number} htPrice - High tariff price
50
+ * @param {number} ntQty - Low tariff quantity
51
+ * @param {number} ntPrice - Low tariff price
52
+ * @returns {object} { htCosts, ntCosts, total }
53
+ */
54
+ function calculateHTNTCosts(htQty, htPrice, ntQty, ntPrice) {
55
+ const ht = (htQty || 0) * (htPrice || 0);
56
+ const nt = (ntQty || 0) * (ntPrice || 0);
57
+
58
+ return {
59
+ htCosts: calculator.roundToDecimals(ht, 2),
60
+ ntCosts: calculator.roundToDecimals(nt, 2),
61
+ total: calculator.roundToDecimals(ht + nt, 2),
62
+ };
63
+ }
64
+
65
+ module.exports = {
66
+ calculateAccumulatedCharges,
67
+ calculateBalance,
68
+ calculateHTNTCosts,
69
+ };
@@ -0,0 +1,47 @@
1
+ 'use strict';
2
+
3
+ const calculator = require('../calculator');
4
+
5
+ /**
6
+ * Shared consumption logic for different managers
7
+ */
8
+
9
+ /**
10
+ * Calculates gas energy (kWh) and volume (m³)
11
+ *
12
+ * @param {number} value - Raw sensor value (m³)
13
+ * @param {number} brennwert - Calorific value
14
+ * @param {number} zZahl - Z-number
15
+ * @returns {object} { energy, volume }
16
+ */
17
+ function calculateGas(value, brennwert, zZahl) {
18
+ const energy = calculator.convertGasM3ToKWh(value, brennwert, zZahl);
19
+ return {
20
+ energy: calculator.roundToDecimals(energy, 2),
21
+ volume: calculator.roundToDecimals(value, 2),
22
+ };
23
+ }
24
+
25
+ /**
26
+ * Returns the suffix (HT/NT) based on current time
27
+ *
28
+ * @param {object} config - Adapter config
29
+ * @param {string} type - Utility type (config name)
30
+ * @returns {string} 'HT', 'NT' or empty if disabled
31
+ */
32
+ function getHTNTSuffix(config, type) {
33
+ if (!config || !type) {
34
+ return '';
35
+ }
36
+ const enabled = config[`${type}HtNtEnabled`];
37
+ if (!enabled) {
38
+ return '';
39
+ }
40
+
41
+ return calculator.isHTTime(config, type) ? 'HT' : 'NT';
42
+ }
43
+
44
+ module.exports = {
45
+ calculateGas,
46
+ getHTNTSuffix,
47
+ };
@@ -0,0 +1,178 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Helper utilities for iobroker.utility-monitor
5
+ */
6
+
7
+ /**
8
+ * Ensures a value is a number, handling German decimal commas if provided as string.
9
+ *
10
+ * @param {any} value - Value to convert
11
+ * @returns {number} The numeric value
12
+ */
13
+ function ensureNumber(value) {
14
+ if (value === undefined || value === null || value === '') {
15
+ return 0;
16
+ }
17
+ if (typeof value === 'number') {
18
+ return isNaN(value) ? 0 : value;
19
+ }
20
+ if (typeof value === 'string') {
21
+ let normalized = value.trim();
22
+ // Handle common European formats: 1.234,56 -> 1234.56 or 1234,56 -> 1234.56
23
+ if (normalized.includes(',') && normalized.includes('.')) {
24
+ // Assume . is thousands and , is decimal
25
+ normalized = normalized.replace(/\./g, '').replace(',', '.');
26
+ } else if (normalized.includes(',')) {
27
+ // Assume , is decimal
28
+ normalized = normalized.replace(',', '.');
29
+ }
30
+ const parsed = parseFloat(normalized);
31
+ return isNaN(parsed) ? 0 : parsed;
32
+ }
33
+ const num = Number(value);
34
+ return isNaN(num) ? 0 : num;
35
+ }
36
+
37
+ /**
38
+ * Rounds a number to specified decimal places
39
+ *
40
+ * @param {number|string} value - Value to round
41
+ * @param {number} decimals - Number of decimal places (default: 2)
42
+ * @returns {number} Rounded value
43
+ */
44
+ function roundToDecimals(value, decimals = 2) {
45
+ const numValue = ensureNumber(value);
46
+ const factor = Math.pow(10, decimals);
47
+ return Math.round(numValue * factor) / factor;
48
+ }
49
+
50
+ /**
51
+ * Parses a German date string (DD.MM.YYYY) into a Date object
52
+ *
53
+ * @param {string} dateStr - Date string in format DD.MM.YYYY
54
+ * @returns {Date|null} Date object or null if invalid
55
+ */
56
+ function parseGermanDate(dateStr) {
57
+ if (!dateStr || typeof dateStr !== 'string') {
58
+ return null;
59
+ }
60
+
61
+ const trimmed = dateStr.trim();
62
+
63
+ // 1. Try German format (DD.MM.YYYY)
64
+ if (trimmed.includes('.') && !trimmed.includes('-')) {
65
+ const parts = trimmed.split('.');
66
+ if (parts.length === 3) {
67
+ const day = parseInt(parts[0], 10);
68
+ const month = parseInt(parts[1], 10) - 1;
69
+ let year = parseInt(parts[2], 10);
70
+
71
+ if (year < 70) {
72
+ year += 2000;
73
+ } else if (year < 100) {
74
+ year += 1900;
75
+ }
76
+
77
+ if (!isNaN(day) && !isNaN(month) && !isNaN(year)) {
78
+ return new Date(year, month, day, 12, 0, 0);
79
+ }
80
+ }
81
+ }
82
+
83
+ // 2. Try ISO or other standard formats
84
+ const fallback = new Date(trimmed);
85
+ if (!isNaN(fallback.getTime())) {
86
+ return fallback;
87
+ }
88
+
89
+ return null;
90
+ }
91
+
92
+ /**
93
+ * Formats a Date object to YYYY-MM-DD HH:mm:ss string
94
+ *
95
+ * @param {Date} date - Date object
96
+ * @returns {string|null} Formatted date string or null
97
+ */
98
+ function formatDateString(date) {
99
+ if (!date || !(date instanceof Date)) {
100
+ return null;
101
+ }
102
+
103
+ const pad = num => num.toString().padStart(2, '0');
104
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
105
+ }
106
+
107
+ /**
108
+ * Checks if a year is a leap year
109
+ *
110
+ * @param {number} year - Year to check
111
+ * @returns {boolean} True if leap year
112
+ */
113
+ function isLeapYear(year) {
114
+ return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
115
+ }
116
+
117
+ /**
118
+ * Calculates the difference in months between two dates
119
+ *
120
+ * @param {Date} startDate - Start date
121
+ * @param {Date} endDate - End date
122
+ * @returns {number} Difference in months
123
+ */
124
+ function getMonthsDifference(startDate, endDate) {
125
+ if (!startDate || !endDate) {
126
+ return 0;
127
+ }
128
+ return (endDate.getFullYear() - startDate.getFullYear()) * 12 + (endDate.getMonth() - startDate.getMonth());
129
+ }
130
+
131
+ /**
132
+ * Normalizes a meter name to a valid ioBroker ID part
133
+ *
134
+ * @param {string} name - The name to normalize
135
+ * @returns {string} The normalized name
136
+ */
137
+ function normalizeMeterName(name) {
138
+ if (!name) {
139
+ return 'unknown';
140
+ }
141
+
142
+ return name
143
+ .toLowerCase()
144
+ .replace(/ä/g, 'ae')
145
+ .replace(/ö/g, 'oe')
146
+ .replace(/ü/g, 'ue')
147
+ .replace(/ß/g, 'ss')
148
+ .replace(/[^a-z0-9]/g, '_')
149
+ .replace(/_+/g, '_')
150
+ .replace(/^_|_$/g, '')
151
+ .substring(0, 32);
152
+ }
153
+
154
+ /**
155
+ * Safe wrapper for setObjectNotExistsAsync with error handling
156
+ *
157
+ * @param {object} adapter - Adapter instance
158
+ * @param {string} id - State ID
159
+ * @param {object} obj - Object definition
160
+ */
161
+ async function safeSetObjectNotExists(adapter, id, obj) {
162
+ try {
163
+ await adapter.setObjectNotExistsAsync(id, obj);
164
+ } catch (e) {
165
+ adapter.log.error(`Error creating object ${id}: ${e.message}`);
166
+ }
167
+ }
168
+
169
+ module.exports = {
170
+ ensureNumber,
171
+ roundToDecimals,
172
+ parseGermanDate,
173
+ formatDateString,
174
+ isLeapYear,
175
+ getMonthsDifference,
176
+ normalizeMeterName,
177
+ safeSetObjectNotExists,
178
+ };
@@ -0,0 +1,19 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Maps internal utility type to config/state name
5
+ *
6
+ * @param {string} type - gas, water, electricity, pv
7
+ * @returns {string} - gas, wasser, strom, pv
8
+ */
9
+ function getConfigType(type) {
10
+ const mapping = {
11
+ electricity: 'strom',
12
+ water: 'wasser',
13
+ gas: 'gas',
14
+ pv: 'pv',
15
+ };
16
+ return mapping[type] || type;
17
+ }
18
+
19
+ module.exports = { getConfigType };
package/main.js CHANGED
@@ -10,6 +10,8 @@ const ConsumptionManager = require('./lib/consumptionManager');
10
10
  const BillingManager = require('./lib/billingManager');
11
11
  const MessagingHandler = require('./lib/messagingHandler');
12
12
  const MultiMeterManager = require('./lib/multiMeterManager');
13
+ const ImportManager = require('./lib/importManager');
14
+ const calculator = require('./lib/calculator');
13
15
 
14
16
  class UtilityMonitor extends utils.Adapter {
15
17
  /**
@@ -29,6 +31,7 @@ class UtilityMonitor extends utils.Adapter {
29
31
  this.consumptionManager = new ConsumptionManager(this);
30
32
  this.billingManager = new BillingManager(this);
31
33
  this.messagingHandler = new MessagingHandler(this);
34
+ this.importManager = new ImportManager(this);
32
35
  this.multiMeterManager = null; // Initialized in onReady after other managers
33
36
 
34
37
  this.periodicTimers = {};
@@ -43,6 +46,9 @@ class UtilityMonitor extends utils.Adapter {
43
46
  // Initialize MultiMeterManager
44
47
  this.multiMeterManager = new MultiMeterManager(this, this.consumptionManager, this.billingManager);
45
48
 
49
+ // Validate configuration before starting
50
+ this.validateConfiguration();
51
+
46
52
  // Initialize each utility type based on configuration
47
53
  // Note: initializeUtility() internally calls multiMeterManager.initializeType()
48
54
  await this.initializeUtility('gas', this.config.gasAktiv);
@@ -82,16 +88,64 @@ class UtilityMonitor extends utils.Adapter {
82
88
  this.log.info('Nebenkosten-Monitor initialized successfully');
83
89
  }
84
90
 
91
+ /**
92
+ * Validates the adapter configuration and logs warnings for missing settings
93
+ */
94
+ validateConfiguration() {
95
+ const types = [
96
+ { key: 'gas', configKey: 'gas', label: 'Gas' },
97
+ { key: 'water', configKey: 'wasser', label: 'Wasser' },
98
+ { key: 'electricity', configKey: 'strom', label: 'Strom' },
99
+ { key: 'pv', configKey: 'pv', label: 'PV' },
100
+ ];
101
+
102
+ for (const type of types) {
103
+ const isActive = this.config[`${type.configKey}Aktiv`];
104
+ if (!isActive) {
105
+ continue;
106
+ }
107
+
108
+ // Check main meter contract start
109
+ const contractStart = this.config[`${type.configKey}ContractStart`];
110
+ if (!contractStart) {
111
+ this.log.warn(
112
+ `${type.label}: Kein Vertragsbeginn konfiguriert! Für korrekte Jahresberechnungen und Abrechnungsperioden sollte ein Vertragsbeginn gesetzt werden.`,
113
+ );
114
+ }
115
+
116
+ // Check main meter sensor
117
+ const sensorDP = this.config[`${type.configKey}SensorDP`];
118
+ if (!sensorDP) {
119
+ this.log.warn(`${type.label}: Kein Sensor-Datenpunkt konfiguriert!`);
120
+ }
121
+
122
+ // Check additional meters
123
+ const additionalMeters = this.config[`${type.configKey}AdditionalMeters`];
124
+ if (Array.isArray(additionalMeters)) {
125
+ for (const meter of additionalMeters) {
126
+ if (meter && meter.name) {
127
+ if (!meter.contractStart) {
128
+ this.log.warn(
129
+ `${type.label} Zähler "${meter.name}": Kein Vertragsbeginn konfiguriert!`,
130
+ );
131
+ }
132
+ if (!meter.sensorDP) {
133
+ this.log.warn(
134
+ `${type.label} Zähler "${meter.name}": Kein Sensor-Datenpunkt konfiguriert!`,
135
+ );
136
+ }
137
+ }
138
+ }
139
+ }
140
+ }
141
+ }
142
+
85
143
  // --- Delegation Methods (backward compatibility for internal calls) ---
86
144
 
87
145
  async initializeUtility(type, isActive) {
88
146
  return this.consumptionManager.initializeUtility(type, isActive);
89
147
  }
90
148
 
91
- async handleSensorUpdate(type, sensorDP, value) {
92
- return this.consumptionManager.handleSensorUpdate(type, sensorDP, value);
93
- }
94
-
95
149
  async updateCurrentPrice(type) {
96
150
  return this.consumptionManager.updateCurrentPrice(type);
97
151
  }
@@ -195,20 +249,15 @@ class UtilityMonitor extends utils.Adapter {
195
249
  if (id.includes('.billing.closePeriod') && state.val === true && !state.ack) {
196
250
  const parts = id.split('.');
197
251
 
198
- // Parse state ID: nebenkosten-monitor.0.gas.erdgeschoss.billing.closePeriod
199
- // Remove adapter prefix: gas.erdgeschoss.billing.closePeriod
252
+ // Parse state ID: nebenkosten-monitor.0.gas.meterName.billing.closePeriod
253
+ // Remove adapter prefix: gas.meterName.billing.closePeriod
200
254
  const statePathParts = parts.slice(2); // Remove "nebenkosten-monitor" and "0"
201
255
 
202
- // Determine if this is main meter or additional meter
203
- if (statePathParts.length === 3) {
204
- // Main meter: gas.billing.closePeriod
205
- const type = statePathParts[0];
206
- this.log.info(`User triggered billing period closure for ${type} (main meter)`);
207
- await this.closeBillingPeriod(type);
208
- } else if (statePathParts.length === 4) {
209
- // Additional meter: gas.erdgeschoss.billing.closePeriod
256
+ // All meters now use: gas.meterName.billing.closePeriod (length === 4)
257
+ if (statePathParts.length === 4) {
210
258
  const type = statePathParts[0];
211
259
  const meterName = statePathParts[1];
260
+
212
261
  this.log.info(`User triggered billing period closure for ${type}.${meterName}`);
213
262
 
214
263
  // Find the meter object from multiMeterManager
@@ -221,40 +270,50 @@ class UtilityMonitor extends utils.Adapter {
221
270
  this.log.error(`Meter "${meterName}" not found for type ${type}!`);
222
271
  await this.setStateAsync(`${type}.${meterName}.billing.closePeriod`, false, true);
223
272
  }
273
+ } else {
274
+ // Invalid path format
275
+ this.log.warn(`Invalid billing period closure trigger: ${id}`);
224
276
  }
225
277
  return;
226
278
  }
227
279
 
228
280
  // Check if this is an adjustment value change
281
+ // New structure: gas.meterName.adjustment.value (4 parts after namespace)
229
282
  if (id.includes('.adjustment.value') && !state.ack) {
230
283
  const parts = id.split('.');
231
- const type = parts[parts.length - 3];
232
- this.log.info(`Adjustment value changed for ${type}: ${state.val}`);
233
- await this.setStateAsync(`${type}.adjustment.applied`, Date.now(), true);
284
+ // Parse: nebenkosten-monitor.0.gas.meterName.adjustment.value
285
+ const statePathParts = parts.slice(2); // Remove "nebenkosten-monitor" and "0"
234
286
 
235
- // Update costs for all meters of this type
236
- await this.updateCosts(type);
237
- return;
238
- }
287
+ if (statePathParts.length === 3) {
288
+ // New structure: gas.meterName.adjustment.value
289
+ const type = statePathParts[0];
290
+ const meterName = statePathParts[1];
239
291
 
240
- // Determine which utility this sensor belongs to
241
- // First check if it's a multi-meter sensor (additional meters)
242
- if (this.multiMeterManager) {
243
- const meterInfo = this.multiMeterManager.findMeterBySensor(id);
244
- if (meterInfo && typeof state.val === 'number') {
245
- await this.multiMeterManager.handleSensorUpdate(meterInfo.type, meterInfo.meterName, id, state.val);
292
+ this.log.info(`Adjustment value changed for ${type}.${meterName}: ${state.val}`);
293
+ await this.setStateAsync(`${type}.${meterName}.adjustment.applied`, Date.now(), true);
294
+
295
+ // Update costs for THIS specific meter
296
+ if (this.multiMeterManager) {
297
+ const meters = this.multiMeterManager.getMetersForType(type);
298
+ const meter = meters.find(m => m.name === meterName);
299
+ if (meter) {
300
+ await this.multiMeterManager.updateCosts(type, meterName, meter.config);
301
+ }
302
+ }
246
303
  return;
247
304
  }
248
305
  }
249
306
 
250
- // Check main meter sensors
251
- const types = ['gas', 'water', 'electricity', 'pv'];
252
- for (const type of types) {
253
- const configType = this.consumptionManager.getConfigType(type);
254
-
255
- if (this.config[`${configType}Aktiv`] && this.config[`${configType}SensorDP`] === id) {
256
- if (typeof state.val === 'number') {
257
- await this.handleSensorUpdate(type, id, state.val);
307
+ // Determine which utility this sensor belongs to
308
+ // All meters (including main) are now handled by multiMeterManager
309
+ if (this.multiMeterManager) {
310
+ const meters = this.multiMeterManager.findMeterBySensor(id);
311
+ if (meters.length > 0 && state.val != null) {
312
+ // Convert sensor value to number (handles strings, German commas, etc.)
313
+ const numValue = calculator.ensureNumber(state.val);
314
+ // Call handleSensorUpdate for each meter using this sensor
315
+ for (const meterInfo of meters) {
316
+ await this.multiMeterManager.handleSensorUpdate(meterInfo.type, meterInfo.meterName, id, numValue);
258
317
  }
259
318
  return;
260
319
  }
@@ -267,7 +326,11 @@ class UtilityMonitor extends utils.Adapter {
267
326
  * @param {Record<string, any>} obj - Message object from config
268
327
  */
269
328
  async onMessage(obj) {
270
- await this.messagingHandler.handleMessage(obj);
329
+ if (obj.command === 'importCSV') {
330
+ await this.importManager.handleImportCSV(obj);
331
+ } else {
332
+ await this.messagingHandler.handleMessage(obj);
333
+ }
271
334
  }
272
335
  }
273
336
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.utility-monitor",
3
- "version": "1.4.5",
3
+ "version": "1.5.0",
4
4
  "description": "Monitor gas, water, and electricity consumption with cost calculation",
5
5
  "author": {
6
6
  "name": "fischi87",
@@ -54,8 +54,14 @@
54
54
  },
55
55
  "main": "main.js",
56
56
  "files": [
57
- "admin{,/!(src)/**}/!(tsconfig|tsconfig.*|.eslintrc).{json,json5}",
58
- "admin{,/!(src)/**}/*.{html,css,png,svg,jpg,js}",
57
+ "admin/*.json",
58
+ "admin/*.json5",
59
+ "admin/*.png",
60
+ "admin/*.svg",
61
+ "admin/*.html",
62
+ "admin/*.js",
63
+ "admin/i18n/*.json",
64
+ "admin/custom/**",
59
65
  "lib/",
60
66
  "www/",
61
67
  "io-package.json",
@@ -63,7 +69,7 @@
63
69
  "main.js"
64
70
  ],
65
71
  "scripts": {
66
- "test:js": "mocha --config test/mocharc.custom.json \"{!(node_modules|test)/**/*.test.js,*.test.js,test/**/*.test.js}\"",
72
+ "test:js": "mocha --config test/mocharc.custom.json \"{!(node_modules|test|admin)/**/*.test.js,*.test.js,test/**/*.test.js}\"",
67
73
  "test:package": "mocha test/package --exit",
68
74
  "test:integration": "mocha test/integration --exit",
69
75
  "test": "npm run test:js && npm run test:package",