diff --git a/config/config.json b/config/config.json index 38bb01d5d3..a99a82f519 100644 --- a/config/config.json +++ b/config/config.json @@ -4,5 +4,7 @@ "CI_LOGGER": false, "ANDROID_DEBUG_UI_MAC": "Google Chrome", "USE_POD_SANDBOX": false, - "DISABLE_HOOKS": false + "DISABLE_HOOKS": false, + "UPLOAD_PLAYGROUND_FILES_ENDPOINT": "https://play.nativescript.org/api/files" , + "SHORTEN_URL_ENDPOINT": "https://play.nativescript.org/api/shortenurl?longUrl=%s" } diff --git a/docs/man_pages/project/testing/preview.md b/docs/man_pages/project/testing/preview.md new file mode 100644 index 0000000000..92d564d3a0 --- /dev/null +++ b/docs/man_pages/project/testing/preview.md @@ -0,0 +1,15 @@ +<% if (isJekyll) { %>--- +title: tns preview +position: 1 +---<% } %> +# tns preview + + +Usage | Synopsis +---|--- +Produces a QR code for deployment in the Preview app. | `tns preview` + +Enjoy NativeScript without any local setup. All you need is a couple of companion apps installed on your devices. When you save changes to the project, the changes are automatically synchronized to Preview app on your device. + +### Options +`--bundle` - Specifies that a bundler (e.g. webpack) should be used if one is present. If no value is passed will default to `webpack` \ No newline at end of file diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 2788fb7783..3a49adf608 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -50,6 +50,8 @@ $injector.requireCommand("run|*all", "./commands/run"); $injector.requireCommand("run|ios", "./commands/run"); $injector.requireCommand("run|android", "./commands/run"); +$injector.requireCommand("preview", "./commands/preview"); + $injector.requireCommand("debug|ios", "./commands/debug"); $injector.requireCommand("debug|android", "./commands/debug"); @@ -120,6 +122,7 @@ $injector.requireCommand("platform|clean", "./commands/platform-clean"); $injector.require("bundleValidatorHelper", "./helpers/bundle-validator-helper"); $injector.require("liveSyncCommandHelper", "./helpers/livesync-command-helper"); $injector.require("deployCommandHelper", "./helpers/deploy-command-helper"); +$injector.require("previewCommandHelper", "./helpers/preview-command-helper"); $injector.requirePublicClass("localBuildService", "./services/local-build-service"); $injector.requirePublicClass("liveSyncService", "./services/livesync/livesync-service"); @@ -127,6 +130,10 @@ $injector.requirePublicClass("androidLivesyncTool", "./services/livesync/android $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("previewAppLiveSyncService", "./services/livesync/playground/preview-app-livesync-service"); +$injector.require("previewAppPluginsService", "./services/livesync/playground/preview-app-plugins-service"); +$injector.require("previewSdkService", "./services/livesync/playground/preview-sdk-service"); +$injector.require("playgroundQrCodeGenerator", "./services/livesync/playground/qr-code-generator"); $injector.requirePublic("sysInfo", "./sys-info"); $injector.require("iOSNotificationService", "./services/ios-notification-service"); @@ -171,3 +178,4 @@ $injector.require("iOSLogParserService", "./services/ios-log-parser-service"); $injector.require("iOSDebuggerPortService", "./services/ios-debugger-port-service"); $injector.require("pacoteService", "./services/pacote-service"); +$injector.require("qrCodeTerminalService", "./services/qr-code-terminal-service"); diff --git a/lib/commands/add-platform.ts b/lib/commands/add-platform.ts index 5a597c5d6f..d10b24859e 100644 --- a/lib/commands/add-platform.ts +++ b/lib/commands/add-platform.ts @@ -1,31 +1,37 @@ -export class AddPlatformCommand implements ICommand { +import { ValidatePlatformCommandBase } from "./command-base"; + +export class AddPlatformCommand extends ValidatePlatformCommandBase implements ICommand { public allowedParameters: ICommandParameter[] = []; - constructor(private $options: IOptions, - private $platformService: IPlatformService, - private $projectData: IProjectData, - private $platformsData: IPlatformsData, + constructor($options: IOptions, + $platformService: IPlatformService, + $projectData: IProjectData, + $platformsData: IPlatformsData, private $errors: IErrors) { - this.$projectData.initializeProjectData(); + super($options, $platformsData, $platformService, $projectData); + this.$projectData.initializeProjectData(); } public async execute(args: string[]): Promise { await this.$platformService.addPlatforms(args, this.$options.platformTemplate, this.$projectData, this.$options, this.$options.frameworkPath); } - public async canExecute(args: string[]): Promise { + public async canExecute(args: string[]): Promise { if (!args || args.length === 0) { this.$errors.fail("No platform specified. Please specify a platform to add"); } + let canExecute = true; for (const arg of args) { this.$platformService.validatePlatform(arg, this.$projectData); - const platformData = this.$platformsData.getPlatformData(arg, this.$projectData); - const platformProjectService = platformData.platformProjectService; - await platformProjectService.validate(this.$projectData); + const output = await super.canExecuteCommandBase(arg); + canExecute = canExecute && output.canExecute; } - return true; + return { + canExecute, + suppressCommandHelp: !canExecute + }; } } diff --git a/lib/commands/build.ts b/lib/commands/build.ts index 71ea5f109e..f90fb8a510 100644 --- a/lib/commands/build.ts +++ b/lib/commands/build.ts @@ -1,13 +1,15 @@ import { ANDROID_RELEASE_BUILD_ERROR_MESSAGE } from "../constants"; +import { ValidatePlatformCommandBase } from "./command-base"; -export class BuildCommandBase { - constructor(protected $options: IOptions, +export abstract class BuildCommandBase extends ValidatePlatformCommandBase { + constructor($options: IOptions, protected $errors: IErrors, - protected $projectData: IProjectData, - protected $platformsData: IPlatformsData, + $projectData: IProjectData, + $platformsData: IPlatformsData, protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - protected $platformService: IPlatformService, + $platformService: IPlatformService, private $bundleValidatorHelper: IBundleValidatorHelper) { + super($options, $platformsData, $platformService, $projectData); this.$projectData.initializeProjectData(); } @@ -43,16 +45,29 @@ export class BuildCommandBase { } } - protected async validatePlatform(platform: string): Promise { + protected validatePlatform(platform: string): void { if (!this.$platformService.isPlatformSupportedForOS(platform, this.$projectData)) { this.$errors.fail(`Applications for platform ${platform} can not be built on this OS`); } this.$bundleValidatorHelper.validate(); + } + + protected async validateArgs(args: string[], platform: string): Promise { + const canExecute = await this.validateArgsCore(args, platform); + return { + canExecute, + suppressCommandHelp: false + }; + } - const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); - const platformProjectService = platformData.platformProjectService; - await platformProjectService.validate(this.$projectData); + private async validateArgsCore(args: string[], platform: string): Promise { + if (args.length !== 0) { + return false; + } + + const result = await this.$platformService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, platform); + return result; } } @@ -73,9 +88,17 @@ export class BuildIosCommand extends BuildCommandBase implements ICommand { return this.executeCore([this.$platformsData.availablePlatforms.iOS]); } - public async canExecute(args: string[]): Promise { - await super.validatePlatform(this.$devicePlatformsConstants.iOS); - return args.length === 0 && this.$platformService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, this.$platformsData.availablePlatforms.iOS); + public async canExecute(args: string[]): Promise { + const platform = this.$devicePlatformsConstants.iOS; + + super.validatePlatform(platform); + + let result = await super.canExecuteCommandBase(platform); + if (result.canExecute) { + result = await super.validateArgs(args, platform); + } + + return result; } } @@ -98,13 +121,20 @@ export class BuildAndroidCommand extends BuildCommandBase implements ICommand { return this.executeCore([this.$platformsData.availablePlatforms.Android]); } - public async canExecute(args: string[]): Promise { - await super.validatePlatform(this.$devicePlatformsConstants.Android); - if (this.$options.release && (!this.$options.keyStorePath || !this.$options.keyStorePassword || !this.$options.keyStoreAlias || !this.$options.keyStoreAliasPassword)) { - this.$errors.fail(ANDROID_RELEASE_BUILD_ERROR_MESSAGE); + public async canExecute(args: string[]): Promise { + const platform = this.$devicePlatformsConstants.Android; + super.validatePlatform(platform); + + let result = await super.canExecuteCommandBase(platform); + if (result.canExecute) { + if (this.$options.release && (!this.$options.keyStorePath || !this.$options.keyStorePassword || !this.$options.keyStoreAlias || !this.$options.keyStoreAliasPassword)) { + this.$errors.fail(ANDROID_RELEASE_BUILD_ERROR_MESSAGE); + } + + result = await super.validateArgs(args, platform); } - return args.length === 0 && await this.$platformService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, this.$platformsData.availablePlatforms.Android); + return result; } } diff --git a/lib/commands/clean-app.ts b/lib/commands/clean-app.ts index 7f0b0ebe77..bd938a3209 100644 --- a/lib/commands/clean-app.ts +++ b/lib/commands/clean-app.ts @@ -1,16 +1,18 @@ -export class CleanAppCommandBase implements ICommand { +import { ValidatePlatformCommandBase } from "./command-base"; + +export class CleanAppCommandBase extends ValidatePlatformCommandBase implements ICommand { public allowedParameters: ICommandParameter[] = []; protected platform: string; - constructor(protected $options: IOptions, - protected $projectData: IProjectData, - protected $platformService: IPlatformService, + constructor($options: IOptions, + $projectData: IProjectData, + $platformService: IPlatformService, protected $errors: IErrors, protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - protected $platformsData: IPlatformsData) { - - this.$projectData.initializeProjectData(); + $platformsData: IPlatformsData) { + super($options, $platformsData, $platformService, $projectData); + this.$projectData.initializeProjectData(); } public async execute(args: string[]): Promise { @@ -27,15 +29,13 @@ export class CleanAppCommandBase implements ICommand { return this.$platformService.cleanDestinationApp(platformInfo); } - public async canExecute(args: string[]): Promise { + public async canExecute(args: string[]): Promise { if (!this.$platformService.isPlatformSupportedForOS(this.platform, this.$projectData)) { this.$errors.fail(`Applications for platform ${this.platform} can not be built on this OS`); } - const platformData = this.$platformsData.getPlatformData(this.platform, this.$projectData); - const platformProjectService = platformData.platformProjectService; - await platformProjectService.validate(this.$projectData); - return true; + const result = await super.canExecuteCommandBase(this.platform); + return result; } } diff --git a/lib/commands/command-base.ts b/lib/commands/command-base.ts new file mode 100644 index 0000000000..e4f778336e --- /dev/null +++ b/lib/commands/command-base.ts @@ -0,0 +1,34 @@ +export abstract class ValidatePlatformCommandBase { + constructor(protected $options: IOptions, + protected $platformsData: IPlatformsData, + protected $platformService: IPlatformService, + protected $projectData: IProjectData) { } + + abstract allowedParameters: ICommandParameter[]; + abstract execute(args: string[]): Promise; + + public async canExecuteCommandBase(platform: string, options?: ICanExecuteCommandOptions): Promise { + options = options || {}; + const validatePlatformOutput = await this.validatePlatformBase(platform, options.notConfiguredEnvOptions); + const canExecute = this.canExecuteCommand(validatePlatformOutput); + let result = { canExecute, suppressCommandHelp: !canExecute }; + + if (canExecute && options.validateOptions) { + const validateOptionsOutput = await this.$platformService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, platform); + result = { canExecute: validateOptionsOutput, suppressCommandHelp: false }; + } + + return result; + } + + private async validatePlatformBase(platform: string, notConfiguredEnvOptions: INotConfiguredEnvOptions): Promise { + const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); + const platformProjectService = platformData.platformProjectService; + const result = await platformProjectService.validate(this.$projectData, this.$options, notConfiguredEnvOptions); + return result; + } + + private canExecuteCommand(validatePlatformOutput: IValidatePlatformOutput): boolean { + return validatePlatformOutput && validatePlatformOutput.checkEnvironmentRequirementsOutput && validatePlatformOutput.checkEnvironmentRequirementsOutput.canExecute; + } +} diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index 6a239677f9..e0421ad2a4 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -2,26 +2,35 @@ import { CONNECTED_STATUS } from "../common/constants"; import { isInteractive } from "../common/helpers"; import { cache } from "../common/decorators"; import { DebugCommandErrors } from "../constants"; +import { ValidatePlatformCommandBase } from "./command-base"; -export class DebugPlatformCommand implements ICommand { +export class DebugPlatformCommand extends ValidatePlatformCommandBase implements ICommand { public allowedParameters: ICommandParameter[] = []; constructor(private platform: string, private $debugService: IDebugService, protected $devicesService: Mobile.IDevicesService, - protected $platformService: IPlatformService, - protected $projectData: IProjectData, - protected $options: IOptions, - protected $platformsData: IPlatformsData, + $platformService: IPlatformService, + $projectData: IProjectData, + $options: IOptions, + $platformsData: IPlatformsData, protected $logger: ILogger, protected $errors: IErrors, private $debugDataService: IDebugDataService, private $liveSyncService: IDebugLiveSyncService, private $prompter: IPrompter, private $liveSyncCommandHelper: ILiveSyncCommandHelper) { + super($options, $platformsData, $platformService, $projectData); } public async execute(args: string[]): Promise { + await this.$devicesService.initialize({ + platform: this.platform, + deviceId: this.$options.device, + emulator: this.$options.emulator, + skipDeviceDetectionInterval: true + }); + const debugOptions = _.cloneDeep(this.$options.argv); const debugData = this.$debugDataService.createDebugData(this.$projectData, this.$options); @@ -99,7 +108,7 @@ export class DebugPlatformCommand implements ICommand { this.$errors.failWithoutHelp(DebugCommandErrors.NO_DEVICES_EMULATORS_FOUND_FOR_OPTIONS); } - public async canExecute(args: string[]): Promise { + public async canExecute(args: string[]): Promise { if (!this.$platformService.isPlatformSupportedForOS(this.platform, this.$projectData)) { this.$errors.fail(`Applications for platform ${this.platform} can not be built on this OS`); } @@ -108,18 +117,8 @@ export class DebugPlatformCommand implements ICommand { this.$errors.fail("--release flag is not applicable to this command"); } - const platformData = this.$platformsData.getPlatformData(this.platform, this.$projectData); - const platformProjectService = platformData.platformProjectService; - await platformProjectService.validate(this.$projectData); - - await this.$devicesService.initialize({ - platform: this.platform, - deviceId: this.$options.device, - emulator: this.$options.emulator, - skipDeviceDetectionInterval: true - }); - - return true; + const result = await super.canExecuteCommandBase(this.platform, { validateOptions: true, notConfiguredEnvOptions: { hideCloudBuildOption: true }}); + return result; } } @@ -138,7 +137,6 @@ export class DebugIOSCommand implements ICommand { private $options: IOptions, private $injector: IInjector, private $projectData: IProjectData, - private $platformsData: IPlatformsData, $iosDeviceOperations: IIOSDeviceOperations, $iOSSimulatorLogProvider: Mobile.IiOSSimulatorLogProvider) { this.$projectData.initializeProjectData(); @@ -154,7 +152,7 @@ export class DebugIOSCommand implements ICommand { return this.debugPlatformCommand.execute(args); } - public async canExecute(args: string[]): Promise { + public async canExecute(args: string[]): Promise { if (!this.$platformService.isPlatformSupportedForOS(this.$devicePlatformsConstants.iOS, this.$projectData)) { this.$errors.fail(`Applications for platform ${this.$devicePlatformsConstants.iOS} can not be built on this OS`); } @@ -164,7 +162,8 @@ export class DebugIOSCommand implements ICommand { this.$errors.fail(`Timeout option specifies the seconds NativeScript CLI will wait to find the inspector socket port from device's logs. Must be a number.`); } - return await this.debugPlatformCommand.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, this.$platformsData.availablePlatforms.iOS); + const result = await this.debugPlatformCommand.canExecute(args); + return result; } private isValidTimeoutOption() { @@ -200,19 +199,17 @@ export class DebugAndroidCommand implements ICommand { constructor(protected $errors: IErrors, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - private $platformService: IPlatformService, - private $options: IOptions, private $injector: IInjector, - private $projectData: IProjectData, - private $platformsData: IPlatformsData) { - this.$projectData.initializeProjectData(); + private $projectData: IProjectData) { + this.$projectData.initializeProjectData(); } public execute(args: string[]): Promise { return this.debugPlatformCommand.execute(args); } - public async canExecute(args: string[]): Promise { - return await this.debugPlatformCommand.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, this.$platformsData.availablePlatforms.Android); + public async canExecute(args: string[]): Promise { + const result = await this.debugPlatformCommand.canExecute(args); + return result; } public platform = this.$devicePlatformsConstants.Android; diff --git a/lib/commands/deploy.ts b/lib/commands/deploy.ts index 2a044b89ed..4d23e661aa 100644 --- a/lib/commands/deploy.ts +++ b/lib/commands/deploy.ts @@ -1,18 +1,20 @@ import { ANDROID_RELEASE_BUILD_ERROR_MESSAGE } from "../constants"; +import { ValidatePlatformCommandBase } from "./command-base"; -export class DeployOnDeviceCommand implements ICommand { +export class DeployOnDeviceCommand extends ValidatePlatformCommandBase implements ICommand { public allowedParameters: ICommandParameter[] = []; - constructor(private $platformService: IPlatformService, + constructor($platformService: IPlatformService, private $platformCommandParameter: ICommandParameter, - private $options: IOptions, - private $projectData: IProjectData, + $options: IOptions, + $projectData: IProjectData, private $deployCommandHelper: IDeployCommandHelper, private $errors: IErrors, private $mobileHelper: Mobile.IMobileHelper, - private $platformsData: IPlatformsData, + $platformsData: IPlatformsData, private $bundleValidatorHelper: IBundleValidatorHelper) { - this.$projectData.initializeProjectData(); + super($options, $platformsData, $platformService, $projectData); + this.$projectData.initializeProjectData(); } public async execute(args: string[]): Promise { @@ -21,7 +23,7 @@ export class DeployOnDeviceCommand implements ICommand { return this.$platformService.deployPlatform(deployPlatformInfo); } - public async canExecute(args: string[]): Promise { + public async canExecute(args: string[]): Promise { this.$bundleValidatorHelper.validate(); if (!args || !args.length || args.length > 1) { return false; @@ -35,11 +37,8 @@ export class DeployOnDeviceCommand implements ICommand { this.$errors.fail(ANDROID_RELEASE_BUILD_ERROR_MESSAGE); } - const platformData = this.$platformsData.getPlatformData(args[0], this.$projectData); - const platformProjectService = platformData.platformProjectService; - await platformProjectService.validate(this.$projectData); - - return this.$platformService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, args[0]); + const result = await super.canExecuteCommandBase(args[0], { validateOptions: true }); + return result; } } diff --git a/lib/commands/install.ts b/lib/commands/install.ts index 69be170ea7..751b2ecfd6 100644 --- a/lib/commands/install.ts +++ b/lib/commands/install.ts @@ -32,7 +32,7 @@ export class InstallCommand implements ICommand { if (frameworkPackageData && frameworkPackageData.version) { try { const platformProjectService = platformData.platformProjectService; - await platformProjectService.validate(this.$projectData); + await platformProjectService.validate(this.$projectData, this.$options); await this.$platformService.addPlatforms([`${platform}@${frameworkPackageData.version}`], this.$options.platformTemplate, this.$projectData, this.$options, this.$options.frameworkPath); } catch (err) { diff --git a/lib/commands/platform-clean.ts b/lib/commands/platform-clean.ts index 7193a75ca1..d7e6a3a1c4 100644 --- a/lib/commands/platform-clean.ts +++ b/lib/commands/platform-clean.ts @@ -26,7 +26,12 @@ export class CleanCommand implements ICommand { this.$platformService.validatePlatformInstalled(platform, this.$projectData); const currentRuntimeVersion = this.$platformService.getCurrentPlatformVersion(platform, this.$projectData); - await this.$platformEnvironmentRequirements.checkEnvironmentRequirements(platform, this.$projectData.projectDir, currentRuntimeVersion); + await this.$platformEnvironmentRequirements.checkEnvironmentRequirements({ + platform, + projectDir: this.$projectData.projectDir, + runtimeVersion: currentRuntimeVersion, + options: this.$options + }); } return true; diff --git a/lib/commands/prepare.ts b/lib/commands/prepare.ts index a9ffd70f51..114e42da47 100644 --- a/lib/commands/prepare.ts +++ b/lib/commands/prepare.ts @@ -1,12 +1,15 @@ -export class PrepareCommand implements ICommand { +import { ValidatePlatformCommandBase } from "./command-base"; + +export class PrepareCommand extends ValidatePlatformCommandBase implements ICommand { public allowedParameters = [this.$platformCommandParameter]; - constructor(private $options: IOptions, - private $platformService: IPlatformService, - private $projectData: IProjectData, + constructor($options: IOptions, + $platformService: IPlatformService, + $projectData: IProjectData, private $platformCommandParameter: ICommandParameter, - private $platformsData: IPlatformsData) { - this.$projectData.initializeProjectData(); + $platformsData: IPlatformsData) { + super($options, $platformsData, $platformService, $projectData); + this.$projectData.initializeProjectData(); } public async execute(args: string[]): Promise { @@ -23,16 +26,15 @@ export class PrepareCommand implements ICommand { await this.$platformService.preparePlatform(platformInfo); } - public async canExecute(args: string[]): Promise { + public async canExecute(args: string[]): Promise { const platform = args[0]; const result = await this.$platformCommandParameter.validate(platform) && await this.$platformService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, platform); - if (result) { - const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); - const platformProjectService = platformData.platformProjectService; - await platformProjectService.validate(this.$projectData); + if (!result) { + return false; } - return result; + const canExecuteOutput = await super.canExecuteCommandBase(platform); + return canExecuteOutput; } } diff --git a/lib/commands/preview.ts b/lib/commands/preview.ts new file mode 100644 index 0000000000..ed2848c621 --- /dev/null +++ b/lib/commands/preview.ts @@ -0,0 +1,32 @@ +export class PreviewCommand implements ICommand { + public allowedParameters: ICommandParameter[] = []; + + constructor(private $liveSyncService: ILiveSyncService, + private $projectData: IProjectData, + private $options: IOptions, + private $playgroundQrCodeGenerator: IPlaygroundQrCodeGenerator, + private $previewCommandHelper: IPreviewCommandHelper) { } + + public async execute(args: string[]): Promise { + this.$previewCommandHelper.run(); + + await this.$liveSyncService.liveSync([], { + syncToPreviewApp: true, + projectDir: this.$projectData.projectDir, + skipWatcher: !this.$options.watch, + watchAllFiles: this.$options.syncAllFiles, + clean: this.$options.clean, + bundle: !!this.$options.bundle, + release: this.$options.release, + env: this.$options.env, + timeout: this.$options.timeout + }); + + await this.$playgroundQrCodeGenerator.generateQrCodeForCurrentApp(); + } + + public async canExecute(args: string[]): Promise { + return true; + } +} +$injector.registerCommand("preview", PreviewCommand); diff --git a/lib/commands/run.ts b/lib/commands/run.ts index b10bd15662..9877ff9740 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -3,6 +3,7 @@ import { ANDROID_RELEASE_BUILD_ERROR_MESSAGE } from "../constants"; import { cache } from "../common/decorators"; export class RunCommandBase implements ICommand { + private liveSyncCommandHelperAdditionalOptions: ILiveSyncCommandHelperAdditionalOptions = {}; public platform: string; constructor(private $projectData: IProjectData, @@ -13,7 +14,7 @@ export class RunCommandBase implements ICommand { public allowedParameters: ICommandParameter[] = []; public async execute(args: string[]): Promise { - return this.$liveSyncCommandHelper.executeCommandLiveSync(this.platform); + return this.$liveSyncCommandHelper.executeCommandLiveSync(this.platform, this.liveSyncCommandHelperAdditionalOptions); } public async canExecute(args: string[]): Promise { @@ -28,7 +29,12 @@ export class RunCommandBase implements ICommand { this.platform = this.$devicePlatformsConstants.Android; } - await this.$liveSyncCommandHelper.validatePlatform(this.platform); + const validatePlatformOutput = await this.$liveSyncCommandHelper.validatePlatform(this.platform); + + if (this.platform && validatePlatformOutput && validatePlatformOutput[this.platform.toLowerCase()]) { + const checkEnvironmentRequirementsOutput = validatePlatformOutput[this.platform.toLowerCase()].checkEnvironmentRequirementsOutput; + this.liveSyncCommandHelperAdditionalOptions.syncToPreviewApp = checkEnvironmentRequirementsOutput && checkEnvironmentRequirementsOutput.selectedOption === "Sync to Playground"; + } return true; } @@ -68,7 +74,8 @@ export class RunIosCommand implements ICommand { } public async canExecute(args: string[]): Promise { - return await this.runCommand.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, this.$platformsData.availablePlatforms.iOS); + const result = await this.runCommand.canExecute(args) && await this.$platformService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, this.$platformsData.availablePlatforms.iOS); + return result; } } @@ -110,6 +117,7 @@ export class RunAndroidCommand implements ICommand { if (this.$options.release && (!this.$options.keyStorePath || !this.$options.keyStorePassword || !this.$options.keyStoreAlias || !this.$options.keyStoreAliasPassword)) { this.$errors.fail(ANDROID_RELEASE_BUILD_ERROR_MESSAGE); } + return this.$platformService.validateOptions(this.$options.provision, this.$options.teamId, this.$projectData, this.$platformsData.availablePlatforms.Android); } } diff --git a/lib/commands/test.ts b/lib/commands/test.ts index 1fe1ea00e0..678de186bb 100644 --- a/lib/commands/test.ts +++ b/lib/commands/test.ts @@ -5,11 +5,13 @@ function RunTestCommandFactory(platform: string) { $options: IOptions, $testExecutionService: ITestExecutionService, $projectData: IProjectData, - $analyticsService: IAnalyticsService) { + $analyticsService: IAnalyticsService, + $platformEnvironmentRequirements: IPlatformEnvironmentRequirements) { $projectData.initializeProjectData(); $analyticsService.setShouldDispose($options.justlaunch || !$options.watch); const projectFilesConfig = helpers.getProjectFilesConfig({ isReleaseBuild: $options.release }); this.execute = (args: string[]): Promise => $testExecutionService.startTestRunner(platform, $projectData, projectFilesConfig); + this.canExecute = (args: string[]): Promise => canExecute({ $platformEnvironmentRequirements, $projectData, $options, platform }); this.allowedParameters = []; }; } @@ -18,14 +20,30 @@ $injector.registerCommand("dev-test|android", RunTestCommandFactory('android')); $injector.registerCommand("dev-test|ios", RunTestCommandFactory('iOS')); function RunKarmaTestCommandFactory(platform: string) { - return function RunKarmaTestCommand($options: IOptions, $testExecutionService: ITestExecutionService, $projectData: IProjectData, $analyticsService: IAnalyticsService) { + return function RunKarmaTestCommand($options: IOptions, $testExecutionService: ITestExecutionService, $projectData: IProjectData, $analyticsService: IAnalyticsService, $platformEnvironmentRequirements: IPlatformEnvironmentRequirements) { $projectData.initializeProjectData(); $analyticsService.setShouldDispose($options.justlaunch || !$options.watch); const projectFilesConfig = helpers.getProjectFilesConfig({ isReleaseBuild: $options.release }); this.execute = (args: string[]): Promise => $testExecutionService.startKarmaServer(platform, $projectData, projectFilesConfig); + this.canExecute = (args: string[]): Promise => canExecute({ $platformEnvironmentRequirements, $projectData, $options, platform }); this.allowedParameters = []; }; } +async function canExecute(input: { $platformEnvironmentRequirements: IPlatformEnvironmentRequirements, $projectData: IProjectData, $options: IOptions, platform: string }): Promise { + const { $platformEnvironmentRequirements, $projectData, $options, platform } = input; + const output = await $platformEnvironmentRequirements.checkEnvironmentRequirements({ + platform, + projectDir: $projectData.projectDir, + options: $options, + notConfiguredEnvOptions: { + hideSyncToPreviewAppOption: true, + hideCloudBuildOption: true + } + }); + + return output.canExecute; +} + $injector.registerCommand("test|android", RunKarmaTestCommandFactory('android')); $injector.registerCommand("test|ios", RunKarmaTestCommandFactory('iOS')); diff --git a/lib/commands/update-platform.ts b/lib/commands/update-platform.ts index d8422438e2..df40f4f8a5 100644 --- a/lib/commands/update-platform.ts +++ b/lib/commands/update-platform.ts @@ -25,16 +25,17 @@ export class UpdatePlatformCommand implements ICommand { for (const arg of args) { const [ platform, versionToBeInstalled ] = arg.split("@"); - const argsToCheckEnvironmentRequirements: string[] = [ platform ]; + const checkEnvironmentRequirementsInput: ICheckEnvironmentRequirementsInput = { platform, options: this.$options }; // If version is not specified, we know the command will install the latest compatible Android runtime. // The latest compatible Android runtime supports Java version, so we do not need to pass it here. // Passing projectDir to the nativescript-doctor validation will cause it to check the runtime from the current package.json // So in this case, where we do not want to validate the runtime, just do not pass both projectDir and runtimeVersion. if (versionToBeInstalled) { - argsToCheckEnvironmentRequirements.push(this.$projectData.projectDir, versionToBeInstalled); + checkEnvironmentRequirementsInput.projectDir = this.$projectData.projectDir; + checkEnvironmentRequirementsInput.runtimeVersion = versionToBeInstalled; } - await this.$platformEnvironmentRequirements.checkEnvironmentRequirements(...argsToCheckEnvironmentRequirements); + await this.$platformEnvironmentRequirements.checkEnvironmentRequirements(checkEnvironmentRequirementsInput); } return true; diff --git a/lib/commands/update.ts b/lib/commands/update.ts index d097f37125..8919fd2266 100644 --- a/lib/commands/update.ts +++ b/lib/commands/update.ts @@ -1,18 +1,20 @@ import * as path from "path"; import * as constants from "../constants"; +import { ValidatePlatformCommandBase } from "./command-base"; -export class UpdateCommand implements ICommand { +export class UpdateCommand extends ValidatePlatformCommandBase implements ICommand { public allowedParameters: ICommandParameter[] = []; - constructor(private $options: IOptions, - private $projectData: IProjectData, - private $platformService: IPlatformService, - private $platformsData: IPlatformsData, + constructor($options: IOptions, + $projectData: IProjectData, + $platformService: IPlatformService, + $platformsData: IPlatformsData, private $pluginsService: IPluginsService, private $projectDataService: IProjectDataService, private $fs: IFileSystem, private $logger: ILogger) { - this.$projectData.initializeProjectData(); + super($options, $platformsData, $platformService, $projectData); + this.$projectData.initializeProjectData(); } static readonly folders: string[] = [ @@ -46,16 +48,30 @@ export class UpdateCommand implements ICommand { } } - public async canExecute(args: string[]): Promise { + public async canExecute(args: string[]): Promise { const platforms = this.getPlatforms(); + let canExecute = true; for (const platform of platforms.packagePlatforms) { - const platformData = this.$platformsData.getPlatformData(platform, this.$projectData); - const platformProjectService = platformData.platformProjectService; - await platformProjectService.validate(this.$projectData); + const output = await super.canExecuteCommandBase(platform); + canExecute = canExecute && output.canExecute; + } + + let result = null; + + if (canExecute) { + result = { + canExecute: args.length < 2 && this.$projectData.projectDir !== "", + suppressCommandHelp: false + }; + } else { + result = { + canExecute: false, + suppressCommandHelp: true + }; } - return args.length < 2 && this.$projectData.projectDir !== ""; + return result; } private async executeCore(args: string[]): Promise { diff --git a/lib/common b/lib/common index 8f8a1b66f6..3e9e870c06 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 8f8a1b66f692f7fe1fe0355ff3d70ed2b18b13bf +Subproject commit 3e9e870c06461285d1a3f3f6b8163a972208d4b0 diff --git a/lib/config.ts b/lib/config.ts index 707ca1e3a2..0ca5323909 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -8,6 +8,8 @@ export class Configuration extends ConfigBase implements IConfiguration { // Use TYPESCRIPT_COMPILER_OPTIONS = {}; ANDROID_DEBUG_UI: string = null; USE_POD_SANDBOX: boolean = false; + UPLOAD_PLAYGROUND_FILES_ENDPOINT: string = null; + SHORTEN_URL_ENDPOINT: string = null; /*don't require logger and everything that has logger as dependency in config.js due to cyclic dependency*/ constructor(protected $fs: IFileSystem) { diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 0dfd16b01f..a00e2e4b67 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -382,6 +382,8 @@ interface IStaticConfig extends Config.IStaticConfig { } interface IConfiguration extends Config.IConfig { ANDROID_DEBUG_UI: string; USE_POD_SANDBOX: boolean; + UPLOAD_PLAYGROUND_FILES_ENDPOINT: string; + SHORTEN_URL_ENDPOINT: string; } interface IApplicationPackage { diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index f7380695b2..a2807ee1bf 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -142,7 +142,7 @@ interface IOptionalSkipWatcher { /** * Describes a LiveSync operation. */ -interface ILiveSyncInfo extends IProjectDir, IEnvOptions, IBundle, IRelease, IOptionalSkipWatcher, IHasUseHotModuleReloadOption { +interface ILiveSyncInfo extends IProjectDir, IEnvOptions, IBundle, IRelease, IOptionalSkipWatcher, IHasUseHotModuleReloadOption, IHasSyncToPreviewAppOption { /** * Defines if all project files should be watched for changes. In case it is not passed, only `app` dir of the project will be watched for changes. * In case it is set to true, the package.json of the project and node_modules directory will also be watched, so any change there will be transferred to device(s). @@ -161,6 +161,13 @@ interface ILiveSyncInfo extends IProjectDir, IEnvOptions, IBundle, IRelease, IOp timeout: string; } +interface IHasSyncToPreviewAppOption { + /** + * Defines if the livesync should be executed in preview app on device. + */ + syncToPreviewApp?: boolean; +} + interface IHasUseHotModuleReloadOption { /** * Defines if the hot module reload should be used. @@ -510,7 +517,7 @@ interface IDevicePathProvider { /** * Describes additional options, that can be passed to LiveSyncCommandHelper. */ -interface ILiveSyncCommandHelperAdditionalOptions extends IBuildPlatformAction, INativePrepare { +interface ILiveSyncCommandHelperAdditionalOptions extends IBuildPlatformAction, INativePrepare, IHasSyncToPreviewAppOption { /** * A map representing devices which have debugging enabled initially. */ @@ -540,7 +547,7 @@ interface ILiveSyncCommandHelper { * @param {string} platform The platform to be validated. * @return {Promise} */ - validatePlatform(platform: string): Promise; + validatePlatform(platform: string): Promise>; /** * Executes livesync operation. Meant to be called from within a command. diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index 23ba577239..c595163aab 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -352,7 +352,7 @@ interface IOptionalProjectChangesInfoComposition { } interface IPreparePlatformCoreInfo extends IPreparePlatformInfoBase, IOptionalProjectChangesInfoComposition { - platformSpecificData: IPlatformSpecificData + platformSpecificData: IPlatformSpecificData; } interface IPreparePlatformInfo extends IPreparePlatformInfoBase, IPlatformConfig, IPlatformTemplate, ISkipNativeCheckOptional { } @@ -369,7 +369,10 @@ interface IOptionalFilesToRemove { filesToRemove?: string[]; } -interface IPreparePlatformInfoBase extends IPlatform, IAppFilesUpdaterOptionsComposition, IProjectDataComposition, IEnvOptions, IOptionalFilesToSync, IOptionalFilesToRemove, IOptionalNativePrepareComposition { } +interface IPreparePlatformInfoBase extends IPlatform, IAppFilesUpdaterOptionsComposition, IProjectDataComposition, IEnvOptions, IOptionalFilesToSync, IOptionalFilesToRemove, IOptionalNativePrepareComposition { + skipCopyTnsModules?: boolean; + skipCopyAppResourcesFiles?: boolean; +} interface IOptionalNativePrepareComposition { nativePrepare?: INativePrepare; @@ -388,5 +391,18 @@ interface IUpdateAppOptions extends IOptionalFilesToSync, IOptionalFilesToRemove } interface IPlatformEnvironmentRequirements { - checkEnvironmentRequirements(platform?: string, projectDir?: string, runtimeVersion?: string): Promise; + checkEnvironmentRequirements(input: ICheckEnvironmentRequirementsInput): Promise; +} + +interface ICheckEnvironmentRequirementsInput { + platform?: string; + projectDir?: string; + runtimeVersion?: string; + options?: IOptions; + notConfiguredEnvOptions?: INotConfiguredEnvOptions; +} + +interface ICheckEnvironmentRequirementsOutput { + canExecute: boolean; + selectedOption: string; } diff --git a/lib/definitions/preview-app-livesync.d.ts b/lib/definitions/preview-app-livesync.d.ts new file mode 100644 index 0000000000..a725e8d160 --- /dev/null +++ b/lib/definitions/preview-app-livesync.d.ts @@ -0,0 +1,34 @@ +import { FilePayload, Device } from "nativescript-preview-sdk"; + +declare global { + interface IPreviewAppLiveSyncService { + initialize(): void; + initialSync(data: IPreviewAppLiveSyncData): Promise; + syncFiles(data: IPreviewAppLiveSyncData, filesToSync: string[]): Promise; + stopLiveSync(): Promise; + } + + interface IPreviewAppLiveSyncData extends IProjectDir, IAppFilesUpdaterOptionsComposition, IEnvOptions { } + + interface IPreviewSdkService extends NodeJS.EventEmitter { + qrCodeUrl: string; + connectedDevices: Device[]; + initialize(): void; + applyChanges(files: FilePayload[], platform: string): Promise; + stop(): void; + } + + interface IPreviewAppPluginsService { + comparePluginsOnDevice(device: Device): Promise; + } + + interface IPreviewCommandHelper { + run(): void; + } + + interface IPlaygroundQrCodeGenerator { + generateQrCodeForiOS(): Promise; + generateQrCodeForAndroid(): Promise; + generateQrCodeForCurrentApp(): Promise; + } +} \ No newline at end of file diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 9c66577db6..add168c549 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -319,7 +319,7 @@ interface ICleanNativeAppData extends IProjectDir, IPlatform { } interface IPlatformProjectService extends NodeJS.EventEmitter, IPlatformProjectServiceBase { getPlatformData(projectData: IProjectData): IPlatformData; - validate(projectData: IProjectData): Promise; + validate(projectData: IProjectData, options: IOptions, notConfiguredEnvOptions?: INotConfiguredEnvOptions): Promise; createProject(frameworkDir: string, frameworkVersion: string, projectData: IProjectData, config: ICreateProjectOptions): Promise; interpolateData(projectData: IProjectData, platformSpecificData: IPlatformSpecificData): Promise; interpolateConfigurationFile(projectData: IProjectData, platformSpecificData: IPlatformSpecificData): void; @@ -438,6 +438,10 @@ interface IPlatformProjectService extends NodeJS.EventEmitter, IPlatformProjectS checkIfPluginsNeedBuild(projectData: IProjectData): Promise>; } +interface IValidatePlatformOutput { + checkEnvironmentRequirementsOutput: ICheckEnvironmentRequirementsOutput; +} + interface ITestExecutionService { startTestRunner(platform: string, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): Promise; startKarmaServer(platform: string, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): Promise; diff --git a/lib/definitions/qr-code.d.ts b/lib/definitions/qr-code.d.ts new file mode 100644 index 0000000000..41fc7baca4 --- /dev/null +++ b/lib/definitions/qr-code.d.ts @@ -0,0 +1,3 @@ +interface IQrCodeTerminalService { + generate(url: string): void; +} \ No newline at end of file diff --git a/lib/helpers/livesync-command-helper.ts b/lib/helpers/livesync-command-helper.ts index 7ce40b06b2..25f3aa3e27 100644 --- a/lib/helpers/livesync-command-helper.ts +++ b/lib/helpers/livesync-command-helper.ts @@ -22,6 +22,10 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { } public async executeCommandLiveSync(platform?: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions) { + if (additionalOptions && additionalOptions.syncToPreviewApp) { + return; + } + if (!this.$options.syncAllFiles) { this.$logger.info("Skipping node_modules folder! Use the syncAllFiles option to sync files from this folder."); } @@ -31,7 +35,6 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { deviceId: this.$options.device, platform, emulator, - skipDeviceDetectionInterval: true, skipInferPlatform: !platform, sdk: this.$options.sdk }); @@ -39,11 +42,6 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { const devices = this.$devicesService.getDeviceInstances() .filter(d => !platform || d.deviceInfo.platform.toLowerCase() === platform.toLowerCase()); - const devicesPlatforms = _(devices).map(d => d.deviceInfo.platform).uniq().value(); - if (this.$options.bundle && devicesPlatforms.length > 1) { - this.$errors.failWithoutHelp("Bundling doesn't work with multiple platforms. Please specify platform to the run command."); - } - await this.executeLiveSyncOperation(devices, platform, additionalOptions); } @@ -123,15 +121,20 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { await this.$liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); } - public async validatePlatform(platform: string) { + public async validatePlatform(platform: string): Promise> { + const result: IDictionary = {}; + const availablePlatforms = this.getPlatformsForOperation(platform); for (const availablePlatform of availablePlatforms) { const platformData = this.$platformsData.getPlatformData(availablePlatform, this.$projectData); const platformProjectService = platformData.platformProjectService; - await platformProjectService.validate(this.$projectData); + const validateOutput = await platformProjectService.validate(this.$projectData, this.$options); + result[availablePlatform.toLowerCase()] = validateOutput; } this.$bundleValidatorHelper.validate(); + + return result; } private async runInReleaseMode(platform: string, additionalOptions?: ILiveSyncCommandHelperAdditionalOptions): Promise { diff --git a/lib/helpers/preview-command-helper.ts b/lib/helpers/preview-command-helper.ts new file mode 100644 index 0000000000..2110dd28f6 --- /dev/null +++ b/lib/helpers/preview-command-helper.ts @@ -0,0 +1,63 @@ +import * as readline from "readline"; +import { ReadStream } from "tty"; +import * as helpers from "../common/helpers"; + +export class PreviewCommandHelper implements IPreviewCommandHelper { + private ctrlcReader: readline.ReadLine; + + constructor(private $playgroundQrCodeGenerator: IPlaygroundQrCodeGenerator, + private $processService: IProcessService) { + this.$processService.attachToProcessExitSignals(this, () => { + this.stopWaitingForCommand(); + }); + } + + public run(): void { + this.startWaitingForCommand(); + } + + private startWaitingForCommand(): void { + if (helpers.isInteractive()) { + this.ctrlcReader = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + this.ctrlcReader.on("SIGINT", process.exit); + + (process.stdin).setRawMode(true); + process.stdin.resume(); + process.stdin.setEncoding("utf8"); + process.stdin.on("data", this.handleKeypress.bind(this)); + } + } + + private stopWaitingForCommand(): void { + if (helpers.isInteractive()) { + process.stdin.removeListener("data", this.handleKeypress); + (process.stdin).setRawMode(false); + process.stdin.resume(); + this.closeCtrlcReader(); + } + } + + private async handleKeypress(key: string): Promise { + switch (key) { + case "a": + await this.$playgroundQrCodeGenerator.generateQrCodeForAndroid(); + return; + case "i": + await this.$playgroundQrCodeGenerator.generateQrCodeForiOS(); + return; + case "c": + await this.$playgroundQrCodeGenerator.generateQrCodeForCurrentApp(); + return; + } + } + + private closeCtrlcReader() { + if (this.ctrlcReader) { + this.ctrlcReader.close(); + } + } +} +$injector.register("previewCommandHelper", PreviewCommandHelper); diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index 66a0222140..a336e71113 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -104,8 +104,8 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject return currentPlatformData && currentPlatformData[constants.VERSION_STRING]; } - public validateOptions(): Promise { - return Promise.resolve(true); + public async validateOptions(): Promise { + return true; } public getAppResourcesDestinationDirectoryPath(projectData: IProjectData): string { @@ -118,12 +118,22 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } } - public async validate(projectData: IProjectData): Promise { + public async validate(projectData: IProjectData, options: IOptions, notConfiguredEnvOptions?: INotConfiguredEnvOptions): Promise { this.validatePackageName(projectData.projectId); this.validateProjectName(projectData.projectName); - await this.$platformEnvironmentRequirements.checkEnvironmentRequirements(this.getPlatformData(projectData).normalizedPlatformName, projectData.projectDir); + const checkEnvironmentRequirementsOutput = await this.$platformEnvironmentRequirements.checkEnvironmentRequirements({ + platform: this.getPlatformData(projectData).normalizedPlatformName, + projectDir: projectData.projectDir, + options, + notConfiguredEnvOptions + }); + this.$androidToolsInfo.validateTargetSdk({ showWarningsAsErrors: true }); + + return { + checkEnvironmentRequirementsOutput + }; } public async validatePlugins(): Promise { /* */ } diff --git a/lib/services/doctor-service.ts b/lib/services/doctor-service.ts index 8b153e11f8..43feda6b68 100644 --- a/lib/services/doctor-service.ts +++ b/lib/services/doctor-service.ts @@ -17,7 +17,7 @@ class DoctorService implements IDoctorService { private $terminalSpinnerService: ITerminalSpinnerService, private $versionsService: IVersionsService) { } - public async printWarnings(configOptions?: { trackResult: boolean , projectDir?: string, runtimeVersion?: string }): Promise { + public async printWarnings(configOptions?: { trackResult: boolean , projectDir?: string, runtimeVersion?: string, options?: IOptions }): Promise { const infos = await this.$terminalSpinnerService.execute({ text: `Getting environment information ${EOL}` }, () => doctor.getInfos({ projectDir: configOptions && configOptions.projectDir, androidRuntimeVersion: configOptions && configOptions.runtimeVersion })); @@ -47,7 +47,12 @@ class DoctorService implements IDoctorService { this.$logger.error("Cannot get the latest versions information from npm. Please try again later."); } - await this.$injector.resolve("platformEnvironmentRequirements").checkEnvironmentRequirements(null, configOptions && configOptions.projectDir); + await this.$injector.resolve("platformEnvironmentRequirements").checkEnvironmentRequirements({ + platform: null, + projectDir: configOptions && configOptions.projectDir, + runtimeVersion: configOptions && configOptions.runtimeVersion, + options: configOptions && configOptions.options + }); } public async runSetupScript(): Promise { diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index b6b9d429fd..1e11cf10d2 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -133,17 +133,26 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ return path.join(this.getPlatformData(projectData).projectRoot, projectData.projectName, "Resources"); } - public async validate(projectData: IProjectData): Promise { + public async validate(projectData: IProjectData, options: IOptions, notConfiguredEnvOptions?: INotConfiguredEnvOptions): Promise { if (!this.$hostInfo.isDarwin) { return; } - await this.$platformEnvironmentRequirements.checkEnvironmentRequirements(this.getPlatformData(projectData).normalizedPlatformName, projectData.projectDir); + const checkEnvironmentRequirementsOutput = await this.$platformEnvironmentRequirements.checkEnvironmentRequirements({ + platform: this.getPlatformData(projectData).normalizedPlatformName, + projectDir: projectData.projectDir, + options, + notConfiguredEnvOptions + }); const xcodeBuildVersion = await this.getXcodeVersion(); if (helpers.versionCompare(xcodeBuildVersion, IOSProjectService.XCODEBUILD_MIN_VERSION) < 0) { this.$errors.fail("NativeScript can only run in Xcode version %s or greater", IOSProjectService.XCODEBUILD_MIN_VERSION); } + + return { + checkEnvironmentRequirementsOutput + }; } // TODO: Remove Promise, reason: readDirectory - unable until androidProjectService has async operations. diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index c498a7b9af..4b597136f8 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -37,13 +37,13 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi private $debugDataService: IDebugDataService, private $analyticsService: IAnalyticsService, private $usbLiveSyncService: DeprecatedUsbLiveSyncService, + private $previewAppLiveSyncService: IPreviewAppLiveSyncService, private $injector: IInjector) { super(); } public async liveSync(deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise { const projectData = this.$projectDataService.getProjectData(liveSyncData.projectDir); - await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData); await this.liveSyncOperation(deviceDescriptors, liveSyncData, projectData); } @@ -318,16 +318,24 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi @hook("liveSync") private async liveSyncOperation(deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo, projectData: IProjectData): Promise { - // In case liveSync is called for a second time for the same projectDir. - const isAlreadyLiveSyncing = this.liveSyncProcessesInfo[projectData.projectDir] && !this.liveSyncProcessesInfo[projectData.projectDir].isStopped; + let deviceDescriptorsForInitialSync: ILiveSyncDeviceInfo[] = []; - // 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. - const currentlyRunningDeviceDescriptors = this.getLiveSyncDeviceDescriptors(projectData.projectDir); - const deviceDescriptorsForInitialSync = isAlreadyLiveSyncing ? _.differenceBy(deviceDescriptors, currentlyRunningDeviceDescriptors, deviceDescriptorPrimaryKey) : deviceDescriptors; + if (liveSyncData.syncToPreviewApp) { + this.$previewAppLiveSyncService.initialize(); + } else { + await this.$pluginsService.ensureAllDependenciesAreInstalled(projectData); + // In case liveSync is called for a second time for the same projectDir. + const isAlreadyLiveSyncing = this.liveSyncProcessesInfo[projectData.projectDir] && !this.liveSyncProcessesInfo[projectData.projectDir].isStopped; + + // 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. + const currentlyRunningDeviceDescriptors = this.getLiveSyncDeviceDescriptors(projectData.projectDir); + deviceDescriptorsForInitialSync = isAlreadyLiveSyncing ? _.differenceBy(deviceDescriptors, currentlyRunningDeviceDescriptors, deviceDescriptorPrimaryKey) : deviceDescriptors; + } - this.setLiveSyncProcessInfo(liveSyncData.projectDir, deviceDescriptors); + this.setLiveSyncProcessInfo(liveSyncData, deviceDescriptors); - if (!liveSyncData.skipWatcher && this.liveSyncProcessesInfo[projectData.projectDir].deviceDescriptors.length) { + const shouldStartWatcher = !liveSyncData.skipWatcher && (liveSyncData.syncToPreviewApp || this.liveSyncProcessesInfo[projectData.projectDir].deviceDescriptors.length); + if (shouldStartWatcher) { // Should be set after prepare this.$usbLiveSyncService.isInitialized = true; await this.startWatcher(projectData, liveSyncData, deviceDescriptors); @@ -336,7 +344,8 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi await this.initialSync(projectData, liveSyncData, deviceDescriptorsForInitialSync); } - private setLiveSyncProcessInfo(projectDir: string, deviceDescriptors: ILiveSyncDeviceInfo[]): void { + private setLiveSyncProcessInfo(liveSyncData: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): void { + const { projectDir } = liveSyncData; this.liveSyncProcessesInfo[projectDir] = this.liveSyncProcessesInfo[projectDir] || Object.create(null); this.liveSyncProcessesInfo[projectDir].actionsChain = this.liveSyncProcessesInfo[projectDir].actionsChain || Promise.resolve(); this.liveSyncProcessesInfo[projectDir].currentSyncAction = this.liveSyncProcessesInfo[projectDir].actionsChain; @@ -448,6 +457,27 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi } private async initialSync(projectData: IProjectData, liveSyncData: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { + if (liveSyncData.syncToPreviewApp) { + await this.initialSyncToPreviewApp(projectData, liveSyncData); + } else { + await this.initialCableSync(projectData, liveSyncData, deviceDescriptors); + } + } + + private async initialSyncToPreviewApp(projectData: IProjectData, liveSyncData: ILiveSyncInfo) { + await this.addActionToChain(projectData.projectDir, async () => { + await this.$previewAppLiveSyncService.initialSync({ + appFilesUpdaterOptions: { + bundle: liveSyncData.bundle, + release: liveSyncData.release + }, + env: liveSyncData.env, + projectDir: projectData.projectDir + }); + }); + } + + private async initialCableSync(projectData: IProjectData, liveSyncData: ILiveSyncInfo, deviceDescriptors: ILiveSyncDeviceInfo[]): Promise { const preparedPlatforms: string[] = []; const rebuiltInformation: ILiveSyncBuildInfo[] = []; @@ -546,88 +576,104 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi currentWatcherInfo.watcher.close(); } - const filesToSync: string[] = []; + let filesToSync: string[] = []; + const filesToSyncMap: IDictionary = {}; let filesToRemove: string[] = []; let timeoutTimer: NodeJS.Timer; - const startSyncFilesTimeout = () => { + const startSyncFilesTimeout = (platform?: string) => { timeoutTimer = setTimeout(async () => { - // Push actions to the queue, do not start them simultaneously - await this.addActionToChain(projectData.projectDir, async () => { - if (filesToSync.length || filesToRemove.length) { - try { - const currentFilesToSync = _.cloneDeep(filesToSync); - filesToSync.splice(0, filesToSync.length); - - const currentFilesToRemove = _.cloneDeep(filesToRemove); - filesToRemove = []; - - const allModifiedFiles = [].concat(currentFilesToSync).concat(currentFilesToRemove); - - const preparedPlatforms: string[] = []; - const rebuiltInformation: ILiveSyncBuildInfo[] = []; - - const latestAppPackageInstalledSettings = this.getDefaultLatestAppPackageInstalledSettings(); - - await this.$devicesService.execute(async (device: Mobile.IDevice) => { - const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectData.projectDir]; - const deviceBuildInfoDescriptor = _.find(liveSyncProcessInfo.deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); - - const appInstalledOnDeviceResult = await this.ensureLatestAppPackageIsInstalledOnDevice({ - device, - preparedPlatforms, - rebuiltInformation, - projectData, - deviceBuildInfoDescriptor, - liveSyncData, - settings: latestAppPackageInstalledSettings, - modifiedFiles: allModifiedFiles, - filesToRemove: currentFilesToRemove, - filesToSync: currentFilesToSync, + if (liveSyncData.syncToPreviewApp) { + await this.addActionToChain(projectData.projectDir, async () => { + if (filesToSync.length || filesToRemove.length) { + await this.$previewAppLiveSyncService.syncFiles({ + appFilesUpdaterOptions: { bundle: liveSyncData.bundle, - release: liveSyncData.release, - env: liveSyncData.env, - skipModulesNativeCheck: !liveSyncData.watchAllFiles - }, { skipNativePrepare: deviceBuildInfoDescriptor.skipNativePrepare }); - - const service = this.getLiveSyncService(device.deviceInfo.platform); - const settings: ILiveSyncWatchInfo = { - projectData, - filesToRemove: currentFilesToRemove, - filesToSync: currentFilesToSync, - isReinstalled: appInstalledOnDeviceResult.appInstalled, - syncAllFiles: liveSyncData.watchAllFiles, - useHotModuleReload: liveSyncData.useHotModuleReload - }; - - const liveSyncResultInfo = await service.liveSyncWatchAction(device, settings); - await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath); - }, - (device: Mobile.IDevice) => { + release: liveSyncData.release + }, + env: liveSyncData.env, + projectDir: projectData.projectDir + }, filesToSync); + } + }); + } else { + // Push actions to the queue, do not start them simultaneously + await this.addActionToChain(projectData.projectDir, async () => { + if (filesToSync.length || filesToRemove.length) { + try { + const currentFilesToSync = _.cloneDeep(filesToSync); + filesToSync.splice(0, filesToSync.length); + + const currentFilesToRemove = _.cloneDeep(filesToRemove); + filesToRemove = []; + + const allModifiedFiles = [].concat(currentFilesToSync).concat(currentFilesToRemove); + + const preparedPlatforms: string[] = []; + const rebuiltInformation: ILiveSyncBuildInfo[] = []; + + const latestAppPackageInstalledSettings = this.getDefaultLatestAppPackageInstalledSettings(); + + await this.$devicesService.execute(async (device: Mobile.IDevice) => { const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectData.projectDir]; - return liveSyncProcessInfo && _.some(liveSyncProcessInfo.deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier); - } - ); - } catch (err) { - const allErrors = (err).allErrors; - - if (allErrors && _.isArray(allErrors)) { - for (const deviceError of allErrors) { - this.$logger.warn(`Unable to apply changes for device: ${deviceError.deviceIdentifier}. Error is: ${deviceError.message}.`); - - this.emit(LiveSyncEvents.liveSyncError, { - error: deviceError, - deviceIdentifier: deviceError.deviceIdentifier, - projectDir: projectData.projectDir, - applicationIdentifier: projectData.projectId - }); - - await this.stopLiveSync(projectData.projectDir, [deviceError.deviceIdentifier], { shouldAwaitAllActions: false }); + const deviceBuildInfoDescriptor = _.find(liveSyncProcessInfo.deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); + + const appInstalledOnDeviceResult = await this.ensureLatestAppPackageIsInstalledOnDevice({ + device, + preparedPlatforms, + rebuiltInformation, + projectData, + deviceBuildInfoDescriptor, + liveSyncData, + settings: latestAppPackageInstalledSettings, + modifiedFiles: allModifiedFiles, + filesToRemove: currentFilesToRemove, + filesToSync: currentFilesToSync, + bundle: liveSyncData.bundle, + release: liveSyncData.release, + env: liveSyncData.env, + skipModulesNativeCheck: !liveSyncData.watchAllFiles + }, { skipNativePrepare: deviceBuildInfoDescriptor.skipNativePrepare }); + + const service = this.getLiveSyncService(device.deviceInfo.platform); + const settings: ILiveSyncWatchInfo = { + projectData, + filesToRemove: currentFilesToRemove, + filesToSync: currentFilesToSync, + isReinstalled: appInstalledOnDeviceResult.appInstalled, + syncAllFiles: liveSyncData.watchAllFiles, + useHotModuleReload: liveSyncData.useHotModuleReload + }; + + const liveSyncResultInfo = await service.liveSyncWatchAction(device, settings); + await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath); + }, + (device: Mobile.IDevice) => { + const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectData.projectDir]; + return (!platform || platform.toLowerCase() === device.deviceInfo.platform.toLowerCase()) && liveSyncProcessInfo && _.some(liveSyncProcessInfo.deviceDescriptors, deviceDescriptor => deviceDescriptor.identifier === device.deviceInfo.identifier); + } + ); + } catch (err) { + const allErrors = (err).allErrors; + + if (allErrors && _.isArray(allErrors)) { + for (const deviceError of allErrors) { + this.$logger.warn(`Unable to apply changes for device: ${deviceError.deviceIdentifier}. Error is: ${deviceError.message}.`); + + this.emit(LiveSyncEvents.liveSyncError, { + error: deviceError, + deviceIdentifier: deviceError.deviceIdentifier, + projectDir: projectData.projectDir, + applicationIdentifier: projectData.projectId + }); + + await this.stopLiveSync(projectData.projectDir, [deviceError.deviceIdentifier], { shouldAwaitAllActions: false }); + } } } } - } - }); + }); + } }, 250); this.liveSyncProcessesInfo[liveSyncData.projectDir].timer = timeoutTimer; @@ -646,8 +692,18 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi platforms }, filesToSync, + filesToSyncMap, filesToRemove, - startSyncFilesTimeout: startSyncFilesTimeout.bind(this) + startSyncFilesTimeout: async (platform: string) => { + if (platform) { + filesToSync = filesToSyncMap[platform]; + await startSyncFilesTimeout(); + filesToSyncMap[platform] = []; + } else { + // This code is added for backwards compatibility with old versions of nativescript-dev-webpack plugin. + await startSyncFilesTimeout(); + } + } } }); @@ -683,6 +739,13 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi this.liveSyncProcessesInfo[liveSyncData.projectDir].timer = timeoutTimer; this.$processService.attachToProcessExitSignals(this, () => { + if (liveSyncData.syncToPreviewApp) { + // Do not await here, we are in process exit's handler. + /* tslint:disable:no-floating-promises */ + this.$previewAppLiveSyncService.stopLiveSync(); + /* tslint:enable:no-floating-promises */ + } + _.keys(this.liveSyncProcessesInfo).forEach(projectDir => { // Do not await here, we are in process exit's handler. /* tslint:disable:no-floating-promises */ diff --git a/lib/services/livesync/playground/preview-app-constants.ts b/lib/services/livesync/playground/preview-app-constants.ts new file mode 100644 index 0000000000..fd3d38d469 --- /dev/null +++ b/lib/services/livesync/playground/preview-app-constants.ts @@ -0,0 +1,20 @@ +export class PreviewSdkEventNames { + public static DEVICE_CONNECTED = "onDeviceConnected"; + public static CHANGE_EVENT_NAME = "change"; +} + +export class PubnubKeys { + public static PUBLISH_KEY = "pub-c-d7893276-cc78-4d18-8ab0-becba06e43de"; + public static SUBSCRIBE_KEY = "sub-c-3dad1ebe-aaa3-11e8-8027-363023237e0b"; +} + +export class PlaygroundStoreUrls { + public static GOOGLE_PLAY_URL = "https://play.google.com/store/apps/details?id=org.nativescript.play"; + public static APP_STORE_URL = "https://itunes.apple.com/us/app/nativescript-playground/id1263543946?mt=8&ls=1"; +} + +export class PluginComparisonMessages { + public static PLUGIN_NOT_INCLUDED_IN_PREVIEW_APP = "Plugin %s is not included in preview app on device %s and will not work."; + public static LOCAL_PLUGIN_WITH_DIFFERENCE_IN_MAJOR_VERSION = "Local plugin %s differs in major version from plugin in preview app. The local plugin has version %s and the plugin in preview app has version %s. Some features might not work as expected."; + public static LOCAL_PLUGIN_WITH_GREATHER_MINOR_VERSION = "Local plugin %s differs in minor version from plugin in preview app. The local plugin has version %s and the plugin in preview app has version %s. Some features might not work as expected."; +} diff --git a/lib/services/livesync/playground/preview-app-livesync-service.ts b/lib/services/livesync/playground/preview-app-livesync-service.ts new file mode 100644 index 0000000000..0d47b8089c --- /dev/null +++ b/lib/services/livesync/playground/preview-app-livesync-service.ts @@ -0,0 +1,166 @@ +import * as path from "path"; +import { FilePayload, Device } from "nativescript-preview-sdk"; +import { PreviewSdkEventNames } from "./preview-app-constants"; +import { APP_FOLDER_NAME, APP_RESOURCES_FOLDER_NAME, TNS_MODULES_FOLDER_NAME } from "../../../constants"; +const isTextOrBinary = require('istextorbinary'); + +export class PreviewAppLiveSyncService implements IPreviewAppLiveSyncService { + private excludedFileExtensions = [".ts", ".sass", ".scss", ".less"]; + private excludedFiles = [".DS_Store"]; + + constructor(private $fs: IFileSystem, + private $hooksService: IHooksService, + private $logger: ILogger, + private $platformService: IPlatformService, + private $platformsData: IPlatformsData, + private $projectDataService: IProjectDataService, + private $previewSdkService: IPreviewSdkService, + private $previewAppPluginsService: IPreviewAppPluginsService, + private $projectFilesManager: IProjectFilesManager, + private $projectFilesProvider: IProjectFilesProvider) { } + + public initialize() { + this.$previewSdkService.initialize(); + } + + public async initialSync(data: IPreviewAppLiveSyncData): Promise { + this.$previewSdkService.on(PreviewSdkEventNames.DEVICE_CONNECTED, async (device: Device) => { + this.$logger.trace("Found connected device", device); + const filesToSyncMap: IDictionary = {}; + let promise = Promise.resolve(); + const startSyncFilesTimeout = async (platform: string) => { + await promise + .then(async () => { + promise = this.syncFilesForPlatformSafe(data, platform, filesToSyncMap[platform]); + await promise; + }); + filesToSyncMap[platform] = []; + }; + await this.$hooksService.executeBeforeHooks("preview-sync", { + hookArgs: { + projectData: this.$projectDataService.getProjectData(data.projectDir), + config: { + env: data.env, + platform: device.platform, + appFilesUpdaterOptions: data.appFilesUpdaterOptions, + }, + filesToSyncMap, +   startSyncFilesTimeout: startSyncFilesTimeout.bind(this) + } +            }); + await this.$previewAppPluginsService.comparePluginsOnDevice(device); + await this.syncFilesForPlatformSafe(data, device.platform); + }); + } + + public async syncFiles(data: IPreviewAppLiveSyncData, files?: string[]): Promise { + this.showWarningsForNativeFiles(files); + + for (const device of this.$previewSdkService.connectedDevices) { + await this.$previewAppPluginsService.comparePluginsOnDevice(device); + } + + const platforms = _(this.$previewSdkService.connectedDevices) + .map(device => device.platform) + .uniq() + .value(); + + for (const platform of platforms) { + await this.syncFilesForPlatformSafe(data, platform, files); + } + } + + public async stopLiveSync(): Promise { + this.$previewSdkService.removeAllListeners(); + this.$previewSdkService.stop(); + } + + private async syncFilesForPlatformSafe(data: IPreviewAppLiveSyncData, platform: string, files?: string[]): Promise { + this.$logger.info(`Start syncing changes for platform ${platform}.`); + + try { + const { appFilesUpdaterOptions, env, projectDir } = data; + const projectData = this.$projectDataService.getProjectData(projectDir); + await this.preparePlatform(platform, appFilesUpdaterOptions, env, projectData); + + await this.applyChanges(projectData, platform, files); + + this.$logger.info(`Successfully synced changes for platform ${platform}.`); + } catch (err) { + this.$logger.warn(`Unable to apply changes for platform ${platform}. Error is: ${err}, ${JSON.stringify(err, null, 2)}.`); + } + } + + private async applyChanges(projectData: IProjectData, platform: string, files: string[]) { + const platformData = this.$platformsData.getPlatformData(platform, projectData); + const payloads = this.getFilePayloads(platformData, projectData, _(files).uniq().value()); + await this.$previewSdkService.applyChanges(payloads, platform); + } + + private getFilePayloads(platformData: IPlatformData, projectData: IProjectData, files?: string[]): FilePayload[] { + const appFolderPath = path.join(projectData.projectDir, APP_FOLDER_NAME); + const platformsAppFolderPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); + + if (files && files.length) { + files = files.map(file => path.join(platformsAppFolderPath, path.relative(appFolderPath, file))); + } else { + files = this.$projectFilesManager.getProjectFiles(platformsAppFolderPath); + } + + const filesToTransfer = files + .filter(file => file.indexOf(TNS_MODULES_FOLDER_NAME) === -1) + .filter(file => file.indexOf(APP_RESOURCES_FOLDER_NAME) === -1) + .filter(file => !_.includes(this.excludedFiles, path.basename(file))) + .filter(file => !_.includes(this.excludedFileExtensions, path.extname(file))); + + this.$logger.trace(`Transferring ${filesToTransfer.join("\n")}.`); + + const payloads = filesToTransfer + .map(file => { + const projectFileInfo = this.$projectFilesProvider.getProjectFileInfo(file, platformData.normalizedPlatformName, null); + const relativePath = path.relative(platformsAppFolderPath, file); + const filePayload: FilePayload = { + event: PreviewSdkEventNames.CHANGE_EVENT_NAME, + file: path.join(path.dirname(relativePath), projectFileInfo.onDeviceFileName), + binary: isTextOrBinary.isBinarySync(file), + fileContents: "" + }; + + if (filePayload.binary) { + const bitmap = this.$fs.readFile(file); + const base64 = Buffer.from(bitmap).toString('base64'); + filePayload.fileContents = base64; + } else { + filePayload.fileContents = this.$fs.readText(path.join(path.dirname(projectFileInfo.filePath), projectFileInfo.onDeviceFileName)); + } + + return filePayload; + }); + + return payloads; + } + + private async preparePlatform(platform: string, appFilesUpdaterOptions: IAppFilesUpdaterOptions, env: Object, projectData: IProjectData): Promise { + const nativePrepare = { skipNativePrepare: true }; + const config = {}; + const platformTemplate = null; + const prepareInfo = { + platform, + appFilesUpdaterOptions, + env, + projectData, + nativePrepare, + config, + platformTemplate, + skipCopyTnsModules: true, + skipCopyAppResourcesFiles: true + }; + await this.$platformService.preparePlatform(prepareInfo); + } + + private showWarningsForNativeFiles(files: string[]): void { + _.filter(files, file => file.indexOf(APP_RESOURCES_FOLDER_NAME) > -1) + .forEach(file => this.$logger.warn(`Unable to apply changes from ${APP_RESOURCES_FOLDER_NAME} folder. You need to build your application in order to make changes in ${APP_RESOURCES_FOLDER_NAME} folder.`)); + } +} +$injector.register("previewAppLiveSyncService", PreviewAppLiveSyncService); diff --git a/lib/services/livesync/playground/preview-app-plugins-service.ts b/lib/services/livesync/playground/preview-app-plugins-service.ts new file mode 100644 index 0000000000..7e5fa75cfa --- /dev/null +++ b/lib/services/livesync/playground/preview-app-plugins-service.ts @@ -0,0 +1,58 @@ +import * as path from "path"; +import * as semver from "semver"; +import * as util from "util"; +import { Device } from "nativescript-preview-sdk"; +import { PluginComparisonMessages } from "./preview-app-constants"; + +export class PreviewAppPluginsService implements IPreviewAppPluginsService { + constructor(private $fs: IFileSystem, + private $logger: ILogger, + private $projectData: IProjectData) { } + + public async comparePluginsOnDevice(device: Device): Promise { + const devicePlugins = this.getDevicePlugins(device); + const localPlugins = this.getLocalPlugins(); + + _.keys(localPlugins).forEach(localPlugin => { + const localPluginVersion = localPlugins[localPlugin]; + const devicePluginVersion = devicePlugins[localPlugin]; + + this.$logger.trace(`Comparing plugin ${localPlugin} with localPluginVersion ${localPluginVersion} and devicePluginVersion ${devicePluginVersion}`); + + if (devicePluginVersion) { + const localPluginVersionData = semver.coerce(localPluginVersion); + const devicePluginVersionData = semver.coerce(devicePluginVersion); + + if (localPluginVersionData.major !== devicePluginVersionData.major) { + this.$logger.warn(util.format(PluginComparisonMessages.LOCAL_PLUGIN_WITH_DIFFERENCE_IN_MAJOR_VERSION, localPlugin, localPluginVersion, devicePluginVersion)); + } + + if (localPluginVersionData.major === devicePluginVersionData.major && localPluginVersionData.minor > devicePluginVersionData.minor) { + this.$logger.warn(util.format(PluginComparisonMessages.LOCAL_PLUGIN_WITH_GREATHER_MINOR_VERSION, localPlugin, localPluginVersion, devicePluginVersion)); + } + } else { + this.$logger.warn(util.format(PluginComparisonMessages.PLUGIN_NOT_INCLUDED_IN_PREVIEW_APP, localPlugin, device.id)); + } + }); + } + + private getDevicePlugins(device: Device): IStringDictionary { + try { + return JSON.parse(device.plugins); + } catch (err) { + this.$logger.trace(`Error while parsing plugins from device ${device.id}. Error is ${err.message}`); + return {}; + } + } + + private getLocalPlugins(): IStringDictionary { + const projectFilePath = path.join(this.$projectData.projectDir, "package.json"); + try { + return this.$fs.readJson(projectFilePath).dependencies; + } catch (err) { + this.$logger.trace(`Error while parsing ${projectFilePath}. Error is ${err.message}`); + return {}; + } + } +} +$injector.register("previewAppPluginsService", PreviewAppPluginsService); diff --git a/lib/services/livesync/playground/preview-sdk-service.ts b/lib/services/livesync/playground/preview-sdk-service.ts new file mode 100644 index 0000000000..ee6ee3d310 --- /dev/null +++ b/lib/services/livesync/playground/preview-sdk-service.ts @@ -0,0 +1,100 @@ +import { FilePayload, MessagingService, Config, Device, DeviceConnectedMessage, SdkCallbacks, ConnectedDevices } from "nativescript-preview-sdk"; +import { EventEmitter } from "events"; +import { PreviewSdkEventNames, PubnubKeys } from "./preview-app-constants"; +const pako = require("pako"); + +export class PreviewSdkService extends EventEmitter implements IPreviewSdkService { + private messagingService: MessagingService = null; + private instanceId: string = null; + public connectedDevices: Device[] = []; + + constructor(private $errors: IErrors, + private $logger: ILogger, + private $httpClient: Server.IHttpClient, + private $config: IConfiguration) { + super(); + } + + public get qrCodeUrl(): string { + return `nsplay://boot?instanceId=${this.instanceId}&pKey=${PubnubKeys.PUBLISH_KEY}&sKey=${PubnubKeys.SUBSCRIBE_KEY}&template=play-ng`; + } + + public initialize(): void { + const initConfig = this.getInitConfig(); + this.messagingService = new MessagingService(); + this.instanceId = this.messagingService.initialize(initConfig); + } + + public applyChanges(files: FilePayload[], platform: string): Promise { + return new Promise((resolve, reject) => { + this.messagingService.applyChanges(this.instanceId, { files, platform }, err => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + } + + public stop(): void { + this.messagingService.stop(); + } + + private getInitConfig(): Config { + return { + pubnubPublishKey: PubnubKeys.PUBLISH_KEY, + pubnubSubscribeKey: PubnubKeys.SUBSCRIBE_KEY, + callbacks: this.getCallbacks(), + getInitialFiles: async () => { + return { + files: [] + }; + } + }; + } + + private getCallbacks(): SdkCallbacks { + return { + onLogSdkMessage: (log: string) => { + this.$logger.trace("Received onLogSdkMessage message: ", log); + }, + onConnectedDevicesChange: (connectedDevices: ConnectedDevices) => ({ }), + onLogMessage: (log: string, deviceName: string) => { + this.$logger.info(`LOG from device ${deviceName}: ${log}`); + }, + onRestartMessage: () => { + this.$logger.trace("Received onRestartMessage event."); + }, + onUncaughtErrorMessage: () => { + this.$errors.failWithoutHelp("Error while communicating with preview app."); + }, + onDeviceConnectedMessage: (deviceConnectedMessage: DeviceConnectedMessage) => ({ }), + onDeviceConnected: (device: Device) => { + this.emit(PreviewSdkEventNames.DEVICE_CONNECTED, device); + if (!_.includes(this.connectedDevices, device)) { + this.connectedDevices.push(device); + } + }, + onDevicesPresence: (devices: Device[]) => ({ }), + onSendingChange: (sending: boolean) => ({ }), + onBiggerFilesUpload: async (filesContent, callback) => { + const gzippedContent = Buffer.from(pako.gzip(filesContent)); + const playgroundUploadResponse = await this.$httpClient.httpRequest({ + url: this.$config.UPLOAD_PLAYGROUND_FILES_ENDPOINT, + method: "POST", + body: gzippedContent, + headers: { + "Content-Encoding": "gzip", + "Content-Type": "text/plain" + } + }); + + const responseBody = JSON.parse(playgroundUploadResponse.body); + const location = responseBody && responseBody.location; + callback(location, playgroundUploadResponse.error); + } + }; + } +} +$injector.register("previewSdkService", PreviewSdkService); diff --git a/lib/services/livesync/playground/qr-code-generator.ts b/lib/services/livesync/playground/qr-code-generator.ts new file mode 100644 index 0000000000..01c2fa6ca4 --- /dev/null +++ b/lib/services/livesync/playground/qr-code-generator.ts @@ -0,0 +1,56 @@ +import { PlaygroundStoreUrls } from "./preview-app-constants"; +import * as util from "util"; +const chalk = require("chalk"); + +export class PlaygroundQrCodeGenerator implements IPlaygroundQrCodeGenerator { + constructor(private $previewSdkService: IPreviewSdkService, + private $httpClient: Server.IHttpClient, + private $qrCodeTerminalService: IQrCodeTerminalService, + private $config: IConfiguration, + private $logger: ILogger) { + } + + public async generateQrCodeForiOS(): Promise { + await this.generateQrCode(PlaygroundStoreUrls.APP_STORE_URL); + } + + public async generateQrCodeForAndroid(): Promise { + await this.generateQrCode(PlaygroundStoreUrls.GOOGLE_PLAY_URL); + } + + public async generateQrCodeForCurrentApp(): Promise { + await this.generateQrCode(this.$previewSdkService.qrCodeUrl); + } + + private async generateQrCode(url: string): Promise { + await this.generateQrCodeCore(url); + this.printUsage(); + } + + private async generateQrCodeCore(url: string): Promise { + const shortenUrlEndpoint = util.format(this.$config.SHORTEN_URL_ENDPOINT, encodeURIComponent(url)); + try { + const response = await this.$httpClient.httpRequest(shortenUrlEndpoint); + const responseBody = JSON.parse(response.body); + url = responseBody.shortURL || url; + } catch (e) { + // use the longUrl + } + + this.$qrCodeTerminalService.generate(url); + } + + private printUsage(): void { + this.$logger.info(` +-> Press ${this.underlineBoldCyan("a")} to show the QR code of NativeScript Playground app for ${this.underlineBoldCyan("Android")} devices +-> Press ${this.underlineBoldCyan("i")} to show the QR code of NativeScript Playground app for ${this.underlineBoldCyan("iOS")} devices +-> Press ${this.underlineBoldCyan("c")} to display the QR code of the ${this.underlineBoldCyan("current application")}. + `); + } + + private underlineBoldCyan(str: string) { + const { bold, underline, cyan } = chalk; + return underline(bold(cyan(str))); + } +} +$injector.register("playgroundQrCodeGenerator", PlaygroundQrCodeGenerator); diff --git a/lib/services/platform-environment-requirements.ts b/lib/services/platform-environment-requirements.ts index b07e52a020..2e6d3e5a03 100644 --- a/lib/services/platform-environment-requirements.ts +++ b/lib/services/platform-environment-requirements.ts @@ -1,6 +1,7 @@ import { NATIVESCRIPT_CLOUD_EXTENSION_NAME, TrackActionNames } from "../constants"; import { isInteractive } from "../common/helpers"; import { EOL } from "os"; +import { cache } from "../common/decorators"; export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequirements { constructor(private $commandsService: ICommandsService, @@ -10,11 +11,23 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ private $nativeScriptCloudExtensionService: INativeScriptCloudExtensionService, private $prompter: IPrompter, private $staticConfig: IStaticConfig, - private $analyticsService: IAnalyticsService) { } + private $analyticsService: IAnalyticsService, + private $injector: IInjector) { } + + @cache() + private get $previewCommandHelper(): IPreviewCommandHelper { + return this.$injector.resolve("previewCommandHelper"); + } + + @cache() + private get $liveSyncService(): ILiveSyncService { + return this.$injector.resolve("liveSyncService"); + } public static CLOUD_SETUP_OPTION_NAME = "Configure for Cloud Builds"; public static LOCAL_SETUP_OPTION_NAME = "Configure for Local Builds"; public static TRY_CLOUD_OPERATION_OPTION_NAME = "Try Cloud Operation"; + public static SYNC_TO_PREVIEW_APP_OPTION_NAME = "Sync to Playground"; public static MANUALLY_SETUP_OPTION_NAME = "Skip Step and Configure Manually"; private static BOTH_CLOUD_SETUP_AND_LOCAL_SETUP_OPTION_NAME = "Configure for Both Local and Cloud Builds"; private static CHOOSE_OPTIONS_MESSAGE = "To continue, choose one of the following options: "; @@ -23,6 +36,8 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ private static MISSING_LOCAL_AND_CLOUD_SETUP_MESSAGE = `You are missing the ${NATIVESCRIPT_CLOUD_EXTENSION_NAME} extension and you will not be able to execute cloud builds. ${PlatformEnvironmentRequirements.MISSING_LOCAL_SETUP_MESSAGE} ${PlatformEnvironmentRequirements.CHOOSE_OPTIONS_MESSAGE} `; private static MISSING_LOCAL_BUT_CLOUD_SETUP_MESSAGE = `You have ${NATIVESCRIPT_CLOUD_EXTENSION_NAME} extension installed, so you can execute cloud builds, but ${_.lowerFirst(PlatformEnvironmentRequirements.MISSING_LOCAL_SETUP_MESSAGE)}`; private static RUN_TNS_SETUP_MESSAGE = 'Run $ tns setup command to run the setup script to try to automatically configure your environment for local builds.'; + private static SYNC_TO_PREVIEW_APP_MESSAGE = `Select "Sync to Playground" to enjoy NativeScript without any local setup. All you need is a couple of companion apps installed on your devices.`; + private static RUN_PREVIEW_COMMAND_MESSAGE = `Run $ tns preview command to enjoy NativeScript without any local setup.`; private cliCommandToCloudCommandName: IStringDictionary = { "build": "tns cloud build", @@ -30,13 +45,22 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ "deploy": "tns cloud deploy" }; - public async checkEnvironmentRequirements(platform?: string, projectDir?: string, runtimeVersion?: string): Promise { + public async checkEnvironmentRequirements(input: ICheckEnvironmentRequirementsInput): Promise { + const { platform, projectDir, runtimeVersion } = input; + const notConfiguredEnvOptions = input.notConfiguredEnvOptions || {}; + const options = input.options || { }; + + let selectedOption = null; + if (process.env.NS_SKIP_ENV_CHECK) { await this.$analyticsService.trackEventActionInGoogleAnalytics({ action: TrackActionNames.CheckEnvironmentRequirements, additionalData: "Skipped: NS_SKIP_ENV_CHECK is set" }); - return true; + return { + canExecute: true, + selectedOption + }; } const canExecute = await this.$doctorService.canExecuteLocalBuild(platform, projectDir, runtimeVersion); @@ -49,30 +73,24 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ this.fail(this.getNonInteractiveConsoleMessage(platform)); } - const infoMessage = this.getInteractiveConsoleMessage(platform); - this.$logger.info(infoMessage); + const infoMessage = this.getInteractiveConsoleMessage(notConfiguredEnvOptions); - const choices = this.$nativeScriptCloudExtensionService.isInstalled() ? [ - PlatformEnvironmentRequirements.TRY_CLOUD_OPERATION_OPTION_NAME, - PlatformEnvironmentRequirements.LOCAL_SETUP_OPTION_NAME, - PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME, - ] : [ - PlatformEnvironmentRequirements.CLOUD_SETUP_OPTION_NAME, - PlatformEnvironmentRequirements.LOCAL_SETUP_OPTION_NAME, - PlatformEnvironmentRequirements.BOTH_CLOUD_SETUP_AND_LOCAL_SETUP_OPTION_NAME, - PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME, - ]; + const choices = this.getChoices(notConfiguredEnvOptions); - const selectedOption = await this.promptForChoice({ infoMessage, choices }); + selectedOption = await this.promptForChoice({ infoMessage, choices }); await this.processCloudBuildsIfNeeded(selectedOption, platform); this.processManuallySetupIfNeeded(selectedOption, platform); + await this.processSyncToPreviewAppIfNeeded(selectedOption, projectDir, options); if (selectedOption === PlatformEnvironmentRequirements.LOCAL_SETUP_OPTION_NAME) { await this.$doctorService.runSetupScript(); if (await this.$doctorService.canExecuteLocalBuild(platform, projectDir, runtimeVersion)) { - return true; + return { + canExecute: true, + selectedOption + }; } if (this.$nativeScriptCloudExtensionService.isInstalled()) { @@ -103,7 +121,10 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ if (selectedOption === PlatformEnvironmentRequirements.BOTH_CLOUD_SETUP_AND_LOCAL_SETUP_OPTION_NAME) { await this.processBothCloudBuildsAndSetupScript(); if (await this.$doctorService.canExecuteLocalBuild(platform, projectDir, runtimeVersion)) { - return true; + return { + canExecute: true, + selectedOption + }; } this.processManuallySetup(platform); @@ -112,7 +133,10 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ this.processTryCloudSetupIfNeeded(selectedOption, platform); } - return true; + return { + canExecute, + selectedOption + }; } private async processCloudBuildsIfNeeded(selectedOption: string, platform?: string): Promise { @@ -131,7 +155,7 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ } private getCloudBuildsMessage(platform?: string): string { - const cloudCommandName = this.cliCommandToCloudCommandName[this.$commandsService.currentCommandData.commandName]; + const cloudCommandName = this.cliCommandToCloudCommandName[(this.$commandsService.currentCommandData || {}).commandName]; if (!cloudCommandName) { return `In order to test your application use the $ tns login command to log in with your account and then $ tns cloud build command to build your app in the cloud.`; } @@ -149,12 +173,33 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ } } - private processManuallySetupIfNeeded(selectedOption: string, platform?: string) { + private processManuallySetupIfNeeded(selectedOption: string, platform?: string) { if (selectedOption === PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME) { this.processManuallySetup(platform); } } + private async processSyncToPreviewAppIfNeeded(selectedOption: string, projectDir: string, options: IOptions) { + if (selectedOption === PlatformEnvironmentRequirements.SYNC_TO_PREVIEW_APP_OPTION_NAME) { + if (!projectDir) { + this.$errors.failWithoutHelp(`No project found. In order to sync to playground you need to go to project directory or specify --path option.`); + } + + this.$previewCommandHelper.run(); + await this.$liveSyncService.liveSync([], { + syncToPreviewApp: true, + projectDir, + skipWatcher: !options.watch, + watchAllFiles: options.syncAllFiles, + clean: options.clean, + bundle: !!options.bundle, + release: options.release, + env: options.env, + timeout: options.timeout + }); + } + } + private processManuallySetup(platform?: string): void { this.fail(`To be able to ${platform ? `build for ${platform}` : 'build'}, verify that your environment is configured according to the system requirements described at ${this.$staticConfig.SYS_REQUIREMENTS_LINK}. In case you have any questions, you can check our forum: 'http://forum.nativescript.org' and our public Slack channel: 'https://nativescriptcommunity.slack.com/'.`); } @@ -177,32 +222,42 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ return this.$nativeScriptCloudExtensionService.isInstalled() ? this.buildMultilineMessage([ `${PlatformEnvironmentRequirements.MISSING_LOCAL_SETUP_MESSAGE} ${PlatformEnvironmentRequirements.CHOOSE_OPTIONS_MESSAGE}`, + PlatformEnvironmentRequirements.RUN_PREVIEW_COMMAND_MESSAGE, PlatformEnvironmentRequirements.RUN_TNS_SETUP_MESSAGE, this.getCloudBuildsMessage(platform), this.getEnvVerificationMessage() ]) : this.buildMultilineMessage([ PlatformEnvironmentRequirements.MISSING_LOCAL_AND_CLOUD_SETUP_MESSAGE, + PlatformEnvironmentRequirements.RUN_PREVIEW_COMMAND_MESSAGE, PlatformEnvironmentRequirements.RUN_TNS_SETUP_MESSAGE, `Run $ tns cloud setup command to install the ${NATIVESCRIPT_CLOUD_EXTENSION_NAME} extension to configure your environment for cloud builds.`, this.getEnvVerificationMessage() ]); } - private getInteractiveConsoleMessage(platform: string) { - return this.$nativeScriptCloudExtensionService.isInstalled() ? - this.buildMultilineMessage([ - `${PlatformEnvironmentRequirements.MISSING_LOCAL_BUT_CLOUD_SETUP_MESSAGE} ${PlatformEnvironmentRequirements.CHOOSE_OPTIONS_MESSAGE}`, - `Select "Configure for Local Builds" to run the setup script and automatically configure your environment for local builds.`, - `Select "Skip Step and Configure Manually" to disregard this option and install any required components manually.` - ]) : - this.buildMultilineMessage([ - PlatformEnvironmentRequirements.MISSING_LOCAL_AND_CLOUD_SETUP_MESSAGE, - `Select "Configure for Cloud Builds" to install the ${NATIVESCRIPT_CLOUD_EXTENSION_NAME} extension and automatically configure your environment for cloud builds.`, - `Select "Configure for Local Builds" to run the setup script and automatically configure your environment for local builds.`, - `Select "Configure for Both Local and Cloud Builds" to automatically configure your environment for both options.`, - `Select "Configure for Both Local and Cloud Builds" to automatically configure your environment for both options.` - ]); + private getInteractiveConsoleMessage(options: INotConfiguredEnvOptions) { + const isNativeScriptCloudExtensionInstalled = this.$nativeScriptCloudExtensionService.isInstalled(); + const message = isNativeScriptCloudExtensionInstalled ? + `${PlatformEnvironmentRequirements.MISSING_LOCAL_BUT_CLOUD_SETUP_MESSAGE} ${PlatformEnvironmentRequirements.CHOOSE_OPTIONS_MESSAGE}` : + PlatformEnvironmentRequirements.MISSING_LOCAL_AND_CLOUD_SETUP_MESSAGE; + const choices = isNativeScriptCloudExtensionInstalled ? [ + `Select "Configure for Local Builds" to run the setup script and automatically configure your environment for local builds.`, + `Select "Skip Step and Configure Manually" to disregard this option and install any required components manually.` + ] : [ + `Select "Configure for Cloud Builds" to install the ${NATIVESCRIPT_CLOUD_EXTENSION_NAME} extension and automatically configure your environment for cloud builds.`, + `Select "Configure for Local Builds" to run the setup script and automatically configure your environment for local builds.`, + `Select "Configure for Both Local and Cloud Builds" to automatically configure your environment for both options.`, + `Select "Configure for Both Local and Cloud Builds" to automatically configure your environment for both options.` + ]; + + if (!options.hideSyncToPreviewAppOption) { + choices.unshift(PlatformEnvironmentRequirements.SYNC_TO_PREVIEW_APP_MESSAGE); + } + + const lines = [message].concat(choices); + const result = this.buildMultilineMessage(lines); + return result; } private async promptForChoice(opts: { infoMessage: string, choices: string[], }): Promise { @@ -230,5 +285,30 @@ export class PlatformEnvironmentRequirements implements IPlatformEnvironmentRequ private buildMultilineMessage(parts: string[]): string { return parts.join(EOL); } + + private getChoices(options: INotConfiguredEnvOptions): string[] { + const choices: string[] = []; + if (this.$nativeScriptCloudExtensionService.isInstalled()) { + choices.push(...[PlatformEnvironmentRequirements.LOCAL_SETUP_OPTION_NAME, + PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME]); + + if (!options.hideCloudBuildOption) { + choices.unshift(PlatformEnvironmentRequirements.TRY_CLOUD_OPERATION_OPTION_NAME); + } + } else { + choices.push(...[ + PlatformEnvironmentRequirements.CLOUD_SETUP_OPTION_NAME, + PlatformEnvironmentRequirements.LOCAL_SETUP_OPTION_NAME, + PlatformEnvironmentRequirements.BOTH_CLOUD_SETUP_AND_LOCAL_SETUP_OPTION_NAME, + PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME, + ]); + } + + if (!options.hideSyncToPreviewAppOption) { + choices.unshift(PlatformEnvironmentRequirements.SYNC_TO_PREVIEW_APP_OPTION_NAME); + } + + return choices; + } } $injector.register("platformEnvironmentRequirements", PlatformEnvironmentRequirements); diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 9e03a279e8..984e5ed98b 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -226,6 +226,8 @@ export class PlatformService extends EventEmitter implements IPlatformService { platformInfo.filesToSync, platformInfo.filesToRemove, platformInfo.nativePrepare, + platformInfo.skipCopyAppResourcesFiles, + platformInfo.skipCopyTnsModules ); this.$projectChangesService.savePrepareInfo(platformInfo.platform, platformInfo.projectData); } else { @@ -240,7 +242,8 @@ export class PlatformService extends EventEmitter implements IPlatformService { platform = this.$mobileHelper.normalizePlatformName(platform); this.$logger.trace("Validate options for platform: " + platform); const platformData = this.$platformsData.getPlatformData(platform, projectData); - return await platformData.platformProjectService.validateOptions(projectData.projectId, provision, teamId); + const result = await platformData.platformProjectService.validateOptions(projectData.projectId, provision, teamId); + return result; } else { let valid = true; for (const availablePlatform in this.$platformsData.availablePlatforms) { @@ -297,7 +300,9 @@ export class PlatformService extends EventEmitter implements IPlatformService { changesInfo?: IProjectChangesInfo, filesToSync?: string[], filesToRemove?: string[], - nativePrepare?: INativePrepare): Promise { + nativePrepare?: INativePrepare, + skipCopyAppResourcesFiles?: boolean, + skipCopyTnsModules?: boolean): Promise { this.$logger.out("Preparing project..."); @@ -313,7 +318,9 @@ export class PlatformService extends EventEmitter implements IPlatformService { changesInfo, filesToSync, filesToRemove, - env + env, + skipCopyAppResourcesFiles, + skipCopyTnsModules }); if (!nativePrepare || !nativePrepare.skipNativePrepare) { @@ -738,7 +745,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { const prepareInfo = this.$projectChangesService.getPrepareInfo(platform, projectData); // In case when no platform is added and webpack plugin is started it produces files in platforms folder. In this case {N} CLI needs to add platform and keeps the already produced files from webpack if (appFilesUpdaterOptions.bundle && this.isPlatformInstalled(platform, projectData) && !this.$fs.exists(platformData.configurationFilePath) && (!prepareInfo || !prepareInfo.nativePlatformStatus || prepareInfo.nativePlatformStatus !== constants.NativePlatformStatus.alreadyPrepared)) { - const tmpDirectoryPath = path.join(projectData.projectDir, "platforms", "tmp"); + const tmpDirectoryPath = path.join(projectData.projectDir, "platforms", `tmp-${platform}`); this.$fs.deleteDirectory(tmpDirectoryPath); this.$fs.ensureDirectoryExists(tmpDirectoryPath); this.$fs.copyFile(path.join(platformData.appDestinationDirectoryPath, "*"), tmpDirectoryPath); diff --git a/lib/services/prepare-platform-js-service.ts b/lib/services/prepare-platform-js-service.ts index 603866928d..ba7bb98a87 100644 --- a/lib/services/prepare-platform-js-service.ts +++ b/lib/services/prepare-platform-js-service.ts @@ -36,6 +36,12 @@ export class PreparePlatformJSService extends PreparePlatformService implements public async preparePlatform(config: IPreparePlatformJSInfo): Promise { if (!config.changesInfo || config.changesInfo.appFilesChanged || config.changesInfo.changesRequirePrepare) { await this.copyAppFiles(config); + if (!config.skipCopyAppResourcesFiles) { + this.copyAppResourcesFiles(config); + } + } + + if (!config.skipCopyAppResourcesFiles && !this.$fs.exists(path.join(config.platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME, constants.APP_RESOURCES_FOLDER_NAME))) { this.copyAppResourcesFiles(config); } @@ -49,6 +55,12 @@ export class PreparePlatformJSService extends PreparePlatformService implements } if (!config.changesInfo || config.changesInfo.modulesChanged) { + if (!config.skipCopyTnsModules) { + await this.copyTnsModules(config.platform, config.platformData, config.projectData, config.appFilesUpdaterOptions, config.projectFilesConfig); + } + } + + if (!config.skipCopyTnsModules && !this.$fs.exists(path.join(config.platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME, constants.TNS_MODULES_FOLDER_NAME, constants.TNS_CORE_MODULES_NAME))) { await this.copyTnsModules(config.platform, config.platformData, config.projectData, config.appFilesUpdaterOptions, config.projectFilesConfig); } } diff --git a/lib/services/qr-code-terminal-service.ts b/lib/services/qr-code-terminal-service.ts new file mode 100644 index 0000000000..824ea0b784 --- /dev/null +++ b/lib/services/qr-code-terminal-service.ts @@ -0,0 +1,16 @@ +const qrcode = require("qrcode-terminal"); + +export class QrCodeTerminalService implements IQrCodeTerminalService { + constructor(private $logger: ILogger) { } + + public generate(url: string): void { + this.$logger.info(`Generating qrcode for url ${url}.`); + + try { + qrcode.generate(url); + } catch (err) { + this.$logger.info(`Failed to generate QR code for ${url}`, err); + } + } +} +$injector.register("qrCodeTerminalService", QrCodeTerminalService); diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 024a1bd252..be0c609787 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -103,6 +103,11 @@ "@types/node": "*" } }, + "@types/pubnub": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/pubnub/-/pubnub-4.0.2.tgz", + "integrity": "sha512-FiyzZib5HpE0INXuTLiK0tRJFXhE/qEXk3chKcvtKiYj9CyuEuEzTRsHLx4pv03Yq7wwt7e8oGxl5Fjanl+NTA==" + }, "@types/qr-image": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@types/qr-image/-/qr-image-3.2.0.tgz", @@ -128,6 +133,11 @@ "integrity": "sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==", "dev": true }, + "@types/shortid": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/shortid/-/shortid-0.0.29.tgz", + "integrity": "sha1-gJPuBBam4r8qpjOBCRFLP7/6Dps=" + }, "@types/sinon": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-4.0.0.tgz", @@ -166,11 +176,12 @@ } }, "@types/xml2js": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.2.tgz", - "integrity": "sha1-pLhLOHn/1HEJU/2Syr/emopOhFY=", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.3.tgz", + "integrity": "sha512-Pv2HGRE4gWLs31In7nsyXEH4uVVsd0HNV9i2dyASvtDIlOtSTr1eczPLDpdEuyv5LWH5LT20GIXwPjkshKWI1g==", "dev": true, "requires": { + "@types/events": "*", "@types/node": "*" } }, @@ -181,9 +192,9 @@ "dev": true }, "acorn": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.5.3.tgz", - "integrity": "sha512-jd5MkIUlbbmb07nXH0DT3y7rDVtkzDi4XZOUVWAer8ajmF/DTSSbl5oNFyDOl/OXA33Bl79+ypHhl2pN20VeOQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.2.tgz", + "integrity": "sha512-cJrKCNcr2kv8dlDnbw+JPUGjHZzo4myaxOLmpOX8a+rgX94YeTcTMv/LFJUSByRpc+i4GgVnnhLxvMu/2Y+rqw==", "dev": true }, "acorn-jsx": { @@ -204,17 +215,17 @@ } }, "agent-base": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.0.tgz", - "integrity": "sha512-c+R/U5X+2zz2+UCrCFv6odQzJdoqI+YecuhnAJLa1zYaMc13zPfwMwZrr91Pd1DYNo/yPRbiM4WVf9whgwFsIg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", + "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", "requires": { "es6-promisify": "^5.0.0" } }, "agentkeepalive": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.4.1.tgz", - "integrity": "sha512-MPIwsZU9PP9kOrZpyu2042kYA8Fdt/AedQYkYXucHgF9QoD9dXVp0ypuGnHXSR0hTstBxdt85Xkh4JolYfK5wg==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.1.tgz", + "integrity": "sha512-Cte/sTY9/XcygXjJ0q58v//SnEQ7ViWExKyJpLJlLqomDbQyMLh6Is4KuWJ/wmxzhiwkGRple7Gqv1zf6Syz5w==", "requires": { "humanize-ms": "^1.2.1" } @@ -343,9 +354,12 @@ "dev": true }, "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } }, "assert-plus": { "version": "1.0.0", @@ -358,6 +372,11 @@ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, + "ast-types": { + "version": "0.11.5", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.11.5.tgz", + "integrity": "sha512-oJjo+5e7/vEc2FBK8gUalV0pba4L3VdBIs2EKhOLHLcOd2FgQIVQN9xb0eZ9IjEWyAL7vq6fGJxOvVvdCHNyMw==" + }, "async": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/async/-/async-1.2.1.tgz", @@ -384,9 +403,9 @@ "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", - "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, "babel-code-frame": { "version": "6.26.0", @@ -425,18 +444,18 @@ "integrity": "sha1-o5mS1yNYSBGYK+XikLtqU9hnAPE=" }, "bcrypt-pbkdf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "optional": true, "requires": { "tweetnacl": "^0.14.3" } }, "big-integer": { - "version": "1.6.28", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.28.tgz", - "integrity": "sha512-OJT3rzgtsYca/5WmmEuFJDPMwROVh5SSjoEX9wIrpfbbWJ4KqRzShs8Cj6jWHaatBYAeWngBA+kmmrcHSklT1g==" + "version": "1.6.35", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.35.tgz", + "integrity": "sha512-jqLsX6dzmPHOhApAUyGwrpzqn3DXpdTqbOM6baPys7A423ys7IsTpcucDVGP0PmzxGsPYbW3xVOJ4SxAzI0vqQ==" }, "bignumber.js": { "version": "2.4.0", @@ -457,6 +476,11 @@ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=" }, + "binaryextensions": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.1.1.tgz", + "integrity": "sha512-XBaoWE9RW8pPdPQNibZsW2zh8TW6gcarXp1FZPwT8Uop8ScSNldJEWf2k9l3HeTqdrEwsOsFcq74RiJECW34yA==" + }, "bluebird": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", @@ -485,6 +509,12 @@ "type-is": "~1.6.10" }, "dependencies": { + "bytes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.2.0.tgz", + "integrity": "sha1-/TVGSkA/b5EXwt42Cez/nK4ABYg=", + "dev": true + }, "debug": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", @@ -494,6 +524,16 @@ "ms": "0.7.1" } }, + "http-errors": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", + "integrity": "sha1-GX4izevUGYWF6GlO9nhhl7ke2UI=", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "statuses": "1" + } + }, "iconv-lite": { "version": "0.4.13", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", @@ -511,6 +551,25 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-5.2.0.tgz", "integrity": "sha1-qfMRQq9GjLcrJbMBNrokVoNJFr4=", "dev": true + }, + "raw-body": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz", + "integrity": "sha1-rf6s4uT7MJgFgBTQjActzFl1h3Q=", + "dev": true, + "requires": { + "bytes": "2.4.0", + "iconv-lite": "0.4.13", + "unpipe": "1.0.0" + }, + "dependencies": { + "bytes": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", + "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=", + "dev": true + } + } } } }, @@ -562,15 +621,20 @@ "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", "dev": true }, + "btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==" + }, "buffer-equal": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", "integrity": "sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs=" }, "buffer-from": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz", - "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, "bufferpack": { "version": "0.0.6", @@ -598,22 +662,21 @@ "integrity": "sha1-90pm+m2P7/iLJyXgsrDPgwzfP4Y=" }, "bytes": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.2.0.tgz", - "integrity": "sha1-/TVGSkA/b5EXwt42Cez/nK4ABYg=", - "dev": true + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" }, "cacache": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.0.2.tgz", - "integrity": "sha512-hMiz7LN4w8sdfmKsvNs80ao/vf2JCGWWdpu95JyY90AJZRbZJmgE71dCefRiNf8OCqiZQDcUBfYiLlUNu4/j5A==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.2.0.tgz", + "integrity": "sha512-IFWl6lfK6wSeYCHUXh+N1lY72UDrpyrYQJNIVQf48paDuWbv5RbAtJYf/4gUQFObTCHZwdZ5sI8Iw7nqwP6nlQ==", "requires": { "bluebird": "^3.5.1", "chownr": "^1.0.1", "figgy-pudding": "^3.1.0", "glob": "^7.1.2", "graceful-fs": "^4.1.11", - "lru-cache": "^4.1.2", + "lru-cache": "^4.1.3", "mississippi": "^3.0.0", "mkdirp": "^0.5.1", "move-concurrently": "^1.0.1", @@ -859,11 +922,11 @@ } }, "color-convert": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", - "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "requires": { - "color-name": "^1.1.1" + "color-name": "1.1.3" } }, "color-name": { @@ -872,9 +935,9 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "color-string": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.2.tgz", - "integrity": "sha1-JuRYFLw8mny9Z1FkikFDRRSnc6k=", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", + "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", "requires": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" @@ -902,6 +965,11 @@ "graceful-readlink": ">= 1.0.0" } }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -924,6 +992,11 @@ "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", "dev": true }, + "cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==" + }, "copy-concurrently": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", @@ -987,12 +1060,6 @@ "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==", "dev": true - }, - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", - "dev": true } } }, @@ -1027,6 +1094,11 @@ "assert-plus": "^1.0.0" } }, + "data-uri-to-buffer": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz", + "integrity": "sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ==" + }, "date-format": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/date-format/-/date-format-0.0.0.tgz", @@ -1075,8 +1147,7 @@ "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" }, "defaults": { "version": "1.0.3", @@ -1086,6 +1157,23 @@ "clone": "^1.0.2" } }, + "degenerator": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-1.0.4.tgz", + "integrity": "sha1-/PSQo37OJmRk2cxDGrmMWBnO0JU=", + "requires": { + "ast-types": "0.x.x", + "escodegen": "1.x.x", + "esprima": "3.x.x" + }, + "dependencies": { + "esprima": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=" + } + } + }, "del": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", @@ -1109,8 +1197,7 @@ "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "dev": true + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" }, "detect-indent": { "version": "4.0.0", @@ -1159,14 +1246,20 @@ } }, "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "optional": true, "requires": { - "jsbn": "~0.1.0" + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" } }, + "editions": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/editions/-/editions-1.3.4.tgz", + "integrity": "sha512-gzao+mxnYDzIysXKMQi/+M1mjy/rjestjg6OPoYTtI+3Izp23oiGZitsl9lPDPiTGXbcSIk1iJWhliSaglxnUg==" + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -1187,9 +1280,9 @@ }, "dependencies": { "iconv-lite": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "requires": { "safer-buffer": ">= 2.1.2 < 3" } @@ -1210,9 +1303,9 @@ "integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=" }, "error-ex": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", - "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "requires": { "is-arrayish": "^0.2.1" }, @@ -1225,9 +1318,9 @@ } }, "es5-ext": { - "version": "0.10.42", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.42.tgz", - "integrity": "sha512-AJxO1rmPe1bDEfSR6TJ/FgMFYuTBhR5R57KW58iCkYACMyFbrkqVyzXSurYoScDGvgyMpk7uRF/lPUPPTmsRSA==", + "version": "0.10.46", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.46.tgz", + "integrity": "sha512-24XxRvJXNFwEMpJb3nOkiRJKRoupmjYmOPVlI65Qy2SrtxwOTB+g6ODjBKOtwEHbYrhWRty9xxOWLNdClT2djw==", "dev": true, "requires": { "es6-iterator": "~2.0.3", @@ -1321,39 +1414,27 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escodegen": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", - "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", - "dev": true, + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.0.tgz", + "integrity": "sha512-IeMV45ReixHS53K/OmfKAIztN/igDHzTJUhZM3k1jMhIZWjk45SMwAtBsEXiJp3vSPmTcu6CXn7mDvFHRN66fw==", "requires": { - "esprima": "^2.7.1", - "estraverse": "^1.9.1", + "esprima": "^3.1.3", + "estraverse": "^4.2.0", "esutils": "^2.0.2", "optionator": "^0.8.1", - "source-map": "~0.2.0" + "source-map": "~0.6.1" }, "dependencies": { "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "estraverse": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", - "dev": true + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=" }, "source-map": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", - "dev": true, - "optional": true, - "requires": { - "amdefine": ">=0.0.4" - } + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true } } }, @@ -1538,14 +1619,12 @@ "estraverse": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=" }, "esutils": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" }, "event-emitter": { "version": "0.3.5", @@ -1597,9 +1676,9 @@ } }, "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "extglob": { "version": "0.3.2", @@ -1627,8 +1706,7 @@ "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, "faye-websocket": { "version": "0.10.0", @@ -1640,9 +1718,9 @@ } }, "figgy-pudding": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.1.0.tgz", - "integrity": "sha512-Gi2vIue0ec6P/7LNpueGhLuvfF2ztuterl8YFBQn1yKgIS46noGxCbi+vviPdObNYtgUSh5FpHy5q0Cw9XhxKQ==" + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", + "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==" }, "figures": { "version": "1.7.0", @@ -1674,6 +1752,11 @@ "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=" }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, "filename-regex": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", @@ -1746,11 +1829,11 @@ } }, "for-each": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.2.tgz", - "integrity": "sha1-LEBFC5NI6X8oEyJZO6lnBLmr1NQ=", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", "requires": { - "is-function": "~1.0.0" + "is-callable": "^1.1.3" } }, "for-in": { @@ -1790,6 +1873,11 @@ "samsam": "1.x" } }, + "formidable": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", + "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==" + }, "from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", @@ -2372,6 +2460,38 @@ } } }, + "ftp": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz", + "integrity": "sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0=", + "requires": { + "readable-stream": "1.1.x", + "xregexp": "2.0.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, "gaze": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.0.tgz", @@ -2381,10 +2501,13 @@ } }, "generate-function": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", - "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=", - "dev": true + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "dev": true, + "requires": { + "is-property": "^1.0.2" + } }, "generate-object-property": { "version": "1.2.0", @@ -2401,9 +2524,9 @@ "integrity": "sha1-7RAEHy5KfxsKOEZtF6XD4n3x38E=" }, "get-caller-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", - "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" }, "get-func-name": { "version": "2.0.0", @@ -2422,6 +2545,19 @@ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" }, + "get-uri": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-2.0.2.tgz", + "integrity": "sha512-ZD325dMZOgerGqF/rF6vZXyFGTAay62svjQIT+X/oU2PtxYpFxvSkbsdi+oxIrsNxlZVd4y8wUDqkaExWTI/Cw==", + "requires": { + "data-uri-to-buffer": "1", + "debug": "2", + "extend": "3", + "file-uri-to-path": "1", + "ftp": "~0.3.10", + "readable-stream": "2" + } + }, "getobject": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", @@ -2437,9 +2573,9 @@ } }, "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -2452,7 +2588,7 @@ "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { "brace-expansion": "^1.1.7" } @@ -2506,20 +2642,13 @@ } }, "globule": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.0.tgz", - "integrity": "sha1-HcScaCLdnoovoAuiopUAboZkvQk=", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", + "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", "requires": { "glob": "~7.1.1", - "lodash": "~4.17.4", + "lodash": "~4.17.10", "minimatch": "~3.0.2" - }, - "dependencies": { - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" - } } }, "graceful-fs": { @@ -2590,9 +2719,9 @@ } }, "iconv-lite": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" @@ -2695,9 +2824,9 @@ } }, "grunt-known-options": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-1.1.0.tgz", - "integrity": "sha1-pCdO6zL6dl2lp6OxcSYXzjsUQUk=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-1.1.1.tgz", + "integrity": "sha512-cHwsLqoighpu7TuYj5RonnEuxGVFnztcUqTqp5rXFGYL4OuPFofwC4Ycg7n9fYwvK6F5WbYgeVOwph9Crs2fsQ==", "dev": true }, "grunt-legacy-log": { @@ -2710,14 +2839,6 @@ "grunt-legacy-log-utils": "~1.0.0", "hooker": "~0.2.3", "lodash": "~4.17.5" - }, - "dependencies": { - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", - "dev": true - } } }, "grunt-legacy-log-utils": { @@ -2777,6 +2898,15 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.3.0.tgz", "integrity": "sha1-79nEpuxT87BUEkKZFcPkgk5NJaQ=", "dev": true + }, + "which": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", + "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } } } }, @@ -2822,12 +2952,6 @@ "integrity": "sha1-8RLCn+paCZhTn8tqL9IUQ9KPBfc=", "dev": true }, - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", - "dev": true - }, "rimraf": { "version": "2.2.6", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.6.tgz", @@ -2915,9 +3039,9 @@ "dev": true }, "hosted-git-info": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", - "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==" + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==" }, "http-cache-semantics": { "version": "3.8.1", @@ -2925,13 +3049,14 @@ "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==" }, "http-errors": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", - "integrity": "sha1-GX4izevUGYWF6GlO9nhhl7ke2UI=", - "dev": true, + "version": "1.6.3", + "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "requires": { - "inherits": "~2.0.1", - "statuses": "1" + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" } }, "http-parser-js": { @@ -3007,9 +3132,9 @@ "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=" }, "ignore": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.8.tgz", - "integrity": "sha512-pUh+xUQQhQzevjRHHFqqcTy0/dP/kS9I8HSrUydhihjuD09W6ldVWFtIrwhXdUJHis3i2rZNqEHpZH/cbinFbg==", + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", "dev": true }, "ignore-walk": { @@ -3243,9 +3368,9 @@ "integrity": "sha1-3FiQdvZZ9BnCIgOaMzFvHHOH7/0=" }, "is-arrayish": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.1.tgz", - "integrity": "sha1-wt/DhquqDD4zxI2z/ocFnmkGXv0=" + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" }, "is-binary-path": { "version": "1.0.1", @@ -3268,6 +3393,11 @@ "builtin-modules": "^1.0.0" } }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==" + }, "is-dotfile": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", @@ -3328,9 +3458,9 @@ "dev": true }, "is-my-json-valid": { - "version": "2.17.2", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz", - "integrity": "sha512-IBhBslgngMQN8DDSppmgDv7RNrlFotuuDsKcrCP3+HbFaVivIBU7u9oiiErw8sH4ynx3+gOGQ3q2otkgiSi6kg==", + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.19.0.tgz", + "integrity": "sha512-mG0f/unGX1HZ5ep4uhRaPOS8EkAY8/j6mDRMJrutq4CqhoJWYp7qAlonIPy3TV7p3ju4TK9fo/PbnoksWmsp5Q==", "dev": true, "requires": { "generate-function": "^2.0.0", @@ -3455,6 +3585,33 @@ "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", "dev": true }, + "escodegen": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", + "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", + "dev": true, + "requires": { + "esprima": "^2.7.1", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.2.0" + }, + "dependencies": { + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + } + } + }, + "estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", + "dev": true + }, "glob": { "version": "5.0.15", "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", @@ -3480,6 +3637,16 @@ "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", "dev": true }, + "source-map": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "dev": true, + "optional": true, + "requires": { + "amdefine": ">=0.0.4" + } + }, "supports-color": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", @@ -3491,6 +3658,16 @@ } } }, + "istextorbinary": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-2.2.1.tgz", + "integrity": "sha512-TS+hoFl8Z5FAFMK38nhBkdLt44CclNRgDHWeMgsV8ko3nDlr/9UI2Sf839sW7enijf8oKsZYXRvM8g0it9Zmcw==", + "requires": { + "binaryextensions": "2", + "editions": "^1.3.3", + "textextensions": "2" + } + }, "jimp": { "version": "0.2.28", "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.2.28.tgz", @@ -3526,9 +3703,9 @@ "dev": true }, "js-yaml": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.11.0.tgz", - "integrity": "sha512-saJstZWv7oNeOyBh3+Dx1qWzhW0+e6/8eDzo7p5rDFqxntSztloLtuKu+Ejhtq82jsilwOIZYsCz+lIjthg1Hw==", + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", "dev": true, "requires": { "argparse": "^1.0.7", @@ -3536,9 +3713,9 @@ }, "dependencies": { "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true } } @@ -3614,9 +3791,9 @@ } }, "just-extend": { - "version": "1.1.27", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-1.1.27.tgz", - "integrity": "sha512-mJVp13Ix6gFo3SBAy9U/kL+oeZqzlYYYLQBwXVBlVzIsZwBqGREnOro24oC/8s8aox+rJhtZ2DiQof++IrkA+g==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-3.0.0.tgz", + "integrity": "sha512-Fu3T6pKBuxjWT/p4DkqGHFRsysc8OauWr4ZRTY9dIx07Y9O0RkoR5jcv28aeD1vuAwhm3nLkDurwLXoALp4DpQ==", "dev": true }, "kind-of": { @@ -3646,12 +3823,16 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, "requires": { "prelude-ls": "~1.1.2", "type-check": "~0.3.2" } }, + "lil-uuid": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/lil-uuid/-/lil-uuid-0.1.1.tgz", + "integrity": "sha1-+e3PI/AOQr9D8PhD2Y2LU/M0HxY=" + }, "livereload-js": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.3.0.tgz", @@ -3659,15 +3840,16 @@ "dev": true }, "load-bmfont": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.3.0.tgz", - "integrity": "sha1-u358cQ3mvK/LE8s7jIHgwBMey8k=", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.0.tgz", + "integrity": "sha512-kT63aTAlNhZARowaNYcY29Fn/QYkc52M3l6V1ifRcPewg2lvUZDAj7R6dXjOL9D0sict76op3T5+odumDSF81g==", "requires": { "buffer-equal": "0.0.1", "mime": "^1.3.4", "parse-bmfont-ascii": "^1.0.3", "parse-bmfont-binary": "^1.0.5", - "parse-bmfont-xml": "^1.1.0", + "parse-bmfont-xml": "^1.1.4", + "phin": "^2.9.1", "xhr": "^2.0.1", "xtend": "^4.0.0" } @@ -3810,9 +3992,9 @@ } }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "requires": { "has-flag": "^3.0.0" } @@ -3830,9 +4012,9 @@ } }, "lolex": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.0.tgz", - "integrity": "sha512-uJkH2e0BVfU5KOJUevbTOtpDduooSarH5PopO+LfM/vZf8Z9sJzODqKev804JYM2i++ktJfUmC1le4LwFQ1VMg==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.1.tgz", + "integrity": "sha512-Oo2Si3RMKV3+lV5MsSWplDQFoTClz/24S0MMHYcgGWWmFXr6TMlqcqk/l1GtH+d5wLBwNRiqGnwDRMirtFalJw==", "dev": true }, "longest": { @@ -3876,6 +4058,31 @@ "promise-retry": "^1.1.1", "socks-proxy-agent": "^4.0.0", "ssri": "^6.0.0" + }, + "dependencies": { + "smart-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.0.1.tgz", + "integrity": "sha512-RFqinRVJVcCAL9Uh1oVqE6FZkqsyLiVOYEZ20TqIOjuX7iFVJ+zsbs4RIghnw/pTs7mZvt8ZHhvm1ZUrR4fykg==" + }, + "socks": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.2.1.tgz", + "integrity": "sha512-0GabKw7n9mI46vcNrVfs0o6XzWzjVa3h6GaSo2UPxtWAROXUWavfJWh1M4PR5tnE0dcnQXZIDFP4yrAysLze/w==", + "requires": { + "ip": "^1.1.5", + "smart-buffer": "^4.0.1" + } + }, + "socks-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.1.tgz", + "integrity": "sha512-Kezx6/VBguXOsEe5oU3lXYyKMi4+gva72TwJ7pQY5JfqUx2nMk7NXA6z/mpNqIlfQjWYVfeuNvQjexiTaTn6Nw==", + "requires": { + "agent-base": "~4.2.0", + "socks": "~2.2.0" + } + } } }, "map-obj": { @@ -3984,6 +4191,11 @@ } } }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, "micromatch": { "version": "2.3.11", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", @@ -4010,16 +4222,16 @@ "integrity": "sha1-Ms2eXGRVO9WNGaVor0Uqz/BJgbE=" }, "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" + "version": "1.36.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz", + "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==" }, "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "version": "2.1.20", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz", + "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==", "requires": { - "mime-db": "~1.33.0" + "mime-db": "~1.36.0" } }, "mimic-fn": { @@ -4049,9 +4261,9 @@ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "minipass": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.3.tgz", - "integrity": "sha512-/jAn9/tEX4gnpyRATxgHEOV6xbcyxgT7iUnxo9Y3+OB0zX00TgKIv/2FZCf5brBbICcwbLqVv2ImjvWWrQMSYw==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.4.tgz", + "integrity": "sha512-mlouk1OHlaUE8Odt1drMtG1bAJA4ZA6B/ehysgV0LUIrDHdKgo1KorZq3pK0b/7Z7LJIQ12MNM6aC+Tn6lUZ5w==", "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -4196,11 +4408,16 @@ "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=" }, "nan": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", - "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.0.tgz", + "integrity": "sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw==", "optional": true }, + "nanoid": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-1.2.3.tgz", + "integrity": "sha512-BAnxAdaihzMoszwhqRy8FPOX+dijs7esUEUYTIQ1KsOSKmCVNYnitAMmBDFxYzA6VQYvuUKw7o2K1AcMBTGzIg==" + }, "natives": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.4.tgz", @@ -4230,6 +4447,18 @@ } } }, + "nativescript-preview-sdk": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/nativescript-preview-sdk/-/nativescript-preview-sdk-0.2.2.tgz", + "integrity": "sha512-+UXRbP8l7dQ53lNJbn5XfexApRabR8NUlpNAnLbKu8PGdkRXvTEK2RSCgZpkixYnOerFDCEAaE2EKgh+INFekA==", + "requires": { + "@types/pubnub": "4.0.2", + "@types/shortid": "0.0.29", + "btoa": "1.2.1", + "pubnub": "4.21.2", + "shortid": "2.2.12" + } + }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -4242,6 +4471,11 @@ "integrity": "sha1-dDmFMW49tFkoG1hxaehFc1oFQ58=", "dev": true }, + "netmask": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-1.0.6.tgz", + "integrity": "sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU=" + }, "next-tick": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", @@ -4249,13 +4483,13 @@ "dev": true }, "nise": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.3.3.tgz", - "integrity": "sha512-v1J/FLUB9PfGqZLGDBhQqODkbLotP0WtLo9R4EJY2PPu5f5Xg4o0rA8FDlmrjFSv9vBBKcfnOSpfYYuu5RTHqg==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.4.4.tgz", + "integrity": "sha512-pxE0c9PzgrUTyhfv5p+5eMIdfU2bLEsq8VQEuE0kxM4zP7SujSar7rk9wpI2F7RyyCEvLyj5O7Is3RER5F36Fg==", "dev": true, "requires": { "@sinonjs/formatio": "^2.0.0", - "just-extend": "^1.1.27", + "just-extend": "^3.0.0", "lolex": "^2.3.2", "path-to-regexp": "^1.7.0", "text-encoding": "^0.6.4" @@ -4313,9 +4547,9 @@ } }, "npm-bundled": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.3.tgz", - "integrity": "sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.5.tgz", + "integrity": "sha512-m/e6jgWu8/v5niCUKQi9qQl8QdeEduFA96xHDDzFGqly0OOjI7c+60KM/2sppfnUU9JJagf+zs+yGhqSOFj71g==" }, "npm-package-arg": { "version": "6.1.0", @@ -4336,18 +4570,13 @@ "os-homedir": "^1.0.0", "os-tmpdir": "^1.0.0" } - }, - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" } } }, "npm-packlist": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.10.tgz", - "integrity": "sha512-AQC0Dyhzn4EiYEfIUjCdMl0JJ61I2ER9ukf/sLxJUcZHfo+VyEfz2rMJgLZSS1v30OxPQe1cN0LZA1xbcaVfWA==", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.11.tgz", + "integrity": "sha512-CxKlZ24urLkJk+9kCm48RTQ7L4hsmgSVzEk0TLGPzzyuFxD7VNgy5Sl24tOLMzQv773a/NeJ1ce1DKeacqffEA==", "requires": { "ignore-walk": "^3.0.1", "npm-bundled": "^1.0.1" @@ -4360,13 +4589,6 @@ "requires": { "npm-package-arg": "^6.0.0", "semver": "^5.4.1" - }, - "dependencies": { - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" - } } }, "npm-run-path": { @@ -4454,7 +4676,6 @@ "version": "0.8.2", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "dev": true, "requires": { "deep-is": "~0.1.3", "fast-levenshtein": "~2.0.4", @@ -4509,9 +4730,9 @@ } }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "requires": { "has-flag": "^3.0.0" } @@ -4550,6 +4771,48 @@ "resolved": "https://registry.npmjs.org/over/-/over-0.0.5.tgz", "integrity": "sha1-8phS5w/X4l82DgE6jsRMgq7bVwg=" }, + "pac-proxy-agent": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-2.0.2.tgz", + "integrity": "sha512-cDNAN1Ehjbf5EHkNY5qnRhGPUCp6SnpyVof5fRzN800QV1Y2OkzbH9rmjZkbBRa8igof903yOnjIl6z0SlAhxA==", + "requires": { + "agent-base": "^4.2.0", + "debug": "^3.1.0", + "get-uri": "^2.0.0", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.1", + "pac-resolver": "^3.0.0", + "raw-body": "^2.2.0", + "socks-proxy-agent": "^3.0.0" + }, + "dependencies": { + "debug": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", + "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "pac-resolver": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-3.0.0.tgz", + "integrity": "sha512-tcc38bsjuE3XZ5+4vP96OfhOugrX+JcnpUbhfuc4LuXBLQhoTthOstZeoQJBDnQUDYzYmdImKsbz0xSl1/9qeA==", + "requires": { + "co": "^4.6.0", + "degenerator": "^1.0.4", + "ip": "^1.1.5", + "netmask": "^1.0.6", + "thunkify": "^2.1.2" + } + }, "pacote": { "version": "8.1.6", "resolved": "https://registry.npmjs.org/pacote/-/pacote-8.1.6.tgz", @@ -4606,22 +4869,14 @@ "requires": { "glob": "^7.0.5" } - }, - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "requires": { - "isexe": "^2.0.0" - } } } }, + "pako": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", + "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==" + }, "parallel-transform": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", @@ -4643,9 +4898,9 @@ "integrity": "sha1-0Di0dtPp3Z2x4RoLDlOiJ5K2kAY=" }, "parse-bmfont-xml": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.3.tgz", - "integrity": "sha1-1rZqNxr9OcUAfZ8O6yYqTyzOe3w=", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.4.tgz", + "integrity": "sha512-bjnliEOmGv3y1aMEfREMBJ9tfL3WR0i0CKPj61DnSLaoxWR3nLrsQrEbCId/8rF4NyRF0cCqisSVXyQYWM+mCQ==", "requires": { "xml-parse-from-string": "^1.0.0", "xml2js": "^0.4.5" @@ -4711,9 +4966,9 @@ "dev": true }, "path-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" }, "path-to-regexp": { "version": "1.7.0", @@ -4763,6 +5018,11 @@ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, + "phin": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/phin/-/phin-2.9.1.tgz", + "integrity": "sha512-aRmHatimRP+73UipPJEK6AWHWjNcwssW6QmOpUcogYVgO8hbSi2Dv/yDWQKs/DmTjK3gCaf6CNsuYcIBWMnlVw==" + }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -4871,8 +5131,7 @@ "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" }, "preserve": { "version": "0.2.0", @@ -4917,6 +5176,41 @@ "genfun": "^4.0.1" } }, + "proxy-agent": { + "version": "2.3.1", + "resolved": "http://registry.npmjs.org/proxy-agent/-/proxy-agent-2.3.1.tgz", + "integrity": "sha512-CNKuhC1jVtm8KJYFTS2ZRO71VCBx3QSA92So/e6NrY6GoJonkx3Irnk4047EsCcswczwqAekRj3s8qLRGahSKg==", + "requires": { + "agent-base": "^4.2.0", + "debug": "^3.1.0", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.1", + "lru-cache": "^4.1.2", + "pac-proxy-agent": "^2.0.1", + "proxy-from-env": "^1.0.0", + "socks-proxy-agent": "^3.0.0" + }, + "dependencies": { + "debug": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", + "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "proxy-from-env": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", + "integrity": "sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=" + }, "proxy-lib": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/proxy-lib/-/proxy-lib-0.4.0.tgz", @@ -4941,6 +5235,17 @@ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, + "pubnub": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/pubnub/-/pubnub-4.21.2.tgz", + "integrity": "sha512-yDDkqrvhFrBYFZsI4EL0Atnilo00OrRR+hCw86iTlkinMMxQbZhdn5yePrSc3GUs62kn3xDC6b73zaIKUYwWFg==", + "requires": { + "agentkeepalive": "^3.1.0", + "lil-uuid": "^0.1.1", + "superagent": "^3.8.1", + "superagent-proxy": "^1.0.2" + } + }, "pullstream": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/pullstream/-/pullstream-0.4.1.tgz", @@ -5015,15 +5320,20 @@ "resolved": "https://registry.npmjs.org/qr-image/-/qr-image-3.2.0.tgz", "integrity": "sha1-n6gpW+rlDEoUnPn5CaHbRkqGcug=" }, + "qrcode-terminal": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz", + "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==" + }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "randomatic": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.0.0.tgz", - "integrity": "sha512-VdxFOIEY3mNO5PtSRkkle/hPJDHvQhK21oa73K4yAc9qmp6N429gAyF1gZMOTMeS0/AYzaV/2Trcef+NaIonSA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.0.tgz", + "integrity": "sha512-KnGPVE0lo2WoXxIZ7cPR8YBpiol4gsSuOwDSg410oHh80ZMp5EiypNqL2K4Z77vJn6lB5rap7IkAmcUlalcnBQ==", "requires": { "is-number": "^4.0.0", "kind-of": "^6.0.0", @@ -5043,27 +5353,23 @@ } }, "raw-body": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz", - "integrity": "sha1-rf6s4uT7MJgFgBTQjActzFl1h3Q=", - "dev": true, + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", "requires": { - "bytes": "2.4.0", - "iconv-lite": "0.4.13", + "bytes": "3.0.0", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", "unpipe": "1.0.0" }, "dependencies": { - "bytes": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", - "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=", - "dev": true - }, "iconv-lite": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", - "integrity": "sha1-H4irpKsLFQjoMSrMOTRfNumS4vI=", - "dev": true + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } } } }, @@ -5192,9 +5498,9 @@ "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" }, "repeat-element": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", - "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" }, "repeat-string": { "version": "1.6.1", @@ -5240,9 +5546,9 @@ }, "dependencies": { "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" } } }, @@ -5267,9 +5573,9 @@ } }, "resolve": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", - "integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", + "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", "requires": { "path-parse": "^1.0.5" } @@ -5371,6 +5677,11 @@ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, "shelljs": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.6.tgz", @@ -5381,6 +5692,14 @@ "rechoir": "^0.6.2" } }, + "shortid": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.12.tgz", + "integrity": "sha512-sw0knB/ioTu/jVYgJz1IP1b5uhPZtZYwQ9ir/EqXZHI4+Jh8rzzGLM3LKptGHBKoDsgTBDfr4yCRNUX7hEIksQ==", + "requires": { + "nanoid": "^1.0.7" + } + }, "should": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/should/-/should-7.0.2.tgz", @@ -5540,9 +5859,9 @@ } }, "smart-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.0.1.tgz", - "integrity": "sha512-RFqinRVJVcCAL9Uh1oVqE6FZkqsyLiVOYEZ20TqIOjuX7iFVJ+zsbs4RIghnw/pTs7mZvt8ZHhvm1ZUrR4fykg==" + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-1.1.15.tgz", + "integrity": "sha1-fxFLW2X6s+KjWqd1uxLw0cZJvxY=" }, "sntp": { "version": "2.1.0", @@ -5553,21 +5872,21 @@ } }, "socks": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.2.0.tgz", - "integrity": "sha512-uRKV9uXQ9ytMbGm2+DilS1jB7N3AC0mmusmW5TVWjNuBZjxS8+lX38fasKVY9I4opv/bY/iqTbcpFFaTwpfwRg==", + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/socks/-/socks-1.1.10.tgz", + "integrity": "sha1-W4t/x8jzQcU+0FbpKbe/Tei6e1o=", "requires": { - "ip": "^1.1.5", - "smart-buffer": "^4.0.1" + "ip": "^1.1.4", + "smart-buffer": "^1.0.13" } }, "socks-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.1.tgz", - "integrity": "sha512-Kezx6/VBguXOsEe5oU3lXYyKMi4+gva72TwJ7pQY5JfqUx2nMk7NXA6z/mpNqIlfQjWYVfeuNvQjexiTaTn6Nw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-3.0.1.tgz", + "integrity": "sha512-ZwEDymm204mTzvdqyUqOdovVr2YRd2NYskrYrF2LXyZ9qDiMAoFESGK8CRphiO7rtbo2Y757k2Nia3x2hGtalA==", "requires": { - "agent-base": "~4.2.0", - "socks": "~2.2.0" + "agent-base": "^4.1.0", + "socks": "^1.1.10" } }, "source-map": { @@ -5627,9 +5946,9 @@ "dev": true }, "sshpk": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", - "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", + "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -5638,19 +5957,22 @@ "ecc-jsbn": "~0.1.1", "getpass": "^0.1.1", "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" } }, "ssri": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.0.tgz", - "integrity": "sha512-zYOGfVHPhxyzwi8MdtdNyxv3IynWCIM4jYReR48lqu0VngxgH1c+C6CmipRdJ55eVByTJV/gboFEEI7TEQI8DA==" + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "requires": { + "figgy-pudding": "^3.5.1" + } }, "statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "dev": true + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, "stream-buffers": { "version": "2.2.0", @@ -5658,9 +5980,9 @@ "integrity": "sha1-kdX1Ew0c75bc+n9yaUUYh0HQnuQ=" }, "stream-each": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.2.tgz", - "integrity": "sha512-mc1dbFhGBxvTM3bIWmAAINbqiuAk9TATcfIQC8P+/+HJefgaiTlMn2dHvkX8qlI12KeYKSQ1Ua9RrIqrn1VPoA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", + "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", "requires": { "end-of-stream": "^1.1.0", "stream-shift": "^1.0.0" @@ -5776,6 +6098,62 @@ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true }, + "superagent": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", + "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", + "requires": { + "component-emitter": "^1.2.0", + "cookiejar": "^2.1.0", + "debug": "^3.1.0", + "extend": "^3.0.0", + "form-data": "^2.3.1", + "formidable": "^1.2.0", + "methods": "^1.1.1", + "mime": "^1.4.1", + "qs": "^6.5.1", + "readable-stream": "^2.3.5" + }, + "dependencies": { + "debug": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", + "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "superagent-proxy": { + "version": "1.0.3", + "resolved": "http://registry.npmjs.org/superagent-proxy/-/superagent-proxy-1.0.3.tgz", + "integrity": "sha512-79Ujg1lRL2ICfuHUdX+H2MjIw73kB7bXsIkxLwHURz3j0XUmEEEoJ+u/wq+mKwna21Uejsm2cGR3OESA00TIjA==", + "requires": { + "debug": "^3.1.0", + "proxy-agent": "2" + }, + "dependencies": { + "debug": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", + "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", @@ -5904,6 +6282,11 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "textextensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-2.2.0.tgz", + "integrity": "sha512-j5EMxnryTvKxwH2Cq+Pb43tsf6sdEgw6Pdwxk83mPaq0ToeFJt6WE4J3s5BqY7vmjlLgkgXvhtXUxo80FyBhCA==" + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -5918,6 +6301,11 @@ "xtend": "~4.0.1" } }, + "thunkify": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/thunkify/-/thunkify-2.1.2.tgz", + "integrity": "sha1-+qDp0jDFGsyVyhOjYawFyn4EVT0=" + }, "tiny-lr": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-0.2.1.tgz", @@ -5985,9 +6373,9 @@ "dev": true }, "tslib": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.1.tgz", - "integrity": "sha512-avfPS28HmGLLc2o4elcc2EIq2FcH++Yo5YxpBZi9Yw93BCTGFthI4HPE4Rpep6vSYQaK8e69PelM44tPj+RaQg==", + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", "dev": true }, "tslint": { @@ -6026,9 +6414,9 @@ } }, "tsutils": { - "version": "2.27.1", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.27.1.tgz", - "integrity": "sha512-AE/7uzp32MmaHvNNFES85hhUDHFdFZp6OAiZcd6y4ZKKIg6orJTm8keYWBhIhrJQH3a4LzNKat7ZPXZt5aTf6w==", + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", "dev": true, "requires": { "tslib": "^1.8.1" @@ -6052,7 +6440,6 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, "requires": { "prelude-ls": "~1.1.2" } @@ -6152,9 +6539,9 @@ "optional": true }, "underscore": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.0.tgz", - "integrity": "sha512-4IV1DSSxC1QK48j9ONFK1MoIAKKkbE8i7u55w2R6IqBqbT7A/iG7aZBCR2Bi8piF0Uz+i/MG1aeqLwl/5vqF+A==" + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", + "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==" }, "underscore.string": { "version": "3.2.3", @@ -6192,8 +6579,7 @@ "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "dev": true + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, "unzip": { "version": "0.1.11", @@ -6259,9 +6645,9 @@ "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=" }, "validate-npm-package-license": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", - "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "requires": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" @@ -6310,10 +6696,9 @@ "dev": true }, "which": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", - "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", - "dev": true, + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "requires": { "isexe": "^2.0.0" } @@ -6336,8 +6721,7 @@ "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" }, "wrap-ansi": { "version": "2.1.0", @@ -6424,6 +6808,11 @@ "version": "https://github.com/telerik/node-XMLHttpRequest/tarball/master", "integrity": "sha512-SPnuri0YNyTOxSIU/E0voUTEOEGkwI0kFuLBe9BEsbG8aIRYa9ASuizKzK3XeqnBjypF6tou5NRpuEsAW0CBxg==" }, + "xregexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", + "integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=" + }, "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", diff --git a/package.json b/package.json index d465dcb385..7a98db67b4 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "ios-device-lib": "0.4.14", "ios-mobileprovision-finder": "1.0.10", "ios-sim-portable": "4.0.2", + "istextorbinary": "2.2.1", "jimp": "0.2.28", "lockfile": "1.0.3", "lodash": "4.17.10", @@ -54,15 +55,18 @@ "mkdirp": "0.5.1", "mute-stream": "0.0.5", "nativescript-doctor": "1.2.0", + "nativescript-preview-sdk": "0.2.2", "open": "0.0.5", "ora": "2.0.0", "osenv": "0.1.3", "pacote": "8.1.6", + "pako": "1.0.6", "pbxproj-dom": "1.0.11", "plist": "1.1.0", "plist-merge-patch": "0.1.1", "proxy-lib": "0.4.0", "qr-image": "3.2.0", + "qrcode-terminal": "0.12.0", "request": "2.85.0", "semver": "5.5.0", "shelljs": "0.7.6", diff --git a/test/helpers/preview-command-helper.ts b/test/helpers/preview-command-helper.ts new file mode 100644 index 0000000000..5709fe005f --- /dev/null +++ b/test/helpers/preview-command-helper.ts @@ -0,0 +1,150 @@ +import { Yok } from "../../lib/common/yok"; +import { PreviewCommandHelper } from "../../lib/helpers/preview-command-helper"; +import * as chai from "chai"; + +interface ITestCase { + name: string; + stdinData: string; + qrCodeProperty?: string; +} + +let qrCodesData: IDictionary = { + isGeneratedForAndroid: false, + isGeneratedForiOS: false, + isGeneratedForCurrentApp: false +}; + +function createTestInjector() { + const injector = new Yok(); + injector.register("logger", { + info: () => ({}) + }); + injector.register("playgroundQrCodeGenerator", { + generateQrCodeForAndroid: async () => { + qrCodesData.isGeneratedForAndroid = true; + }, + generateQrCodeForiOS: async () => { + qrCodesData.isGeneratedForiOS = true; + }, + generateQrCodeForCurrentApp: async () => { + qrCodesData.isGeneratedForCurrentApp = true; + } + }); + injector.register("processService", { + attachToProcessExitSignals: () => ({}) + }); + injector.register("previewCommandHelper", PreviewCommandHelper); + (process.stdin).setRawMode = () => ({}); + return injector; +} + +function arrange() { + const injector = createTestInjector(); + const previewCommandHelper = injector.resolve("previewCommandHelper"); + return { + previewCommandHelper + }; +} + +function act(previewCommandHelper: IPreviewCommandHelper, sdtinStrToEmit: string): void { + previewCommandHelper.run(); + process.stdin.emit("data", sdtinStrToEmit); +} + +function assert(qrCodeProperty: string) { + _.keys(qrCodesData).forEach(prop => { + if (prop === qrCodeProperty) { + chai.assert.isTrue(qrCodesData[prop]); + } else { + chai.assert.isFalse(qrCodesData[prop]); + } + }); +} + +function makeInteractive() { + process.stdout.isTTY = true; + process.stdin.isTTY = true; +} + +function makeNonInteractive() { + process.stdout.isTTY = false; + process.stdin.isTTY = false; +} + +function reset() { + qrCodesData = { + isGeneratedForAndroid: false, + isGeneratedForiOS: false, + isGeneratedForCurrentApp: false + }; + process.stdin.removeAllListeners("data"); +} + +function execute(testCases: ITestCase[]) { + testCases.forEach(testCase => { + it(`${testCase.name}`, async () => { + const { previewCommandHelper } = arrange(); + act(previewCommandHelper, testCase.stdinData); + assert(testCase.qrCodeProperty); + }); + }); +} + +describe("previewCommandHelper", () => { + describe("when console is interactive", () => { + beforeEach(() => { + makeInteractive(); + }); + + afterEach(() => { + reset(); + }); + + const testCases = [ + { + name: "should generate qr code for android when a key is pressed", + stdinData: "a", + qrCodeProperty: "isGeneratedForAndroid", + }, + { + name: "should generate qr code for iOS when i key is pressed", + stdinData: "i", + qrCodeProperty: "isGeneratedForiOS", + }, + { + name: "should generate qr code for current app when c key is pressed", + stdinData: "c", + qrCodeProperty: "isGeneratedForCurrentApp", + } + ]; + + execute(testCases); + }); + + describe("when console is non interactive", () => { + beforeEach(() => { + makeNonInteractive(); + }); + + afterEach(() => { + reset(); + }); + + const testCases = [ + { + name: "should not generate qr code for android when a key is pressed", + stdinData: "a" + }, + { + name: "should not generate qr code for iOS when i key is pressed", + stdinData: "i" + }, + { + name: "should not generate qr code for current app when c key is pressed", + stdinData: "c" + } + ]; + + execute(testCases); + }); +}); diff --git a/test/platform-commands.ts b/test/platform-commands.ts index f460fc08d8..9be0bcf7cc 100644 --- a/test/platform-commands.ts +++ b/test/platform-commands.ts @@ -29,8 +29,13 @@ class PlatformData implements IPlatformData { frameworkPackageName = "tns-android"; normalizedPlatformName = "Android"; platformProjectService: IPlatformProjectService = { - validate: async (projectData: IProjectData): Promise => { - // intentionally left blank + validate: async (projectData: IProjectData): Promise => { + return { + checkEnvironmentRequirementsOutput: { + canExecute: true, + selectedOption: "" + } + }; } }; projectRoot = ""; @@ -164,7 +169,12 @@ function createTestInjector() { }); testInjector.register("filesHashService", {}); testInjector.register("platformEnvironmentRequirements", { - checkEnvironmentRequirements: async (platform?: string, projectDir?: string, runtimeVersion?: string): Promise => true + checkEnvironmentRequirements: async (platform?: string, projectDir?: string, runtimeVersion?: string): Promise => { + return { + canExecute: true, + selectedOption: "" + }; + } }); testInjector.register("pacoteService", { extractPackage: async (packageName: string, destinationDirectory: string, options?: IPacoteExtractOptions): Promise => undefined diff --git a/test/services/livesync-service.ts b/test/services/livesync-service.ts index 87b0dd9fe7..5ce618c523 100644 --- a/test/services/livesync-service.ts +++ b/test/services/livesync-service.ts @@ -36,6 +36,7 @@ const createTestInjector = (): IInjector => { iOS: "iOS" } }); + testInjector.register("previewAppLiveSyncService", {}); return testInjector; }; @@ -57,6 +58,7 @@ class LiveSyncServiceInheritor extends LiveSyncService { $analyticsService: IAnalyticsService, $usbLiveSyncService: DeprecatedUsbLiveSyncService, $injector: IInjector, + $previewAppLiveSyncService: IPreviewAppLiveSyncService, $platformsData: IPlatformsData) { super( @@ -75,6 +77,7 @@ class LiveSyncServiceInheritor extends LiveSyncService { $debugDataService, $analyticsService, $usbLiveSyncService, + $previewAppLiveSyncService, $injector ); } diff --git a/test/services/platform-environment-requirements.ts b/test/services/platform-environment-requirements.ts index 28d577b120..6f29f390dd 100644 --- a/test/services/platform-environment-requirements.ts +++ b/test/services/platform-environment-requirements.ts @@ -7,8 +7,8 @@ import { EOL } from "os"; const platform = "android"; const cloudBuildsErrorMessage = `In order to test your application use the $ tns login command to log in with your account and then $ tns cloud build command to build your app in the cloud.`; const manuallySetupErrorMessage = `To be able to build for ${platform}, verify that your environment is configured according to the system requirements described at `; -const nonInteractiveConsoleMessageWhenExtensionIsNotInstalled = `You are missing the nativescript-cloud extension and you will not be able to execute cloud builds. Your environment is not configured properly and you will not be able to execute local builds. To continue, choose one of the following options: ${EOL}Run $ tns setup command to run the setup script to try to automatically configure your environment for local builds.${EOL}Run $ tns cloud setup command to install the nativescript-cloud extension to configure your environment for cloud builds.${EOL}Verify that your environment is configured according to the system requirements described at `; -const nonInteractiveConsoleMessageWhenExtensionIsInstalled = `Your environment is not configured properly and you will not be able to execute local builds. To continue, choose one of the following options: ${EOL}Run $ tns setup command to run the setup script to try to automatically configure your environment for local builds.${EOL}In order to test your application use the $ tns login command to log in with your account and then $ tns cloud build command to build your app in the cloud.${EOL}Verify that your environment is configured according to the system requirements described at .`; +const nonInteractiveConsoleMessageWhenExtensionIsNotInstalled = `You are missing the nativescript-cloud extension and you will not be able to execute cloud builds. Your environment is not configured properly and you will not be able to execute local builds. To continue, choose one of the following options: ${EOL}Run $ tns preview command to enjoy NativeScript without any local setup.${EOL}Run $ tns setup command to run the setup script to try to automatically configure your environment for local builds.${EOL}Run $ tns cloud setup command to install the nativescript-cloud extension to configure your environment for cloud builds.${EOL}Verify that your environment is configured according to the system requirements described at `; +const nonInteractiveConsoleMessageWhenExtensionIsInstalled = `Your environment is not configured properly and you will not be able to execute local builds. To continue, choose one of the following options: ${EOL}Run $ tns preview command to enjoy NativeScript without any local setup.${EOL}Run $ tns setup command to run the setup script to try to automatically configure your environment for local builds.${EOL}In order to test your application use the $ tns login command to log in with your account and then $ tns cloud build command to build your app in the cloud.${EOL}Verify that your environment is configured according to the system requirements described at .`; function createTestInjector() { const testInjector = new Yok(); @@ -83,8 +83,8 @@ describe("platformEnvironmentRequirements ", () => { it("should return true when environment is configured", async () => { mockDoctorService({ canExecuteLocalBuild: true }); - const result = await platformEnvironmentRequirements.checkEnvironmentRequirements(platform); - assert.isTrue(result); + const result = await platformEnvironmentRequirements.checkEnvironmentRequirements({ platform }); + assert.isTrue(result.canExecute); assert.isTrue(promptForChoiceData.length === 0); }); it("should show prompt when environment is not configured and nativescript-cloud extension is not installed", async () => { @@ -92,26 +92,50 @@ describe("platformEnvironmentRequirements ", () => { mockPrompter({ firstCallOptionName: PlatformEnvironmentRequirements.CLOUD_SETUP_OPTION_NAME }); mockNativeScriptCloudExtensionService({ isInstalled: false }); - await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform)); + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements({ platform })); assert.isTrue(promptForChoiceData.length === 1); assert.isTrue(isExtensionInstallCalled); assert.deepEqual("To continue, choose one of the following options: ", promptForChoiceData[0].message); - assert.deepEqual(['Configure for Cloud Builds', 'Configure for Local Builds', 'Configure for Both Local and Cloud Builds', 'Skip Step and Configure Manually'], promptForChoiceData[0].choices); + assert.deepEqual(['Sync to Playground', 'Configure for Cloud Builds', 'Configure for Local Builds', 'Configure for Both Local and Cloud Builds', 'Skip Step and Configure Manually'], promptForChoiceData[0].choices); }); it("should show prompt when environment is not configured and nativescript-cloud extension is installed", async () => { mockDoctorService({ canExecuteLocalBuild: false }); mockPrompter({ firstCallOptionName: PlatformEnvironmentRequirements.CLOUD_SETUP_OPTION_NAME }); mockNativeScriptCloudExtensionService({ isInstalled: true }); - await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform)); + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements({ platform })); assert.isTrue(promptForChoiceData.length === 1); assert.deepEqual("To continue, choose one of the following options: ", promptForChoiceData[0].message); + assert.deepEqual(['Sync to Playground', 'Try Cloud Operation', 'Configure for Local Builds', 'Skip Step and Configure Manually'], promptForChoiceData[0].choices); + }); + it("should not show 'Sync to Playground' option when hideSyncToPreviewAppOption is provided", async () => { + mockDoctorService({ canExecuteLocalBuild: false }); + mockPrompter({ firstCallOptionName: PlatformEnvironmentRequirements.CLOUD_SETUP_OPTION_NAME }); + mockNativeScriptCloudExtensionService({ isInstalled: 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); assert.deepEqual(['Try Cloud Operation', 'Configure for Local Builds', 'Skip Step and Configure Manually'], promptForChoiceData[0].choices); }); - it("should skip env chech when NS_SKIP_ENV_CHECK environment variable is passed", async() => { + it("should not show 'Try Cloud Build' option when hideCloudBuildOption is provided", async () => { + mockDoctorService({ canExecuteLocalBuild: false }); + mockPrompter({ firstCallOptionName: PlatformEnvironmentRequirements.CLOUD_SETUP_OPTION_NAME }); + mockNativeScriptCloudExtensionService({ isInstalled: 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() => { process.env.NS_SKIP_ENV_CHECK = true; - assert.isTrue(await platformEnvironmentRequirements.checkEnvironmentRequirements(platform)); + const output = await platformEnvironmentRequirements.checkEnvironmentRequirements({ platform }); + + assert.isTrue(output.canExecute); assert.isFalse(isExtensionInstallCalled); assert.isTrue(promptForChoiceData.length === 0); }); @@ -128,7 +152,8 @@ describe("platformEnvironmentRequirements ", () => { mockNativeScriptCloudExtensionService({ isInstalled: null }); - assert.isTrue(await platformEnvironmentRequirements.checkEnvironmentRequirements(platform)); + const output = await platformEnvironmentRequirements.checkEnvironmentRequirements({ platform }); + assert.isTrue(output.canExecute); }); describe("and env is not configured after executing setup script", () => { @@ -137,7 +162,7 @@ describe("platformEnvironmentRequirements ", () => { mockPrompter({ firstCallOptionName: PlatformEnvironmentRequirements.LOCAL_SETUP_OPTION_NAME, secondCallOptionName: PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME }); mockNativeScriptCloudExtensionService({ isInstalled: true }); - await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform), manuallySetupErrorMessage); + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements({ platform }), manuallySetupErrorMessage); }); describe("and cloud extension is not installed", () => { beforeEach(() => { @@ -147,18 +172,18 @@ describe("platformEnvironmentRequirements ", () => { it("should list 2 posibile options to select", async () => { mockPrompter({ firstCallOptionName: PlatformEnvironmentRequirements.LOCAL_SETUP_OPTION_NAME }); - await platformEnvironmentRequirements.checkEnvironmentRequirements(platform); + await platformEnvironmentRequirements.checkEnvironmentRequirements({ platform }); assert.deepEqual(promptForChoiceData[1].choices, ['Configure for Cloud Builds', 'Skip Step and Configure Manually']); }); it("should install nativescript-cloud extension when 'Configure for Cloud Builds' option is selected", async () => { mockPrompter({ firstCallOptionName: PlatformEnvironmentRequirements.LOCAL_SETUP_OPTION_NAME, secondCallOptionName: PlatformEnvironmentRequirements.CLOUD_SETUP_OPTION_NAME }); - await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform), cloudBuildsErrorMessage); + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements({ platform }), cloudBuildsErrorMessage); assert.deepEqual(isExtensionInstallCalled, true); }); it("should setup manually when 'Skip Step and Configure Manually' option is selected", async () => { mockPrompter({ firstCallOptionName: PlatformEnvironmentRequirements.LOCAL_SETUP_OPTION_NAME, secondCallOptionName: PlatformEnvironmentRequirements.MANUALLY_SETUP_OPTION_NAME }); - await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform), manuallySetupErrorMessage); + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements({ platform }), manuallySetupErrorMessage); }); }); }); @@ -172,7 +197,7 @@ describe("platformEnvironmentRequirements ", () => { it("should install nativescript-cloud extension when it is not installed", async () => { mockNativeScriptCloudExtensionService({ isInstalled: false }); - await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform), cloudBuildsErrorMessage); + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements({ platform }), cloudBuildsErrorMessage); assert.isTrue(isExtensionInstallCalled); }); }); @@ -185,11 +210,11 @@ describe("platformEnvironmentRequirements ", () => { it("should fail when nativescript-cloud extension is installed", async () => { mockNativeScriptCloudExtensionService({ isInstalled: true }); - await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform), manuallySetupErrorMessage); + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements({ platform }), manuallySetupErrorMessage); }); it("should fail when nativescript-cloud extension is not installed", async () => { mockNativeScriptCloudExtensionService({ isInstalled: false }); - await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform), manuallySetupErrorMessage); + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements({ platform }), manuallySetupErrorMessage); }); }); @@ -202,11 +227,11 @@ describe("platformEnvironmentRequirements ", () => { it("should fail when nativescript-cloud extension is installed", async () => { mockNativeScriptCloudExtensionService({ isInstalled: true }); - await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform), nonInteractiveConsoleMessageWhenExtensionIsInstalled); + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements({ platform }), nonInteractiveConsoleMessageWhenExtensionIsInstalled); }); it("should fail when nativescript-cloud extension is not installed", async () => { mockNativeScriptCloudExtensionService({ isInstalled: false }); - await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements(platform), nonInteractiveConsoleMessageWhenExtensionIsNotInstalled); + await assert.isRejected(platformEnvironmentRequirements.checkEnvironmentRequirements({ platform }), nonInteractiveConsoleMessageWhenExtensionIsNotInstalled); }); }); }); diff --git a/test/services/playground/preview-app-livesync-service.ts b/test/services/playground/preview-app-livesync-service.ts new file mode 100644 index 0000000000..9301d71a36 --- /dev/null +++ b/test/services/playground/preview-app-livesync-service.ts @@ -0,0 +1,347 @@ +import { Yok } from "../../../lib/common/yok"; +import { LoggerStub } from "../../stubs"; +import { FilePayload, Device } from "nativescript-preview-sdk"; +import { EventEmitter } from "events"; +import { PreviewAppLiveSyncService } from "../../../lib/services/livesync/playground/preview-app-livesync-service"; +import * as chai from "chai"; +import * as path from "path"; +import { ProjectFilesManager } from "../../../lib/common/services/project-files-manager"; + +interface ITestCase { + name: string; + appFiles?: string[]; + expectedFiles?: string[]; + actOptions?: IActOptions; + assertOptions?: IAssertOptions; +} + +interface IActOptions { + emitDeviceConnected: boolean; +} + +interface IAssertOptions { + checkWarnings?: boolean; + isComparePluginsOnDeviceCalled?: boolean; +} + +interface IActInput { + previewAppLiveSyncService?: IPreviewAppLiveSyncService; + previewSdkService?: IPreviewSdkService; + projectFiles?: string[]; + actOptions?: IActOptions; +} + +let isComparePluginsOnDeviceCalled = false; +let applyChangesParams: FilePayload[] = []; +let readTextParams: string[] = []; +let warnParams: string[] = []; +const nativeFilesWarning = "Unable to apply changes from App_Resources folder. You need to build your application in order to make changes in App_Resources folder."; + +const projectDirPath = "/path/to/my/project"; +const platformsDirPath = path.join(projectDirPath, "platforms"); +const normalizedPlatformName = 'iOS'; + +const deviceMockData = { + platform: normalizedPlatformName +}; +const defaultProjectFiles = [ + "my/test/file1.js", + "my/test/file2.js", + "my/test/file3.js", + "my/test/nested/file1.js", + "my/test/nested/file2.js", + "my/test/nested/file3.js" +]; +const syncFilesMockData = { + projectDir: projectDirPath, + appFilesUpdaterOptions: { + release: false, + bundle: false + }, + env: { } +}; + +class PreviewSdkServiceMock extends EventEmitter implements IPreviewSdkService { + public get qrCodeUrl() { + return "my_cool_qr_code_url"; + } + + public connectedDevices: Device[] = [deviceMockData]; + public initialize() { /* empty block */ } + + public async applyChanges(files: FilePayload[]) { + applyChangesParams.push(...files); + } + + public stop() { /* empty block */ } +} + +class LoggerMock extends LoggerStub { + warn(...args: string[]): void { + warnParams.push(...args); + } +} + +function createTestInjector(options?: { + projectFiles?: string[] +}) { + options = options || {}; + + const injector = new Yok(); + injector.register("logger", LoggerMock); + injector.register("platformService", { + preparePlatform: async () => ({}) + }); + injector.register("platformsData", { + getPlatformData: () => ({ + appDestinationDirectoryPath: platformsDirPath, + normalizedPlatformName + }) + }); + injector.register("projectDataService", { + getProjectData: () => ({ + projectDir: projectDirPath + }) + }); + injector.register("previewSdkService", PreviewSdkServiceMock); + injector.register("previewAppPluginsService", { + comparePluginsOnDevice: async () => { + isComparePluginsOnDeviceCalled = true; + } + }); + injector.register("projectFilesManager", ProjectFilesManager); + injector.register("previewAppLiveSyncService", PreviewAppLiveSyncService); + injector.register("fs", { + readText: (filePath: string) => { + readTextParams.push(filePath); + }, + enumerateFilesInDirectorySync: (projectFilesPath: string) => { + if (options.projectFiles) { + return options.projectFiles.map(file => path.join(projectDirPath, file)); + } + + return defaultProjectFiles.map(file => path.join(platformsDirPath, "app", file)); + } + }); + injector.register("localToDevicePathDataFactory", {}); + injector.register("projectFilesProvider", { + getProjectFileInfo: (filePath: string, platform: string) => { + return { + filePath, + onDeviceFileName: path.basename(filePath), + shouldIncludeFile: true + }; + } + }); + injector.register("hooksService", { + executeBeforeHooks: () => ({}) + }); + + return injector; +} + +function arrange(options?: { projectFiles ?: string[] }) { + options = options || {}; + + const injector = createTestInjector({ projectFiles: options.projectFiles }); + const previewAppLiveSyncService: IPreviewAppLiveSyncService = injector.resolve("previewAppLiveSyncService"); + const previewSdkService: IPreviewSdkService = injector.resolve("previewSdkService"); + + return { + previewAppLiveSyncService, + previewSdkService + }; +} + +async function initialSync(input?: IActInput) { + input = input || {}; + + const { previewAppLiveSyncService, previewSdkService, actOptions } = input; + + await previewAppLiveSyncService.initialSync(syncFilesMockData); + if (actOptions.emitDeviceConnected) { + previewSdkService.emit("onDeviceConnected", deviceMockData); + } +} + +async function syncFiles(input?: IActInput) { + input = input || { }; + + const { previewAppLiveSyncService, previewSdkService, projectFiles, actOptions } = input; + + if (actOptions.emitDeviceConnected) { + previewSdkService.emit("onDeviceConnected", deviceMockData); + } + + await previewAppLiveSyncService.syncFiles(syncFilesMockData, projectFiles); +} + +async function assert(expectedFiles: string[], options?: IAssertOptions) { + options = options || {}; + + chai.assert.deepEqual(applyChangesParams, mapFiles(expectedFiles)); + + if (options.checkWarnings) { + chai.assert.deepEqual(warnParams, [nativeFilesWarning]); + } + + if (options.isComparePluginsOnDeviceCalled) { + chai.assert.isTrue(isComparePluginsOnDeviceCalled); + } +} + +function reset() { + isComparePluginsOnDeviceCalled = false; + applyChangesParams = []; + readTextParams = []; + warnParams = []; +} + +function mapFiles(files: string[]): FilePayload[] { + if (!files) { + return []; + } + + return files.map(file => { + return { + event: "change", + file: path.relative(path.join(platformsDirPath, "app"), path.join(platformsDirPath, "app", file)), + fileContents: undefined, + binary: false + }; + }); +} + +function setDefaults(testCase: ITestCase): ITestCase { + if (!testCase.actOptions) { + testCase.actOptions = { + emitDeviceConnected: true + }; + } + + return testCase; +} + +function execute(options: { + testCases: ITestCase[], + act: (input: IActInput) => Promise +}) { + const { testCases, act } = options; + + testCases.forEach(testCase => { + testCase = setDefaults(testCase); + + it(`${testCase.name}`, async () => { + const projectFiles = testCase.appFiles ? testCase.appFiles.map(file => path.join(projectDirPath, "app", file)) : null; + const { previewAppLiveSyncService, previewSdkService } = arrange({ projectFiles }); + await act.apply(null, [{ previewAppLiveSyncService, previewSdkService, projectFiles, actOptions: testCase.actOptions }]); + await assert(testCase.expectedFiles, testCase.assertOptions); + }); + }); +} + +describe("previewAppLiveSyncService", () => { + describe("initialSync", () => { + afterEach(() => reset()); + + let testCases: ITestCase[] = [ + { + name: "should compare local plugins and plugins from preview app when devices are emitted" + } + ]; + + testCases = testCases.map(testCase => { + testCase.assertOptions = { isComparePluginsOnDeviceCalled: true }; + return testCase; + }); + + execute({ testCases, act: initialSync }); + }); + + describe("syncFiles", () => { + afterEach(() => reset()); + + const excludeFilesTestCases: ITestCase[] = [ + { + name: ".ts files", + appFiles: ["dir1/file.js", "file.ts"], + expectedFiles: [`dir1/file.js`] + }, + { + name: ".sass files", + appFiles: ["myDir1/mySubDir/myfile.css", "myDir1/mySubDir/myfile.sass"], + expectedFiles: [`myDir1/mySubDir/myfile.css`] + }, + { + name: ".scss files", + appFiles: ["myDir1/mySubDir/myfile1.css", "myDir1/mySubDir/myfile.scss", "my/file.js"], + expectedFiles: [`myDir1/mySubDir/myfile1.css`, `my/file.js`] + }, + { + name: ".less files", + appFiles: ["myDir1/mySubDir/myfile1.css", "myDir1/mySubDir/myfile.less", "my/file.js"], + expectedFiles: [`myDir1/mySubDir/myfile1.css`, `my/file.js`] + }, + { + name: ".DS_Store file", + appFiles: ["my/test/file.js", ".DS_Store"], + expectedFiles: [`my/test/file.js`] + } + ]; + + const nativeFilesTestCases: ITestCase[] = [ + { + name: "Android manifest is changed", + appFiles: ["App_Resources/Android/src/main/AndroidManifest.xml"], + expectedFiles: [] + }, + { + name: "Android app.gradle is changed", + appFiles: ["App_Resources/Android/app.gradle"], + expectedFiles: [] + }, + { + name: "iOS Info.plist is changed", + appFiles: ["App_Resources/iOS/Info.plist"], + expectedFiles: [] + }, + { + name: "iOS build.xcconfig is changed", + appFiles: ["App_Resources/iOS/build.xcconfig"], + expectedFiles: [] + } + ]; + + const noAppFilesTestCases: ITestCase[] = [ + { + name: "should transfer correctly default project files", + expectedFiles: defaultProjectFiles + } + ]; + + const testCategories = [ + { + name: "should exclude", + testCases: excludeFilesTestCases + }, + { + name: "should show warning and not transfer native files when", + testCases: nativeFilesTestCases.map(testCase => { + testCase.assertOptions = { checkWarnings: true }; + return testCase; + }) + }, + { + name: "should handle correctly when no files are provided", + testCases: noAppFilesTestCases + } + ]; + + testCategories.forEach(category => { + describe(`${category.name}`, () => { + const testCases = category.testCases; + execute({ testCases, act: syncFiles }); + }); + }); + }); +}); diff --git a/test/services/playground/preview-app-plugins-service.ts b/test/services/playground/preview-app-plugins-service.ts new file mode 100644 index 0000000000..61c7cfa105 --- /dev/null +++ b/test/services/playground/preview-app-plugins-service.ts @@ -0,0 +1,188 @@ +import { Yok } from "../../../lib/common/yok"; +import { PreviewAppPluginsService } from "../../../lib/services/livesync/playground/preview-app-plugins-service"; +import { Device } from "nativescript-preview-sdk"; +import { assert } from "chai"; +import * as util from "util"; +import { PluginComparisonMessages } from "../../../lib/services/livesync/playground/preview-app-constants"; + +let readJsonParams: string[] = []; +let warnParams: string[] = []; + +function createTestInjector(localPlugins: IStringDictionary): IInjector { + const injector = new Yok(); + injector.register("fs", { + readJson: (filePath: string) => { + readJsonParams.push(filePath); + return { + dependencies: localPlugins + }; + } + }); + injector.register("logger", { + trace: () => ({}), + warn: (message: string) => warnParams.push(message) + }); + injector.register("projectData", { + projectDir: "testProjectDir" + }); + injector.register("previewAppPluginsService", PreviewAppPluginsService); + return injector; +} + +const deviceId = "myTestDeviceId"; + +function createDevice(plugins: string): Device { + return { + id: deviceId, + platform: "iOS", + model: "myTestDeviceModel", + name: "myTestDeviceName", + osVersion: "10.0", + previewAppVersion: "28.0", + runtimeVersion: "4.3.0", + plugins, + pluginsExpanded: false + }; +} + +function setup(localPlugins: IStringDictionary, previewAppPlugins: IStringDictionary): any { + const injector = createTestInjector(localPlugins); + const previewAppPluginsService = injector.resolve("previewAppPluginsService"); + const device = createDevice(JSON.stringify(previewAppPlugins)); + + return { + previewAppPluginsService, + device + }; +} + +describe("previewAppPluginsService", () => { + describe("comparePluginsOnDevice", () => { + const testCases = [ + { + name: "should show warning for plugin not included in preview app", + localPlugins: { + "nativescript-facebook": "2.2.3", + "nativescript-theme-core": "~1.0.4", + "tns-core-modules": "~4.2.0" + }, + previewAppPlugins: { + "nativescript-theme-core": "~1.0.4", + "tns-core-modules": "~4.2.0" + }, + expectedWarnings: [ + util.format(PluginComparisonMessages.PLUGIN_NOT_INCLUDED_IN_PREVIEW_APP, "nativescript-facebook", deviceId) + ] + }, + { + name: "should show warnings for plugins not included in preview app", + localPlugins: { + "nativescript-facebook": "2.2.3", + "nativescript-theme-core": "~1.0.4", + "tns-core-modules": "~4.2.0" + }, + previewAppPlugins: { + }, + expectedWarnings: [ + util.format(PluginComparisonMessages.PLUGIN_NOT_INCLUDED_IN_PREVIEW_APP, "nativescript-facebook", deviceId), + util.format(PluginComparisonMessages.PLUGIN_NOT_INCLUDED_IN_PREVIEW_APP, "nativescript-theme-core", deviceId), + util.format(PluginComparisonMessages.PLUGIN_NOT_INCLUDED_IN_PREVIEW_APP, "tns-core-modules", deviceId) + ] + }, + { + name: "should not show warnings when all plugins are included in preview app", + localPlugins: { + "nativescript-theme-core": "1.0.4", + "nativescript-facebook": "2.2.3" + }, + previewAppPlugins: { + "nativescript-theme-core": "1.1.4", + "nativescript-facebook": "2.2.3" + }, + expectedWarnings: [] + }, + { + name: "should show warning when local plugin has lower major version", + localPlugins: { + "nativescript-theme-core": "2.0.0" + }, + previewAppPlugins: { + "nativescript-theme-core": "3.4.0" + }, + expectedWarnings: [ + util.format(PluginComparisonMessages.LOCAL_PLUGIN_WITH_DIFFERENCE_IN_MAJOR_VERSION, "nativescript-theme-core", "2.0.0", "3.4.0") + ] + }, + { + name: "should show warning when local plugin has greater major version", + localPlugins: { + "nativescript-theme-core": "4.0.0" + }, + previewAppPlugins: { + "nativescript-theme-core": "3.0.0" + }, + expectedWarnings: [ + util.format(PluginComparisonMessages.LOCAL_PLUGIN_WITH_DIFFERENCE_IN_MAJOR_VERSION, "nativescript-theme-core", "4.0.0", "3.0.0") + ] + }, + { + name: "should show warning when local plugin has greater minor version and the same major version", + localPlugins: { + "nativescript-theme-core": "3.5.0" + }, + previewAppPlugins: { + "nativescript-theme-core": "3.0.0" + }, + expectedWarnings: [ + util.format(PluginComparisonMessages.LOCAL_PLUGIN_WITH_GREATHER_MINOR_VERSION, "nativescript-theme-core", "3.5.0", "3.0.0") + ] + }, + { + name: "should not show warning when local plugin has lower minor version and the same major version", + localPlugins: { + "nativescript-theme-core": "3.1.0" + }, + previewAppPlugins: { + "nativescript-theme-core": "3.2.0" + }, + expectedWarnings: [] + }, + { + name: "should not show warning when plugins differ only in patch versions (lower local patch version)", + localPlugins: { + "nativescript-theme-core": "3.5.0" + }, + previewAppPlugins: { + "nativescript-theme-core": "3.5.1" + }, + expectedWarnings: [] + }, + { + name: "should not show warning when plugins differ only in patch versions (greater local patch version)", + localPlugins: { + "nativescript-theme-core": "3.5.1" + }, + previewAppPlugins: { + "nativescript-theme-core": "3.5.0" + }, + expectedWarnings: [] + } + ]; + + afterEach(() => { + warnParams = []; + readJsonParams = []; + }); + + for (const testCase of testCases) { + it(`${testCase.name}`, async () => { + const { previewAppPluginsService, device } = setup(testCase.localPlugins, testCase.previewAppPlugins); + + await previewAppPluginsService.comparePluginsOnDevice(device); + + assert.equal(warnParams.length, testCase.expectedWarnings.length); + testCase.expectedWarnings.forEach(warning => assert.include(warnParams, warning)); + }); + } + }); +}); diff --git a/test/stubs.ts b/test/stubs.ts index 572d1d3bbb..61c02ca43d 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -331,8 +331,8 @@ export class PlatformProjectServiceStub extends EventEmitter implements IPlatfor validateOptions(): Promise { return Promise.resolve(true); } - validate(): Promise { - return Promise.resolve(); + validate(): Promise { + return Promise.resolve({}); } validatePlugins(projectData: IProjectData) { return Promise.resolve(); diff --git a/test/update.ts b/test/update.ts index 3f9343d41c..42fb4bdbfa 100644 --- a/test/update.ts +++ b/test/update.ts @@ -14,7 +14,14 @@ function createTestInjector( installedPlatforms: string[] = [], availablePlatforms: string[] = [], projectDir: string = projectFolder, - validate: Function = (): Promise => Promise.resolve() + validate: Function = async (): Promise => { + return { + checkEnvironmentRequirementsOutput: { + canExecute: true, + selectedOption: "" + } + }; + } ): IInjector { const testInjector: IInjector = new yok.Yok(); testInjector.register("logger", stubs.LoggerStub); @@ -94,25 +101,25 @@ describe("update command method tests", () => { it("returns false if too many artuments", async () => { const testInjector = createTestInjector([], ["android"]); const updateCommand = testInjector.resolve(UpdateCommand); - const canExecute = updateCommand.canExecute(["333", "111", "444"]); + const canExecuteOutput = await updateCommand.canExecute(["333", "111", "444"]); - return assert.eventually.equal(canExecute, false); + return assert.equal(canExecuteOutput.canExecute, false); }); it("returns false if projectDir empty string", async () => { const testInjector = createTestInjector([], ["android"], ""); const updateCommand = testInjector.resolve(UpdateCommand); - const canExecute = updateCommand.canExecute([]); + const canExecuteOutput = await updateCommand.canExecute([]); - return assert.eventually.equal(canExecute, false); + return assert.equal(canExecuteOutput.canExecute, false); }); it("returns true all ok", async () => { const testInjector = createTestInjector([], ["android"]); const updateCommand = testInjector.resolve(UpdateCommand); - const canExecute = updateCommand.canExecute(["3.3.0"]); + const canExecuteOutput = await updateCommand.canExecute(["3.3.0"]); - return assert.eventually.equal(canExecute, true); + return assert.equal(canExecuteOutput.canExecute, true); }); });