Skip to content

Commit 6d2aaf9

Browse files
committed
Initial version of preview command
1 parent 78fda2a commit 6d2aaf9

File tree

12 files changed

+1824
-1179
lines changed

12 files changed

+1824
-1179
lines changed

lib/bootstrap.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ $injector.requireCommand("run|*all", "./commands/run");
5050
$injector.requireCommand("run|ios", "./commands/run");
5151
$injector.requireCommand("run|android", "./commands/run");
5252

53+
$injector.requireCommand("preview", "./commands/preview");
54+
5355
$injector.requireCommand("debug|ios", "./commands/debug");
5456
$injector.requireCommand("debug|android", "./commands/debug");
5557

@@ -127,6 +129,8 @@ $injector.requirePublicClass("androidLivesyncTool", "./services/livesync/android
127129
$injector.require("androidLiveSyncService", "./services/livesync/android-livesync-service");
128130
$injector.require("iOSLiveSyncService", "./services/livesync/ios-livesync-service");
129131
$injector.require("usbLiveSyncService", "./services/livesync/livesync-service"); // The name is used in https://github.com/NativeScript/nativescript-dev-typescript
132+
$injector.require("previewAppLiveSyncService", "./services/livesync/playground/preview-app-livesync-service");
133+
$injector.require("previewSdkService", "./services/livesync/playground/preview-sdk-service");
130134
$injector.requirePublic("sysInfo", "./sys-info");
131135

132136
$injector.require("iOSNotificationService", "./services/ios-notification-service");

lib/commands/preview.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
export class PreviewCommand implements ICommand {
2+
public allowedParameters: ICommandParameter[] = [];
3+
4+
constructor(private $liveSyncService: ILiveSyncService,
5+
private $projectData: IProjectData,
6+
private $options: IOptions) { }
7+
8+
public async execute(args: string[]): Promise<void> {
9+
await this.$liveSyncService.liveSync([], {
10+
syncToPreviewApp: true,
11+
projectDir: this.$projectData.projectDir,
12+
skipWatcher: !this.$options.watch,
13+
watchAllFiles: this.$options.syncAllFiles,
14+
clean: this.$options.clean,
15+
bundle: !!this.$options.bundle,
16+
release: this.$options.release,
17+
env: this.$options.env,
18+
timeout: this.$options.timeout
19+
});
20+
}
21+
22+
public async canExecute(args: string[]): Promise<boolean> {
23+
return true;
24+
}
25+
}
26+
$injector.registerCommand("preview", PreviewCommand);

lib/definitions/livesync.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ interface ILiveSyncInfo extends IProjectDir, IEnvOptions, IBundle, IRelease, IOp
159159
* If not provided, defaults to 10seconds.
160160
*/
161161
timeout: string;
162+
163+
syncToPreviewApp?: boolean;
162164
}
163165

164166
interface IHasUseHotModuleReloadOption {
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { FilePayload, DeviceConnectedMessage } from "nativescript-preview-sdk";
2+
3+
declare global {
4+
interface IPreviewAppLiveSyncService {
5+
initialSync(data: IPreviewAppLiveSyncData): Promise<void>;
6+
syncFiles(data: IPreviewAppLiveSyncData, filesToSync: string[]): Promise<void>;
7+
stopLiveSync(): Promise<void>;
8+
}
9+
10+
interface IPreviewAppLiveSyncData extends IProjectDataComposition, IAppFilesUpdaterOptionsComposition, IEnvOptions { }
11+
12+
interface IPreviewSdkService extends NodeJS.EventEmitter {
13+
connectedDevices: DeviceConnectedMessage[];
14+
initialize(): void;
15+
applyChanges(files: FilePayload[]): Promise<void>;
16+
stop(): void;
17+
}
18+
}

lib/services/livesync/livesync-service.ts

Lines changed: 123 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,13 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
3737
private $debugDataService: IDebugDataService,
3838
private $analyticsService: IAnalyticsService,
3939
private $usbLiveSyncService: DeprecatedUsbLiveSyncService,
40+
private $previewAppLiveSyncService: IPreviewAppLiveSyncService,
4041
private $injector: IInjector) {
4142
super();
4243
}
4344

