diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f39b5bdf7..4c5bfccb76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,19 @@ NativeScript CLI Changelog ================ +6.3.3 (2020, January 13) +=== + +### New + +* [Implemented #5205](https://github.com/NativeScript/nativescript-cli/issues/5205): Support build hooks +* [Implemented #5210](https://github.com/NativeScript/nativescript-cli/issues/5210): Support environment check hooks + +### Fixed + +* [Fixed #3818](https://github.com/NativeScript/nativescript-cli/issues/3818): Cannot set property 'socket' of null + + 6.3.2 (2020, January 9) === diff --git a/lib/services/livesync/android-livesync-tool.ts b/lib/services/livesync/android-livesync-tool.ts index f2ad2880c3..ff7081d040 100644 --- a/lib/services/livesync/android-livesync-tool.ts +++ b/lib/services/livesync/android-livesync-tool.ts @@ -75,7 +75,7 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { abstractPort: `localabstract:${configuration.appIdentifier}-livesync` }); - const connectionResult = await this.connectEventuallyUntilTimeout(this.createSocket.bind(this, port), connectTimeout); + const connectionResult = await this.connectEventuallyUntilTimeout(this.createSocket.bind(this, port), connectTimeout, configuration); this.handleConnection(connectionResult); } @@ -299,19 +299,33 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { }); } - private connectEventuallyUntilTimeout(factory: () => ILiveSyncSocket, timeout: number): Promise<{ socket: ILiveSyncSocket, data: Buffer | string }> { + private connectEventuallyUntilTimeout(factory: () => ILiveSyncSocket, timeout: number, configuration: IAndroidLivesyncToolConfiguration): Promise<{ socket: ILiveSyncSocket, data: Buffer | string }> { return new Promise((resolve, reject) => { let lastKnownError: Error | string, isConnected = false; - const connectionTimer = setTimeout(() => { + const connectionTimer = setTimeout(async () => { if (!isConnected) { isConnected = true; if (this.pendingConnectionData && this.pendingConnectionData.socketTimer) { clearTimeout(this.pendingConnectionData.socketTimer); } - reject(lastKnownError || new Error(AndroidLivesyncTool.SOCKET_CONNECTION_TIMED_OUT_ERROR)); + const applicationPid = await this.$androidProcessService.getAppProcessId(configuration.deviceIdentifier, configuration.appIdentifier); + if (!applicationPid) { + this.$logger.trace("In Android LiveSync tool, lastKnownError is: ", lastKnownError); + this.$logger.info(`Application ${configuration.appIdentifier} is not running on device ${configuration.deviceIdentifier}.`.yellow); + this.$logger.info( + `This issue may be caused by: + * crash at startup (try \`tns debug android --debug-brk\` to check why it crashes) + * different application identifier in your package.json and in your gradle files (check your identifier in \`package.json\` and in all *.gradle files in your App_Resources directory) + * device is locked + * manual closing of the application`.cyan); + reject(new Error(`Application ${configuration.appIdentifier} is not running`)); + } else { + reject(lastKnownError || new Error(AndroidLivesyncTool.SOCKET_CONNECTION_TIMED_OUT_ERROR)); + } + this.pendingConnectionData = null; } }, timeout); diff --git a/lib/services/platform-environment-requirements.ts b/lib/services/platform-environment-requirements.ts index 86e1cb9060..2d30286da9 100644 --- a/lib/services/platform-environment-requirements.ts +++ b/lib/services/platform-environment-requirements.ts @@ -1,5 +1,5 @@ import { NATIVESCRIPT_CLOUD_EXTENSION_NAME, TrackActionNames } from "../constants"; -import { isInteractive } from "../common/helpers"; +import { isInteractive, hook } from "../common/helpers"; import { EOL } from "os"; export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequirements { @@ -39,6 +39,7 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ "deploy": "tns cloud deploy" }; + @hook("checkEnvironment") public async checkEnvironmentRequirements(input: ICheckEnvironmentRequirementsInput): Promise { const { platform, projectDir, runtimeVersion } = input; const notConfiguredEnvOptions = input.notConfiguredEnvOptions || {}; diff --git a/test/services/livesync/android-livesync-tool.ts b/test/services/livesync/android-livesync-tool.ts index 25224f7a7e..2e058845a4 100644 --- a/test/services/livesync/android-livesync-tool.ts +++ b/test/services/livesync/android-livesync-tool.ts @@ -70,7 +70,8 @@ const createTestInjector = (socket: INetSocket, fileStreams: IDictionary Promise.resolve("") + forwardFreeTcpToAbstractPort: () => Promise.resolve(""), + getAppProcessId: () => Promise.resolve("1234") }); testInjector.register("LiveSyncSocket", () => socket); testInjector.register("devicePlatformsConstants", DevicePlatformsConstants); diff --git a/test/services/platform-environment-requirements.ts b/test/services/platform-environment-requirements.ts index 9699a2c91e..cad355614f 100644 --- a/test/services/platform-environment-requirements.ts +++ b/test/services/platform-environment-requirements.ts @@ -18,8 +18,9 @@ function createTestInjector() { testInjector.register("analyticsService", { trackEventActionInGoogleAnalytics: () => ({}) }); - testInjector.register("commandsService", {currentCommandData: {commandName: "test", commandArguments: [""]}}); + testInjector.register("commandsService", { currentCommandData: { commandName: "test", commandArguments: [""] } }); testInjector.register("doctorService", {}); + testInjector.register("hooksService", stubs.HooksServiceStub); testInjector.register("errors", { fail: (err: any) => { throw new Error(err.formatStr || err.message || err); @@ -47,10 +48,10 @@ describe("platformEnvironmentRequirements ", () => { describe("checkRequirements", () => { let testInjector: IInjector = null; let platformEnvironmentRequirements: IPlatformEnvironmentRequirements = null; - let promptForChoiceData: {message: string, choices: string[]}[] = []; + let promptForChoiceData: { message: string, choices: string[] }[] = []; let isExtensionInstallCalled = false; - function mockDoctorService(data: {canExecuteLocalBuild: boolean, mockSetupScript?: boolean}) { + function mockDoctorService(data: { canExecuteLocalBuild: boolean, mockSetupScript?: boolean }) { const doctorService = testInjector.resolve("doctorService"); doctorService.canExecuteLocalBuild = () => data.canExecuteLocalBuild; if (data.mockSetupScript) { @@ -58,10 +59,10 @@ describe("platformEnvironmentRequirements ", () => { } } - function mockPrompter(data: {firstCallOptionName: string, secondCallOptionName?: string}) { + function mockPrompter(data: { firstCallOptionName: string, secondCallOptionName?: string }) { const prompter = testInjector.resolve("prompter"); prompter.promptForChoice = (message: string, choices: string[]) => { - promptForChoiceData.push({message: message, choices: choices}); + promptForChoiceData.push({ message: message, choices: choices }); if (promptForChoiceData.length === 1) { return Promise.resolve(data.firstCallOptionName); @@ -73,7 +74,7 @@ describe("platformEnvironmentRequirements ", () => { }; } - function mockNativeScriptCloudExtensionService(data: {isInstalled: boolean}) { + function mockNativeScriptCloudExtensionService(data: { isInstalled: boolean }) { const nativeScriptCloudExtensionService = testInjector.resolve("nativeScriptCloudExtensionService"); nativeScriptCloudExtensionService.isInstalled = () => data.isInstalled; nativeScriptCloudExtensionService.install = () => { isExtensionInstallCalled = true; }; @@ -124,7 +125,7 @@ describe("platformEnvironmentRequirements ", () => { mockPrompter({ firstCallOptionName: PlatformEnvironmentRequirements.CLOUD_SETUP_OPTION_NAME }); mockNativeScriptCloudExtensionService({ isInstalled: true }); - await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements({ platform, notConfiguredEnvOptions: { hideSyncToPreviewAppOption: true }})); + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements({ platform, notConfiguredEnvOptions: { hideSyncToPreviewAppOption: true } })); assert.isTrue(promptForChoiceData.length === 1); assert.isTrue(isExtensionInstallCalled); assert.deepEqual("To continue, choose one of the following options: ", promptForChoiceData[0].message); @@ -135,13 +136,13 @@ describe("platformEnvironmentRequirements ", () => { mockPrompter({ firstCallOptionName: PlatformEnvironmentRequirements.CLOUD_SETUP_OPTION_NAME }); mockNativeScriptCloudExtensionService({ isInstalled: true }); - await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements({ platform, notConfiguredEnvOptions: { hideCloudBuildOption: true }})); + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements({ platform, notConfiguredEnvOptions: { hideCloudBuildOption: true } })); assert.isTrue(promptForChoiceData.length === 1); assert.isTrue(isExtensionInstallCalled); assert.deepEqual("To continue, choose one of the following options: ", promptForChoiceData[0].message); assert.deepEqual(['Sync to Playground', 'Configure for Local Builds', 'Skip Step and Configure Manually'], promptForChoiceData[0].choices); }); - it("should skip env check when NS_SKIP_ENV_CHECK environment variable is passed", async() => { + it("should skip env check when NS_SKIP_ENV_CHECK environment variable is passed", async () => { (process.env).NS_SKIP_ENV_CHECK = true; const output = await platformEnvironmentRequirements.checkEnvironmentRequirements({ platform }); @@ -153,7 +154,7 @@ describe("platformEnvironmentRequirements ", () => { describe("when local setup option is selected", () => { beforeEach(() => { - mockPrompter( {firstCallOptionName: PlatformEnvironmentRequirements.LOCAL_SETUP_OPTION_NAME}); + mockPrompter({ firstCallOptionName: PlatformEnvironmentRequirements.LOCAL_SETUP_OPTION_NAME }); }); it("should return true when env is configured after executing setup script", async () => { @@ -169,7 +170,7 @@ describe("platformEnvironmentRequirements ", () => { describe("and env is not configured after executing setup script", () => { it("should setup manually when cloud extension is installed", async () => { - mockDoctorService( { canExecuteLocalBuild: false, mockSetupScript: true }); + mockDoctorService({ canExecuteLocalBuild: false, mockSetupScript: true }); mockPrompter({ firstCallOptionName: PlatformEnvironmentRequirements.LOCAL_SETUP_OPTION_NAME, secondCallOptionName: PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME }); mockNativeScriptCloudExtensionService({ isInstalled: true });