From 64bab0996a6c2b2de9ac298f6ec79fc8c7fbf47e Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Tue, 15 May 2018 22:20:26 +0300 Subject: [PATCH] feat: Deprecate support for all macOS versions below High Sierra Add deprecation message in case the current macOS version is below High Sierra. As the latest Xcode versions can be installed only on this OS and we state that we support only latest Xcode, we have to deprecate the support for other OS versions. The deprecation message will be shown on each CLI command. As the deprecation messages from CLI should be shown in Sidekick as well, expose new methods in CLI's `sysInfo` moodule, so Sidekick can get the warnings and show them. In order to get the warnings for Node.js version, change the way `verifyNodeVersion` is called. Expose method for getting the currently supported Node.js version range. Add tests for the new methods. --- PublicAPI.md | 46 +++++++++++++++ bin/tns | 2 +- lib/bootstrap.ts | 2 +- lib/common | 2 +- lib/constants.ts | 8 +++ lib/nativescript-cli.ts | 8 +++ lib/sys-info.ts | 41 ++++++++++++- test/nativescript-cli-lib.ts | 4 ++ test/sys-info.ts | 109 +++++++++++++++++++++++++++++++++++ 9 files changed, 218 insertions(+), 4 deletions(-) create mode 100644 test/sys-info.ts diff --git a/PublicAPI.md b/PublicAPI.md index 4f1bd3b159..40cba83daf 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -50,6 +50,9 @@ const tns = require("nativescript"); * [generateSplashScreens](#generatesplashscreens) * [androidProcessService](#androidprocessservice) * [getAppProcessId](#getappprocessid) +* [sysInfo](#sysinfo) + * [getSupportedNodeVersionRange](#getsupportednodeversionrange) + * [getSystemWarnings](#getsystemwarnings) ## Module projectService @@ -1017,6 +1020,7 @@ tns.liveSyncService.on("debuggerDetached", debugInfo => { console.log(`Detached debugger for device with id ${debugInfo.deviceIdentifier}`); }); ``` + ## analyticsSettingsService Provides methods for accessing the analytics settings file data. @@ -1078,6 +1082,7 @@ tns.analyticsSettingsService.getPlaygroundInfo("/my/project/path") console.log(playgroundInfo.usedTutorial); }); ``` + ## constants Contains various constants related to NativeScript. @@ -1151,6 +1156,47 @@ tns.androidProcessService.getAppProcessId("4df18f307d8a8f1b", "org.nativescript. .catch(err => console.error(`Error while checking for PID: ${err}`)); ``` +## sysInfo +The `sysInfo` module exposes methods to get the current environment setup and warnings for it. + +### getSupportedNodeVersionRange +The `getSupportedNodeVersionRange` method gives information about the supported Node.js versions for the current CLI. The result is a valid semver range, for example `>=6.0.0`. + +* Definition +```TypeScript +/** + * Returns the value of engines.node key from CLI's package.json file. + * @return {string} The range of supported Node.js versions. + */ +getSupportedNodeVersionRange(): string; +``` + +* Usage +```JavaScript +const nodeJsRange = tns.sysInfo.getSupportedNodeVersionRange(); +console.log(nodeJsRange); +``` + +### getSystemWarnings +The `getSystemWarnings` methods returns all deprecation warnings for current environment. For example, in case the support for the current OS is deprecated by CLI, the method will return array with one message describing the deprecation. + +* Definition +```TypeScript +/** + * Gets all global warnings for the current environment, for example Node.js version compatibility, OS compatibility, etc. + * @return {Promise} All warnings. Empty array is returned in case the system is setup correctly. + */ +getSystemWarnings(): Promise; +``` + +* Usage +```JavaScript +tns.sysInfo.getSystemWarnings() + .then(warnings => { + warnings.forEach(warn => console.log(warn)); + }) + .catch(err => console.error(`Error while trying to get system warnings: ${err}`)); +``` ## How to add a new method to Public API CLI is designed as command line tool and when it is used as a library, it does not give you access to all of the methods. This is mainly implementation detail. Most of the CLI's code is created to work in command line, not as a library, so before adding method to public API, most probably it will require some modification. diff --git a/bin/tns b/bin/tns index a411dd44eb..79216cd7c7 100755 --- a/bin/tns +++ b/bin/tns @@ -6,7 +6,7 @@ var path = require("path"), pathToLib = path.join(__dirname, "..", "lib"), pathToCommon = path.join(pathToLib, "common"); -require(path.join(pathToCommon, "verify-node-version")).verifyNodeVersion(node, "NativeScript", [ "^6.0.0" ]); +require(path.join(pathToCommon, "verify-node-version")).verifyNodeVersion(); var pathToCliExecutable = path.join(pathToLib, "nativescript-cli.js"); diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 0c162f69c4..ded2d1d86b 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -125,7 +125,7 @@ $injector.requirePublicClass("liveSyncService", "./services/livesync/livesync-se $injector.require("androidLiveSyncService", "./services/livesync/android-livesync-service"); $injector.require("iOSLiveSyncService", "./services/livesync/ios-livesync-service"); $injector.require("usbLiveSyncService", "./services/livesync/livesync-service"); // The name is used in https://github.com/NativeScript/nativescript-dev-typescript -$injector.require("sysInfo", "./sys-info"); +$injector.requirePublic("sysInfo", "./sys-info"); $injector.require("iOSNotificationService", "./services/ios-notification-service"); $injector.require("socketProxyFactory", "./device-sockets/ios/socket-proxy-factory"); diff --git a/lib/common b/lib/common index 816eec8e9f..8a13846f54 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 816eec8e9f7865dcc1853edbd8df2bf39e927060 +Subproject commit 8a13846f54df67c62516f862001c59fe39c3ac6b diff --git a/lib/constants.ts b/lib/constants.ts index 7983e71a1a..63c7001f37 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -170,3 +170,11 @@ export class AssetConstants { public static defaultScale = 1; public static defaultOverlayImageScale = 0.8; } + +// https://en.wikipedia.org/wiki/Darwin_(operating_system)#Release_history +export class MacOSVersions { + public static Sierra = "10.12"; + public static HighSierra = "10.13"; +} + +export const MacOSDeprecationStringFormat = "Support for macOS %s is deprecated and will be removed in one of the next releases of NativeScript. Please, upgrade to the latest macOS version."; diff --git a/lib/nativescript-cli.ts b/lib/nativescript-cli.ts index 1f30074c49..d38c172ad2 100644 --- a/lib/nativescript-cli.ts +++ b/lib/nativescript-cli.ts @@ -1,4 +1,5 @@ require("./bootstrap"); +import { EOL } from "os"; import * as shelljs from "shelljs"; shelljs.config.silent = true; shelljs.config.fatal = true; @@ -21,6 +22,13 @@ import { settlePromises } from "./common/helpers"; logger.trace("Unable to load extensions. Error is: ", err); } + const $sysInfo = $injector.resolve("sysInfo"); + const macOSWarning = await $sysInfo.getMacOSWarningMessage(); + if (macOSWarning) { + const message = EOL + macOSWarning + EOL ; + logger.warn(message); + } + const commandDispatcher: ICommandDispatcher = $injector.resolve("commandDispatcher"); const messages: IMessagesService = $injector.resolve("$messagesService"); diff --git a/lib/sys-info.ts b/lib/sys-info.ts index 3db406aebf..5850b92777 100644 --- a/lib/sys-info.ts +++ b/lib/sys-info.ts @@ -1,15 +1,22 @@ import * as path from "path"; +import { format } from "util"; import { sysInfo } from "nativescript-doctor"; +import { MacOSVersions, MacOSDeprecationStringFormat } from "./constants"; +import { getNodeWarning } from "./common/verify-node-version"; +import { exported } from "./common/decorators"; export class SysInfo implements ISysInfo { private sysInfo: ISysInfoData = null; + constructor(private $fs: IFileSystem, + private $hostInfo: IHostInfo) { } + public async getSysInfo(config?: NativeScriptDoctor.ISysInfoConfig): Promise { if (!this.sysInfo) { const pathToNativeScriptCliPackageJson = (config && config.pathToNativeScriptCliPackageJson) || path.join(__dirname, "..", "package.json"); const androidToolsInfo = config && config.androidToolsInfo; - this.sysInfo = await sysInfo.getSysInfo({pathToNativeScriptCliPackageJson, androidToolsInfo}); + this.sysInfo = await sysInfo.getSysInfo({ pathToNativeScriptCliPackageJson, androidToolsInfo }); } return this.sysInfo; @@ -26,5 +33,37 @@ export class SysInfo implements ISysInfo { public getJavaCompilerVersion(): Promise { return sysInfo.getJavaCompilerVersion(); } + + @exported("sysInfo") + public async getSystemWarnings(): Promise { + const warnings: string[] = []; + const macOSWarningMessage = await this.getMacOSWarningMessage(); + if (macOSWarningMessage) { + warnings.push(macOSWarningMessage); + } + + const nodeWarning = getNodeWarning(); + if (nodeWarning) { + warnings.push(nodeWarning); + } + + return warnings; + } + + @exported("sysInfo") + public getSupportedNodeVersionRange(): string { + const pathToCLIPackageJson = path.join(__dirname, "..", "package.json"); + const jsonContent = this.$fs.readJson(pathToCLIPackageJson); + return jsonContent && jsonContent.engines && jsonContent.engines.node; + } + + public async getMacOSWarningMessage(): Promise { + const macOSVersion = await this.$hostInfo.getMacOSVersion(); + if (macOSVersion && macOSVersion < MacOSVersions.HighSierra) { + return format(MacOSDeprecationStringFormat, macOSVersion); + } + + return null; + } } $injector.register("sysInfo", SysInfo); diff --git a/test/nativescript-cli-lib.ts b/test/nativescript-cli-lib.ts index 9edc5bf7e6..0919267280 100644 --- a/test/nativescript-cli-lib.ts +++ b/test/nativescript-cli-lib.ts @@ -51,6 +51,10 @@ describe("nativescript-cli-lib", () => { ], androidProcessService: [ "getAppProcessId" + ], + sysInfo: [ + "getSupportedNodeVersionRange", + "getSystemWarnings" ] }; diff --git a/test/sys-info.ts b/test/sys-info.ts new file mode 100644 index 0000000000..cb7dfbe6f2 --- /dev/null +++ b/test/sys-info.ts @@ -0,0 +1,109 @@ +import { SysInfo } from "../lib/sys-info"; +import { Yok } from "../lib/common/yok"; +import { assert } from "chai"; +import { format } from "util"; +import * as sinon from "sinon"; +import { MacOSVersions, MacOSDeprecationStringFormat } from "../lib/constants"; +const verifyNodeVersion = require("../lib/common/verify-node-version"); + +describe("sysInfo", () => { + let sandbox: sinon.SinonSandbox = null; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + const createTestInjector = (): IInjector => { + const testInjector = new Yok(); + testInjector.register("hostInfo", { + getMacOSVersion: async (): Promise => null + }); + + testInjector.register("fs", { + readJson: (filename: string, encoding?: string): any => null + }); + + testInjector.register("sysInfo", SysInfo); + + return testInjector; + }; + + describe("getSystemWarnings", () => { + const getSystemWarnings = async (opts?: { nodeJsWarning?: string, macOSDeprecatedVersion?: string }): Promise => { + sandbox.stub(verifyNodeVersion, "getNodeWarning").returns(opts && opts.nodeJsWarning); + + const testInjector = createTestInjector(); + const $hostInfo = testInjector.resolve("hostInfo"); + $hostInfo.getMacOSVersion = async (): Promise => opts && opts.macOSDeprecatedVersion; + const sysInfo = testInjector.resolve("sysInfo"); + const warnings = await sysInfo.getSystemWarnings(); + return warnings; + }; + + it("returns empty array when there are no warnings", async () => { + const warnings = await getSystemWarnings(); + assert.deepEqual(warnings, []); + }); + + it("returns correct single warning when macOS version is deprecated", async () => { + const macOSDeprecatedVersion = MacOSVersions.Sierra; + const macOSWarning = format(MacOSDeprecationStringFormat, macOSDeprecatedVersion); + const warnings = await getSystemWarnings({ macOSDeprecatedVersion }); + assert.deepEqual(warnings, [macOSWarning]); + }); + + it("returns correct single warning when Node.js version is deprecated", async () => { + const nodeJsWarning = "Node.js Warning"; + const warnings = await getSystemWarnings({ nodeJsWarning }); + assert.deepEqual(warnings, [nodeJsWarning]); + }); + + it("returns correct warnings when both Node.js and macOS versions are deprecated", async () => { + const macOSDeprecatedVersion = MacOSVersions.Sierra; + const macOSWarning = format(MacOSDeprecationStringFormat, macOSDeprecatedVersion); + const nodeJsWarning = "Node.js Warning"; + const warnings = await getSystemWarnings({ macOSDeprecatedVersion, nodeJsWarning }); + assert.deepEqual(warnings, [macOSWarning, nodeJsWarning]); + }); + }); + + describe("getMacOSWarningMessage", () => { + const getMacOSWarning = async (macOSDeprecatedVersion?: string): Promise => { + sandbox.stub(verifyNodeVersion, "getNodeWarning").returns(null); + + const testInjector = createTestInjector(); + const $hostInfo = testInjector.resolve("hostInfo"); + $hostInfo.getMacOSVersion = async (): Promise => macOSDeprecatedVersion; + const sysInfo = testInjector.resolve("sysInfo"); + const warning = await sysInfo.getMacOSWarningMessage(); + return warning; + }; + + it("returns null when macOS version is supported", async () => { + const warning = await getMacOSWarning(); + assert.deepEqual(warning, null); + }); + + it("returns correct single warning when macOS version is deprecated", async () => { + const macOSDeprecatedVersion = MacOSVersions.Sierra; + const macOSWarning = format(MacOSDeprecationStringFormat, macOSDeprecatedVersion); + const warning = await getMacOSWarning(macOSDeprecatedVersion); + assert.deepEqual(warning, macOSWarning); + }); + }); + + describe("getSupportedNodeVersionRange", () => { + it("returns range from CLI's package.json", () => { + const testInjector = createTestInjector(); + const expectedRange = require("../package.json").engines.node; + const fs = testInjector.resolve("fs"); + fs.readJson = () => require("../package.json"); + const sysInfo = testInjector.resolve("sysInfo"); + const actualRange = sysInfo.getSupportedNodeVersionRange(); + assert.equal(actualRange, expectedRange); + }); + }); +});