@alwaysai/device-agent 0.0.8 → 0.0.9

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 (64) hide show
  1. package/lib/application-control/backup.d.ts.map +1 -1
  2. package/lib/application-control/backup.js +2 -0
  3. package/lib/application-control/backup.js.map +1 -1
  4. package/lib/application-control/config.d.ts +17 -0
  5. package/lib/application-control/config.d.ts.map +1 -0
  6. package/lib/application-control/config.js +62 -0
  7. package/lib/application-control/config.js.map +1 -0
  8. package/lib/application-control/environment-variables.d.ts.map +1 -1
  9. package/lib/application-control/environment-variables.js +4 -12
  10. package/lib/application-control/environment-variables.js.map +1 -1
  11. package/lib/application-control/index.d.ts +2 -1
  12. package/lib/application-control/index.d.ts.map +1 -1
  13. package/lib/application-control/index.js +6 -1
  14. package/lib/application-control/index.js.map +1 -1
  15. package/lib/application-control/install.d.ts +12 -10
  16. package/lib/application-control/install.d.ts.map +1 -1
  17. package/lib/application-control/install.js +79 -41
  18. package/lib/application-control/install.js.map +1 -1
  19. package/lib/application-control/models.d.ts +3 -0
  20. package/lib/application-control/models.d.ts.map +1 -1
  21. package/lib/application-control/models.js +92 -19
  22. package/lib/application-control/models.js.map +1 -1
  23. package/lib/application-control/utils.d.ts +4 -3
  24. package/lib/application-control/utils.d.ts.map +1 -1
  25. package/lib/application-control/utils.js +30 -10
  26. package/lib/application-control/utils.js.map +1 -1
  27. package/lib/cloud-connection/device-agent-cloud-connection.d.ts +16 -9
  28. package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
  29. package/lib/cloud-connection/device-agent-cloud-connection.js +165 -89
  30. package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
  31. package/lib/infrastructure/agent-config.d.ts.map +1 -1
  32. package/lib/infrastructure/agent-config.js +7 -18
  33. package/lib/infrastructure/agent-config.js.map +1 -1
  34. package/lib/infrastructure/agent-config.test.js +47 -0
  35. package/lib/infrastructure/agent-config.test.js.map +1 -1
  36. package/lib/subcommands/login.d.ts.map +1 -1
  37. package/lib/subcommands/login.js +4 -3
  38. package/lib/subcommands/login.js.map +1 -1
  39. package/lib/util/copy-dir.d.ts.map +1 -1
  40. package/lib/util/copy-dir.js +3 -1
  41. package/lib/util/copy-dir.js.map +1 -1
  42. package/lib/util/run-in-dir.d.ts +2 -0
  43. package/lib/util/run-in-dir.d.ts.map +1 -0
  44. package/lib/util/run-in-dir.js +17 -0
  45. package/lib/util/run-in-dir.js.map +1 -0
  46. package/package.json +3 -3
  47. package/src/application-control/backup.ts +3 -0
  48. package/src/application-control/config.ts +61 -0
  49. package/src/application-control/environment-variables.ts +4 -10
  50. package/src/application-control/index.ts +5 -0
  51. package/src/application-control/install.ts +121 -52
  52. package/src/application-control/models.ts +132 -22
  53. package/src/application-control/utils.ts +37 -12
  54. package/src/cloud-connection/device-agent-cloud-connection.ts +197 -105
  55. package/src/infrastructure/agent-config.test.ts +56 -0
  56. package/src/infrastructure/agent-config.ts +10 -19
  57. package/src/subcommands/login.ts +6 -4
  58. package/src/util/copy-dir.ts +3 -1
  59. package/src/util/run-in-dir.ts +15 -0
  60. package/lib/util/run-cli-cmd.d.ts +0 -5
  61. package/lib/util/run-cli-cmd.d.ts.map +0 -1
  62. package/lib/util/run-cli-cmd.js +0 -24
  63. package/lib/util/run-cli-cmd.js.map +0 -1
  64. package/src/util/run-cli-cmd.ts +0 -18
