Skip to content

Commit 762fcd6

Browse files
committed
fix: build the platform when there are native changes on initial sync and add unit tests for that functionality
1 parent 6ba3274 commit 762fcd6

24 files changed

+287
-278
lines changed

.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"program": "${workspaceRoot}/lib/nativescript-cli.js",
1616

1717
// example commands
18-
"args": [ "test", "ios", "--path", "${workspaceRoot}/scratch/webpackApp", "--bundle"]
18+
"args": [ "create", "cliapp", "--path", "${workspaceRoot}/scratch"]
1919
// "args": [ "test", "android", "--justlaunch"]
2020
// "args": [ "platform", "add", "android@1.3.0", "--path", "cliapp"]
2121
// "args": [ "platform", "remove", "android", "--path", "cliapp"]

lib/commands/preview.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ export class PreviewCommand implements ICommand {
2626

2727
await this.$previewAppLiveSyncService.initialize({
2828
projectDir: this.$projectData.projectDir,
29-
bundle: !!this.$options.bundle,
3029
useHotModuleReload: this.$options.hmr,
3130
env: this.$options.env
3231
});

lib/constants.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,8 @@ export const POST_INSTALL_COMMAND_NAME = "post-install-cli";
139139
export const ANDROID_RELEASE_BUILD_ERROR_MESSAGE = "When producing a release build, you need to specify all --key-store-* options.";
140140
export const CACACHE_DIRECTORY_NAME = "_cacache";
141141

142-
export const FILES_CHANGE_EVENT_NAME = "filesChangeEventData";
143-
export const INITIAL_SYNC_EVENT_NAME = "initialSyncEventData";
142+
export const FILES_CHANGE_EVENT_NAME = "filesChangeEvent";
143+
export const INITIAL_SYNC_EVENT_NAME = "initialSyncEvent";
144144

145145
export class DebugCommandErrors {
146146
public static UNABLE_TO_USE_FOR_DEVICE_AND_EMULATOR = "The options --for-device and --emulator cannot be used simultaneously. Please use only one of them.";

lib/controllers/main-controller.ts

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,13 @@ export class MainController extends EventEmitter {
3131
private $workflowDataService: WorkflowDataService
3232
) { super(); }
3333

34-
public async preparePlatform(platform: string, projectDir: string, options: IOptions): Promise<void> {
34+
public async preparePlatform(platform: string, projectDir: string, options: IOptions): Promise<boolean> {
3535
const { nativePlatformData, projectData, addPlatformData, preparePlatformData } = this.$workflowDataService.createWorkflowData(platform, projectDir, options);
3636

3737
await this.$addPlatformService.addPlatformIfNeeded(nativePlatformData, projectData, addPlatformData);
38-
await this.$preparePlatformService.preparePlatform(nativePlatformData, projectData, preparePlatformData);
38+
const result = await this.$preparePlatformService.preparePlatform(nativePlatformData, projectData, preparePlatformData);
39+
40+
return result;
3941
}
4042

4143
public async buildPlatform(platform: string, projectDir: string, options: IOptions | any): Promise<string> {
@@ -74,34 +76,40 @@ export class MainController extends EventEmitter {
7476
await this.$addPlatformService.addPlatformIfNeeded(nativePlatformData, projectData, addPlatformData);
7577
}
7678

77-
// TODO: Consider to handle correctly the descriptors when livesync is executed for second time for the same projectDir
79+
const currentRunOnDevicesData = this.$runOnDevicesDataService.getDataForProject(projectData.projectDir);
80+
const isAlreadyLiveSyncing = currentRunOnDevicesData && !currentRunOnDevicesData.isStopped;
81+
// Prevent cases where liveSync is called consecutive times with the same device, for example [ A, B, C ] and then [ A, B, D ] - we want to execute initialSync only for D.
82+
const deviceDescriptorsForInitialSync = isAlreadyLiveSyncing ? _.differenceBy(deviceDescriptors, currentRunOnDevicesData.deviceDescriptors, "identifier") : deviceDescriptors;
7883

79-
this.$runOnDevicesDataService.persistData(projectDir, deviceDescriptors);
84+
this.$runOnDevicesDataService.persistData(projectDir, deviceDescriptors, platforms);
8085

8186
const shouldStartWatcher = !liveSyncInfo.skipWatcher && (liveSyncInfo.syncToPreviewApp || this.$runOnDevicesDataService.hasDeviceDescriptors(projectDir));
8287
if (shouldStartWatcher) {
83-
this.handleRunOnDeviceEvents(projectDir);
88+
this.handleRunOnDeviceError(projectDir);
8489

8590
this.$platformWatcherService.on(INITIAL_SYNC_EVENT_NAME, async (data: IInitialSyncEventData) => {
86-
await this.$runOnDevicesController.syncInitialDataOnDevice(data, projectData, liveSyncInfo, deviceDescriptors);
91+
await this.$runOnDevicesController.syncInitialDataOnDevices(data, projectData, liveSyncInfo, deviceDescriptorsForInitialSync);
8792
});
8893
this.$platformWatcherService.on(FILES_CHANGE_EVENT_NAME, async (data: IFilesChangeEventData) => {
89-
await this.$runOnDevicesController.syncChangedDataOnDevice(data, projectData, liveSyncInfo, deviceDescriptors);
94+
await this.$runOnDevicesController.syncChangedDataOnDevices(data, projectData, liveSyncInfo, deviceDescriptors);
9095
});
9196

9297
for (const platform of platforms) {
9398
const { nativePlatformData, preparePlatformData } = this.$workflowDataService.createWorkflowData(platform, projectDir, liveSyncInfo);
9499
await this.$platformWatcherService.startWatchers(nativePlatformData, projectData, preparePlatformData);
95100
}
101+
} else {
102+
for (const platform of platforms) {
103+
const hasNativeChanges = await this.preparePlatform(platform, projectDir, <any>liveSyncInfo);
104+
await this.$runOnDevicesController.syncInitialDataOnDevices({ platform, hasNativeChanges }, projectData, liveSyncInfo, deviceDescriptorsForInitialSync);
105+
}
96106
}
97107

98-
// TODO: Consider how to handle --justlaunch
99-
100108
this.attachDeviceLostHandler();
101109
}
102110

103111
public async stopRunOnDevices(projectDir: string, deviceIdentifiers?: string[], stopOptions?: { shouldAwaitAllActions: boolean }): Promise<void> {
104-
const liveSyncProcessInfo = this.$runOnDevicesDataService.getData(projectDir);
112+
const liveSyncProcessInfo = this.$runOnDevicesDataService.getDataForProject(projectDir);
105113
if (liveSyncProcessInfo && !liveSyncProcessInfo.isStopped) {
106114
// In case we are coming from error during livesync, the current action is the one that erred (but we are still executing it),
107115
// so we cannot await it as this will cause infinite loop.
@@ -112,15 +120,22 @@ export class MainController extends EventEmitter {
112120
const removedDeviceIdentifiers = _.remove(liveSyncProcessInfo.deviceDescriptors, descriptor => _.includes(deviceIdentifiersToRemove, descriptor.identifier))
113121
.map(descriptor => descriptor.identifier);
114122

123+
// Handle the case when no more devices left for any of the persisted platforms
124+
_.each(liveSyncProcessInfo.platforms, platform => {
125+
const devices = this.$devicesService.getDevicesForPlatform(platform);
126+
if (!devices || !devices.length) {
127+
this.$platformWatcherService.stopWatchers(projectDir, platform);
128+
}
129+
});
130+
115131
// In case deviceIdentifiers are not passed, we should stop the whole LiveSync.
116132
if (!deviceIdentifiers || !deviceIdentifiers.length || !liveSyncProcessInfo.deviceDescriptors || !liveSyncProcessInfo.deviceDescriptors.length) {
117133
if (liveSyncProcessInfo.timer) {
118134
clearTimeout(liveSyncProcessInfo.timer);
119135
}
120136

121-
_.each(liveSyncProcessInfo.deviceDescriptors, deviceDescriptor => {
122-
const device = this.$devicesService.getDeviceByIdentifier(deviceDescriptor.identifier);
123-
this.$platformWatcherService.stopWatchers(projectDir, device.deviceInfo.platform);
137+
_.each(liveSyncProcessInfo.platforms, platform => {
138+
this.$platformWatcherService.stopWatchers(projectDir, platform);
124139
});
125140

126141
liveSyncProcessInfo.isStopped = true;
@@ -131,12 +146,6 @@ export class MainController extends EventEmitter {
131146

132147
liveSyncProcessInfo.deviceDescriptors = [];
133148

134-
if (liveSyncProcessInfo.syncToPreviewApp) {
135-
// await this.$previewAppLiveSyncService.stopLiveSync();
136-
// this.$previewAppLiveSyncService.removeAllListeners();
137-
}
138-
139-
// Kill typescript watcher
140149
const projectData = this.$projectDataService.getProjectData(projectDir);
141150
await this.$hooksService.executeAfterHooks('watch', {
142151
hookArgs: {
@@ -158,22 +167,12 @@ export class MainController extends EventEmitter {
158167
return this.$runOnDevicesDataService.getDeviceDescriptors(projectDir);
159168
}
160169

161-
private handleRunOnDeviceEvents(projectDir: string): void {
162-
this.$runOnDevicesController.on(RunOnDeviceEvents.runOnDeviceError, async data => {
170+
private handleRunOnDeviceError(projectDir: string): void {
171+
this.$runOnDevicesEmitter.on(RunOnDeviceEvents.runOnDeviceError, async data => {
163172
await this.stopRunOnDevices(projectDir, [data.deviceIdentifier], { shouldAwaitAllActions: false });
164173
});
165174
}
166175

167-
// TODO: expose previewOnDevice() method { }
168-
// TODO: enableDebugging -> mainController
169-
// TODO: disableDebugging -> mainController
170-
// TODO: attachDebugger -> mainController
171-
// mainController.runOnDevices(), runOnDevicesController.on("event", () => {})
172-
173-
// debugOnDevicesController.enableDebugging()
174-
// debugOnDevicesController.disableDebugging()
175-
// debugOnDevicesController.attachDebugger
176-
177176
private async initializeSetup(projectData: IProjectData): Promise<void> {
178177
try {
179178
await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData);

lib/controllers/run-on-devices-controller.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { RunOnDevicesDataService } from "../services/run-on-devices-data-service
88
import { RunOnDevicesEmitter } from "../run-on-devices-emitter";
99
import { WorkflowDataService } from "../services/workflow/workflow-data-service";
1010
import { HmrConstants } from "../common/constants";
11+
import { PreparePlatformService } from "../services/platform/prepare-platform-service";
1112

1213
export class RunOnDevicesController extends EventEmitter {
1314
constructor(
@@ -20,29 +21,32 @@ export class RunOnDevicesController extends EventEmitter {
2021
public $hooksService: IHooksService,
2122
private $liveSyncServiceResolver: LiveSyncServiceResolver,
2223
private $logger: ILogger,
24+
private $preparePlatformService: PreparePlatformService,
2325
private $runOnDevicesDataService: RunOnDevicesDataService,
2426
private $runOnDevicesEmitter: RunOnDevicesEmitter,
2527
private $workflowDataService: WorkflowDataService
2628
) { super(); }
2729

28-
public async syncInitialDataOnDevice(data: IInitialSyncEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise<void> {
30+
public async syncInitialDataOnDevices(data: IInitialSyncEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise<void> {
2931
const deviceAction = async (device: Mobile.IDevice) => {
3032
const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier);
3133
const { nativePlatformData: platformData, buildPlatformData } = this.$workflowDataService.createWorkflowData(device.deviceInfo.platform, projectData.projectDir, liveSyncInfo);
3234

3335
try {
3436
const outputPath = deviceDescriptor.outputPath || platformData.getBuildOutputPath(buildPlatformData);
35-
const packageFilePath = await this.$buildPlatformService.buildPlatformIfNeeded(platformData, projectData, buildPlatformData, outputPath);
37+
const packageFilePath = data.hasNativeChanges ?
38+
await this.$buildPlatformService.buildPlatform(platformData, projectData, buildPlatformData) :
39+
await this.$buildPlatformService.buildPlatformIfNeeded(platformData, projectData, buildPlatformData, outputPath);
3640

3741
await this.$deviceInstallAppService.installOnDeviceIfNeeded(device, platformData, projectData, buildPlatformData, packageFilePath, outputPath);
3842

3943
const platformLiveSyncService = this.$liveSyncServiceResolver.resolveLiveSyncService(platformData.platformNameLowerCase);
4044
const { force, useHotModuleReload, skipWatcher } = liveSyncInfo;
4145
const liveSyncResultInfo = await platformLiveSyncService.fullSync({ force, useHotModuleReload, projectData, device, watch: !skipWatcher, liveSyncDeviceInfo: deviceDescriptor });
4246

43-
const refreshInfo = await this.$deviceRefreshAppService.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, deviceDescriptor);
47+
const refreshInfo = await this.$deviceRefreshAppService.refreshApplication(projectData, liveSyncResultInfo, deviceDescriptor);
4448

45-
this.$runOnDevicesEmitter.emitRunOnDeviceExecutedEvent(projectData, liveSyncResultInfo.deviceAppData.device, {
49+
this.$runOnDevicesEmitter.emitRunOnDeviceExecutedEvent(projectData, device, {
4650
syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()),
4751
isFullSync: liveSyncResultInfo.isFullSync
4852
});
@@ -58,22 +62,20 @@ export class RunOnDevicesController extends EventEmitter {
5862
this.$logger.warn(`Unable to apply changes on device: ${device.deviceInfo.identifier}. Error is: ${err.message}.`);
5963

6064
this.$runOnDevicesEmitter.emitRunOnDeviceErrorEvent(projectData, device, err);
61-
62-
// TODO: Consider to call here directly stopRunOnDevices
6365
}
6466
};
6567

6668
await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(deviceAction, (device: Mobile.IDevice) => device.deviceInfo.platform.toLowerCase() === data.platform.toLowerCase() && _.some(deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier)));
6769
}
6870

69-
public async syncChangedDataOnDevice(data: IFilesChangeEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise<void> {
71+
public async syncChangedDataOnDevices(data: IFilesChangeEventData, projectData: IProjectData, liveSyncInfo: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise<void> {
7072
const deviceAction = async (device: Mobile.IDevice) => {
7173
const deviceDescriptor = _.find(deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier);
72-
const { nativePlatformData, buildPlatformData } = this.$workflowDataService.createWorkflowData(device.deviceInfo.platform, projectData.projectDir, liveSyncInfo);
74+
const { nativePlatformData, preparePlatformData, buildPlatformData } = this.$workflowDataService.createWorkflowData(device.deviceInfo.platform, projectData.projectDir, liveSyncInfo);
7375

7476
try {
7577
if (data.hasNativeChanges) {
76-
// TODO: Consider to handle nativePluginsChange here (aar rebuilt)
78+
await this.$preparePlatformService.prepareNativePlatform(nativePlatformData, projectData, preparePlatformData);
7779
await this.$buildPlatformService.buildPlatform(nativePlatformData, projectData, buildPlatformData);
7880
}
7981

@@ -123,13 +125,13 @@ export class RunOnDevicesController extends EventEmitter {
123125
};
124126

125127
await this.addActionToChain(projectData.projectDir, () => this.$devicesService.execute(deviceAction, (device: Mobile.IDevice) => {
126-
const liveSyncProcessInfo = this.$runOnDevicesDataService.getData(projectData.projectDir);
128+
const liveSyncProcessInfo = this.$runOnDevicesDataService.getDataForProject(projectData.projectDir);
127129
return (data.platform.toLowerCase() === device.deviceInfo.platform.toLowerCase()) && liveSyncProcessInfo && _.some(liveSyncProcessInfo.deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier);
128130
}));
129131
}
130132

131133
private async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, deviceDescriptor: ILiveSyncDeviceInfo) {
132-
const refreshInfo = await this.$deviceRefreshAppService.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, deviceDescriptor);
134+
const refreshInfo = await this.$deviceRefreshAppService.refreshApplication(projectData, liveSyncResultInfo, deviceDescriptor);
133135

134136
this.$runOnDevicesEmitter.emitRunOnDeviceExecutedEvent(projectData, liveSyncResultInfo.deviceAppData.device, {
135137
syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()),
@@ -142,7 +144,7 @@ export class RunOnDevicesController extends EventEmitter {
142144
}
143145

144146
private async addActionToChain<T>(projectDir: string, action: () => Promise<T>): Promise<T> {
145-
const liveSyncInfo = this.$runOnDevicesDataService.getData(projectDir);
147+
const liveSyncInfo = this.$runOnDevicesDataService.getDataForProject(projectDir);
146148
if (liveSyncInfo) {
147149
liveSyncInfo.actionsChain = liveSyncInfo.actionsChain.then(async () => {
148150
if (!liveSyncInfo.isStopped) {

lib/declarations.d.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -433,10 +433,6 @@ interface IOpener {
433433
open(target: string, appname: string): void;
434434
}
435435

436-
interface IBundle {
437-
bundle: boolean;
438-
}
439-
440436
interface IBundleString {
441437
bundle: string;
442438
}

lib/definitions/livesync.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ declare global {
88
deviceDescriptors: ILiveSyncDeviceInfo[];
99
currentSyncAction: Promise<any>;
1010
syncToPreviewApp: boolean;
11+
platforms: string[];
1112
}
1213

1314
interface IOptionalOutputPath {

lib/definitions/preview-app-livesync.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ declare global {
1818
filesToRemove?: string[];
1919
}
2020

21-
interface IPreviewAppLiveSyncData extends IProjectDir, IHasUseHotModuleReloadOption, IBundle, IEnvOptions { }
21+
interface IPreviewAppLiveSyncData extends IProjectDir, IHasUseHotModuleReloadOption, IEnvOptions { }
2222

2323
interface IPreviewSdkService extends EventEmitter {
2424
getQrCodeUrl(options: IGetQrCodeUrlOptions): string;

lib/helpers/bundle-validator-helper.ts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,17 @@ export class BundleValidatorHelper extends VersionValidatorHelper implements IBu
1515
}
1616

1717
public validate(minSupportedVersion?: string): void {
18-
if (this.$options.bundle) {
19-
const bundlePluginName = this.bundlersMap[this.$options.bundle];
20-
const bundlerVersionInDependencies = this.$projectData.dependencies && this.$projectData.dependencies[bundlePluginName];
21-
const bundlerVersionInDevDependencies = this.$projectData.devDependencies && this.$projectData.devDependencies[bundlePluginName];
22-
if (!bundlePluginName || (!bundlerVersionInDependencies && !bundlerVersionInDevDependencies)) {
23-
this.$errors.failWithoutHelp(BundleValidatorMessages.MissingBundlePlugin);
24-
}
18+
const bundlePluginName = this.bundlersMap["webpack"];
19+
const bundlerVersionInDependencies = this.$projectData.dependencies && this.$projectData.dependencies[bundlePluginName];
20+
const bundlerVersionInDevDependencies = this.$projectData.devDependencies && this.$projectData.devDependencies[bundlePluginName];
21+
if (!bundlePluginName || (!bundlerVersionInDependencies && !bundlerVersionInDevDependencies)) {
22+
this.$errors.failWithoutHelp(BundleValidatorMessages.MissingBundlePlugin);
23+
}
2524

26-
const currentVersion = bundlerVersionInDependencies || bundlerVersionInDevDependencies;
27-
const shouldThrowError = minSupportedVersion && this.isValidVersion(currentVersion) && this.isVersionLowerThan(currentVersion, minSupportedVersion);
28-
if (shouldThrowError) {
29-
this.$errors.failWithoutHelp(util.format(BundleValidatorMessages.NotSupportedVersion, minSupportedVersion));
30-
}
25+
const currentVersion = bundlerVersionInDependencies || bundlerVersionInDevDependencies;
26+
const shouldThrowError = minSupportedVersion && this.isValidVersion(currentVersion) && this.isVersionLowerThan(currentVersion, minSupportedVersion);
27+
if (shouldThrowError) {
28+
this.$errors.failWithoutHelp(util.format(BundleValidatorMessages.NotSupportedVersion, minSupportedVersion));
3129
}
3230
}
3331
}

lib/helpers/livesync-command-helper.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { BuildPlatformService } from "../services/platform/build-platform-service";
22
import { MainController } from "../controllers/main-controller";
33

4-
// import { LiveSyncEvents } from "../constants";
5-
64
export class LiveSyncCommandHelper implements ILiveSyncCommandHelper {
75
public static MIN_SUPPORTED_WEBPACK_VERSION_WITH_HMR = "0.17.0";
86

0 commit comments

Comments
 (0)