@alwaysai/device-agent 1.4.0 → 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 (139) hide show
  1. package/lib/application-control/config.js +2 -2
  2. package/lib/application-control/config.js.map +1 -1
  3. package/lib/application-control/install.d.ts.map +1 -1
  4. package/lib/application-control/install.js +1 -0
  5. package/lib/application-control/install.js.map +1 -1
  6. package/lib/application-control/models.d.ts +5 -0
  7. package/lib/application-control/models.d.ts.map +1 -1
  8. package/lib/application-control/models.js +24 -12
  9. package/lib/application-control/models.js.map +1 -1
  10. package/lib/application-control/status.d.ts.map +1 -1
  11. package/lib/application-control/status.js +10 -12
  12. package/lib/application-control/status.js.map +1 -1
  13. package/lib/application-control/utils.js +2 -2
  14. package/lib/application-control/utils.js.map +1 -1
  15. package/lib/cloud-connection/device-agent-cloud-connection.d.ts +2 -2
  16. package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
  17. package/lib/cloud-connection/device-agent-cloud-connection.js +35 -15
  18. package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
  19. package/lib/cloud-connection/live-updates-handler.d.ts +3 -2
  20. package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
  21. package/lib/cloud-connection/live-updates-handler.js +24 -21
  22. package/lib/cloud-connection/live-updates-handler.js.map +1 -1
  23. package/lib/cloud-connection/live-updates-handler.test.js +132 -16
  24. package/lib/cloud-connection/live-updates-handler.test.js.map +1 -1
  25. package/lib/cloud-connection/passthrough-handler.d.ts +5 -3
  26. package/lib/cloud-connection/passthrough-handler.d.ts.map +1 -1
  27. package/lib/cloud-connection/passthrough-handler.js +76 -62
  28. package/lib/cloud-connection/passthrough-handler.js.map +1 -1
  29. package/lib/cloud-connection/shadow-handler.d.ts +2 -0
  30. package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
  31. package/lib/cloud-connection/shadow-handler.js +5 -5
  32. package/lib/cloud-connection/shadow-handler.js.map +1 -1
  33. package/lib/cloud-connection/transaction-manager.d.ts +3 -0
  34. package/lib/cloud-connection/transaction-manager.d.ts.map +1 -1
  35. package/lib/cloud-connection/transaction-manager.js +11 -0
  36. package/lib/cloud-connection/transaction-manager.js.map +1 -1
  37. package/lib/cloud-connection/transaction-manager.test.js +102 -0
  38. package/lib/cloud-connection/transaction-manager.test.js.map +1 -1
  39. package/lib/device-control/device-control.d.ts +10 -2
  40. package/lib/device-control/device-control.d.ts.map +1 -1
  41. package/lib/device-control/device-control.js +86 -10
  42. package/lib/device-control/device-control.js.map +1 -1
  43. package/lib/docker/docker-compose.d.ts +14 -0
  44. package/lib/docker/docker-compose.d.ts.map +1 -0
  45. package/lib/docker/docker-compose.js +56 -0
  46. package/lib/docker/docker-compose.js.map +1 -0
  47. package/lib/index.js +2 -5
  48. package/lib/index.js.map +1 -1
  49. package/lib/infrastructure/agent-config.d.ts +45 -14
  50. package/lib/infrastructure/agent-config.d.ts.map +1 -1
  51. package/lib/infrastructure/agent-config.js +30 -15
  52. package/lib/infrastructure/agent-config.js.map +1 -1
  53. package/lib/infrastructure/agent-config.test.js +3 -0
  54. package/lib/infrastructure/agent-config.test.js.map +1 -1
  55. package/lib/local-connection/rabbitmq-connection.js +11 -11
  56. package/lib/local-connection/rabbitmq-connection.js.map +1 -1
  57. package/lib/secure-tunneling/secure-tunneling.d.ts +14 -22
  58. package/lib/secure-tunneling/secure-tunneling.d.ts.map +1 -1
  59. package/lib/secure-tunneling/secure-tunneling.js +34 -34
  60. package/lib/secure-tunneling/secure-tunneling.js.map +1 -1
  61. package/lib/secure-tunneling/secure-tunneling.test.js +18 -18
  62. package/lib/secure-tunneling/secure-tunneling.test.js.map +1 -1
  63. package/lib/subcommands/device/clean.js +5 -5
  64. package/lib/subcommands/device/clean.js.map +1 -1
  65. package/lib/subcommands/device/get-info.d.ts +2 -0
  66. package/lib/subcommands/device/get-info.d.ts.map +1 -0
  67. package/lib/subcommands/device/get-info.js +36 -0
  68. package/lib/subcommands/device/get-info.js.map +1 -0
  69. package/lib/subcommands/device/index.d.ts.map +1 -1
  70. package/lib/subcommands/device/index.js +11 -2
  71. package/lib/subcommands/device/index.js.map +1 -1
  72. package/lib/subcommands/device/init.d.ts +5 -0
  73. package/lib/subcommands/device/init.d.ts.map +1 -0
  74. package/lib/subcommands/device/{device.js → init.js} +2 -41
  75. package/lib/subcommands/device/init.js.map +1 -0
  76. package/lib/subcommands/device/refresh.d.ts +2 -0
  77. package/lib/subcommands/device/refresh.d.ts.map +1 -0
  78. package/lib/subcommands/device/refresh.js +24 -0
  79. package/lib/subcommands/device/refresh.js.map +1 -0
  80. package/lib/subcommands/device/restart.d.ts +2 -0
  81. package/lib/subcommands/device/restart.d.ts.map +1 -0
  82. package/lib/subcommands/device/restart.js +14 -0
  83. package/lib/subcommands/device/restart.js.map +1 -0
  84. package/lib/util/check-for-updates.d.ts +3 -0
  85. package/lib/util/check-for-updates.d.ts.map +1 -0
  86. package/lib/util/check-for-updates.js +69 -0
  87. package/lib/util/check-for-updates.js.map +1 -0
  88. package/lib/util/file.d.ts +7 -0
  89. package/lib/util/file.d.ts.map +1 -0
  90. package/lib/util/file.js +66 -0
  91. package/lib/util/file.js.map +1 -0
  92. package/lib/util/file.test.d.ts +2 -0
  93. package/lib/util/file.test.d.ts.map +1 -0
  94. package/lib/util/file.test.js +87 -0
  95. package/lib/util/file.test.js.map +1 -0
  96. package/package.json +8 -7
  97. package/readme.md +3 -3
  98. package/src/application-control/config.ts +1 -1
  99. package/src/application-control/install.ts +1 -0
  100. package/src/application-control/models.ts +36 -13
  101. package/src/application-control/status.ts +9 -7
  102. package/src/application-control/utils.ts +1 -1
  103. package/src/cloud-connection/device-agent-cloud-connection.ts +54 -30
  104. package/src/cloud-connection/live-updates-handler.test.ts +161 -20
  105. package/src/cloud-connection/live-updates-handler.ts +30 -26
  106. package/src/cloud-connection/passthrough-handler.ts +98 -76
  107. package/src/cloud-connection/shadow-handler.ts +19 -7
  108. package/src/cloud-connection/transaction-manager.test.ts +124 -0
  109. package/src/cloud-connection/transaction-manager.ts +15 -0
  110. package/src/device-control/device-control.ts +86 -11
  111. package/src/docker/docker-compose.ts +60 -0
  112. package/src/index.ts +2 -6
  113. package/src/infrastructure/agent-config.test.ts +3 -0
  114. package/src/infrastructure/agent-config.ts +38 -40
  115. package/src/local-connection/rabbitmq-connection.ts +8 -8
  116. package/src/secure-tunneling/secure-tunneling.test.ts +26 -26
  117. package/src/secure-tunneling/secure-tunneling.ts +48 -55
  118. package/src/subcommands/device/clean.ts +1 -1
  119. package/src/subcommands/device/get-info.ts +49 -0
  120. package/src/subcommands/device/index.ts +11 -2
  121. package/src/subcommands/device/{device.ts → init.ts} +0 -58
  122. package/src/subcommands/device/refresh.ts +22 -0
  123. package/src/subcommands/device/restart.ts +11 -0
  124. package/src/util/check-for-updates.ts +69 -0
  125. package/src/util/file.test.ts +90 -0
  126. package/src/util/file.ts +76 -0
  127. package/lib/docker/docker-compose-cmd.d.ts +0 -5
  128. package/lib/docker/docker-compose-cmd.d.ts.map +0 -1
  129. package/lib/docker/docker-compose-cmd.js +0 -16
  130. package/lib/docker/docker-compose-cmd.js.map +0 -1
  131. package/lib/subcommands/device/device.d.ts +0 -7
  132. package/lib/subcommands/device/device.d.ts.map +0 -1
  133. package/lib/subcommands/device/device.js.map +0 -1
  134. package/lib/util/safe-rimraf.d.ts +0 -2
  135. package/lib/util/safe-rimraf.d.ts.map +0 -1
  136. package/lib/util/safe-rimraf.js +0 -16
  137. package/lib/util/safe-rimraf.js.map +0 -1
  138. package/src/docker/docker-compose-cmd.ts +0 -15
  139. package/src/util/safe-rimraf.ts +0 -14