@@ -13,7 +13,11 @@ import {
13
13
  getAppLogs,
14
14
  getAppStatus,
15
15
  } from '../application-control/status';
16
- import { installApp, uninstallApp } from '../application-control/install';
16
+ import {
17
+ getInstalledApps,
18
+ installApp,
19
+ uninstallApp,
20
+ } from '../application-control/install';
17
21
  import {
18
22
  keyMirrors,
19
23
  validateClientMessage,
@@ -29,14 +33,18 @@ import {
29
33
  AppStateMessage,
30
34
  AppLogsMessage,
31
35
  AppInstallStatusMessage,
32
- AppInstallSignedUrlsRequestMessage,
36
+ SignedUrlsRequestMessage,
33
37
  DeviceAgentMessage,
34
38
  ClientMessage,
39
+ AppDetailsPacket,
35
40
  } from '@alwaysai/device-agent-schemas';
36
41
  import { getDeviceId } from '../util/get-device-id';
37
- import { logger } from 'alwaysai/lib/util';
42
+ import { JsSpawner, logger } from 'alwaysai/lib/util';
38
43
  import { getCpuUtil, getDiskUtil, getMemUtil } from '../device-control/device-control';
39
44
  import { AgentConfigFile } from '../infrastructure/agent-config';
45
+ import { buildApp, getAppConfig, getAppDir } from '../application-control/utils';
46
+ import { updateModelsWithPresignedUrls } from '../application-control/models';
47
+ import { updateAppConfig } from '../application-control/config';
40
48
 
41
49
  export class DeviceAgentCloudConnection {
42
50
  private clientId = getDeviceId();
@@ -55,21 +63,63 @@ export class DeviceAgentCloudConnection {
55
63
  [keyMirrors.agentMessageType.app_install_status]: 5000,
56
64
  };
57
65
  private appLogStreams = new Set<string>();
58
- private readonly agentTopicPrefix = `destination/agent/device/${this.clientId}/topic/`;
59
- private readonly cloudTopicPrefix = `destination/cloud/device/${this.clientId}/topic/`;
60
- private readonly publishableTopics = {
61
- deviceStats: `${this.cloudTopicPrefix}device-management`,
62
- appState: `${this.cloudTopicPrefix}application-management`,
63
- appLogs: `${this.cloudTopicPrefix}application-management`,
64
- appInstallStatus: `${this.cloudTopicPrefix}installation-status`,
65
- cloudRequest: `${this.cloudTopicPrefix}request`,
66
- };
67
- private readonly subsribedTopics = {
68
- command: `${this.agentTopicPrefix}command`,
69
- response: `${this.agentTopicPrefix}response`,
66
+ private deviceType = 'aai-device';
67
+ private readonly shadowPrefix = `$aws/things/${this.clientId}/shadow/name/`;
68
+ private readonly shadowTopics = {
69
+ projects: {
70
+ updateDelta: `${this.shadowPrefix}projects/update/delta`,
71
+ getAccepted: `${this.shadowPrefix}projects/get/accepted`,
72
+ },
70
73
  };
74
+ private readonly toCloudTopic = `topic/to_cloud/${this.deviceType}/${this.clientId}`;
75
+ private readonly toClientTopic = `topic/to_client/${this.deviceType}/${this.clientId}`;
76
+ private readonly toDeviceTopic = `topic/to_device/${this.deviceType}/${this.clientId}`;
77
+
78
+ // device shadow utils
79
+
80
+ public getShadowPrefix() {
81
+ return this.shadowPrefix;
82
+ }
83
+
84
+ private async handleNamedShadowUpdate({ payload }: { payload: string }) {
85
+ const delta = JSON.parse(payload);
86
+ const deltaKeys = Object.keys(delta);
87
+
88
+ for (const projectId of deltaKeys) {
89
+ const projectShadow = delta[projectId];
90
+ if (projectShadow.appConfig) {
91
+ const appConfig = projectShadow.appConfig;
92
+ const appDir = getAppDir(projectId);
93
+ await updateAppConfig(projectId, appConfig);
94
+
95
+ if (appConfig.models) {
96
+ this.publishCloudRequest({
97
+ messageType: keyMirrors.agentMessageType.signed_urls_request,
98
+ modelsOnlyUrlsRequest: {
99
+ projectId,
100
+ models: appConfig.models,
101
+ },
102
+ });
103
+ }
104
+
105
+ if (appConfig.scripts && !appConfig.models) {
106
+ const appState = await getAppStatus({ projectId });
107
+
108
+ await buildApp({ appDir });
71
109
 
72
- public async startAppLogStream(projectId: string) {
110
+ if (
111
+ appState.services.length &&
112
+ appState.services[0].state !== keyMirrors.appState.stopped
113
+ ) {
114
+ restartApp({ projectId });
115
+ }
116
+ await this.publishReportedState(projectId);
117
+ }
118
+ }
119
+ }
120
+ }
121
+
122
+ private async startAppLogStream(projectId: string) {
73
123
  this.appLogStreams.add(projectId);
74
124
  const readable = await getAppLogs({
75
125
  projectId,
@@ -93,10 +143,10 @@ export class DeviceAgentCloudConnection {
93
143
  };
94
144
  const packet = this.buildMessagePacket(
95
145
  this.getClientId(),
96
- this.publishableTopics.appLogs,
146
+ this.toClientTopic,
97
147
  message,
98
148
  );
99
- this.publishMessage(this.publishableTopics.appLogs, JSON.stringify(packet));
149
+ this.publishMessage(this.toClientTopic, JSON.stringify(packet));
100
150
  });
101
151
 
102
152
  readable.on('error', (error) => {
@@ -104,7 +154,7 @@ export class DeviceAgentCloudConnection {
104
154
  });
105
155
 
106
156
  readable.on('finished', () => {
107
- console.log(`App logs finished piping for project ${projectId}`);
157
+ logger.info(`App logs finished piping for project ${projectId}`);
108
158
  });
109
159
  }
110
160
 
@@ -233,73 +283,6 @@ export class DeviceAgentCloudConnection {
233
283
  }
234
284
  }
235
285
 
236
- private async handleCloudResponse(message: ClientMessage) {
237
- const payload = message.payload;
238
- switch (payload.messageType) {
239
- case keyMirrors.clientMessageType.app_install_cloud_response: {
240
- const { projectId, appReleaseHash, appInstallPayload, modelsInstallPayload } =
241
- payload.appInstallCloudResponse;
242
-
243
- this.initAppInstallStatus({
244
- status: keyMirrors.appInstallStatus.in_progress,
245
- appReleaseHash,
246
- });
247
-
248
- this.startPublishingLiveUpdates(
249
- this.publishableTopics.appInstallStatus,
250
- keyMirrors.agentMessageType.app_install_status,
251
- this.getAppInstallStatusMessage,
252
- );
253
-
254
- // Install the app and models
255
- try {
256
- const signedUrlsPayload = {
257
- appInstallPayload,
258
- modelsInstallPayload,
259
- };
260
- await installApp({
261
- projectId,
262
- appReleaseHash,
263
- signedUrlsPayload,
264
- });
265
- this.updateAppInstallStatus({
266
- status: keyMirrors.appInstallStatus.success,
267
- });
268
- } catch (e) {
269
- console.error(e);
270
- const message: string = e.message;
271
-
272
- // uninstall the failed app to put system back in good state
273
- await uninstallApp({ projectId });
274
- this.updateAppInstallStatus({
275
- status: keyMirrors.appInstallStatus.failure,
276
- message,
277
- });
278
- }
279
- break;
280
- }
281
- default:
282
- logger.error(`Invalid cloud response message type '${message}'`);
283
- }
284
- }
285
-
286
- private handleClientMessage(message: ClientMessage) {
287
- const payload = message.payload;
288
- switch (payload.messageType) {
289
- case keyMirrors.clientMessageType.app_state_control:
290
- this.handleAppStateControl(payload.appStateControl);
291
- break;
292
- case keyMirrors.clientMessageType.app_version_control:
293
- this.handleAppVersionControl(payload.appVersionControl);
294
- break;
295
- case keyMirrors.clientMessageType.live_state_updates:
296
- this.handleAgentCommand(payload);
297
- break;
298
- default:
299
- logger.error(`Invalid Client Message '${JSON.stringify(payload)}'`);
300
- }
301
- }
302
-
303
286
  private handleAppStateControl(payload: AppStateControlPacket) {
304
287
  const { baseCommand, projectId } = payload;
305
288
  switch (baseCommand) {
@@ -319,7 +302,7 @@ export class DeviceAgentCloudConnection {
319
302
  const { projectId, appReleaseHash } = payload;
320
303
  const signedUrlsRequest = { projectId, appReleaseHash };
321
304
  this.publishCloudRequest({
322
- messageType: keyMirrors.agentMessageType.app_install_signed_urls_request,
305
+ messageType: keyMirrors.agentMessageType.signed_urls_request,
323
306
  signedUrlsRequest,
324
307
  });
325
308
  }
@@ -363,12 +346,11 @@ export class DeviceAgentCloudConnection {
363
346
  };
364
347
  }) {
365
348
  this.restartLiveUpdatesTimeout();
366
-
367
349
  if (deviceStats !== undefined) {
368
350
  this.liveUpdatesAlive.device_stats = deviceStats;
369
351
  if (deviceStats) {
370
352
  this.startPublishingLiveUpdates(
371
- this.publishableTopics.deviceStats,
353
+ this.toClientTopic,
372
354
  keyMirrors.agentMessageType.device_stats,
373
355
  this.getDeviceStatsMessage,
374
356
  );
@@ -379,7 +361,7 @@ export class DeviceAgentCloudConnection {
379
361
  this.liveUpdatesAlive.app_state = appState;
380
362
  if (appState) {
381
363
  this.startPublishingLiveUpdates(
382
- this.publishableTopics.appState,
364
+ this.toClientTopic,
383
365
  keyMirrors.agentMessageType.app_state,
384
366
  this.getAppStateMessage,
385
367
  );
@@ -395,8 +377,20 @@ export class DeviceAgentCloudConnection {
395
377
  }
396
378
  }
397
379
 
398
- private async publishCloudRequest(payload: AppInstallSignedUrlsRequestMessage) {
399
- const topic = this.publishableTopics.cloudRequest;
380
+ private async publishReportedState(projectId) {
381
+ const newAppCfg = await getAppConfig(projectId);
382
+ const packet = {
383
+ state: {
384
+ reported: {
385
+ [projectId]: { appConfig: newAppCfg },
386
+ },
387
+ },
388
+ };
389
+ this.publishMessage(`${this.shadowPrefix}projects/update`, JSON.stringify(packet));
390
+ }
391
+
392
+ private async publishCloudRequest(payload: SignedUrlsRequestMessage) {
393
+ const topic = this.toCloudTopic;
400
394
  const deviceRequestPacket = this.buildMessagePacket(
401
395
  this.getClientId(),
402
396
  topic,
@@ -418,9 +412,9 @@ export class DeviceAgentCloudConnection {
418
412
  host: this.host,
419
413
  });
420
414
 
421
- Object.values(this.subsribedTopics).forEach((topic: string) => {
422
- this.device.subscribe(topic);
423
- });
415
+ this.device.subscribe(this.toDeviceTopic);
416
+ this.device.subscribe(this.shadowTopics.projects.getAccepted);
417
+ this.device.subscribe(this.shadowTopics.projects.updateDelta);
424
418
  }
425
419
 
426
420
  public getClientId(): string {
@@ -432,17 +426,105 @@ export class DeviceAgentCloudConnection {
432
426
  this.device.publish(topic, message);
433
427
  }
434
428
 
435
- public handleMessageTopic({
429
+ public async handleClientMessage({
436
430
  topic,
437
431
  message,
438
432
  }: {
439
433
  topic: string;
440
434
  message: ClientMessage;
441
435
  }) {
442
- if (topic === this.subsribedTopics.response) {
443
- this.handleCloudResponse(message);
444
- } else {
445
- this.handleClientMessage(message);
436
+ const payload = message.payload;
437
+ switch (payload.messageType) {
438
+ case keyMirrors.clientMessageType.app_state_control: {
439
+ this.handleAppStateControl(payload.appStateControl);
440
+ break;
441
+ }
442
+ case keyMirrors.clientMessageType.app_version_control: {
443
+ this.handleAppVersionControl(payload.appVersionControl);
444
+ break;
445
+ }
446
+ case keyMirrors.clientMessageType.live_state_updates: {
447
+ this.handleAgentCommand(payload);
448
+ break;
449
+ }
450
+ case keyMirrors.clientMessageType.app_install_cloud_response: {
451
+ const { projectId, appReleaseHash, appInstallPayload, modelsInstallPayload } =
452
+ payload.appInstallCloudResponse;
453
+
454
+ this.initAppInstallStatus({
455
+ status: keyMirrors.appInstallStatus.in_progress,
456
+ appReleaseHash,
457
+ });
458
+
459
+ this.startPublishingLiveUpdates(
460
+ this.toClientTopic,
461
+ keyMirrors.agentMessageType.app_install_status,
462
+ this.getAppInstallStatusMessage,
463
+ );
464
+
465
+ // Install the app and models
466
+ try {
467
+ const signedUrlsPayload = {
468
+ appInstallPayload,
469
+ modelsInstallPayload,
470
+ };
471
+ await installApp({
472
+ projectId,
473
+ appReleaseHash,
474
+ signedUrlsPayload,
475
+ });
476
+ this.updateAppInstallStatus({
477
+ status: keyMirrors.appInstallStatus.success,
478
+ });
479
+
480
+ // update app config shadow for project
481
+ await this.publishReportedState(projectId);
482
+ } catch (e) {
483
+ console.error(e);
484
+ const message: string = e.message;
485
+
486
+ // uninstall the failed app to put system back in good state
487
+ await uninstallApp({ projectId });
488
+ this.updateAppInstallStatus({
489
+ status: keyMirrors.appInstallStatus.failure,
490
+ message,
491
+ });
492
+
493
+ // delete shadow for project
494
+ this.publishMessage(`${this.shadowPrefix}${projectId}/delete`, '');
495
+ }
496
+ break;
497
+ }
498
+ case keyMirrors.clientMessageType.models_install_cloud_response: {
499
+ const { projectId, newModels } = payload.modelsInstallCloudResponse;
500
+
501
+ try {
502
+ await updateModelsWithPresignedUrls(projectId, newModels);
503
+
504
+ await this.publishReportedState(projectId);
505
+ } catch (e) {
506
+ console.error(e);
507
+ }
508
+ break;
509
+ }
510
+ default:
511
+ logger.error(`Invalid Client Message '${JSON.stringify(payload)}'`);
512
+ }
513
+ }
514
+
515
+ public async handleShadowTopic({ topic, payload }: { topic: string; payload: string }) {
516
+ const shadowName = topic.split('/')[5];
517
+ const message = JSON.parse(payload);
518
+ if (topic === this.shadowTopics.projects.updateDelta) {
519
+ this.handleNamedShadowUpdate({ payload });
520
+ } else if (topic === this.shadowTopics.projects.getAccepted) {
521
+ if (message.delta) {
522
+ this.handleNamedShadowUpdate({
523
+ payload: JSON.stringify(message.delta),
524
+ });
525
+ } else {
526
+ console.log(`No delta updates in shadow ${shadowName}`);
527
+ }
446
528
  }
447
529
  }
448
530
  }
@@ -453,6 +535,9 @@ export function runDeviceAgentCloudInterface() {
453
535
  deviceAgent.device.on('connect', function () {
454
536
  deviceAgent.publishMessage('connection', deviceAgent.getClientId());
455
537
  console.log('Device Agent has connected to the cloud');
538
+
539
+ // Get shadow updates
540
+ deviceAgent.publishMessage(`${deviceAgent.getShadowPrefix()}projects/get`, '');
456
541
  });
457
542
 
458
543
  deviceAgent.device.on('disconnect', function () {
@@ -462,14 +547,21 @@ export function runDeviceAgentCloudInterface() {
462
547
  deviceAgent.device.on('message', function (topic: string, payload: string) {
463
548
  try {
464
549
  const jsonPacket = JSON.parse(payload);
465
- const valid = validateClientMessage(jsonPacket);
466
- if (!valid) {
467
- logger.error(JSON.stringify(validateClientMessage.errors));
550
+ if (jsonPacket.hasOwnProperty('state')) {
551
+ deviceAgent.handleShadowTopic({
552
+ topic,
553
+ payload: JSON.stringify(jsonPacket.state),
554
+ });
468
555
  } else {
469
- deviceAgent.handleMessageTopic({ topic, message: jsonPacket });
556
+ const valid = validateClientMessage(jsonPacket);
557
+ if (!valid) {
558
+ console.error(JSON.stringify(validateClientMessage.errors));
559
+ } else {
560
+ deviceAgent.handleClientMessage({ topic, message: jsonPacket });
561
+ }
470
562
  }
471
563
  } catch (error) {
472
- logger.error(error);
564
+ console.error(error);
473
565
  }
474
566
  });
475
567
  }
@@ -95,7 +95,9 @@ describe('Test Agent Config', () => {
95
95
  const version = 'im-a-version';
96
96
 
97
97
  await configFile.setAppInstalling({ projectId, version });
98
+ expect(await configFile.getAppVersion({ projectId })).toEqual(version);
98
99
  await configFile.setAppInstalled({ projectId, version });
100
+ expect(await configFile.getAppVersion({ projectId })).toEqual(version);
99
101
  await configFile.setAppUninstalled({ projectId });
100
102
  const apps = await configFile.getApps();
101
103
  expect(apps).toEqual([]);
@@ -110,9 +112,13 @@ describe('Test Agent Config', () => {
110
112
  test('Set and get backup', async () => {
111
113
  const projectId = 'add-me';
112
114
  const version = 'im-a-version';
115
+ const version2 = 'newer-version';
113
116
 
117
+ // first installation, we use the version
114
118
  await configFile.setAppInstalling({ projectId, version });
119
+ expect(await configFile.getAppVersion({ projectId })).toEqual(version);
115
120
  await configFile.setAppInstalled({ projectId, version });
121
+ expect(await configFile.getAppVersion({ projectId })).toEqual(version);
116
122
  const backup1 = await configFile.getAppBackup({ projectId });
117
123
  expect(backup1).toBeNull();
118
124
  await configFile.setAppBackup({ projectId });
@@ -138,6 +144,56 @@ describe('Test Agent Config', () => {
138
144
  });
139
145
  const backup2 = await configFile.getAppBackup({ projectId });
140
146
  expect(backup2).toEqual({ version });
147
+
148
+ await configFile.setAppInstalling({ projectId, version: version2 });
149
+ expect(await configFile.getAppVersion({ projectId })).toEqual(version);
150
+ await configFile.setAppInstalled({ projectId, version: version2 });
151
+ expect(await configFile.getAppVersion({ projectId })).toEqual(version2);
152
+ const backup3 = await configFile.getAppBackup({ projectId });
153
+ expect(backup3).toEqual({ version });
154
+ });
155
+
156
+ test('Test app rollback', async () => {
157
+ const projectId = 'add-me';
158
+ const version1 = 'original-version';
159
+ const version2 = 'newer-version';
160
+ await configFile.setAppInstalling({ projectId, version: version1 });
161
+ await configFile.setAppInstalled({ projectId, version: version1 });
162
+ expect(await configFile.getAppVersion({ projectId })).toEqual(version1);
163
+ expect(await configFile.getAppBackup({ projectId })).toBeNull();
164
+
165
+ // next installation - verify app is present and ready
166
+ expect(await configFile.isAppPresent({ projectId })).toBe(true);
167
+ expect(await configFile.isAppReady({ projectId })).toBe(true);
168
+
169
+ // test to see if app has backup
170
+ await configFile.setAppBackup({ projectId });
171
+ const expectedBackupVersion = (await configFile.getAppBackup({ projectId }))
172
+ ?.version;
173
+ expect(expectedBackupVersion).toEqual(version1);
174
+ expect(await configFile.getAppVersion({ projectId })).toEqual(version1);
175
+
176
+ // simulate second version install
177
+ await configFile.setAppInstalling({ projectId, version: version2 });
178
+ const newExpectedBackupVersion = (await configFile.getAppBackup({ projectId }))
179
+ ?.version;
180
+ expect(newExpectedBackupVersion).toEqual(version1);
181
+ await configFile.setAppInstalled({ projectId, version: version2 });
182
+ expect(await configFile.getAppVersion({ projectId })).toEqual(version2);
183
+
184
+ // simulate the rollback step: set the version that we will roll back to
185
+ await configFile.setAppInstalling({ projectId, version: version1 });
186
+
187
+ // test state during rollback
188
+ expect(await configFile.isAppReady({ projectId })).toEqual(false);
189
+ expect(await configFile.getAppVersion({ projectId })).toEqual(version2);
190
+
191
+ // test final state of rolled-back app
192
+ await configFile.setAppInstalled({ projectId, version: version1 });
193
+ const finalBackupVersion = (await configFile.getAppBackup({ projectId }))?.version;
194
+ expect(finalBackupVersion).toEqual(version1);
195
+ expect(await configFile.isAppReady({ projectId })).toEqual(true);
196
+ expect(await configFile.getAppVersion({ projectId })).toEqual(version1);
141
197
  });
142
198
  });
143
199
  });
@@ -2,7 +2,6 @@ import { ConfigFileSchema } from '@alwaysai/config-nodejs';
2
2
  import { JSONSchemaType } from 'ajv';
3
3
  import { homedir } from 'os';
4
4
  import { join } from 'path';
5
- import { ALWAYSAI_DEVICE_AGENT_MODE } from '../environment';
6
5
 
7
6
  export interface AppBackupConfig {
8
7
  version: string;
@@ -134,17 +133,16 @@ export function AgentConfigFile(dir = ALWAYSAI_CONFIG_DIR) {
134
133
 
135
134
  async function setAppInstalling(props: { projectId: string; version: string }) {
136
135
  const { projectId, version } = props;
136
+
137
137
  const app = await getApp({ projectId });
138
138
  if (app) {
139
139
  await removeApp({ projectId });
140
140
  const config = configFile.read();
141
- config.applications.push({
142
- projectId,
143
- version,
144
- ready: false,
145
- });
141
+ // NOTE: do not update the version for an existing app until it is installed
142
+ config.applications.push({ ...app, ...{ ready: false } });
146
143
  configFile.write(config);
147
144
  } else {
145
+ // NOTE: for a brand-new app, we need to specify the version
148
146
  const config = configFile.read();
149
147
  config.applications.push({
150
148
  projectId,
@@ -161,21 +159,14 @@ export function AgentConfigFile(dir = ALWAYSAI_CONFIG_DIR) {
161
159
  if (app) {
162
160
  await removeApp({ projectId });
163
161
  const config = configFile.read();
164
- app.version = version;
165
- config.applications.push({
166
- projectId,
167
- version,
168
- ready: true,
169
- });
162
+ config.applications.push({ ...app, ...{ version, ready: true } });
170
163
  configFile.write(config);
171
164
  } else {
172
- const config = configFile.read();
173
- config.applications.push({
174
- projectId,
175
- version,
176
- ready: true,
177
- });
178
- configFile.write(config);
165
+ // NOTE: we should never be setting an app as installed
166
+ // if it doesn't exist (setAppInstalling was never called)
167
+ throw new Error(
168
+ `App ${projectId} was not previously configured and could not be set to installed!`,
169
+ );
179
170
  }
180
171
  }
181
172
 
@@ -1,6 +1,6 @@
1
- import { CliLeaf, CliStringInput, CliUsageError } from '@alwaysai/alwayscli';
1
+ import { CliLeaf, CliStringInput } from '@alwaysai/alwayscli';
2
+ import { alwaysaiUserLoginYesComponent } from 'alwaysai/lib/components/user';
2
3
  import { writeCertificateAndToken } from '../infrastructure/certificates-and-tokens';
3
- import { runCliCmd } from '../util/run-cli-cmd';
4
4
 
5
5
  export const loginCliLeaf = CliLeaf({
6
6
  name: 'login',
@@ -21,8 +21,10 @@ export const loginCliLeaf = CliLeaf({
21
21
  },
22
22
  async action(_, opts) {
23
23
  const { email, password, device } = opts;
24
- await runCliCmd({
25
- cmd: ['user', 'login', '--yes', '--email', email, '--password', password],
24
+
25
+ await alwaysaiUserLoginYesComponent({
26
+ alwaysaiUserEmail: email,
27
+ alwaysaiUserPassword: password,
26
28
  });
27
29
  if (device) {
28
30
  await writeCertificateAndToken({ deviceUuid: device });
@@ -6,5 +6,7 @@ export async function copyDir(props: { srcPath: string; destPath: string }) {
6
6
  const allFileNames = await src.readdir();
7
7
  const dest = JsSpawner({ path: destPath });
8
8
  await dest.mkdirp();
9
- await dest.untar(await src.tar(...allFileNames));
9
+ if (allFileNames.length) {
10
+ await dest.untar(await src.tar(...allFileNames));
11
+ }
10
12
  }
@@ -0,0 +1,15 @@
1
+ export async function runInDir<T extends any[], R extends any>(
2
+ func: (...args: T) => R,
3
+ args: T,
4
+ dir: string,
5
+ ) {
6
+ const origCwd = process.cwd();
7
+ let out: R;
8
+ try {
9
+ process.chdir(dir);
10
+ out = await func(...args);
11
+ } finally {
12
+ process.chdir(origCwd);
13
+ }
14
+ return out;
15
+ }
@@ -1,5 +0,0 @@
1
- export declare function runCliCmd(props: {
2
- cmd: string[];
3
- cwd?: string;
4
- }): Promise<void>;
5
- //# sourceMappingURL=run-cli-cmd.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"run-cli-cmd.d.ts","sourceRoot":"","sources":["../../src/util/run-cli-cmd.ts"],"names":[],"mappings":"AAEA,wBAAsB,SAAS,CAAC,KAAK,EAAE;IAAE,GAAG,EAAE,MAAM,EAAE,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAerF"}
@@ -1,24 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.runCliCmd = void 0;
4
- const alwaysai = require("alwaysai");
5
- async function runCliCmd(props) {
6
- const { cmd, cwd } = props;
7
- const origCwd = process.cwd();
8
- try {
9
- if (cwd) {
10
- process.chdir(cwd);
11
- }
12
- await alwaysai.aai(...cmd);
13
- }
14
- catch (err) {
15
- throw err;
16
- }
17
- finally {
18
- if (cwd) {
19
- process.chdir(origCwd);
20
- }
21
- }
22
- }
23
- exports.runCliCmd = runCliCmd;
24
- //# sourceMappingURL=run-cli-cmd.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"run-cli-cmd.js","sourceRoot":"","sources":["../../src/util/run-cli-cmd.ts"],"names":[],"mappings":";;;AAAA,qCAAqC;AAE9B,KAAK,UAAU,SAAS,CAAC,KAAsC;IACpE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC;IAC3B,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC9B,IAAI;QACF,IAAI,GAAG,EAAE;YACP,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;SACpB;QACD,MAAM,QAAQ,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC;KAC5B;IAAC,OAAO,GAAG,EAAE;QACZ,MAAM,GAAG,CAAC;KACX;YAAS;QACR,IAAI,GAAG,EAAE;YACP,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;SACxB;KACF;AACH,CAAC;AAfD,8BAeC"}
@@ -1,18 +0,0 @@
1
- import * as alwaysai from 'alwaysai';
2
-
3
- export async function runCliCmd(props: { cmd: string[]; cwd?: string }): Promise<void> {
4
- const { cmd, cwd } = props;
5
- const origCwd = process.cwd();
6
- try {
7
- if (cwd) {
8
- process.chdir(cwd);
9
- }
10
- await alwaysai.aai(...cmd);
11
- } catch (err) {
12
- throw err;
13
- } finally {
14
- if (cwd) {
15
- process.chdir(origCwd);
16
- }
17
- }
18
- }