homey-lib 2.45.2 → 2.45.4

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 (38) hide show
  1. package/.eslintignore +2 -1
  2. package/assets/app/schema.d.ts +507 -0
  3. package/assets/capability/schema.d.ts +53 -0
  4. package/helpers/index.js +11 -0
  5. package/index.js +47 -24
  6. package/lib/App/index.js +125 -7
  7. package/lib/Capability/index.js +42 -0
  8. package/lib/Device/index.js +10 -0
  9. package/lib/Energy/index.js +6 -0
  10. package/lib/Media/index.js +3 -0
  11. package/lib/Signal/index.js +25 -0
  12. package/lib/Signal/validators.js +21 -0
  13. package/lib/Util/index.js +10 -0
  14. package/package.json +11 -3
  15. package/tsconfig.types.json +15 -0
  16. package/types/assets/app/schema.d.ts +507 -0
  17. package/types/assets/capability/schema.d.ts +53 -0
  18. package/types/helpers/index.d.ts +11 -0
  19. package/types/helpers/index.d.ts.map +1 -0
  20. package/types/index.d.ts +23 -0
  21. package/types/index.d.ts.map +1 -0
  22. package/types/lib/App/index.d.ts +140 -0
  23. package/types/lib/App/index.d.ts.map +1 -0
  24. package/types/lib/Capability/index.d.ts +61 -0
  25. package/types/lib/Capability/index.d.ts.map +1 -0
  26. package/types/lib/Device/index.d.ts +18 -0
  27. package/types/lib/Device/index.d.ts.map +1 -0
  28. package/types/lib/Energy/index.d.ts +12 -0
  29. package/types/lib/Energy/index.d.ts.map +1 -0
  30. package/types/lib/Media/index.d.ts +8 -0
  31. package/types/lib/Media/index.d.ts.map +1 -0
  32. package/types/lib/Signal/index.d.ts +52 -0
  33. package/types/lib/Signal/index.d.ts.map +1 -0
  34. package/types/lib/Signal/validators.d.ts +37 -0
  35. package/types/lib/Signal/validators.d.ts.map +1 -0
  36. package/types/lib/Util/index.d.ts +23 -0
  37. package/types/lib/Util/index.d.ts.map +1 -0
  38. package/webpack/index.js +1 -1
package/lib/App/index.js CHANGED
@@ -30,6 +30,8 @@ const {
30
30
  dirname,
31
31
  } = require('../../helpers');
32
32
 