@@ -0,0 +1 @@
1
+ {"version":3,"file":"restart.js","sourceRoot":"","sources":["../../../src/subcommands/device/restart.ts"],"names":[],"mappings":";;;AAAA,mDAA8C;AAC9C,wEAA6D;AAEhD,QAAA,cAAc,GAAG,IAAA,mBAAO,EAAC;IACpC,IAAI,EAAE,SAAS;IACf,WAAW,EAAE,oBAAoB;IACjC,WAAW,EAAE,EAAE;IACf,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI;QAClB,MAAM,IAAA,uBAAM,GAAE,CAAC;IACjB,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare function getDeviceAgentVersion(): Promise<string>;
2
+ export declare function checkForUpdatesAndPrompt(): Promise<void>;
3
+ //# sourceMappingURL=check-for-updates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-for-updates.d.ts","sourceRoot":"","sources":["../../src/util/check-for-updates.ts"],"names":[],"mappings":"AAgCA,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,MAAM,CAAC,CAY7D;AAQD,wBAAsB,wBAAwB,kBAgB7C"}
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkForUpdatesAndPrompt = exports.getDeviceAgentVersion = void 0;
4
+ const logger_1 = require("../util/logger");
5
+ const util_1 = require("alwaysai/lib/util");
6
+ const path = require("path");
7
+ const https_1 = require("https");
8
+ const appPrompt = 'Please restart the Device Agent or reboot the device to update. ';
9
+ const updatePrompt = `A new version of the Device Agent is available! \n\n`;
10
+ function getLatestVersion(packageName) {
11
+ return new Promise((resolve, reject) => {
12
+ const url = `https://registry.npmjs.org/${packageName}/latest`;
13
+ (0, https_1.get)(url, (res) => {
14
+ let data = '';
15
+ res.on('data', (chunk) => {
16
+ data += chunk;
17
+ });
18
+ res.on('end', () => {
19
+ try {
20
+ const latestPackage = JSON.parse(data);
21
+ resolve(latestPackage.version);
22
+ }
23
+ catch (error) {
24
+ reject(error);
25
+ }
26
+ });
27
+ }).on('error', (error) => {
28
+ reject(error);
29
+ });
30
+ });
31
+ }
32
+ async function getDeviceAgentVersion() {
33
+ let daVersion = '';
34
+ try {
35
+ const packagePath = path.join(__dirname, '../../');
36
+ const spawner = (0, util_1.JsSpawner)({ path: packagePath });
37
+ const output = await spawner.readFile('package.json');
38
+ const parsed = JSON.parse(output);
39
+ daVersion = parsed.version;
40
+ }
41
+ catch (e) {
42
+ logger_1.logger.error(`Could not retrieve the Device Agent version: $ {e}`);
43
+ }
44
+ return daVersion;
45
+ }
46
+ exports.getDeviceAgentVersion = getDeviceAgentVersion;
47
+ function constructPrompt(props) {
48
+ const { currentVer, latestVer } = props;
49
+ const prompt = `${updatePrompt}${appPrompt}\nYour version: ${currentVer}. Newest version: ${latestVer}`;
50
+ return prompt;
51
+ }
52
+ async function checkForUpdatesAndPrompt() {
53
+ try {
54
+ logger_1.logger.debug('Checking for updates...');
55
+ const latestVersion = await getLatestVersion('@alwaysai/device-agent');
56
+ const version = await getDeviceAgentVersion();
57
+ if (version !== latestVersion) {
58
+ logger_1.logger.warn(constructPrompt({
59
+ currentVer: version,
60
+ latestVer: latestVersion
61
+ }));
62
+ }
63
+ }
64
+ catch (error) {
65
+ logger_1.logger.error(`Could not check for updates: ${error}`);
66
+ }
67
+ }
68
+ exports.checkForUpdatesAndPrompt = checkForUpdatesAndPrompt;
69
+ //# sourceMappingURL=check-for-updates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-for-updates.js","sourceRoot":"","sources":["../../src/util/check-for-updates.ts"],"names":[],"mappings":";;;AAAA,2CAAwC;AACxC,4CAA8C;AAC9C,6BAA6B;AAC7B,iCAA4B;AAE5B,MAAM,SAAS,GACb,kEAAkE,CAAC;AACrE,MAAM,YAAY,GAAG,sDAAsD,CAAC;AAE5E,SAAS,gBAAgB,CAAC,WAAmB;IAC3C,OAAO,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC7C,MAAM,GAAG,GAAG,8BAA8B,WAAW,SAAS,CAAC;QAE/D,IAAA,WAAG,EAAC,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE;YACf,IAAI,IAAI,GAAG,EAAE,CAAC;YACd,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;gBACvB,IAAI,IAAI,KAAK,CAAC;YAChB,CAAC,CAAC,CAAC;YACH,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;gBACjB,IAAI;oBACF,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;oBACvC,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;iBAChC;gBAAC,OAAO,KAAK,EAAE;oBACd,MAAM,CAAC,KAAK,CAAC,CAAC;iBACf;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YACvB,MAAM,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAEM,KAAK,UAAU,qBAAqB;IACzC,IAAI,SAAS,GAAG,EAAE,CAAC;IACnB,IAAI;QACF,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QACnD,MAAM,OAAO,GAAG,IAAA,gBAAS,EAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;QACjD,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;QACtD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAClC,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC;KAC5B;IAAC,OAAO,CAAC,EAAE;QACV,eAAM,CAAC,KAAK,CAAC,oDAAoD,CAAC,CAAC;KACpE;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAZD,sDAYC;AAED,SAAS,eAAe,CAAC,KAAgD;IACvE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,KAAK,CAAC;IACxC,MAAM,MAAM,GAAG,GAAG,YAAY,GAAG,SAAS,mBAAmB,UAAU,qBAAqB,SAAS,EAAE,CAAC;IACxG,OAAO,MAAM,CAAC;AAChB,CAAC;AAEM,KAAK,UAAU,wBAAwB;IAC5C,IAAI;QACF,eAAM,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;QACxC,MAAM,aAAa,GAAG,MAAM,gBAAgB,CAAC,wBAAwB,CAAC,CAAC;QACvE,MAAM,OAAO,GAAG,MAAM,qBAAqB,EAAE,CAAC;QAC9C,IAAI,OAAO,KAAK,aAAa,EAAE;YAC7B,eAAM,CAAC,IAAI,CACT,eAAe,CAAC;gBACd,UAAU,EAAE,OAAO;gBACnB,SAAS,EAAE,aAAa;aACzB,CAAC,CACH,CAAC;SACH;KACF;IAAC,OAAO,KAAK,EAAE;QACd,eAAM,CAAC,KAAK,CAAC,gCAAgC,KAAK,EAAE,CAAC,CAAC;KACvD;AACH,CAAC;AAhBD,4DAgBC"}
@@ -0,0 +1,7 @@
1
+ export declare function safeRimraf(path: string): Promise<void>;
2
+ export declare function pruneDir({ path, exclude, recurse }: {
3
+ path: string;
4
+ exclude?: string[];
5
+ recurse?: boolean;
6
+ }): Promise<boolean>;
7
+ //# sourceMappingURL=file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../src/util/file.ts"],"names":[],"mappings":"AAKA,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,iBAU5C;AAED,wBAAsB,QAAQ,CAAC,EAC7B,IAAI,EACJ,OAAO,EACP,OAAO,EACR,EAAE;IACD,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,oBAkDA"}
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pruneDir = exports.safeRimraf = void 0;
4
+ const rimraf_1 = require("rimraf");
5
+ const logger_1 = require("../util/logger");
6
+ const promises_1 = require("fs/promises");
7
+ const path_1 = require("path");
8
+ async function safeRimraf(path) {
9
+ logger_1.logger.debug(`Removing ${path}`);
10
+ try {
11
+ await (0, rimraf_1.rimraf)(path);
12
+ }
13
+ catch (e) {
14
+ logger_1.logger.error(`Failed to remove ${path}. Please manually delete the file or directory.`);
15
+ logger_1.logger.debug(`Error removing ${path}: ${e}`);
16
+ }
17
+ }
18
+ exports.safeRimraf = safeRimraf;
19
+ async function pruneDir({ path, exclude, recurse }) {
20
+ /**
21
+ * Deletes all files and directories in a given directory path, excluding specific paths.
22
+ * Will not delete top-level directory
23
+ * @param {string} path path of directory to prune.
24
+ * @param {string[]} exclude list of paths to exclude when pruning
25
+ * @param {boolean} recurse flag to prune recursively into directories
26
+ */
27
+ try {
28
+ const files = await (0, promises_1.readdir)(path);
29
+ let canPruneParent = true;
30
+ for (const file of files) {
31
+ const filePath = (0, path_1.join)(path, file);
32
+ const isExcluded = exclude === null || exclude === void 0 ? void 0 : exclude.some((excludePath) => filePath.endsWith(excludePath));
33
+ if (isExcluded) {
34
+ logger_1.logger.debug(`Excluding file ${filePath} during pruning.`);
35
+ canPruneParent = false;
36
+ continue;
37
+ }
38
+ const fileStats = await (0, promises_1.stat)(filePath);
39
+ if (fileStats.isDirectory()) {
40
+ if (recurse) {
41
+ const dirIsEmpty = await pruneDir({
42
+ path: filePath,
43
+ exclude,
44
+ recurse
45
+ });
46
+ if (dirIsEmpty) {
47
+ await (0, promises_1.rmdir)(filePath);
48
+ }
49
+ else {
50
+ canPruneParent = false;
51
+ }
52
+ }
53
+ }
54
+ else {
55
+ await (0, promises_1.unlink)(filePath);
56
+ }
57
+ }
58
+ return canPruneParent;
59
+ }
60
+ catch (error) {
61
+ logger_1.logger.error(`Error pruning directory ${path}: ${JSON.stringify(error)}`);
62
+ return false;
63
+ }
64
+ }
65
+ exports.pruneDir = pruneDir;
66
+ //# sourceMappingURL=file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file.js","sourceRoot":"","sources":["../../src/util/file.ts"],"names":[],"mappings":";;;AAAA,mCAAgC;AAChC,2CAAwC;AACxC,0CAA2D;AAC3D,+BAA4B;AAErB,KAAK,UAAU,UAAU,CAAC,IAAY;IAC3C,eAAM,CAAC,KAAK,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC;IACjC,IAAI;QACF,MAAM,IAAA,eAAM,EAAC,IAAI,CAAC,CAAC;KACpB;IAAC,OAAO,CAAC,EAAE;QACV,eAAM,CAAC,KAAK,CACV,oBAAoB,IAAI,iDAAiD,CAC1E,CAAC;QACF,eAAM,CAAC,KAAK,CAAC,kBAAkB,IAAI,KAAK,CAAC,EAAE,CAAC,CAAC;KAC9C;AACH,CAAC;AAVD,gCAUC;AAEM,KAAK,UAAU,QAAQ,CAAC,EAC7B,IAAI,EACJ,OAAO,EACP,OAAO,EAKR;IACC;;;;;;OAMG;IACH,IAAI;QACF,MAAM,KAAK,GAAG,MAAM,IAAA,kBAAO,EAAC,IAAI,CAAC,CAAC;QAElC,IAAI,cAAc,GAAG,IAAI,CAAC;QAE1B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE;YACxB,MAAM,QAAQ,GAAG,IAAA,WAAI,EAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAElC,MAAM,UAAU,GAAG,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE,CAC/C,QAAQ,CAAC,QAAQ,CAAC,WAAW,CAAC,CAC/B,CAAC;YAEF,IAAI,UAAU,EAAE;gBACd,eAAM,CAAC,KAAK,CAAC,kBAAkB,QAAQ,kBAAkB,CAAC,CAAC;gBAC3D,cAAc,GAAG,KAAK,CAAC;gBACvB,SAAS;aACV;YAED,MAAM,SAAS,GAAG,MAAM,IAAA,eAAI,EAAC,QAAQ,CAAC,CAAC;YACvC,IAAI,SAAS,CAAC,WAAW,EAAE,EAAE;gBAC3B,IAAI,OAAO,EAAE;oBACX,MAAM,UAAU,GAAG,MAAM,QAAQ,CAAC;wBAChC,IAAI,EAAE,QAAQ;wBACd,OAAO;wBACP,OAAO;qBACR,CAAC,CAAC;oBAEH,IAAI,UAAU,EAAE;wBACd,MAAM,IAAA,gBAAK,EAAC,QAAQ,CAAC,CAAC;qBACvB;yBAAM;wBACL,cAAc,GAAG,KAAK,CAAC;qBACxB;iBACF;aACF;iBAAM;gBACL,MAAM,IAAA,iBAAM,EAAC,QAAQ,CAAC,CAAC;aACxB;SACF;QACD,OAAO,cAAc,CAAC;KACvB;IAAC,OAAO,KAAK,EAAE;QACd,eAAM,CAAC,KAAK,CAAC,2BAA2B,IAAI,KAAK,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC1E,OAAO,KAAK,CAAC;KACd;AACH,CAAC;AA1DD,4BA0DC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=file.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file.test.d.ts","sourceRoot":"","sources":["../../src/util/file.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const file_1 = require("../../src/util/file");
4
+ const promises_1 = require("fs/promises");
5
+ jest.mock('fs/promises');
6
+ describe('pruneDir function', () => {
7
+ afterEach(() => {
8
+ jest.clearAllMocks();
9
+ });
10
+ it('should return true if directory is successfully pruned', async () => {
11
+ const path = '/path/to/directory';
12
+ const files = ['file1.txt', 'file2.txt'];
13
+ promises_1.readdir.mockResolvedValue(files);
14
+ promises_1.stat.mockResolvedValue({ isDirectory: () => false });
15
+ const result = await (0, file_1.pruneDir)({ path });
16
+ expect(result).toBe(true);
17
+ expect(promises_1.readdir).toHaveBeenCalledWith(path);
18
+ expect(promises_1.stat).toHaveBeenCalledTimes(2); // Called for each file
19
+ expect(promises_1.unlink).toHaveBeenCalledTimes(2); // Called for each file
20
+ });
21
+ it('should exclude specified files from pruning', async () => {
22
+ const path = '/path/to/directory';
23
+ const files = ['file1.txt', 'file2.txt'];
24
+ const exclude = ['file1.txt']; // Exclude file1.txt
25
+ promises_1.readdir.mockResolvedValue(files);
26
+ promises_1.stat.mockResolvedValue({ isDirectory: () => false });
27
+ const result = await (0, file_1.pruneDir)({ path, exclude });
28
+ expect(result).toBe(false);
29
+ expect(promises_1.readdir).toHaveBeenCalledWith(path);
30
+ expect(promises_1.stat).toHaveBeenCalledTimes(1); // Called only for file2.txt
31
+ expect(promises_1.unlink).toHaveBeenCalledTimes(1); // Called only for file2.txt
32
+ });
33
+ it('should recursively prune directories if recurse option is true', async () => {
34
+ const rootPath = '/path/to/directory';
35
+ const subDirPath = '/path/to/directory/subdir';
36
+ const files = ['file1.txt', 'subdir'];
37
+ const subDirFiles = ['file2.txt'];
38
+ promises_1.readdir.mockImplementation((path) => {
39
+ if (path === rootPath)
40
+ return files;
41
+ if (path === subDirPath)
42
+ return subDirFiles;
43
+ });
44
+ promises_1.stat.mockImplementation((path) => {
45
+ if (path === `${rootPath}/file1.txt`)
46
+ return { isDirectory: () => false };
47
+ if (path === subDirPath)
48
+ return { isDirectory: () => true };
49
+ if (path === `${subDirPath}/file2.txt`)
50
+ return { isDirectory: () => false };
51
+ });
52
+ const result = await (0, file_1.pruneDir)({ path: rootPath, recurse: true });
53
+ expect(result).toBe(true);
54
+ expect(promises_1.readdir).toHaveBeenCalledWith(rootPath);
55
+ expect(promises_1.readdir).toHaveBeenCalledWith(subDirPath);
56
+ expect(promises_1.stat).toHaveBeenCalledTimes(3); // Called for file1.txt, subdir, and file2.txt
57
+ expect(promises_1.unlink).toHaveBeenCalledWith(`${rootPath}/file1.txt`);
58
+ expect(promises_1.unlink).toHaveBeenCalledWith(`${subDirPath}/file2.txt`);
59
+ expect(promises_1.rmdir).toHaveBeenCalledWith(subDirPath);
60
+ });
61
+ it('should not recurse into directories if recurse option is false', async () => {
62
+ const rootPath = '/path/to/directory';
63
+ const subDirPath = '/path/to/directory/subdir';
64
+ const files = ['file1.txt', 'subdir'];
65
+ const subDirFiles = ['file2.txt'];
66
+ promises_1.readdir.mockImplementation((path) => {
67
+ if (path === rootPath)
68
+ return files;
69
+ if (path === subDirPath)
70
+ return subDirFiles;
71
+ });
72
+ promises_1.stat.mockImplementation((path) => {
73
+ if (path === `${rootPath}/file1.txt`)
74
+ return { isDirectory: () => false };
75
+ if (path === subDirPath)
76
+ return { isDirectory: () => true };
77
+ });
78
+ const result = await (0, file_1.pruneDir)({ path: rootPath, recurse: false });
79
+ expect(result).toBe(true);
80
+ expect(promises_1.readdir).toHaveBeenCalledWith(rootPath);
81
+ expect(promises_1.stat).toHaveBeenCalledTimes(2); // Called for file1.txt and subdir
82
+ expect(promises_1.unlink).toHaveBeenCalledWith(`${rootPath}/file1.txt`);
83
+ expect(promises_1.unlink).not.toHaveBeenCalledWith(`${subDirPath}/file2.txt`);
84
+ expect(promises_1.rmdir).not.toHaveBeenCalled();
85
+ });
86
+ });
87
+ //# sourceMappingURL=file.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file.test.js","sourceRoot":"","sources":["../../src/util/file.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@alwaysai/device-agent",
3
3
  "description": "The alwaysAI Device Agent",
4
- "version": "1.4.0",
4
+ "version": "1.5.0",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
7
7
  "publishConfig": {
@@ -37,26 +37,27 @@
37
37
  "@alwaysai/alwayscli": "0.3.2",
38
38
  "@alwaysai/app-configuration-schemas": "0.1.0",
39
39
  "@alwaysai/config-nodejs": "0.3.0",
40
- "@alwaysai/device-agent-schemas": "2.3.1",
40
+ "@alwaysai/device-agent-schemas": "2.3.3",
41
41
  "@carnesen/coded-error": "0.4.0",
42
42
  "ajv": "8.11.0",
43
- "alwaysai": "2.2.0",
43
+ "alwaysai": "2.5.3",
44
44
  "amqplib": "0.10.3",
45
45
  "aws-iot-device-sdk": "2.2.12",
46
- "docker-compose": "0.23.17",
46
+ "docker-compose": "0.24.3",
47
47
  "lodash": "4.17.21",
48
48
  "node-fetch": "2.6.1",
49
49
  "node-os-utils": "1.3.7",
50
50
  "rimraf": "4.4.0",
51
51
  "systeminformation": "5.21.13",
52
52
  "uuid": "9.0.0",
53
- "winston": "3.8.2",
54
- "winston-daily-rotate-file": "4.7.1",
53
+ "winston": "3.13.0",
54
+ "winston-daily-rotate-file": "5.0.0",
55
55
  "yaml": "2.2.1"
56
56
  },
57
57
  "devDependencies": {
58
- "@alwaysai/tsconfig": "0.0.1",
59
58
  "@alwaysai/eslint-config": "0.1.0",
59
+ "@alwaysai/tsconfig": "0.0.1",
60
+ "@types/amqplib": "0.10.5",
60
61
  "@types/destroy": "1.0.0",
61
62
  "@types/jest": "28.1.2",
62
63
  "@types/node": "16.11.12",
package/readme.md CHANGED
@@ -42,13 +42,13 @@ change.
42
42
 
43
43
  * Supported OS:
44
44
  * Debian Bookworm, Bullseye
45
- * Ubuntu 23.10, 22.04, 20.04, 18.04
46
- * NVIDIA Jetpack 5.1, 4.6.x
45
+ * Ubuntu 24.04, 23.10, 22.04, 20.04
46
+ * NVIDIA JetPack 5.1.1, 5.1
47
47
  * Supported target architecture
48
48
  * amd64
49
49
  * aarch64
50
50
  * `docker` >= 19.03
51
- * `docker-compose` >= 1.29.0; < 2.0.0
51
+ * `docker-compose` >= 1.29.0
52
52
  * `curl` installed (required to download provisioning scripts)
53
53
  * Passwordless `sudo` for `npm` if using `pm2`
54
54
  * Passwordless `sudo` for `/sbin/shutdown` for device restart functionality
@@ -7,7 +7,7 @@ import {
7
7
  requireAppReady
8
8
  } from './utils';
9
9
  import { JsSpawner } from 'alwaysai/lib/util';
10
- import compose from 'docker-compose';
10
+ import { compose } from '../docker/docker-compose';
11
11
  import { assign, merge } from 'lodash';
12
12
  import { AppJsonFile } from 'alwaysai/lib/core/app';
13
13
  import { AppConfig } from '@alwaysai/app-configuration-schemas';
@@ -231,6 +231,7 @@ export async function uninstallApp(props: {
231
231
  logger.info(`Application ${projectId} not installed`);
232
232
  return;
233
233
  }
234
+ await AgentConfigFile().setAppUninstalling({ projectId });
234
235
  try {
235
236
  await stopApp({ projectId });
236
237
  } catch (e) {
@@ -1,4 +1,3 @@
1
- import * as path from 'path';
2
1
  import { ModelInstallPayload } from '@alwaysai/device-agent-schemas';
3
2
  import {
4
3
  appModelsAddComponent,
@@ -23,8 +22,9 @@ import {
23
22
  } from './utils';
24
23
  import { MODEL_JSON_FILE_NAME } from 'alwaysai/lib/core/model';
25
24
  import { writeAppCfgFile } from './config';
26
- import { AppConfig } from '@alwaysai/app-configuration-schemas';
27
25
  import { APP_MODELS_DIRECTORY_NAME } from 'alwaysai/lib/paths';
26
+ import { pruneDir } from '../util/file';
27
+ import { AppConfig } from '@alwaysai/app-configuration-schemas';
28
28
 
29
29
  export async function getAppModels(props: { projectId: string }) {
30
30
  const { projectId } = props;
@@ -127,9 +127,9 @@ export async function installModelsWithPresignedURLs(
127
127
  modelPayloads.map(async (payload: ModelInstallPayload) => {
128
128
  logger.info(`Installing ${payload.id}: ${payload.version}`);
129
129
  const version = payload.version;
130
- const modelDest = path.join(targetDir, payload.id);
130
+ const modelDest = join(targetDir, payload.id);
131
131
  await spawner.mkdirp(modelDest);
132
- const localDest = path.join(modelDest, `${payload.version}.tar.gz`);
132
+ const localDest = join(modelDest, `${payload.version}.tar.gz`);
133
133
  await downloadPackageUsingPresignedUrl({
134
134
  localDest,
135
135
  presignedUrl: payload.modelSignedUrl
@@ -178,20 +178,21 @@ export async function updateModelsWithPresignedUrls(props: {
178
178
  version: appReleaseHash
179
179
  });
180
180
 
181
- const ogDir = path.join(appDir, APP_MODELS_DIRECTORY_NAME);
182
- // Copy all current models to restore dir in case of failure
183
- const restoreDir = `${ogDir}.restore`;
184
- await spawner.rimraf(restoreDir);
185
- await copyDir({ srcPath: ogDir, destPath: restoreDir });
186
-
187
- // Create temp dir to install new models
181
+ const ogDir = join(appDir, APP_MODELS_DIRECTORY_NAME);
188
182
  const tmpDir = `${ogDir}.tmp`;
189
183
 
190
184
  try {
191
185
  await spawner.rimraf(tmpDir);
192
186
  await copyDir({ srcPath: ogDir, destPath: tmpDir });
187
+
188
+ await pruneModels({
189
+ projectId,
190
+ appCfg: newAppCfg,
191
+ path: tmpDir
192
+ });
193
+
193
194
  await installModelsWithPresignedURLs(modelInstallPayloads, tmpDir);
194
- // TODO: Purge outdated models
195
+
195
196
  await spawner.rimraf(ogDir);
196
197
  await copyDir({ srcPath: tmpDir, destPath: ogDir });
197
198
  await spawner.rimraf(tmpDir);
@@ -211,6 +212,28 @@ export async function updateModelsWithPresignedUrls(props: {
211
212
  logger.info(`Models installed for project ${projectId}`);
212
213
  } finally {
213
214
  await spawner.rimraf(tmpDir);
214
- await spawner.rimraf(restoreDir);
215
215
  }
216
216
  }
217
+
218
+ export async function pruneModels(props: {
219
+ projectId: string;
220
+ appCfg: AppConfig;
221
+ path?: string;
222
+ }) {
223
+ const { projectId, appCfg, path } = props;
224
+
225
+ const modelsPath = path || join(getAppDir(projectId), 'models');
226
+
227
+ if (!existsSync(modelsPath)) {
228
+ logger.error(
229
+ `Attempted to prune models from ${modelsPath} for project app ${projectId} that does not exist or has no models dir.`
230
+ );
231
+ return;
232
+ }
233
+
234
+ await pruneDir({
235
+ path: modelsPath,
236
+ exclude: Object.keys(appCfg.models),
237
+ recurse: true
238
+ });
239
+ }
@@ -1,4 +1,8 @@
1
- import compose from 'docker-compose';
1
+ import {
2
+ compose,
3
+ runStreamingDockerComposeCmd,
4
+ runDockerComposeCmd
5
+ } from '../docker/docker-compose';
2
6
  import { JsSpawner } from 'alwaysai/lib/util';
3
7
 
4
8
  import { runDockerLogin } from '../docker/docker-cmd';
@@ -48,9 +52,8 @@ export async function getAppState(props: {
48
52
  const spawner = JsSpawner({ path: appDir });
49
53
  for (const name of composeServices.data.services) {
50
54
  // Get container name for service
51
- const containerId = await spawner.run({
52
- exe: 'docker-compose',
53
- cwd: appDir,
55
+ const containerId = await runDockerComposeCmd({
56
+ dir: appDir,
54
57
  args: ['ps', '-q', name]
55
58
  });
56
59
  if (containerId === '') {
@@ -136,10 +139,9 @@ export async function getAppLogs(props: {
136
139
  const argsList = args || [];
137
140
 
138
141
  // Use direct command with spawner in order to get a readable stream
139
- return await JsSpawner().runStreaming({
140
- exe: 'docker-compose',
142
+ return await runStreamingDockerComposeCmd({
141
143
  args: ['logs', '-f', ...argsList, ...serviceList],
142
- cwd: appDir
144
+ dir: appDir
143
145
  });
144
146
  }
145
147
 
@@ -1,4 +1,4 @@
1
- import compose from 'docker-compose';
1
+ import { compose } from '../docker/docker-compose';
2
2
  import * as path from 'path';
3
3
  import * as fs from 'fs';
4
4