Skip to content

Commit b534f2e

Browse files
feat: add outputformat api support
1 parent 4c5db77 commit b534f2e

File tree

4 files changed

+302
-34
lines changed

4 files changed

+302
-34
lines changed

src/cli/commands/force/lightning/local/app/install.ts

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import {
1919
IOSEnvironmentRequirements,
2020
RequirementProcessor
2121
} from '@salesforce/lwc-dev-mobile-core';
22+
import { z } from 'zod';
23+
import { DeviceOperationResultSchema, DeviceOperationResultType, DeviceSchema } from '../schema/device.js';
2224

2325
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
2426
const messages = Messages.loadMessages('@salesforce/lwc-dev-mobile', 'app-install');
@@ -30,6 +32,7 @@ export class Install extends BaseCommand {
3032
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
3133
public static readonly flags = {
3234
...CommandLineUtils.createFlag(FlagsConfigType.JsonFlag, false),
35+
...CommandLineUtils.createFlag(FlagsConfigType.OutputFormatFlag, false),
3336
...CommandLineUtils.createFlag(FlagsConfigType.LogLevelFlag, false),
3437
...CommandLineUtils.createFlag(FlagsConfigType.PlatformFlag, true),
3538
target: Flags.string({
@@ -62,28 +65,58 @@ export class Install extends BaseCommand {
6265
return this.flagValues.appbundlepath as string;
6366
}
6467

65-
public async run(): Promise<void> {
68+
protected static getOutputSchema(): z.ZodTypeAny {
69+
return DeviceOperationResultSchema;
70+
}
71+
72+
public async run(): Promise<DeviceOperationResultType | void> {
6673
this.logger.info(`app install command invoked for ${this.platform}`);
6774

68-
return RequirementProcessor.execute(this.commandRequirements)
69-
.then(() => {
70-
// environment requirements met, continue with app install
75+
const successMessage = messages.getMessage('app.install.successStatus', [this.appBundlePath, this.target]);
76+
77+
try {
78+
// if not in JSON mode, execute the requirements and start the CLI action
79+
if (!this.jsonEnabled()) {
80+
await RequirementProcessor.execute(this.commandRequirements);
7181
this.logger.info('Setup requirements met, continuing with app install');
7282
CommonUtils.startCliAction(
7383
messages.getMessage('app.install.action'),
7484
messages.getMessage('app.install.status', [this.appBundlePath, this.target])
7585
);
76-
return this.executeAppInstall();
77-
})
78-
.then(() => {
79-
const message = messages.getMessage('app.install.successStatus', [this.appBundlePath, this.target]);
80-
CommonUtils.stopCliAction(message);
81-
})
82-
.catch((error: Error) => {
83-
this.logger.warn(`App Install failed for ${this.platform} - ${error.message}`);
86+
}
87+
88+
// execute the app install command
89+
await this.executeAppInstall();
90+
91+
if (this.jsonEnabled()) {
92+
// In JSON mode, get the device and return it in the format of the DeviceSchema
93+
const deviceManager = CommandLineUtils.platformFlagIsAndroid(this.platform)
94+
? new AndroidDeviceManager()
95+
: new AppleDeviceManager();
96+
const device = await deviceManager.getDevice(this.target);
97+
98+
if (!device) {
99+
throw new Error(messages.getMessage('error.target.doesNotExist', [this.target]));
100+
}
101+
102+
const deviceInfo = DeviceSchema.parse(device);
103+
return {
104+
device: deviceInfo,
105+
success: true,
106+
message: successMessage
107+
};
108+
} else {
109+
// if cli mode, stop the CLI action
110+
CommonUtils.stopCliAction(successMessage);
111+
}
112+
} catch (error) {
113+
if (!this.jsonEnabled()) {
114+
// if cli mode, stop the CLI action
84115
CommonUtils.stopCliAction(messages.getMessage('app.install.failureStatus'));
85-
return Promise.reject(error);
86-
});
116+
}
117+
this.logger.warn(`App Install failed for ${this.platform} - ${(error as Error).message}`);
118+
throw error;
119+
}
87120
}
88121

89122
protected populateCommandRequirements(): void {

src/cli/commands/force/lightning/local/app/launch.ts

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import {
2020
LaunchArgument,
2121
RequirementProcessor
2222
} from '@salesforce/lwc-dev-mobile-core';
23+
import { z } from 'zod';
24+
import { DeviceOperationResultSchema, DeviceOperationResultType, DeviceSchema } from '../schema/device.js';
2325

2426
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
2527
const messages = Messages.loadMessages('@salesforce/lwc-dev-mobile', 'app-launch');
@@ -31,6 +33,7 @@ export class Launch extends BaseCommand {
3133
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
3234
public static readonly flags = {
3335
...CommandLineUtils.createFlag(FlagsConfigType.JsonFlag, false),
36+
...CommandLineUtils.createFlag(FlagsConfigType.OutputFormatFlag, false),
3437
...CommandLineUtils.createFlag(FlagsConfigType.LogLevelFlag, false),
3538
...CommandLineUtils.createFlag(FlagsConfigType.PlatformFlag, true),
3639
target: Flags.string({
@@ -119,28 +122,58 @@ export class Launch extends BaseCommand {
119122
return this.flagValues.bundleid as string;
120123
}
121124

122-
public async run(): Promise<void> {
125+
protected static getOutputSchema(): z.ZodTypeAny {
126+
return DeviceOperationResultSchema;
127+
}
128+
129+
public async run(): Promise<DeviceOperationResultType | void> {
123130
this.logger.info(`app launch command invoked for ${this.platform}`);
124131

125-
return RequirementProcessor.execute(this.commandRequirements)
126-
.then(() => {
127-
// environment requirements met, continue with app launch
132+
const successMessage = messages.getMessage('app.launch.successStatus', [this.bundleId, this.target]);
133+
134+
try {
135+
// if not in JSON mode, execute the requirements and start the CLI action
136+
if (!this.jsonEnabled()) {
137+
await RequirementProcessor.execute(this.commandRequirements);
128138
this.logger.info('Setup requirements met, continuing with app launch');
129139
CommonUtils.startCliAction(
130140
messages.getMessage('app.launch.action'),
131141
messages.getMessage('app.launch.status', [this.bundleId, this.target])
132142
);
133-
return this.executeAppLaunch();
134-
})
135-
.then(() => {
136-
const message = messages.getMessage('app.launch.successStatus', [this.bundleId, this.target]);
137-
CommonUtils.stopCliAction(message);
138-
})
139-
.catch((error: Error) => {
140-
this.logger.warn(`App launch failed for ${this.platform} - ${error.message}`);
143+
}
144+
145+
// execute the app launch command
146+
await this.executeAppLaunch();
147+
148+
if (this.jsonEnabled()) {
149+
// In JSON mode, get the device and return it in the format of the DeviceSchema
150+
const deviceManager = CommandLineUtils.platformFlagIsAndroid(this.platform)
151+
? new AndroidDeviceManager()
152+
: new AppleDeviceManager();
153+
const device = await deviceManager.getDevice(this.target);
154+
155+
if (!device) {
156+
throw new Error(messages.getMessage('error.target.doesNotExist', [this.target]));
157+
}
158+
159+
const deviceInfo = DeviceSchema.parse(device);
160+
return {
161+
device: deviceInfo,
162+
success: true,
163+
message: successMessage
164+
};
165+
} else {
166+
// if cli mode, stop the CLI action
167+
CommonUtils.stopCliAction(successMessage);
168+
}
169+
} catch (error) {
170+
if (!this.jsonEnabled()) {
171+
// if cli mode, stop the CLI action
141172
CommonUtils.stopCliAction(messages.getMessage('app.launch.failureStatus'));
142-
return Promise.reject(error);
143-
});
173+
}
174+
this.logger.warn(`App launch failed for ${this.platform} - ${(error as Error).message}`);
175+
throw error;
176+
}
144177
}
145178

146179
protected populateCommandRequirements(): void {

test/unit/cli/commands/force/lightning/local/app/install.test.ts

Lines changed: 106 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,16 @@ import { Logger } from '@salesforce/core';
1010
import { TestContext } from '@salesforce/core/testSetup';
1111
import { stubMethod } from '@salesforce/ts-sinon';
1212
import {
13+
AndroidDevice,
1314
AndroidDeviceManager,
15+
AndroidOSType,
16+
AppleDevice,
1417
AppleDeviceManager,
18+
AppleOSType,
1519
CommonUtils,
16-
RequirementProcessor
20+
DeviceType,
21+
RequirementProcessor,
22+
Version
1723
} from '@salesforce/lwc-dev-mobile-core';
1824
import { expect } from 'chai';
1925
import { Install } from '../../../../../../../../src/cli/commands/force/lightning/local/app/install.js';
@@ -48,7 +54,7 @@ describe('App Install Tests', () => {
4854
});
4955

5056
describe('Android Platform Tests', () => {
51-
it('Should successfully install app on Android device', async () => {
57+
it('Should successfully install app on Android device for cli mode', async () => {
5258
const mockDevice = {
5359
boot: sinon.stub().resolves(),
5460
installApp: sinon.stub().resolves()
@@ -68,7 +74,39 @@ describe('App Install Tests', () => {
6874
expect(loggerInfoMock.called).to.be.true;
6975
});
7076

71-
it('Should handle Android device not found error', async () => {
77+
it('Should successfully install app on Android device for JSON mode', async () => {
78+
const androidDevice = new AndroidDevice(
79+
androidTarget,
80+
'Test Android Device',
81+
DeviceType.mobile,
82+
AndroidOSType.googleAPIs,
83+
new Version(35, 0, 0),
84+
false
85+
);
86+
87+
const bootMock = stubMethod($$.SANDBOX, AndroidDevice.prototype, 'boot');
88+
bootMock.resolves();
89+
const installAppMock = stubMethod($$.SANDBOX, AndroidDevice.prototype, 'installApp');
90+
installAppMock.resolves();
91+
92+
const getDeviceMock = stubMethod($$.SANDBOX, AndroidDeviceManager.prototype, 'getDevice');
93+
getDeviceMock.resolves(androidDevice);
94+
95+
const result = await Install.run(['-p', 'android', '-t', androidTarget, '-a', appBundlePath, '--json']);
96+
97+
expect(executeSetupMock.called).to.be.false;
98+
expect(getDeviceMock.calledWith(androidTarget)).to.be.true;
99+
expect(bootMock.calledWith(true)).to.be.true;
100+
expect(installAppMock.calledWith(appBundlePath)).to.be.true;
101+
expect(startCliActionMock.called).to.be.false;
102+
expect(stopCliActionMock.called).to.be.false;
103+
expect(loggerInfoMock.called).to.be.true;
104+
expect(result).to.have.property('success', true);
105+
expect(result).to.have.property('device');
106+
expect(result).to.have.property('message');
107+
});
108+
109+
it('Should handle Android device not found error for cli mode', async () => {
72110
const getDeviceMock = stubMethod($$.SANDBOX, AndroidDeviceManager.prototype, 'getDevice');
73111
getDeviceMock.resolves(null);
74112

@@ -84,6 +122,22 @@ describe('App Install Tests', () => {
84122
}
85123
});
86124

125+
it('Should handle Android device not found error for JSON mode', async () => {
126+
const getDeviceMock = stubMethod($$.SANDBOX, AndroidDeviceManager.prototype, 'getDevice');
127+
getDeviceMock.resolves(null);
128+
129+
try {
130+
await Install.run(['-p', 'android', '-t', androidTarget, '-a', appBundlePath, '--json']);
131+
expect.fail('Should have thrown an error');
132+
} catch (error) {
133+
expect(executeSetupMock.called).to.be.false;
134+
expect(getDeviceMock.calledWith(androidTarget)).to.be.true;
135+
expect(startCliActionMock.called).to.be.false;
136+
expect(stopCliActionMock.called).to.be.false;
137+
expect(loggerWarnMock.called).to.be.true;
138+
}
139+
});
140+
87141
it('Should handle Android device boot failure', async () => {
88142
const mockDevice = {
89143
boot: sinon.stub().rejects(new Error('Boot failed')),
@@ -146,7 +200,7 @@ describe('App Install Tests', () => {
146200
});
147201

148202
describe('iOS Platform Tests', () => {
149-
it('Should successfully install app on iOS device', async () => {
203+
it('Should successfully install app on iOS device cli mode', async () => {
150204
const mockDevice = {
151205
boot: sinon.stub().resolves(),
152206
installApp: sinon.stub().resolves()
@@ -166,7 +220,38 @@ describe('App Install Tests', () => {
166220
expect(loggerInfoMock.called).to.be.true;
167221
});
168222

169-
it('Should handle iOS device not found error', async () => {
223+
it('Should successfully install app on iOS device for JSON mode', async () => {
224+
const appleDevice = new AppleDevice(
225+
iosTarget,
226+
'Test iOS Device',
227+
DeviceType.mobile,
228+
AppleOSType.iOS,
229+
new Version(18, 0, 0)
230+
);
231+
232+
const bootMock = stubMethod($$.SANDBOX, AppleDevice.prototype, 'boot');
233+
bootMock.resolves();
234+
const installAppMock = stubMethod($$.SANDBOX, AppleDevice.prototype, 'installApp');
235+
installAppMock.resolves();
236+
237+
const getDeviceMock = stubMethod($$.SANDBOX, AppleDeviceManager.prototype, 'getDevice');
238+
getDeviceMock.resolves(appleDevice);
239+
240+
const result = await Install.run(['-p', 'ios', '-t', iosTarget, '-a', iosAppBundlePath, '--json']);
241+
242+
expect(executeSetupMock.called).to.be.false;
243+
expect(getDeviceMock.calledWith(iosTarget)).to.be.true;
244+
expect(bootMock.calledWith(true)).to.be.true;
245+
expect(installAppMock.calledWith(iosAppBundlePath)).to.be.true;
246+
expect(startCliActionMock.called).to.be.false;
247+
expect(stopCliActionMock.called).to.be.false;
248+
expect(loggerInfoMock.called).to.be.true;
249+
expect(result).to.have.property('success', true);
250+
expect(result).to.have.property('device');
251+
expect(result).to.have.property('message');
252+
});
253+
254+
it('Should handle iOS device not found error for cli mode', async () => {
170255
const getDeviceMock = stubMethod($$.SANDBOX, AppleDeviceManager.prototype, 'getDevice');
171256
getDeviceMock.resolves(null);
172257

@@ -182,6 +267,22 @@ describe('App Install Tests', () => {
182267
}
183268
});
184269

270+
it('Should handle iOS device not found error for JSON mode', async () => {
271+
const getDeviceMock = stubMethod($$.SANDBOX, AppleDeviceManager.prototype, 'getDevice');
272+
getDeviceMock.resolves(null);
273+
274+
try {
275+
await Install.run(['-p', 'ios', '-t', iosTarget, '-a', iosAppBundlePath, '--json']);
276+
expect.fail('Should have thrown an error');
277+
} catch (error) {
278+
expect(executeSetupMock.called).to.be.false;
279+
expect(getDeviceMock.calledWith(iosTarget)).to.be.true;
280+
expect(startCliActionMock.called).to.be.false;
281+
expect(stopCliActionMock.called).to.be.false;
282+
expect(loggerWarnMock.called).to.be.true;
283+
}
284+
});
285+
185286
it('Should handle iOS device boot failure', async () => {
186287
const mockDevice = {
187288
boot: sinon.stub().rejects(new Error('Boot failed')),

0 commit comments

Comments
 (0)