33
+ /** @typedef {import('../../assets/app/schema').App} AppManifest */
34
+
33
35
  const VALIDATION_LEVELS = [
34
36
  'debug',
35
37
  'publish',
@@ -76,6 +78,9 @@ class App {
76
78
  static REQUIRED_PYTHON_PLATFORMS = ['arm64', 'amd64']
77
79
  static SUPPORTED_PYTHON_VERSIONS = ['3.13', '3.14']
78
80
 
81
+ /**
82
+ * @param {string} path
83
+ */
79
84
  constructor(path) {
80
85
  this._path = path;
81
86
 
@@ -84,11 +89,19 @@ class App {
84
89
  }
85
90
  }
86
91
 
92
+ /**
93
+ * @param {...unknown} args
94
+ * @returns {void}
95
+ */
87
96
  debug(...args) {
88
97
  if (!this._debug) return;
89
98
  console.log('[dbg]', ...args);
90
99
  }
91
100
 
101
+ /**
102
+ * @param {{ level?: 'debug' | 'publish' | 'verified', debug?: boolean }} [options]
103
+ * @returns {Promise<void>}
104
+ */
92
105
  async validate({
93
106
  level = 'debug',
94
107
  debug = false,
@@ -104,8 +117,9 @@ class App {
104
117
  const levelPublish = (level === 'publish' || level === 'verified');
105
118
  const levelVerified = (level === 'verified');
106
119
 
107
- let appJson = await readFileAsync(join(this._path, 'app.json'));
108
- appJson = JSON.parse(appJson);
120
+ const appJsonBuffer = await readFileAsync(join(this._path, 'app.json'));
121
+ /** @type {AppManifest} */
122
+ const appJson = JSON.parse(appJsonBuffer);
109
123
 
110
124
  const schema = App.getJSONSchema();
111
125
 
@@ -216,9 +230,8 @@ class App {
216
230
  // validate that Python apps target compatibility of at least >=13.0.0
217
231
  if (appJson.runtime === 'python') {
218
232
  const minVersion = semver.minVersion(appJson.compatibility);
219
- // tmp set to 12.11.1
220
- if (!semver.gte(minVersion, '12.11.1')) {
221
- throw new Error(`Invalid compatibility (${appJson.compatibility}), Python apps must have a compatibility of at least >=12.11.1`);
233
+ if (!semver.gte(minVersion, '13.0.0')) {
234
+ throw new Error(`Invalid compatibility (${appJson.compatibility}), Python apps must have a compatibility of at least >=13.0.0`);
222
235
  }
223
236
  }
224
237
 
@@ -371,9 +384,32 @@ class App {
371
384
  // validate `appJson.drivers[].capabilitiesOptions`
372
385
  if (driver.capabilitiesOptions) {
373
386
  for (const [capabilityId, capabilityOptions] of Object.entries(driver.capabilitiesOptions)) {
374
- // Enforce fixed values for target_power_mode always use the canonical values from the capability definition
387
+ // Merge canonical + extra values for target_power_mode - canonical always present, extras appended
375
388
  if (Capability.isInstanceOfId(capabilityId, 'target_power_mode')) {
376
- capabilityOptions.values = Capability.getCapability('target_power_mode').values;
389
+ const canonicalValues = Capability.getCapability('target_power_mode').values;
390
+ const providedValues = Array.isArray(capabilityOptions.values) ? capabilityOptions.values : [];
391
+
392
+ const seenIds = new Set(canonicalValues.map(canonical => canonical.id));
393
+ const extraValues = [];
394
+
395
+ for (const value of providedValues) {
396
+ if (!value || typeof value !== 'object' || typeof value.id !== 'string') continue;
397
+ if (seenIds.has(value.id)) continue;
398
+ seenIds.add(value.id);
399
+ extraValues.push(value);
400
+ }
401
+
402
+ const RESERVED_PREFIXES = ['homey_', 'device_'];
403
+ const reservedValues = extraValues.filter(value =>
404
+ RESERVED_PREFIXES.some(prefix => value.id.startsWith(prefix))
405
+ );
406
+
407
+ if (reservedValues.length) {
408
+ const ids = reservedValues.map(value => value.id).join(', ');
409
+ throw new Error(`drivers.${driver.id}.capabilitiesOptions.${capabilityId} custom values cannot use reserved prefixes "homey_" or "device_": ${ids}`);
410
+ }
411
+
412
+ capabilityOptions.values = [...canonicalValues, ...extraValues];
377
413
  }
378
414
 
379
415
  // validate target_power exclude range must include 0
@@ -764,6 +800,10 @@ class App {
764
800
  this.debug('Validated successfully');
765
801
  }
766
802
 
803
+ /**
804
+ * @param {AppManifest} appJson
805
+ * @returns {void}
806
+ */
767
807
  static validatePythonVersion(appJson) {
768
808
  // validate that Python apps specify a runtime version
769
809
  if (appJson.runtime === 'python' && !App.SUPPORTED_PYTHON_VERSIONS.includes(appJson.pythonVersion)) {
@@ -771,6 +811,11 @@ class App {
771
811
  }
772
812
  }
773
813
 
814
+ /**
815
+ * @param {Record<string, unknown>} setting
816
+ * @param {{ id: string }} driver
817
+ * @returns {void}
818
+ */
774
819
  _checkPrivateSettingPrefixUse(setting, driver) {
775
820
  if (
776
821
  setting.type &&
@@ -795,6 +840,11 @@ class App {
795
840
  }
796
841
  }
797
842
 
843
+ /**
844
+ * @param {Record<string, unknown> | null | undefined} driver
845
+ * @param {Record<string, unknown> | null | undefined} setting
846
+ * @returns {void}
847
+ */
798
848
  _checkZwaveForSetting(driver, setting) {
799
849
  if (!driver || !setting || !setting.zwave) return;
800
850
  if (typeof setting.zwave.index !== 'number' || typeof setting.zwave.size !== 'number') throw new Error(`Missing property in "zwave" at ${driver.id}, ${setting.id}`);
@@ -818,6 +868,10 @@ class App {
818
868
  }
819
869
  }
820
870
 
871
+ /**
872
+ * @param {string} filepath
873
+ * @returns {Promise<string[]>}
874
+ */
821
875
  async _getDirectoryContents(filepath) {
822
876
  await this._fileExistsCaseSensitive(filepath);
823
877
 
@@ -830,6 +884,10 @@ class App {
830
884
  });
831
885
  }
832
886
 
887
+ /**
888
+ * @param {string} filepath
889
+ * @returns {Promise<void>}
890
+ */
833
891
  async _ensureFileExistsCaseSensitive(filepath) {
834
892
  const exists = await this._fileExistsCaseSensitive(filepath);
835
893
  if (exists !== true) {
@@ -837,6 +895,10 @@ class App {
837
895
  }
838
896
  }
839
897
 
898
+ /**
899
+ * @param {string} filepath
900
+ * @returns {Promise<boolean>}
901
+ */
840
902
  async _fileExistsCaseSensitive(filepath) {
841
903
  filepath = join(this._path, filepath);
842
904
  const dir = dirname(filepath);
@@ -852,6 +914,12 @@ class App {
852
914
  }
853
915
  }
854
916
 
917
+ /**
918
+ * @param {{ small: string, large: string }} imagesObj
919
+ * @param {'app' | 'driver'} type
920
+ * @param {string} [errorPath]
921
+ * @returns {Promise<void>}
922
+ */
855
923
  async _validateImages(imagesObj, type, errorPath) {
856
924
  const sizes = ['small', 'large'];
857
925
  for (let i = 0; i < sizes.length; i++) {
@@ -881,6 +949,9 @@ class App {
881
949
  }
882
950
  }
883
951
 
952
+ /**
953
+ * @returns {Promise<void>}
954
+ */
884
955
  async _validateModules() {
885
956
  const nodeModulesPath = join(this._path, 'node_modules');
886
957
 
@@ -904,6 +975,13 @@ class App {
904
975
  }
905
976
  }
906
977
 
978
+ /**
979
+ * @param {Record<string, unknown>} card
980
+ * @param {string} errorPath
981
+ * @param {AppManifest} appJson
982
+ * @param {{ levelPublish: boolean, levelVerified: boolean }} options
983
+ * @returns {void}
984
+ */
907
985
  _validateFlowCard(card, errorPath, appJson, { levelPublish, levelVerified }) {
908
986
  if (Array.isArray(card.tokens)) {
909
987
  for (const token of card.tokens) {
@@ -983,6 +1061,12 @@ class App {
983
1061
  }
984
1062
  }
985
1063
 
1064
+ /**
1065
+ * @param {string} titleFormatted
1066
+ * @param {{ name: string }[]} args
1067
+ * @param {string} errorPath
1068
+ * @returns {void}
1069
+ */
986
1070
  static _checkTitleFormatted(titleFormatted, args, errorPath) {
987
1071
  const argsPresent = args.reduce((obj, arg) => {
988
1072
  obj[arg.name] = false;
@@ -1017,6 +1101,11 @@ class App {
1017
1101
  }
1018
1102
  }
1019
1103
 
1104
+ /**
1105
+ * @param {string} filepath
1106
+ * @param {number} numBytes
1107
+ * @returns {Promise<Buffer>}
1108
+ */
1020
1109
  async _readBytes(filepath, numBytes) {
1021
1110
  filepath = join(this._path, filepath);
1022
1111
 
@@ -1027,6 +1116,10 @@ class App {
1027
1116
  return buffer;
1028
1117
  }
1029
1118
 
1119
+ /**
1120
+ * @param {string} appId
1121
+ * @returns {boolean}
1122
+ */
1030
1123
  static isValidId(appId) {
1031
1124
  if (typeof appId !== 'string') return false;
1032
1125
  if (appId.length < 1) return false;
@@ -1035,16 +1128,26 @@ class App {
1035
1128
  return true;
1036
1129
  }
1037
1130
 
1131
+ /**
1132
+ * @param {string} color
1133
+ * @returns {boolean}
1134
+ */
1038
1135
  static isValidBrandColor(color) {
1039
1136
  return tinycolor(color).getBrightness() <= 184; // empirically determined by many colorpicker samples
1040
1137
  }
1041
1138
 
1139
+ /**
1140
+ * @returns {Record<string, unknown>}
1141
+ */
1042
1142
  static getJSONSchema() {
1043
1143
  // eslint-disable-next-line global-require
1044
1144
  const schema = require('../../assets/app/schema.json');
1045
1145
  return JSON.parse(JSON.stringify(schema));
1046
1146
  }
1047
1147
 
1148
+ /**
1149
+ * @returns {Record<string, Record<string, unknown>>}
1150
+ */
1048
1151
  static getPermissions() {
1049
1152
  // eslint-disable-next-line global-require
1050
1153
  const permissions = require('../../assets/app/permissions.json');
@@ -1059,15 +1162,25 @@ class App {
1059
1162
  return permissions;
1060
1163
  }
1061
1164
 
1165
+ /**
1166
+ * @returns {string[]}
1167
+ */
1062
1168
  static getCategories() {
1063
1169
  return ['lights', 'video', 'music', 'appliances', 'security', 'climate', 'tools', 'internet', 'localization', 'energy'];
1064
1170
  }
1065
1171
 
1172
+ /**
1173
+ * @returns {string[]}
1174
+ */
1066
1175
  static getLocales() {
1067
1176
  // eslint-disable-next-line max-len
1068
1177
  return ['ab', 'aa', 'af', 'ak', 'sq', 'am', 'ar', 'an', 'hy', 'as', 'av', 'ae', 'ay', 'az', 'bm', 'ba', 'eu', 'be', 'bn', 'bh', 'bi', 'bs', 'br', 'bg', 'my', 'ca', 'ch', 'ce', 'ny', 'zh', 'cv', 'kw', 'co', 'cr', 'hr', 'cs', 'da', 'dv', 'nl', 'dz', 'en', 'eo', 'et', 'ee', 'fo', 'fj', 'fi', 'fr', 'ff', 'gl', 'ka', 'de', 'el', 'gn', 'gu', 'ht', 'ha', 'he', 'hz', 'hi', 'ho', 'hu', 'ia', 'id', 'ie', 'ga', 'ig', 'ik', 'io', 'is', 'it', 'iu', 'ja', 'jv', 'kl', 'kn', 'kr', 'ks', 'kk', 'km', 'ki', 'rw', 'ky', 'kv', 'kg', 'ko', 'ku', 'kj', 'la', 'lb', 'lg', 'li', 'ln', 'lo', 'lt', 'lu', 'lv', 'gv', 'mk', 'mg', 'ms', 'ml', 'mt', 'mi', 'mr', 'mh', 'mn', 'na', 'nv', 'nd', 'ne', 'ng', 'nb', 'nn', 'no', 'ii', 'nr', 'oc', 'oj', 'cu', 'om', 'or', 'os', 'pa', 'pi', 'fa', 'pl', 'ps', 'pt', 'qu', 'rm', 'rn', 'ro', 'ru', 'sa', 'sc', 'sd', 'se', 'sm', 'sg', 'sr', 'gd', 'sn', 'si', 'sk', 'sl', 'so', 'st', 'es', 'su', 'sw', 'ss', 'sv', 'ta', 'te', 'tg', 'th', 'ti', 'bo', 'tk', 'tl', 'tn', 'to', 'tr', 'ts', 'tt', 'tw', 'ty', 'ug', 'uk', 'ur', 'uz', 've', 'vi', 'vo', 'wa', 'cy', 'wo', 'fy', 'xh', 'yi', 'yo', 'za', 'zu'];
1069
1178
  }
1070
1179
 
1180
+ /**
1181
+ * @param {string} appId
1182
+ * @returns {string}
1183
+ */
1071
1184
  static getBrandColor(appId) {
1072
1185
  const appIdHex = Buffer.from(appId).toString('hex');
1073
1186
  let brandColor;
@@ -1091,6 +1204,11 @@ class App {
1091
1204
  return brandColor;
1092
1205
  }
1093
1206
 
1207
+ /**
1208
+ * @param {Array<Record<string, unknown>> | undefined} errors
1209
+ * @param {AppManifest} appJson
1210
+ * @returns {string | null}
1211
+ */
1094
1212
  static errorsText(errors, appJson) {
1095
1213
  if (Array.isArray(errors) === false) return null;
1096
1214
 
@@ -4,19 +4,33 @@
4
4
 
5
5
  const Ajv = require('ajv');
6
6
 
7
+ /** @typedef {import('../../assets/capability/schema').Capability} CapabilityDefinition */
8
+
9
+ /** @type {Record<string, CapabilityDefinition> | undefined} */
7
10
  let capabilitiesCache;
8
11
 
9
12
  class Capability {
10
13
 
14
+ /**
15
+ * @param {CapabilityDefinition} capability
16
+ */
11
17
  constructor(capability) {
12
18
  this._capability = capability;
13
19
  }
14
20
 
21
+ /**
22
+ * @param {...unknown} args
23
+ * @returns {void}
24
+ */
15
25
  debug(...args) {
16
26
  if (!this._debug) return;
17
27
  console.log('[dbg]', ...args);
18
28
  }
19
29
 
30
+ /**
31
+ * @param {{ debug?: boolean }} [options]
32
+ * @returns {Promise<void>}
33
+ */
20
34
  async validate({
21
35
  debug = false,
22
36
  } = {}) {
@@ -33,11 +47,17 @@ class Capability {
33
47
  this.debug('Validated successfully');
34
48
  }
35
49
 
50
+ /**
51
+ * @returns {Record<string, unknown>}
52
+ */
36
53
  static getJSONSchema() {
37
54
  // eslint-disable-next-line global-require
38
55
  return require('../../assets/capability/schema.json');
39
56
  }
40
57
 
58
+ /**
59
+ * @returns {Record<string, CapabilityDefinition>}
60
+ */
41
61
  static getCapabilities() {
42
62
  if (capabilitiesCache) return capabilitiesCache;
43
63
 
@@ -52,6 +72,10 @@ class Capability {
52
72
  return capabilitiesCache;
53
73
  }
54
74
 
75
+ /**
76
+ * @param {string} id
77
+ * @returns {CapabilityDefinition}
78
+ */
55
79
  static getCapability(id) {
56
80
  const capabilities = Capability.getCapabilities();
57
81
  const capability = capabilities[id];
@@ -61,11 +85,20 @@ class Capability {
61
85
  return capability;
62
86
  }
63
87
 
88
+ /**
89
+ * @param {string} id
90
+ * @returns {boolean}
91
+ */
64
92
  static hasCapability(id) {
65
93
  const capabilities = this.getCapabilities();
66
94
  return !!capabilities[id];
67
95
  }
68
96
 
97
+ /**
98
+ * @param {string} capabilityId
99
+ * @param {CapabilityDefinition} capability
100
+ * @returns {CapabilityDefinition}
101
+ */
69
102
  static _composeCapability(capabilityId, capability) {
70
103
  if (capability.flow) console.warn(`Warning: using \`capability.flow\` (${capabilityId}), expected a \`capability.$flow\``);
71
104
  if (capability.$flow) {
@@ -109,12 +142,21 @@ class Capability {
109
142
  return capability;
110
143
  }
111
144
 
145
+ /**
146
+ * @param {string} capabilityIdToCheck
147
+ * @param {string} capabilityId
148
+ * @returns {boolean}
149
+ */
112
150
  static isInstanceOfId(capabilityIdToCheck, capabilityId) {
113
151
  return (
114
152
  capabilityId === capabilityIdToCheck || capabilityIdToCheck.startsWith(`${capabilityId}.`)
115
153
  );
116
154
  }
117
155
 
156
+ /**
157
+ * @param {string} capabilityId
158
+ * @returns {string}
159
+ */
118
160
  static getBaseId(capabilityId) {
119
161
  const parts = capabilityId.split('.');
120
162
  return parts[0];
@@ -6,6 +6,9 @@ let classesCache;
6
6
 
7
7
  class Device {
8
8
 
9
+ /**
10
+ * @returns {Record<string, Record<string, unknown>>}
11
+ */
9
12
  static getClasses() {
10
13
  if (classesCache) return classesCache;
11
14
 
@@ -19,6 +22,10 @@ class Device {
19
22
  return classesCache;
20
23
  }
21
24
 
25
+ /**
26
+ * @param {string} id
27
+ * @returns {Record<string, unknown>}
28
+ */
22
29
  static getClass(id) {
23
30
  const deviceClasses = Device.getClasses();
24
31
  const deviceClass = deviceClasses[id];
@@ -29,6 +36,9 @@ class Device {
29
36
  }
30
37
 
31
38
  // legacy
39
+ /**
40
+ * @returns {ReturnType<typeof Capability.getCapabilities>}
41
+ */
32
42
  static getCapabilities() {
33
43
  return Capability.getCapabilities();
34
44
  }
@@ -2,11 +2,17 @@
2
2
 
3
3
  class Energy {
4
4
 
5
+ /**
6
+ * @returns {Record<string, Record<string, unknown>>}
7
+ */
5
8
  static getCurrencies() {
6
9
  // eslint-disable-next-line global-require
7
10
  return require('../../assets/energy/currencies.json');
8
11
  }
9
12
 
13
+ /**
14
+ * @returns {string[]}
15
+ */
10
16
  static getBatteries() {
11
17
  return [
12
18
  'LS14250',
@@ -2,6 +2,9 @@
2
2
 
3
3
  class Media {
4
4
 
5
+ /**
6
+ * @returns {Record<string, Record<string, unknown>>}
7
+ */
5
8
  static getCodecs() {
6
9
  // eslint-disable-next-line global-require
7
10
  return require('../../assets/media/codecs.json');
@@ -11,8 +11,16 @@ const {
11
11
  prontoValidator,
12
12
  } = require('./validators');
13
13
 
14
+ /**
15
+ * @typedef {'433' | '868' | 'ir'} SignalFrequency
16
+ */
17
+
14
18
  class Signal {
15
19
 
20
+ /**
21
+ * @param {Record<string, unknown>} signal
22
+ * @param {{ frequency?: SignalFrequency }} [options]
23
+ */
16
24
  constructor(signal, { frequency = undefined } = {}) {
17
25
  this._signal = signal;
18
26
  this._frequency = frequency;
@@ -20,6 +28,10 @@ class Signal {
20
28
  this._check = this._check.bind(this);
21
29
  }
22
30
 
31
+ /**
32
+ * @param {...unknown} args
33
+ * @returns {void}
34
+ */
23
35
  debug(...args) {
24
36
  if (!this._debug) return;
25
37
 
@@ -27,12 +39,21 @@ class Signal {
27
39
  console.log('[dbg]', ...args);
28
40
  }
29
41
 
42
+ /**
43
+ * @param {string} message
44
+ * @param {boolean} result
45
+ * @returns {void}
46
+ */
30
47
  _check(message, result) {
31
48
  if (result !== true) {
32
49
  throw new Error(message);
33
50
  }
34
51
  }
35
52
 
53
+ /**
54
+ * @param {{ debug?: boolean }} [options]
55
+ * @returns {Promise<void>}
56
+ */
36
57
  async validate({
37
58
  debug = false,
38
59
  } = {}) {
@@ -65,6 +86,10 @@ class Signal {
65
86
  this.debug('Validated successfully');
66
87
  }
67
88
 
89
+ /**
90
+ * @param {Record<string, (value: unknown, signal: Record<string, unknown>) => { result: boolean, msg: string }>} validatorEngine
91
+ * @returns {void}
92
+ */
68
93
  _validateWithEngine(validatorEngine) {
69
94
  return validate(validatorEngine, this._check, this._signal);
70
95
  }
@@ -2,7 +2,21 @@
2
2
 
3
3
  'use strict';
4
4
 
5
+ /**
6
+ * @typedef {Record<string, unknown>} SignalDefinition
7
+ * @typedef {{ result: boolean, msg: string }} ValidationResult
8
+ * @typedef {(value: unknown, signal: SignalDefinition) => ValidationResult} Validator
9
+ * @typedef {Record<string, Validator>} ValidatorEngine
10
+ * @typedef {(message: string, result: boolean) => void} CheckFn
11
+ */
12
+
5
13
  /* static functions */
14
+ /**
15
+ * @param {ValidatorEngine} validator
16
+ * @param {CheckFn} check
17
+ * @param {SignalDefinition} signal
18
+ * @returns {void}
19
+ */
6
20
  function validate(validator, check, signal) {
7
21
  for (const propName in signal) {
8
22
  const property = signal[propName];
@@ -70,6 +84,7 @@ function _validateGenericData(data, signal) {
70
84
  return res;
71
85
  }
72
86
 
87
+ /** @type {ValidatorEngine} */
73
88
  const genericValidator = {
74
89
  words(words, signal) {
75
90
  const res = { result: true, msg: 'invalid_words' };
@@ -166,6 +181,7 @@ const rfBounds = {
166
181
  repetitions: { min: 1, max: 255 },
167
182
  };
168
183
 
184
+ /** @type {ValidatorEngine} */
169
185
  const rfValidator = {
170
186
 
171
187
  words(words, signal) {
@@ -222,6 +238,7 @@ const modulationBounds = {
222
238
  channelDeviation: { min: 5000, max: 50000 },
223
239
  };
224
240
 
241
+ /** @type {ValidatorEngine} */
225
242
  const modulationValidator = {
226
243
  modulation(modulation, signal) {
227
244
  // replace by default modulation props in homey-microcontroller?
@@ -256,6 +273,7 @@ const modulationValidator = {
256
273
  },
257
274
  };
258
275
 
276
+ /** @type {ValidatorEngine} */
259
277
  const prontoValidator = {
260
278
  cmds(cmds, signal) {
261
279
  const res = !Object.keys(cmds).some(cmd => {
@@ -279,6 +297,7 @@ const rf433Bounds = {
279
297
  carrier: { min: 433000000, max: 433990000 },
280
298
  };
281
299
 
300
+ /** @type {ValidatorEngine} */
282
301
  const rf433Validator = {
283
302
  carrier(carrier, signal) {
284
303
  const res = _valid_bounds(carrier, 433000000, 433990000);
@@ -290,6 +309,7 @@ const rf868Bounds = {
290
309
  carrier: { min: 868000000, max: 868900000 },
291
310
  };
292
311
 
312
+ /** @type {ValidatorEngine} */
293
313
  const rf868Validator = {
294
314
  carrier(carrier, signal) {
295
315
  const res = _valid_bounds(carrier, rf868Bounds.carrier.min, rf868Bounds.carrier.max);
@@ -302,6 +322,7 @@ const irBounds = {
302
322
  dutyCycle: { min: 30, max: 70 },
303
323
  };
304
324
 
325
+ /** @type {ValidatorEngine} */
305
326
  const irValidator = {
306
327
  carrier(carrier, signal) {
307
328
  const res = _valid_bounds(carrier, irBounds.carrier.min, irBounds.carrier.max);
package/lib/Util/index.js CHANGED
@@ -2,10 +2,19 @@
2
2
 
3
3
  class Util {
4
4
 
5
+ /**
6
+ * @param {string} modelId
7
+ * @returns {string[]}
8
+ */
5
9
  static getPlatformLocalFeatures(modelId) {
6
10
  return Util._platformLocalFeatures[modelId] || [];
7
11
  }
8
12
 
13
+ /**
14
+ * @param {string} modelId
15
+ * @param {string[]} wantedFeatures
16
+ * @returns {string[]}
17
+ */
9
18
  static getMissingPlatformLocalFeatures(modelId, wantedFeatures) {
10
19
  const features = Util.getPlatformLocalFeatures(modelId);
11
20
  return wantedFeatures.filter(feature => !features.includes(feature));
@@ -18,6 +27,7 @@ Util.FEATURE_LED_RING = 'ledring';
18
27
  Util.FEATURE_NFC = 'nfc';
19
28
  Util.FEATURE_MATTER = 'matter';
20
29
  Util.FEATURE_CAMERA_STREAMING = 'camera-streaming';
30
+ /** @type {Record<string, string[]>} */
21
31
  Util._platformLocalFeatures = {
22
32
  homey1s: [Util.FEATURE_SPEAKER, Util.FEATURE_LED_RING, Util.FEATURE_NFC],
23
33
  homey1d: [Util.FEATURE_SPEAKER, Util.FEATURE_LED_RING, Util.FEATURE_NFC],
package/package.json CHANGED
@@ -1,15 +1,21 @@
1
1
  {
2
2
  "name": "homey-lib",
3
- "version": "2.45.2",
3
+ "version": "2.45.4",
4
4
  "description": "Shared Library for Homey",
5
+ "types": "types/index.d.ts",
5
6
  "main": "index.js",
6
7
  "scripts": {
7
- "build": "npm ci; npm run pack",
8
+ "build": "npm ci; npm run types:generate; npm run schema:generate; npm run schema:copy; npm run pack",
8
9
  "pack": "webpack",
9
10
  "lint": "eslint .",
10
11
  "test": "mocha --parallel",
11
12
  "locales:apply": "node scripts/apply-locale-files.js",
12
- "locales:generate": "node scripts/generate-locale-files.js"
13
+ "locales:generate": "node scripts/generate-locale-files.js",
14
+ "types:generate": "tsc --project tsconfig.types.json",
15
+ "schema:generate": "npm run capability-schema:generate && npm run app-schema:generate",
16
+ "schema:copy": "mkdir -p types/assets/app types/assets/capability && cp assets/app/schema.d.ts types/assets/app/schema.d.ts && cp assets/capability/schema.d.ts types/assets/capability/schema.d.ts",
17
+ "capability-schema:generate": "json2ts -i assets/capability/schema.json -o assets/capability/schema.d.ts --unknownAny",
18
+ "app-schema:generate": "json2ts -i assets/app/schema.json -o assets/app/schema.d.ts --unknownAny"
13
19
  },
14
20
  "engines": {
15
21
  "node": ">=12.13.0"
@@ -34,10 +40,12 @@
34
40
  "devDependencies": {
35
41
  "eslint": "^7.31.0",
36
42
  "eslint-config-athom": "^2.1.1",
43
+ "json-schema-to-typescript": "^15.0.4",
37
44
  "mocha": "^9.1.3",
38
45
  "mock-fs": "^5.1.2",
39
46
  "object-path": "^0.11.8",
40
47
  "set-value": "^4.1.0",
48
+ "typescript": "^5.9.3",
41
49
  "webpack": "^5.98.0",
42
50
  "webpack-cli": "^5.1.4"
43
51
  }
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "allowJs": true,
4
+ "checkJs": false,
5
+ "declaration": true,
6
+ "emitDeclarationOnly": true,
7
+ "declarationMap": true,
8
+ "outDir": "./types",
9
+ "module": "commonjs",
10
+ "target": "ES2020",
11
+ "strict": false
12
+ },
13
+ "include": ["index.js", "lib/**/*.js"],
14
+ "exclude": ["test/**", "node_modules/**"]
15
+ }