4445
public async liveSync(deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise<void> {
4546
const projectData = this.$projectDataService.getProjectData(liveSyncData.projectDir);
46-
await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData);
4747
await this.liveSyncOperation(deviceDescriptors, liveSyncData, projectData);
4848
}
4949

@@ -318,16 +318,22 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
318318

319319
@hook("liveSync")
320320
private async liveSyncOperation(deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo, projectData: IProjectData): Promise<void> {
321-
// In case liveSync is called for a second time for the same projectDir.
322-
const isAlreadyLiveSyncing = this.liveSyncProcessesInfo[projectData.projectDir] && !this.liveSyncProcessesInfo[projectData.projectDir].isStopped;
321+
let deviceDescriptorsForInitialSync: ILiveSyncDeviceInfo[] = [];
323322

324-
// 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.
325-
const currentlyRunningDeviceDescriptors = this.getLiveSyncDeviceDescriptors(projectData.projectDir);
326-
const deviceDescriptorsForInitialSync = isAlreadyLiveSyncing ? _.differenceBy(deviceDescriptors, currentlyRunningDeviceDescriptors, deviceDescriptorPrimaryKey) : deviceDescriptors;
323+
if (!liveSyncData.syncToPreviewApp) {
324+
await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData);
325+
// In case liveSync is called for a second time for the same projectDir.
326+
const isAlreadyLiveSyncing = this.liveSyncProcessesInfo[projectData.projectDir] && !this.liveSyncProcessesInfo[projectData.projectDir].isStopped;
327+
328+
// 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.
329+
const currentlyRunningDeviceDescriptors = this.getLiveSyncDeviceDescriptors(projectData.projectDir);
330+
deviceDescriptorsForInitialSync = isAlreadyLiveSyncing ? _.differenceBy(deviceDescriptors, currentlyRunningDeviceDescriptors, deviceDescriptorPrimaryKey) : deviceDescriptors;
331+
}
327332

328333
this.setLiveSyncProcessInfo(liveSyncData.projectDir, deviceDescriptors);
329334

