diff --git a/packages/@ionic/cli/src/commands/deploy/add.ts b/packages/@ionic/cli/src/commands/deploy/add.ts index 729e9e437f..ae2b7ec4a5 100644 --- a/packages/@ionic/cli/src/commands/deploy/add.ts +++ b/packages/@ionic/cli/src/commands/deploy/add.ts @@ -2,7 +2,6 @@ import { MetadataGroup } from '@ionic/cli-framework'; import { CommandInstanceInfo, CommandLineInputs, CommandLineOptions, CommandMetadata } from '../../definitions'; import { input } from '../../lib/color'; -import { FatalException } from '../../lib/errors'; import { runCommand } from '../../lib/executor'; import { DeployConfCommand } from './core'; @@ -95,20 +94,12 @@ For Cordova projects it just takes care of running the proper Cordova CLI comman } async preRun(inputs: CommandLineInputs, options: CommandLineOptions): Promise { - // check if it is already installed - const alreadyAdded = await this.checkDeployInstalled(); - if (alreadyAdded) { - throw new FatalException( - `Appflow Deploy plugin is already installed.` - ); - } // check if there are native integration installed await this.requireNativeIntegration(); await this.preRunCheckInputs(options); } - async run(inputs: CommandLineInputs, options: CommandLineOptions, runinfo: CommandInstanceInfo): Promise { - const integration = await this.getAppIntegration(); + async addPlugin(options: CommandLineOptions, runinfo: CommandInstanceInfo, integration?: string) { if (integration === 'cordova') { let deployCommand = ['cordova', 'plugin', 'add', 'cordova-plugin-ionic']; const userOptions = this.buildCordovaDeployOptions(options); @@ -125,6 +116,22 @@ For Cordova projects it just takes care of running the proper Cordova CLI comman ); // install the plugin with npm await this.env.shell.run(installer, installerArgs, { stdio: 'inherit' }); + } + + } + + async run(inputs: CommandLineInputs, options: CommandLineOptions, runinfo: CommandInstanceInfo): Promise { + const integration = await this.getAppIntegration(); + + // check if it is already installed + const alreadyAdded = await this.checkDeployInstalled(); + if (!alreadyAdded) { + await this.addPlugin(options, runinfo, integration); + } else { + this.env.log.warn("Live Updates plugin already added. Reconfiguring only."); + } + + if (integration === 'capacitor') { // generate the manifest await runCommand(runinfo, ['deploy', 'manifest']); // run capacitor sync diff --git a/packages/@ionic/cli/src/commands/deploy/configure.ts b/packages/@ionic/cli/src/commands/deploy/configure.ts index c72a6add5c..d6b0f3ce68 100644 --- a/packages/@ionic/cli/src/commands/deploy/configure.ts +++ b/packages/@ionic/cli/src/commands/deploy/configure.ts @@ -4,6 +4,7 @@ import { CommandInstanceInfo, CommandMetadata } from '../../definitions'; import { input } from '../../lib/color'; import { FatalException } from '../../lib/errors'; + import { DeployConfCommand } from './core'; export class ConfigureCommand extends DeployConfCommand { diff --git a/packages/@ionic/cli/src/commands/deploy/core.ts b/packages/@ionic/cli/src/commands/deploy/core.ts index 17e6a7914e..38c4b1683f 100644 --- a/packages/@ionic/cli/src/commands/deploy/core.ts +++ b/packages/@ionic/cli/src/commands/deploy/core.ts @@ -36,7 +36,7 @@ export abstract class DeployCoreCommand extends Command { export abstract class DeployConfCommand extends DeployCoreCommand { - protected readonly optionsToPlistKeys = { + protected readonly optionsToPlistKeys: {[key: string]: string} = { 'app-id': 'IonAppId', 'channel-name': 'IonChannelName', 'update-method': 'IonUpdateMethod', @@ -53,6 +53,24 @@ export abstract class DeployConfCommand extends DeployCoreCommand { 'update-api': 'ionic_update_api', }; + protected readonly requiredOptionsDefaults: {[key: string]: string} = { + 'max-store': '2', + 'min-background-duration': '30', + 'update-api': 'https://api.ionicjs.com', + } + + protected readonly requiredOptionsFromPlistVal: {[key: string]: string} = { + 'IonMaxVersions': 'max-store', + 'IonMinBackgroundDuration': 'min-background-duration', + 'IonApi': 'update-api', + } + + protected readonly requiredOptionsFromXmlVal = { + 'ionic_max_versions': 'max-store', + 'ionic_min_background_duration': 'min-background-duration', + 'ionic_update_api': 'update-api', + } + protected async getAppId(): Promise { if (this.project) { return this.project.config.get('id'); @@ -132,7 +150,10 @@ export abstract class DeployConfCommand extends DeployCoreCommand { return false; } if (!plistPath) { - this.env.log.info(`No Capacitor iOS project found.`); + this.env.log.warn( + `No ${strong('Capacitor iOS')} project found\n` + + `You will need to rerun ${input('ionic deploy configure')} if you add it later.\n` + ); return false; } // try to load the plist file first @@ -169,6 +190,7 @@ export abstract class DeployConfCommand extends DeployCoreCommand { } // check which options are set (configure might not have all of them set) const setOptions: { [key: string]: string } = {}; + for (const [optionKey, plistKey] of Object.entries(this.optionsToPlistKeys)) { if (options[optionKey]) { setOptions[optionKey] = plistKey; @@ -183,19 +205,39 @@ export abstract class DeployConfCommand extends DeployCoreCommand { const pdictChildren = pdict.getchildren(); // there is no way to refer to a first right sibling in elementtree, so we use flags let removeNextStringTag = false; + let existingRequiredKeys = []; for (const element of pdictChildren) { + + // find required options and keep track of what is already existing + if ((element.tag === 'key') && (element.text) && this.requiredOptionsFromPlistVal[element.text as string] != undefined) { + existingRequiredKeys.push(this.requiredOptionsFromPlistVal[element.text as string]) + } + // we remove all the existing element if there if ((element.tag === 'key') && (element.text) && Object.values(setOptions).includes(element.text as string)) { pdict.remove(element); removeNextStringTag = true; continue; } + // and remove the first right sibling (this will happen at the next iteration of the loop if ((element.tag === 'string') && removeNextStringTag) { pdict.remove(element); removeNextStringTag = false; } } + + // set any missing required keys to default + for (const key of Object.keys(this.requiredOptionsDefaults)) { + if (existingRequiredKeys.includes(key)) { + continue; + } + setOptions[key] = this.optionsToPlistKeys[key]; + if (!options[key]) { + options[key] = this.requiredOptionsDefaults[key]; + } + } + // add again the new settings for (const [optionKey, plistKey] of Object.entries(setOptions)) { const plistValue = options[optionKey]; @@ -237,7 +279,9 @@ export abstract class DeployConfCommand extends DeployCoreCommand { return false; } if (!stringXmlPath) { - this.env.log.info(`No Capacitor Android project found.`); + this.env.log.warn( + `No ${strong('Capacitor Android')} project found\n`+ + `You will need to rerun ${input('ionic deploy configure')} if you add it later.\n`); return false; } // try to load the plist file first @@ -289,6 +333,22 @@ export abstract class DeployConfCommand extends DeployCoreCommand { element.text = options[optionKey] as string; } } + + // make sure required keys are set + for (const [stringKey, optionKey] of Object.entries(this.requiredOptionsFromXmlVal)) { + let element = root.find(`./string[@name="${stringKey}"]`); + // if the tag already exists, just update the content + if (element) { + continue; + } else { + // otherwise create the tag and set to default + element = et.SubElement(root, 'string'); + element.set('name', stringKey); + console.log(optionKey, 'opoitn key'); + element.text = this.requiredOptionsDefaults[optionKey]; + } + } + // write back the modified plist const newXML = etree.write({ encoding: 'utf-8',