From 728590d594d0e017e007a18325827961bd6f22f1 Mon Sep 17 00:00:00 2001 From: Natalia-Hristova Date: Thu, 1 Feb 2018 13:31:53 +0200 Subject: [PATCH 1/5] Changelog for {N} v3.4.2 --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d84c1b648e..7c8be4bbb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,17 @@ NativeScript CLI Changelog ================ +3.4.2 (2018, February 01) +== + +### New +* [Implemented #2127](https://github.com/NativeScript/nativescript-cli/issues/2127): Feature Request: Ability to disable spinner during install process + +### Fixed +* [Fixed #3337](https://github.com/NativeScript/nativescript-cli/issues/3337): Empty Chrome DevTools when using tns debug ios for iOS Simulator +* [Fixed #3338](https://github.com/NativeScript/nativescript-cli/issues/3338): `tns debug ios --chrome` can not stop on first line + + 3.4.1 (2018, January 11) == From 76e29f81adab4c32bca7463bdecc789bcc8c5e76 Mon Sep 17 00:00:00 2001 From: fatme Date: Tue, 27 Feb 2018 14:06:12 +0200 Subject: [PATCH 2/5] feat(Analytics): Respect playground key from package.json file We need to track users that export their projects from playground and opens them in CLI or Sidekick. To support that we introduce playground key in nativescript key in package.json file. For example: ``` { "nativescript": { "playground": { "id": "some user quid", "usedTutorial": false // is not obligatory. In case it is present, can be true or false } } } ``` If case when package.json file contains playground key, {N} CLI reads playground data and saves them in userSettings file. If usedTutorial=true is already saved in userSettings file, {N} CLI does not overwrite it. After that {N} CLI deletes playground key from package.json file. In case when package.json file does not contain playground key, {N} CLI checks if playground key is already saved in userSettings file. It this is the case, {N} CLI reads playground data from userSettings file. --- PublicAPI.md | 23 ++ lib/bootstrap.ts | 2 + lib/common | 2 +- lib/definitions/project.d.ts | 2 +- lib/services/analytics-settings-service.ts | 8 +- .../google-analytics-custom-dimensions.d.ts | 8 - .../analytics/google-analytics-provider.ts | 10 +- lib/services/playground-service.ts | 49 ++++ test/platform-commands.ts | 3 + test/plugins-service.ts | 3 + test/services/playground-service.ts | 212 ++++++++++++++++++ 11 files changed, 309 insertions(+), 13 deletions(-) delete mode 100644 lib/services/analytics/google-analytics-custom-dimensions.d.ts create mode 100644 lib/services/playground-service.ts create mode 100644 test/services/playground-service.ts diff --git a/PublicAPI.md b/PublicAPI.md index 10b6a2f488..9e42137ba9 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -898,6 +898,29 @@ getUserAgentString(identifier: string): string; const userAgentString = tns.analyticsSettingsService.getUserAgentString("tns/3.3.0"); ``` +### getPlaygroundInfo +The `getPlaygroundInfo` method allows retrieving information for projects that are exported from playground + +* Definition: +```TypeScript +/** + * Gets information for projects that are exported from playground. + * Returns null in case when project does not have playground key in package.json file (e.g is not exported from playground) and no playground info is saved in userSettings file + * @param {string} projectDir The project directory. + * @returns {Promise} Playground info. { id: string, usedTutorial: boolean } + */ +getPlaygroundInfo(projectDir: string): Promise; +``` + +* Usage: +```JavaScript +tns.analyticsSettingsService.getPlaygroundInfo("/my/project/path") + .then(playgroundInfo => { + console.log(playgroundInfo.id); + console.log(playgroundInfo.usedTutorial); + }); +``` + ## 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. For example the `$options` injected module contains information about all `--` options passed on the terminal. When the CLI is used as a library, the options are not populated. Before adding method to public API, make sure its implementation does not rely on `$options`. diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 08dcd977b3..12106bcd8c 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -144,3 +144,5 @@ $injector.requirePublic("extensibilityService", "./services/extensibility-servic $injector.require("nodeModulesDependenciesBuilder", "./tools/node-modules/node-modules-dependencies-builder"); $injector.require("subscriptionService", "./services/subscription-service"); + +$injector.require('playgroundService', './services/playground-service'); diff --git a/lib/common b/lib/common index 623c2d8afc..8bba82ae34 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 623c2d8afc376f726f1c2ecb784eef667398a395 +Subproject commit 8bba82ae3457b45775d244e498f6e3b9e8236f6d diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index c4d3607321..5044c0879d 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -105,7 +105,7 @@ interface IProjectDataService { */ removeDependency(projectDir: string, dependencyName: string): void; - getProjectData(projectDir: string): IProjectData; + getProjectData(projectDir?: string): IProjectData; } /** diff --git a/lib/services/analytics-settings-service.ts b/lib/services/analytics-settings-service.ts index 8930796165..4a0a85d4e2 100644 --- a/lib/services/analytics-settings-service.ts +++ b/lib/services/analytics-settings-service.ts @@ -8,7 +8,8 @@ class AnalyticsSettingsService implements IAnalyticsSettingsService { private $staticConfig: IStaticConfig, private $hostInfo: IHostInfo, private $osInfo: IOsInfo, - private $logger: ILogger) { } + private $logger: ILogger, + private $playgroundService: IPlaygroundService) { } public async canDoRequest(): Promise { return true; @@ -23,6 +24,11 @@ class AnalyticsSettingsService implements IAnalyticsSettingsService { return this.getSettingValueOrDefault(this.$staticConfig.ANALYTICS_INSTALLATION_ID_SETTING_NAME); } + @exported("analyticsSettingsService") + public async getPlaygroundInfo(projectDir: string): Promise { + return this.$playgroundService.getPlaygroundInfo(projectDir); + } + public getClientName(): string { return "" + this.$staticConfig.CLIENT_NAME_ALIAS.cyan.bold; } diff --git a/lib/services/analytics/google-analytics-custom-dimensions.d.ts b/lib/services/analytics/google-analytics-custom-dimensions.d.ts deleted file mode 100644 index 488439b814..0000000000 --- a/lib/services/analytics/google-analytics-custom-dimensions.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -declare const enum GoogleAnalyticsCustomDimensions { - cliVersion = "cd1", - projectType = "cd2", - clientID = "cd3", - sessionID = "cd4", - client = "cd5", - nodeVersion = "cd6" -} diff --git a/lib/services/analytics/google-analytics-provider.ts b/lib/services/analytics/google-analytics-provider.ts index 347625b9e8..2ce3a8284e 100644 --- a/lib/services/analytics/google-analytics-provider.ts +++ b/lib/services/analytics/google-analytics-provider.ts @@ -46,7 +46,7 @@ export class GoogleAnalyticsProvider implements IGoogleAnalyticsProvider { this.setCrossClientCustomDimensions(visitor, sessionId); break; default: - this.setCustomDimensions(visitor, trackInfo.customDimensions, sessionId); + await this.setCustomDimensions(visitor, trackInfo.customDimensions, sessionId); break; } @@ -60,7 +60,7 @@ export class GoogleAnalyticsProvider implements IGoogleAnalyticsProvider { } } - private setCustomDimensions(visitor: ua.Visitor, customDimensions: IStringDictionary, sessionId: string): void { + private async setCustomDimensions(visitor: ua.Visitor, customDimensions: IStringDictionary, sessionId: string): Promise { const defaultValues: IStringDictionary = { [GoogleAnalyticsCustomDimensions.cliVersion]: this.$staticConfig.version, [GoogleAnalyticsCustomDimensions.nodeVersion]: process.version, @@ -70,6 +70,12 @@ export class GoogleAnalyticsProvider implements IGoogleAnalyticsProvider { [GoogleAnalyticsCustomDimensions.client]: AnalyticsClients.Unknown }; + const playgrounInfo = await this.$analyticsSettingsService.getPlaygroundInfo(); + if (playgrounInfo && playgrounInfo.id) { + defaultValues[GoogleAnalyticsCustomDimensions.playgroundId] = playgrounInfo.id; + defaultValues[GoogleAnalyticsCustomDimensions.usedTutorial] = playgrounInfo.usedTutorial.toString(); + } + customDimensions = _.merge(defaultValues, customDimensions); _.each(customDimensions, (value, key) => { diff --git a/lib/services/playground-service.ts b/lib/services/playground-service.ts new file mode 100644 index 0000000000..a84fb17759 --- /dev/null +++ b/lib/services/playground-service.ts @@ -0,0 +1,49 @@ +export class PlaygroundService implements IPlaygroundService { + constructor(private $fs: IFileSystem, + private $projectDataService: IProjectDataService, + private $userSettingsService: IUserSettingsService) { } + + public async getPlaygroundInfo(projectDir?: string): Promise { + const projectData = this.getProjectData(projectDir); + if (projectData) { + const projectFileContent = this.$fs.readJson(projectData.projectFilePath); + if (this.hasPlaygroundKey(projectFileContent)) { + const id = projectFileContent.nativescript.playground.id; + let usedTutorial = projectFileContent.nativescript.playground.usedTutorial || false; + + // In case when usedTutorial=true is already saved in userSettings file, we shouldn't overwrite it + const playgroundInfo = await this.getPlaygroundInfoFromUserSettingsFile(); + if (playgroundInfo && playgroundInfo.usedTutorial) { + usedTutorial = true; + } + + delete projectFileContent.nativescript.playground; + this.$fs.writeJson(projectData.projectFilePath, projectFileContent); + + const result = { id , usedTutorial }; + await this.$userSettingsService.saveSettings({playground: result}); + return result; + } + } + + return this.getPlaygroundInfoFromUserSettingsFile(); + } + + private getProjectData(projectDir: string): IProjectData { + try { + return this.$projectDataService.getProjectData(projectDir); + } catch (e) { + // in case command is executed in non-project folder + return null; + } + } + + private hasPlaygroundKey(projectFileContent: any): boolean { + return projectFileContent && projectFileContent.nativescript && projectFileContent.nativescript.playground && projectFileContent.nativescript.playground.id; + } + + private async getPlaygroundInfoFromUserSettingsFile(): Promise { + return this.$userSettingsService.getSettingValue("playground"); + } +} +$injector.register('playgroundService', PlaygroundService); diff --git a/test/platform-commands.ts b/test/platform-commands.ts index 1ab1d431af..779e49f021 100644 --- a/test/platform-commands.ts +++ b/test/platform-commands.ts @@ -160,6 +160,9 @@ function createTestInjector() { message: (): void => undefined }) }); + testInjector.register("analyticsSettingsService", { + getPlaygroundInfo: () => Promise.resolve(null) + }); return testInjector; } diff --git a/test/plugins-service.ts b/test/plugins-service.ts index 1cdfbb5f67..ba10fd20ca 100644 --- a/test/plugins-service.ts +++ b/test/plugins-service.ts @@ -110,6 +110,9 @@ function createTestInjector() { message: (): void => undefined }) }); + testInjector.register("analyticsSettingsService", { + getPlaygroundInfo: () => Promise.resolve(null) + }); return testInjector; } diff --git a/test/services/playground-service.ts b/test/services/playground-service.ts new file mode 100644 index 0000000000..b160d25df1 --- /dev/null +++ b/test/services/playground-service.ts @@ -0,0 +1,212 @@ +import { assert } from "chai"; +import { FileSystemStub } from "../stubs"; +import { PlaygroundService } from "../../lib/services/playground-service"; +import { Yok } from "../../lib/common/yok"; + +let userSettings: any = null; + +function createTestInjector(): IInjector { + const testInjector = new Yok(); + + testInjector.register("playgroundService", PlaygroundService); + testInjector.register("fs", FileSystemStub); + testInjector.register("projectDataService", {}); + testInjector.register("userSettingsService", {}); + testInjector.register("injector", testInjector); + + return testInjector; +} + +function mockPlaygroundService(testInjector: IInjector, data?: { projectData?: any, nativescriptKey?: any, userSettingsData?: any}) { + const projectDataService = testInjector.resolve("projectDataService"); + projectDataService.getProjectData = () => (data && data.projectData) || {}; + + const userSettingsService = testInjector.resolve("userSettingsService"); + userSettingsService.getSettingValue = async (keyName: string) => { + return data && data.userSettingsData ? data.userSettingsData[keyName] : null; + }; + userSettingsService.saveSettings = async (settings: any) => { userSettings = settings; }; + + const fs = testInjector.resolve("fs"); + fs.readJson = () => (data && data.nativescriptKey) || {}; +} + +describe("PlaygroundService", () => { + let testInjector: IInjector = null; + let playgroundService: IPlaygroundService = null; + + beforeEach(() => { + testInjector = createTestInjector(); + playgroundService = testInjector.resolve("playgroundService"); + }); + + describe("getPlaygroundInfo", () => { + it("should return null when projectDir is not specified and no playground data is saved in userSettings file", async () => { + mockPlaygroundService(testInjector, { userSettingsData: null }); + const result = await playgroundService.getPlaygroundInfo(); + assert.equal(result, null); + }); + it("should return saved playgroundData from userSettings file when projectDir is not specified and playground data is already saved in userSettings file", async () => { + mockPlaygroundService(testInjector, { userSettingsData: {playground: {id: "test-playground-identifier", usedTutorial: false}}}); + const actualResult = await playgroundService.getPlaygroundInfo(); + const expectedResult = {id: "test-playground-identifier", usedTutorial: false}; + assert.deepEqual(actualResult, expectedResult); + }); + it("should return null when projectFile has no nativescript key in package.json file and no playground data is saved in userSettings file", async () => { + mockPlaygroundService(testInjector, { userSettingsData: null }); + const result = await playgroundService.getPlaygroundInfo(); + assert.equal(result, null); + }); + it("should return saved playgroundData from userSettings file when projectFile has no nativescript key in package.json and some playground data is already saved in userSettings file", async () => { + mockPlaygroundService(testInjector, { userSettingsData: {playground: {id: "test-playground-identifier", usedTutorial: true}}}); + const actualResult = await playgroundService.getPlaygroundInfo(); + const expectedResult = {id: "test-playground-identifier", usedTutorial: true}; + assert.deepEqual(actualResult, expectedResult); + }); + + describe("should return playgroundInfo when project has playground key in package.json", () => { + it("and no usedTutorial", async () => { + const nativescriptKey = { + nativescript: { + playground: { + id: "test-guid" + } + } + }; + mockPlaygroundService(testInjector, { nativescriptKey }); + const actualResult = await playgroundService.getPlaygroundInfo(); + const expectedResult = { id: "test-guid", usedTutorial: false }; + assert.deepEqual(actualResult, expectedResult); + }); + it("and usedTutorial is true", async() => { + const nativescriptKey = { + nativescript: { + playground: { + id: "test-guid", + usedTutorial: true + } + } + }; + mockPlaygroundService(testInjector, { nativescriptKey }); + const actualResult = await playgroundService.getPlaygroundInfo(); + const expectedResult = { id: 'test-guid', usedTutorial: true }; + assert.deepEqual(actualResult, expectedResult); + }); + it("and usedTutorial is false", async () => { + const nativescriptKey = { + nativescript: { + playground: { + id: "playground-test-guid", + usedTutorial: false + } + } + }; + mockPlaygroundService(testInjector, { nativescriptKey }); + const actualResult = await playgroundService.getPlaygroundInfo(); + const expectedResult = { id: "playground-test-guid", usedTutorial: false }; + assert.deepEqual(actualResult, expectedResult); + }); + }); + + describe("should return playgroundInfo from userSettings file", () => { + it("when usedTutorial is true", async () => { + mockPlaygroundService(testInjector, { userSettingsData: {playground: {id: "test-playground-identifier", usedTutorial: true}}}); + const actualResult = await playgroundService.getPlaygroundInfo(); + const expectedResult = { id: 'test-playground-identifier', usedTutorial: true }; + assert.deepEqual(actualResult, expectedResult); + }); + it("when usedTutorial is false", async () => { + mockPlaygroundService(testInjector, { userSettingsData: {playground: {id: "test-playground-identifier", usedTutorial: false}}}); + const actualResult = await playgroundService.getPlaygroundInfo(); + const expectedResult = { id: 'test-playground-identifier', usedTutorial: false }; + assert.deepEqual(actualResult, expectedResult); + }); + }); + + it("should return undefined when userSettings file does not have playground key", async () => { + mockPlaygroundService(testInjector, { userSettingsData: {}}); + const actualResult = await playgroundService.getPlaygroundInfo(); + assert.deepEqual(actualResult, undefined); + }); + it("should replace playgroundId when another id is already saved in userSettings file", async () => { + const nativescriptKey = { + nativescript: { + playground: { + id: "test-guid" + } + } + }; + mockPlaygroundService(testInjector, { nativescriptKey }); + let actualResult = await playgroundService.getPlaygroundInfo(); + let expectedResult = { id: "test-guid", usedTutorial: false }; + assert.deepEqual(actualResult, expectedResult); + + const secondNativescriptKey = { + nativescript: { + playground: { + id: "another-test-guid" + } + } + }; + mockPlaygroundService(testInjector, { nativescriptKey: secondNativescriptKey }); + actualResult = await playgroundService.getPlaygroundInfo(); + expectedResult = { id: 'another-test-guid', usedTutorial: false }; + assert.deepEqual(actualResult, expectedResult); + assert.deepEqual(userSettings, { playground: { id: 'another-test-guid', usedTutorial: false }}); + }); + it("should replace usedTutorial when false value is already saved in userSettings file", async () => { + const nativescriptKey = { + nativescript: { + playground: { + id: "test-guid", + usedTutorial: false + } + } + }; + mockPlaygroundService(testInjector, { nativescriptKey }); + let actualResult = await playgroundService.getPlaygroundInfo(); + let expectedResult = { id: "test-guid", usedTutorial: false }; + assert.deepEqual(actualResult, expectedResult); + + const secondNativescriptKey = { + nativescript: { + playground: { + id: "another-test-guid", + usedTutorial: true + } + } + }; + mockPlaygroundService(testInjector, { nativescriptKey: secondNativescriptKey, userSettingsData: {playground: { id: "test-guid", usedTutorial: false }} }); + actualResult = await playgroundService.getPlaygroundInfo(); + expectedResult = { id: 'another-test-guid', usedTutorial: true }; + assert.deepEqual(actualResult, expectedResult); + }); + it("shouldn't replace usedTutorial when true value is already saved in userSettings file", async () => { + const nativescriptKey = { + nativescript: { + playground: { + id: "test-guid", + usedTutorial: true + } + } + }; + mockPlaygroundService(testInjector, { nativescriptKey }); + let actualResult = await playgroundService.getPlaygroundInfo(); + let expectedResult = { id: "test-guid", usedTutorial: true }; + assert.deepEqual(actualResult, expectedResult); + + const secondNativescriptKey = { + nativescript: { + playground: { + id: "another-test-guid", + usedTutorial: false + } + } + }; + mockPlaygroundService(testInjector, { nativescriptKey: secondNativescriptKey, userSettingsData: {playground: { id: "test-guid", usedTutorial: true }} }); + actualResult = await playgroundService.getPlaygroundInfo(); + expectedResult = { id: 'another-test-guid', usedTutorial: true }; + assert.deepEqual(actualResult, expectedResult); + }); + }); +}); From 93237d94b673b848de374df75d5c11e3d83cf92a Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Thu, 1 Mar 2018 12:06:18 +0200 Subject: [PATCH 3/5] feat(create): Keep pluginsData key from template's package.json In case project template has a preconfigured plugins data (for example API keys), it will be stored in the template's package.json. After creating the project, CLI removes all keys except the one defined in `PackageJsonKeysToKeep` array from `app/package.json` file. So it removes the predefined configuration of the pluginData. Fix this by adding the entry "pluginsData" in the array, so we'll keep it as well. --- lib/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/constants.ts b/lib/constants.ts index 0525e4f425..4d343e3526 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -41,7 +41,7 @@ export class LiveSyncTrackActionNames { static DEVICE_INFO = `Device Info for ${liveSyncOperation}`; } -export const PackageJsonKeysToKeep: Array = ["name", "main", "android", "version"]; +export const PackageJsonKeysToKeep: Array = ["name", "main", "android", "version", "pluginsData"]; export class SaveOptions { static PRODUCTION = "save"; From 94c8acd7f33d02f0d9ced8258db009c6ebddf9d9 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Thu, 1 Mar 2018 16:03:25 +0200 Subject: [PATCH 4/5] chore(release): Preapre for 3.4.3 release Update version. Update Changelog. --- CHANGELOG.md | 8 ++++++++ npm-shrinkwrap.json | 2 +- package.json | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c8be4bbb7..e66fde4878 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ NativeScript CLI Changelog ================ +3.4.3 (2018, March 02) +== + +### New +* [Implemented #3407](https://github.com/NativeScript/nativescript-cli/issues/3407) Allow templates to predefine plugin configurations in `package.json` +* [Implemented #3408](https://github.com/NativeScript/nativescript-cli/issues/3408) Add additional tracking for users who have exported projects from Playground + + 3.4.2 (2018, February 01) == diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 832ab7353c..028e67ed89 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,6 +1,6 @@ { "name": "nativescript", - "version": "3.4.1", + "version": "3.4.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 1abc5ef6c0..63b3349c00 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nativescript", "preferGlobal": true, - "version": "3.4.2", + "version": "3.4.3", "author": "Telerik ", "description": "Command-line interface for building NativeScript projects", "bin": { From 9134edcbb55cb1ab857b130066522ba1b861518c Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Wed, 14 Mar 2018 20:23:26 +0200 Subject: [PATCH 5/5] chore: Update to latest common lib --- lib/common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common b/lib/common index 6a21306e45..fad16ce2df 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 6a21306e4573af63a4880cb4aeb1d09a496d13b2 +Subproject commit fad16ce2df7b7a8dedc5720bea4fdc50acb7eb7e