330-
if (!liveSyncData.skipWatcher && this.liveSyncProcessesInfo[projectData.projectDir].deviceDescriptors.length) {
335+
const shouldStartWatcher = liveSyncData.syncToPreviewApp || (!liveSyncData.skipWatcher && this.liveSyncProcessesInfo[projectData.projectDir].deviceDescriptors.length);
336+
if (shouldStartWatcher) {
331337
// Should be set after prepare
332338
this.$usbLiveSyncService.isInitialized = true;
333339
await this.startWatcher(projectData, liveSyncData, deviceDescriptors);
@@ -448,6 +454,27 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
448454
}
449455

450456
private async initialSync(projectData: IProjectData, liveSyncData: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise<void> {
457+
if (liveSyncData.syncToPreviewApp) {
458+
await this.initialSyncToPreviewApp(projectData, liveSyncData);
459+
} else {
460+
await this.initialCableSync(projectData, liveSyncData, deviceDescriptors);
461+
}
462+
}
463+
464+
private async initialSyncToPreviewApp(projectData: IProjectData, liveSyncData: ILiveSyncInfo) {
465+
this.addActionToChain(projectData.projectDir, async () => {
466+
await this.$previewAppLiveSyncService.initialSync({
467+
appFilesUpdaterOptions: {
468+
bundle: liveSyncData.bundle,
469+
release: liveSyncData.release
470+
},
471+
env: liveSyncData.env,
472+
projectData
473+
});
474+
});
475+
}
476+
477+
private async initialCableSync(projectData: IProjectData, liveSyncData: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise<void> {
451478
const preparedPlatforms: string[] = [];
452479
const rebuiltInformation: ILiveSyncBuildInfo[] = [];
453480

@@ -552,82 +579,95 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
552579

553580
const startSyncFilesTimeout = () => {
554581
timeoutTimer = setTimeout(async () => {
555-
// Push actions to the queue, do not start them simultaneously
556-
await this.addActionToChain(projectData.projectDir, async () => {
557-
if (filesToSync.length || filesToRemove.length) {
558-
try {
559-
const currentFilesToSync = _.cloneDeep(filesToSync);
560-
filesToSync.splice(0, filesToSync.length);
561-
562-
const currentFilesToRemove = _.cloneDeep(filesToRemove);
563-
filesToRemove = [];
564-
565-
const allModifiedFiles = [].concat(currentFilesToSync).concat(currentFilesToRemove);
566-
567-
const preparedPlatforms: string[] = [];
568-
const rebuiltInformation: ILiveSyncBuildInfo[] = [];
569-
570-
const latestAppPackageInstalledSettings = this.getDefaultLatestAppPackageInstalledSettings();
571-
572-
await this.$devicesService.execute(async (device: Mobile.IDevice) => {
573-
const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectData.projectDir];
574-
const deviceBuildInfoDescriptor = _.find(liveSyncProcessInfo.deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier);
575-
576-
const appInstalledOnDeviceResult = await this.ensureLatestAppPackageIsInstalledOnDevice({
577-
device,
578-
preparedPlatforms,
579-
rebuiltInformation,
580-
projectData,
581-
deviceBuildInfoDescriptor,
582-
liveSyncData,
583-
settings: latestAppPackageInstalledSettings,
584-
modifiedFiles: allModifiedFiles,
585-
filesToRemove: currentFilesToRemove,
586-
filesToSync: currentFilesToSync,
587-
bundle: liveSyncData.bundle,
588-
release: liveSyncData.release,
589-
env: liveSyncData.env,
590-
skipModulesNativeCheck: !liveSyncData.watchAllFiles
591-
}, { skipNativePrepare: deviceBuildInfoDescriptor.skipNativePrepare });
592-
593-
const service = this.getLiveSyncService(device.deviceInfo.platform);
594-
const settings: ILiveSyncWatchInfo = {
595-
projectData,
596-
filesToRemove: currentFilesToRemove,
597-
filesToSync: currentFilesToSync,
598-
isReinstalled: appInstalledOnDeviceResult.appInstalled,
599-
syncAllFiles: liveSyncData.watchAllFiles,
600-
useHotModuleReload: liveSyncData.useHotModuleReload
601-
};
602-
603-
const liveSyncResultInfo = await service.liveSyncWatchAction(device, settings);
604-
await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath);
582+
if (liveSyncData.syncToPreviewApp) {
583+
await this.addActionToChain(projectData.projectDir, async () => {
584+
await this.$previewAppLiveSyncService.syncFiles({
585+
appFilesUpdaterOptions: {
586+
bundle: liveSyncData.bundle,
587+
release: liveSyncData.release
605588
},
606-
(device: Mobile.IDevice) => {
589+
env: liveSyncData.env,
590+
projectData: projectData
591+
}, filesToSync);
592+
});
593+
} else {
594+
// Push actions to the queue, do not start them simultaneously
595+
await this.addActionToChain(projectData.projectDir, async () => {
596+
if (filesToSync.length || filesToRemove.length) {
597+
try {
598+
const currentFilesToSync = _.cloneDeep(filesToSync);
599+
filesToSync.splice(0, filesToSync.length);
600+
601+
const currentFilesToRemove = _.cloneDeep(filesToRemove);
602+
filesToRemove = [];
603+
604+
const allModifiedFiles = [].concat(currentFilesToSync).concat(currentFilesToRemove);
605+
606+
const preparedPlatforms: string[] = [];
607+
const rebuiltInformation: ILiveSyncBuildInfo[] = [];
608+
609+
const latestAppPackageInstalledSettings = this.getDefaultLatestAppPackageInstalledSettings();
610+
611+
await this.$devicesService.execute(async (device: Mobile.IDevice) => {
607612
const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectData.projectDir];
608-
return liveSyncProcessInfo && _.some(liveSyncProcessInfo.deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier);
609-
}
610-
);
611-
} catch (err) {
612-
const allErrors = (<Mobile.IDevicesOperationError>err).allErrors;
613-
614-
if (allErrors && _.isArray(allErrors)) {
615-
for (const deviceError of allErrors) {
616-
this.$logger.warn(`Unable to apply changes for device: ${deviceError.deviceIdentifier}. Error is: ${deviceError.message}.`);
617-
618-
this.emit(LiveSyncEvents.liveSyncError, {
619-
error: deviceError,
620-
deviceIdentifier: deviceError.deviceIdentifier,
621-
projectDir: projectData.projectDir,
622-
applicationIdentifier: projectData.projectId
623-
});
624-
625-
await this.stopLiveSync(projectData.projectDir, [deviceError.deviceIdentifier], { shouldAwaitAllActions: false });
613+
const deviceBuildInfoDescriptor = _.find(liveSyncProcessInfo.deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier);
614+
615+
const appInstalledOnDeviceResult = await this.ensureLatestAppPackageIsInstalledOnDevice({
616+
device,
617+
preparedPlatforms,
618+
rebuiltInformation,
619+
projectData,
620+
deviceBuildInfoDescriptor,
621+
liveSyncData,
622+
settings: latestAppPackageInstalledSettings,
623+
modifiedFiles: allModifiedFiles,
624+
filesToRemove: currentFilesToRemove,
625+
filesToSync: currentFilesToSync,
626+
bundle: liveSyncData.bundle,
627+
release: liveSyncData.release,
628+
env: liveSyncData.env,
629+
skipModulesNativeCheck: !liveSyncData.watchAllFiles
630+
}, { skipNativePrepare: deviceBuildInfoDescriptor.skipNativePrepare });
631+
632+
const service = this.getLiveSyncService(device.deviceInfo.platform);
633+
const settings: ILiveSyncWatchInfo = {
634+
projectData,
635+
filesToRemove: currentFilesToRemove,
636+
filesToSync: currentFilesToSync,
637+
isReinstalled: appInstalledOnDeviceResult.appInstalled,
638+
syncAllFiles: liveSyncData.watchAllFiles,
639+
useHotModuleReload: liveSyncData.useHotModuleReload
640+
};
641+
642+
const liveSyncResultInfo = await service.liveSyncWatchAction(device, settings);
643+
await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath);
644+
},
645+
(device: Mobile.IDevice) => {
646+
const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectData.projectDir];
647+
return liveSyncProcessInfo && _.some(liveSyncProcessInfo.deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier);
648+
}
649+
);
650+
} catch (err) {
651+
const allErrors = (<Mobile.IDevicesOperationError>err).allErrors;
652+
653+
if (allErrors && _.isArray(allErrors)) {
654+
for (const deviceError of allErrors) {
655+
this.$logger.warn(`Unable to apply changes for device: ${deviceError.deviceIdentifier}. Error is: ${deviceError.message}.`);
656+
657+
this.emit(LiveSyncEvents.liveSyncError, {
658+
error: deviceError,
659+
deviceIdentifier: deviceError.deviceIdentifier,
660+
projectDir: projectData.projectDir,
661+
applicationIdentifier: projectData.projectId
662+
});
663+
664+
await this.stopLiveSync(projectData.projectDir, [deviceError.deviceIdentifier], { shouldAwaitAllActions: false });
665+
}
626666
}
627667
}
628668
}
629-
}
630-
});
669+
});
670+
}
631671
}, 250);
632672

633673
this.liveSyncProcessesInfo[liveSyncData.projectDir].timer = timeoutTimer;
@@ -683,6 +723,11 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
683723
this.liveSyncProcessesInfo[liveSyncData.projectDir].timer = timeoutTimer;
684724

685725
this.$processService.attachToProcessExitSignals(this, () => {
726+
if (liveSyncData.syncToPreviewApp) {
727+
// Do not await here, we are in process exit's handler.
728+
this.$previewAppLiveSyncService.stopLiveSync();
729+
}
730+
686731
_.keys(this.liveSyncProcessesInfo).forEach(projectDir => {
687732
// Do not await here, we are in process exit's handler.
688733
/* tslint:disable:no-floating-promises */
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export class PreviewSdkEventNames {
2+
public static DEVICE_CONNECTED = "onDeviceConnected";
3+
public static CHANGE_EVENT_NAME = "change";
4+
}
5+
6+
export class PubnubKeys {
7+
public static PUBLISH_KEY = "pub-c-78911a3d-9dc9-4316-96e5-83e6fd17263f";
8+
public static SUBSCRIBE_KEY = "sub-c-2c059576-47a1-11e7-b66e-0619f8945a4f";
9+
}

0 commit comments

Comments
 (0)