HEX
Server: Apache/2.4.41 (Ubuntu)
System: Linux ip-172-31-42-149 5.15.0-1084-aws #91~20.04.1-Ubuntu SMP Fri May 2 07:00:04 UTC 2025 aarch64
User: ubuntu (1000)
PHP: 7.4.33
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: //proc/self/root/usr/local/lib/node_modules/@ionic/cli/commands/live-update/core.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LiveUpdatesConfCommand = exports.LiveUpdatesCoreCommand = void 0;
const tslib_1 = require("tslib");
const cli_framework_1 = require("@ionic/cli-framework");
const utils_fs_1 = require("@ionic/utils-fs");
const et = tslib_1.__importStar(require("elementtree"));
const path = tslib_1.__importStar(require("path"));
const color_1 = require("../../lib/color");
const command_1 = require("../../lib/command");
const errors_1 = require("../../lib/errors");
class LiveUpdatesCoreCommand extends command_1.Command {
    async getAppIntegration() {
        if (this.project) {
            if (this.project.getIntegration('capacitor') !== undefined) {
                return 'capacitor';
            }
            if (this.project.getIntegration('cordova') !== undefined) {
                return 'cordova';
            }
        }
        return undefined;
    }
    async requireNativeIntegration() {
        const integration = await this.getAppIntegration();
        if (!integration) {
            throw new errors_1.FatalException(`It looks like your app isn't integrated with Capacitor or Cordova.\n` +
                `In order to use the Ionic Live Updates plugin, you will need to integrate your app with Capacitor or Cordova. See the docs for setting up native projects:\n\n` +
                `iOS: ${(0, color_1.strong)('https://ionicframework.com/docs/building/ios')}\n` +
                `Android: ${(0, color_1.strong)('https://ionicframework.com/docs/building/android')}\n`);
        }
    }
}
exports.LiveUpdatesCoreCommand = LiveUpdatesCoreCommand;
class LiveUpdatesConfCommand extends LiveUpdatesCoreCommand {
    constructor() {
        super(...arguments);
        this.optionsToPlistKeys = {
            'app-id': 'IonAppId',
            'channel-name': 'IonChannelName',
            'update-method': 'IonUpdateMethod',
            'max-store': 'IonMaxVersions',
            'min-background-duration': 'IonMinBackgroundDuration',
            'update-api': 'IonApi',
        };
        this.optionsToStringXmlKeys = {
            'app-id': 'ionic_app_id',
            'channel-name': 'ionic_channel_name',
            'update-method': 'ionic_update_method',
            'max-store': 'ionic_max_versions',
            'min-background-duration': 'ionic_min_background_duration',
            'update-api': 'ionic_update_api',
        };
        this.requiredOptionsDefaults = {
            'max-store': '2',
            'min-background-duration': '30',
            'update-api': 'https://api.ionicjs.com',
        };
        this.requiredOptionsFromPlistVal = {
            'IonMaxVersions': 'max-store',
            'IonMinBackgroundDuration': 'min-background-duration',
            'IonApi': 'update-api',
        };
        this.requiredOptionsFromXmlVal = {
            'ionic_max_versions': 'max-store',
            'ionic_min_background_duration': 'min-background-duration',
            'ionic_update_api': 'update-api',
        };
    }
    async getAppId() {
        if (this.project) {
            return this.project.config.get('id');
        }
        return undefined;
    }
    async checkLiveUpdatesInstalled() {
        if (!this.project) {
            return false;
        }
        const packageJson = await this.project.requirePackageJson();
        return packageJson.dependencies ? 'cordova-plugin-ionic' in packageJson.dependencies : false;
    }
    printPlistInstructions(options) {
        let outputString = `You will need to manually modify the Info.plist for your iOS project.\n Please add the following content to your Info.plist file:\n`;
        for (const [optionKey, pKey] of Object.entries(this.optionsToPlistKeys)) {
            outputString = `${outputString}<key>${pKey}</key>\n<string>${options[optionKey]}</string>\n`;
        }
        this.env.log.warn(outputString);
    }
    printStringXmlInstructions(options) {
        let outputString = `You will need to manually modify the string.xml for your Android project.\n Please add the following content to your string.xml file:\n`;
        for (const [optionKey, pKey] of Object.entries(this.optionsToPlistKeys)) {
            outputString = `${outputString}<string name="${pKey}">${options[optionKey]}</string>\n`;
        }
        this.env.log.warn(outputString);
    }
    async getIosCapPlist() {
        if (!this.project) {
            return '';
        }
        const capIntegration = this.project.getIntegration('capacitor');
        if (!capIntegration) {
            return '';
        }
        // check first if iOS exists
        if (!await (0, utils_fs_1.pathExists)(path.join(capIntegration.root, 'ios'))) {
            return '';
        }
        const assumedPlistPath = path.join(capIntegration.root, 'ios', 'App', 'App', 'Info.plist');
        if (!await (0, utils_fs_1.pathWritable)(assumedPlistPath)) {
            throw new Error('The iOS Info.plist could not be found.');
        }
        return assumedPlistPath;
    }
    async getAndroidCapString() {
        if (!this.project) {
            return '';
        }
        const capIntegration = this.project.getIntegration('capacitor');
        if (!capIntegration) {
            return '';
        }
        // check first if iOS exists
        if (!await (0, utils_fs_1.pathExists)(path.join(capIntegration.root, 'android'))) {
            return '';
        }
        const assumedStringXmlPath = path.join(capIntegration.root, 'android', 'app', 'src', 'main', 'res', 'values', 'strings.xml');
        if (!await (0, utils_fs_1.pathWritable)(assumedStringXmlPath)) {
            throw new Error('The Android string.xml could not be found.');
        }
        return assumedStringXmlPath;
    }
    async addConfToIosPlist(options) {
        let plistPath;
        try {
            plistPath = await this.getIosCapPlist();
        }
        catch (e) {
            this.env.log.warn(e.message);
            this.printPlistInstructions(options);
            return false;
        }
        if (!plistPath) {
            this.env.log.warn(`No ${(0, color_1.strong)('Capacitor iOS')} project found\n` +
                `You will need to rerun ${(0, color_1.input)('ionic live-update configure')} if you add it later.\n`);
            return false;
        }
        // try to load the plist file first
        let plistData;
        try {
            const plistFile = await (0, utils_fs_1.readFile)(plistPath);
            plistData = plistFile.toString();
        }
        catch (e) {
            this.env.log.error(`The iOS Info.plist could not be read.`);
            this.printPlistInstructions(options);
            return false;
        }
        // parse it with elementtree
        let etree;
        try {
            etree = et.parse(plistData);
        }
        catch (e) {
            this.env.log.error(`Impossible to parse the XML in the Info.plist`);
            this.printPlistInstructions(options);
            return false;
        }
        // check that it is an actual plist file (root tag plist and first child dict)
        const root = etree.getroot();
        if (root.tag !== 'plist') {
            this.env.log.error(`Info.plist is not a valid plist file because the root is not a <plist> tag`);
            this.printPlistInstructions(options);
            return false;
        }
        const pdict = root.find('./dict');
        if (!pdict) {
            this.env.log.error(`Info.plist is not a valid plist file because the first child is not a <dict> tag`);
            this.printPlistInstructions(options);
            return false;
        }
        // check which options are set (configure might not have all of them set)
        const setOptions = {};
        for (const [optionKey, plistKey] of Object.entries(this.optionsToPlistKeys)) {
            if (options[optionKey]) {
                setOptions[optionKey] = plistKey;
            }
        }
        if (Object.entries(setOptions).length === 0) {
            this.env.log.warn(`No new options detected for Info.plist`);
            return false;
        }
        // because elementtree has limited XPath support we cannot just run a smart selection, so we need to loop over all the elements
        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] != undefined) {
                existingRequiredKeys.push(this.requiredOptionsFromPlistVal[element.text]);
            }
            // we remove all the existing element if there
            if ((element.tag === 'key') && (element.text) && Object.values(setOptions).includes(element.text)) {
                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];
            if (!plistValue) {
                throw new errors_1.FatalException(`This should never have happened: a parameter is missing so we cannot write the Info.plist`);
            }
            const pkey = et.SubElement(pdict, 'key');
            pkey.text = plistKey;
            const pstring = et.SubElement(pdict, 'string');
            pstring.text = plistValue;
        }
        // finally write back the modified plist
        const newXML = etree.write({
            encoding: 'utf-8',
            indent: 2,
            xml_declaration: false,
        });
        // elementtree cannot write a doctype, so little hack
        const xmlToWrite = `<?xml version="1.0" encoding="UTF-8"?>\n` +
            `<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n` +
            newXML;
        try {
            await (0, utils_fs_1.writeFile)(plistPath, xmlToWrite, { encoding: 'utf-8' });
        }
        catch (e) {
            this.env.log.error(`Changes to Info.plist could not be written.`);
            this.printPlistInstructions(options);
        }
        this.env.log.ok(`cordova-plugin-ionic variables correctly added to the iOS project`);
        return true;
    }
    async addConfToAndroidString(options) {
        let stringXmlPath;
        try {
            stringXmlPath = await this.getAndroidCapString();
        }
        catch (e) {
            this.env.log.warn(e.message);
            this.printPlistInstructions(options);
            return false;
        }
        if (!stringXmlPath) {
            this.env.log.warn(`No ${(0, color_1.strong)('Capacitor Android')} project found\n` +
                `You will need to rerun ${(0, color_1.input)('ionic live-update configure')} if you add it later.\n`);
            return false;
        }
        // try to load the plist file first
        let stringData;
        try {
            const stringFile = await (0, utils_fs_1.readFile)(stringXmlPath);
            stringData = stringFile.toString();
        }
        catch (e) {
            this.env.log.error(`The Android string.xml could not be read.`);
            this.printStringXmlInstructions(options);
            return false;
        }
        // parse it with elementtree
        let etree;
        try {
            etree = et.parse(stringData);
        }
        catch (e) {
            this.env.log.error(`Impossible to parse the XML in the string.xml`);
            this.printStringXmlInstructions(options);
            return false;
        }
        // check that it is an actual string.xml file (root tag is resources)
        const root = etree.getroot();
        if (root.tag !== 'resources') {
            this.env.log.error(`string.xml is not a valid android string.xml file because the root is not a <resources> tag`);
            this.printStringXmlInstructions(options);
            return false;
        }
        // check which options are set (configure might not have all of them set)
        const setOptions = {};
        for (const [optionKey, plistKey] of Object.entries(this.optionsToStringXmlKeys)) {
            if (options[optionKey]) {
                setOptions[optionKey] = plistKey;
            }
        }
        if (Object.entries(setOptions).length === 0) {
            this.env.log.warn(`No new options detected for string.xml`);
            return false;
        }
        for (const [optionKey, stringKey] of Object.entries(setOptions)) {
            let element = root.find(`./string[@name="${stringKey}"]`);
            // if the tag already exists, just update the content
            if (element) {
                element.text = options[optionKey];
            }
            else {
                // otherwise create the tag
                element = et.SubElement(root, 'string');
                element.set('name', stringKey);
                element.text = options[optionKey];
            }
        }
        // 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',
            indent: 2,
        });
        try {
            await (0, utils_fs_1.writeFile)(stringXmlPath, newXML, { encoding: 'utf-8' });
        }
        catch (e) {
            this.env.log.error(`Changes to string.xml could not be written.`);
            this.printStringXmlInstructions(options);
        }
        this.env.log.ok(`cordova-plugin-ionic variables correctly added to the Android project`);
        return true;
    }
    async preRunCheckInputs(options) {
        const updateMethodList = ['auto', 'background', 'none'];
        const defaultUpdateMethod = 'background';
        // handle the app-id option in case the user wants to override it
        if (!options['app-id'] && this.env.flags.interactive) {
            const appId = await this.getAppId();
            if (!appId) {
                this.env.log.warn(`No app ID found in the project.\n` +
                    `Consider running ${(0, color_1.input)('ionic link')} to connect local apps to Ionic.\n`);
            }
            const appIdOption = await this.env.prompt({
                type: 'input',
                name: 'app-id',
                message: `Appflow App ID:`,
                default: appId,
            });
            options['app-id'] = appIdOption;
        }
        if (!options['channel-name'] && this.env.flags.interactive) {
            options['channel-name'] = await this.env.prompt({
                type: 'input',
                name: 'channel-name',
                message: `Channel Name:`,
                validate: v => cli_framework_1.validators.required(v),
            });
        }
        // validate that the update-method is allowed
        let overrideUpdateMethodChoice = false;
        if (options['update-method'] && !updateMethodList.includes(options['update-method'])) {
            if (this.env.flags.interactive) {
                this.env.log.nl();
                this.env.log.warn(`${(0, color_1.input)(options['update-method'])} is not a valid update method.\n` +
                    `Please choose a different value for ${(0, color_1.input)('--update-method')}. Valid update methods are: ${updateMethodList.map(m => (0, color_1.input)(m)).join(', ')}\n`);
            }
            overrideUpdateMethodChoice = true;
        }
        if ((!options['update-method'] || overrideUpdateMethodChoice) && this.env.flags.interactive) {
            options['update-method'] = await this.env.prompt({
                type: 'list',
                name: 'update-method',
                choices: updateMethodList,
                message: `Update Method:`,
                default: defaultUpdateMethod,
                validate: v => (0, cli_framework_1.combine)(cli_framework_1.validators.required, (0, cli_framework_1.contains)(updateMethodList, {}))(v),
            });
        }
        // check advanced options if present
        if (options['max-store'] && cli_framework_1.validators.numeric(options['max-store']) !== true) {
            if (this.env.flags.interactive) {
                this.env.log.nl();
                this.env.log.warn(`${(0, color_1.input)(options['max-store'])} is not a valid value for the maximum number of versions to store.\n` +
                    `Please specify an integer for ${(0, color_1.input)('--max-store')}.\n`);
            }
            options['max-store'] = await this.env.prompt({
                type: 'input',
                name: 'max-store',
                message: `Max Store:`,
                validate: v => (0, cli_framework_1.combine)(cli_framework_1.validators.required, cli_framework_1.validators.numeric)(v),
            });
        }
        if (options['min-background-duration'] && cli_framework_1.validators.numeric(options['min-background-duration']) !== true) {
            if (this.env.flags.interactive) {
                this.env.log.nl();
                this.env.log.warn(`${(0, color_1.input)(options['min-background-duration'])} is not a valid value for the number of seconds to wait before checking for updates in the background.\n` +
                    `Please specify an integer for ${(0, color_1.input)('--min-background-duration')}.\n`);
            }
            options['min-background-duration'] = await this.env.prompt({
                type: 'input',
                name: 'min-background-duration',
                message: `Min Background Duration:`,
                validate: v => (0, cli_framework_1.combine)(cli_framework_1.validators.required, cli_framework_1.validators.numeric)(v),
            });
        }
        if (options['update-api'] && cli_framework_1.validators.url(options['update-api']) !== true) {
            if (this.env.flags.interactive) {
                this.env.log.nl();
                this.env.log.warn(`${(0, color_1.input)(options['update-api'])} is not a valid value for the URL of the API to use.\n` +
                    `Please specify a valid URL for ${(0, color_1.input)('--update-api')}.\n`);
            }
            options['update-api'] = await this.env.prompt({
                type: 'input',
                name: 'update-api',
                message: `Update Url:`,
                validate: v => (0, cli_framework_1.combine)(cli_framework_1.validators.required, cli_framework_1.validators.url)(v),
            });
        }
    }
}
exports.LiveUpdatesConfCommand = LiveUpdatesConfCommand;