| /* |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, |
| software distributed under the License is distributed on an |
| "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| KIND, either express or implied. See the License for the |
| specific language governing permissions and limitations |
| under the License. |
| */ |
| |
| var Q = require('q'); |
| var fs = require('fs'); |
| var path = require('path'); |
| var utils = require('./utils'); |
| var AppxManifest = require('./AppxManifest'); |
| var events = require('cordova-common').events; |
| var spawn = require('cordova-common').superspawn.spawn; |
| var CordovaError = require('cordova-common').CordovaError; |
| |
| // returns folder that contains package with chip architecture, |
| // build and project types specified by script parameters |
| module.exports.getPackage = function (projectType, buildtype, buildArch) { |
| var appPackages = path.resolve(path.join(__dirname, '..', '..', 'AppPackages')); |
| // reject promise if AppPackages folder doesn't exist |
| if (!fs.existsSync(appPackages)) { |
| return Q.reject('AppPackages folder doesn\'t exist'); |
| } |
| // find out and resolve paths for all folders inside AppPackages |
| var pkgDirs = fs.readdirSync(appPackages).map(function (relative) { |
| // resolve path to folder |
| return path.join(appPackages, relative); |
| }).filter(function (pkgDir) { |
| // check that it is a directory |
| return fs.statSync(pkgDir).isDirectory(); |
| }); |
| |
| for (var dirIndex = 0; dirIndex < pkgDirs.length; dirIndex++) { |
| var dir = pkgDirs[dirIndex]; |
| var packageFiles = fs.readdirSync(dir).filter(function (e) { |
| return e.match('.*.(appx|appxbundle)$'); |
| }); |
| |
| for (var pkgIndex = 0; pkgIndex < packageFiles.length; pkgIndex++) { |
| var pkgFile = packageFiles[pkgIndex]; |
| |
| var packageFile = path.join(dir, pkgFile); |
| var pkgInfo = module.exports.getPackageFileInfo(packageFile); |
| |
| if (pkgInfo && |
| pkgInfo.type === projectType && |
| pkgInfo.buildtype === buildtype && |
| ((pkgInfo.arch === buildArch) || |
| (pkgInfo.archs && pkgInfo.archs.indexOf(buildArch) > -1))) { |
| // if package's properties match properties provided |
| // resolve the promise with this package's info |
| return Q.resolve(pkgInfo); |
| } |
| } |
| } |
| |
| // reject because seems that no corresponding packages found |
| return Q.reject('Package with specified parameters not found in AppPackages folder'); |
| }; |
| |
| function getPackagePhoneProductId (packageFile) { |
| var windowsPlatformPath = path.join(packageFile, '..', '..', '..'); |
| return module.exports.getAppId(windowsPlatformPath); |
| } |
| |
| // returns package info object or null if it is not valid package |
| module.exports.getPackageFileInfo = function (packageFile) { |
| var pkgName = path.basename(packageFile); |
| |
| // CordovaApp.Windows_0.0.1.0_anycpu_debug.appx |
| // CordovaApp.Phone_0.0.1.0_x86_debug.appxbundle |
| // CordovaApp.Windows10_0.0.1.0_x64_x86_arm.appxbundle |
| |
| var props = /.*\.(Phone|Windows|Windows10)_((?:\d*\.)*\d*)*((?:_(AnyCPU|x86|x64|ARM)){1,4})(?:(_Debug))?.(appx|appxbundle)$/i.exec(pkgName); |
| if (props) { |
| return { |
| type: props[1].toLowerCase(), |
| arch: props[3].toLowerCase().substring(1), |
| archs: props[3].toLowerCase().substring(1).split('_'), |
| buildtype: props[5] ? props[5].substring(1).toLowerCase() : 'release', |
| appx: packageFile, |
| script: path.join(packageFile, '..', 'Add-AppDevPackage.ps1'), |
| phoneId: getPackagePhoneProductId(packageFile) |
| }; |
| } |
| return null; |
| }; |
| |
| // return package app ID fetched from appxmanifest |
| // return rejected promise if appxmanifest not valid |
| module.exports.getAppId = function (platformPath) { |
| try { |
| return AppxManifest.get(path.join(platformPath, 'package.phone.appxmanifest')) |
| .getPhoneIdentity().getPhoneProductId(); |
| } catch (e) { |
| throw new Error('Can\'t read appId from phone manifest', e); |
| } |
| }; |
| |
| // return package name fetched from appxmanifest |
| // return rejected promise if appxmanifest not valid |
| function getPackageName (platformPath) { |
| // Can reliably read from package.windows.appxmanifest even if targeting Windows 10 |
| // because the function is only used for desktop deployment, which always has the same |
| // package name when uninstalling / reinstalling |
| try { |
| return Q.when(AppxManifest.get(path.join(platformPath, 'package.windows.appxmanifest')) |
| .getIdentity().getName()); |
| } catch (e) { |
| return Q.reject('Can\'t read package name from manifest ' + e); |
| } |
| } |
| |
| // returns one of available devices which name match with provided string |
| // return rejected promise if device with name specified not found |
| module.exports.findDevice = function (deploymentTool, target) { |
| target = target.toLowerCase(); |
| return deploymentTool.enumerateDevices().then(function (deviceList) { |
| // CB-7617 since we use partial match shorter names should go first, |
| // example case is ['Emulator 8.1 WVGA 4 inch 512MB', 'Emulator 8.1 WVGA 4 inch'] |
| // In CB-9283, we need to differentiate between emulator, device, and target. |
| // So, for emulators to honor the above CB-7617, we preserve the original behavior. |
| // Else, we choose either the target by ID (DeviceInfo.index) or if it's just device, |
| // we choose the default (aka first) device. |
| if (target === 'emulator') { |
| var sortedList = deviceList.concat().sort(function (l, r) { return l.toString().length > r.toString().length; }); |
| for (var idx in sortedList) { |
| if (sortedList[idx].toString().toLowerCase().indexOf(target) > -1) { |
| // we should return index based on original list |
| return Q.resolve(sortedList[idx]); |
| } |
| } |
| } else if (target === 'device') { |
| return Q.resolve(deviceList[0]); |
| } else { |
| var candidateList = deviceList.filter(function (device) { |
| if (device.index === parseInt(target, 10)) { |
| return true; |
| } else if (device.name.toLowerCase().indexOf(target.toLowerCase()) >= 0) { |
| return true; |
| } else { |
| return false; |
| } |
| }); |
| |
| if (candidateList.length > 0) { |
| return candidateList[0]; |
| } |
| } |
| return Q.reject('Specified device not found'); |
| }); |
| }; |
| |
| // returns array of available devices names |
| module.exports.listDevices = function (deploymentTool) { |
| |
| return deploymentTool.enumerateDevices().then(function (deviceList) { |
| return deviceList.map(function (device) { |
| return device.toString(); |
| }); |
| |
| }, function (e) { |
| events.emit('error', new Error('Failed to list devices: ' + e)); |
| }); |
| }; |
| |
| function uninstallAppFromPhone (appDeployUtils, pkg, target) { |
| events.emit('log', 'Attempting to remove previously installed application...'); |
| return appDeployUtils.uninstallAppPackage(pkg.phoneId, target); |
| } |
| |
| // deploys specified phone package to device/emulator and launches it |
| module.exports.deployToPhone = function (pkg, deployTarget, targetWindows10, deploymentTool) { |
| var deployment; |
| if (deploymentTool) { |
| deployment = Q(deploymentTool); |
| } else { |
| deployment = utils.getAppDeployUtils(targetWindows10); |
| } |
| |
| return deployment.then(function (deploymentTool) { |
| return module.exports.findDevice(deploymentTool, deployTarget).then(function (target) { |
| return uninstallAppFromPhone(deploymentTool, pkg, target) |
| .then(function () {}, function () {}) |
| .then(function () { |
| // shouldUpdate = false because we've already uninstalled |
| events.emit('log', 'Deploying app package...'); |
| return deploymentTool.installAppPackage(pkg.appx, target, /* shouldLaunch */ true, /* shouldUpdate */ false); |
| }) |
| .then(function () { }, function (error) { |
| if (error.message.indexOf('Error code 2148734208 for command') === 0) { |
| return deploymentTool.installAppPackage(pkg.appx, target, /* shouldLaunch */ true, /* shouldUpdate */ true); |
| } else if (error.message.indexOf('Error code -2146233088') === 0) { |
| throw new CordovaError('No Windows Phone device was detected.'); |
| } else { |
| throw new CordovaError('Unexpected error from installation: ' + error.message + |
| ' You may have previously installed the app with an earlier version of cordova-windows.' + |
| ' Ensure the app is uninstalled from the phone and then try to run again.'); |
| } |
| }); |
| }); |
| }); |
| }; |
| |
| // deploys specified package to desktop |
| module.exports.deployToDesktop = function (pkg, deployTarget) { |
| if (deployTarget !== 'device' && deployTarget !== 'emulator') { |
| return Q.reject('Deploying desktop apps to specific target not supported'); |
| } |
| |
| return utils.getAppStoreUtils().then(function (appStoreUtils) { |
| return getPackageName(path.join(__dirname, '..', '..')).then(function (pkgname) { |
| |
| var oldArch; |
| // uninstalls previous application instance (if exists) |
| events.emit('log', 'Attempting to uninstall previous application version...'); |
| return spawn('powershell', ['-ExecutionPolicy', 'RemoteSigned', |
| 'Import-Module "' + appStoreUtils + '"; Uninstall-App ' + pkgname], { stdio: 'inherit' }) |
| .then(function () { |
| events.emit('log', 'Attempting to install application...'); |
| oldArch = process.env.PROCESSOR_ARCHITECTURE; |
| if (pkg.arch === 'x64') { |
| process.env.PROCESSOR_ARCHITECTURE = 'AMD64'; |
| } |
| return spawn('powershell', ['-ExecutionPolicy', 'RemoteSigned', |
| 'Import-Module "' + appStoreUtils + '"; Install-App', utils.quote(pkg.script)], { stdio: 'inherit' }); |
| }).then(function () { |
| process.env.PROCESSOR_ARCHITECTURE = oldArch; |
| events.emit('log', 'Starting application...'); |
| return spawn('powershell', ['-ExecutionPolicy', 'RemoteSigned', |
| 'Import-Module "' + appStoreUtils + '"; Start-Locally', pkgname], { stdio: 'inherit' }); |
| }, function (error) { |
| process.env.PROCESSOR_ARCHITECTURE = oldArch; |
| throw error; |
| }); |
| }); |
| }); |
| }; |