Skip to content

Commit

Permalink
fix(cordova): rely on package.json for plugins/platforms
Browse files Browse the repository at this point in the history
closes #3984
fixes #4013
  • Loading branch information
imhoffd committed Jun 26, 2019
1 parent f4c7fc2 commit 286917f
Show file tree
Hide file tree
Showing 11 changed files with 88 additions and 78 deletions.
4 changes: 2 additions & 2 deletions packages/ionic/src/commands/cordova/base.ts
Expand Up @@ -112,7 +112,7 @@ export abstract class CordovaCommand extends Command {
`See ${strong(`https://ionicframework.com/docs/${this.project.type}/overview`)} for detailed information.\n`
);
}
const { loadConfigXml } = await import('../../lib/integrations/cordova/config');
const { loadCordovaConfig } = await import('../../lib/integrations/cordova/config');

await this.checkCordova(runinfo);

Expand All @@ -130,7 +130,7 @@ export abstract class CordovaCommand extends Command {
}
}

const conf = await loadConfigXml(this.integration);
const conf = await loadCordovaConfig(this.integration);
conf.resetContentSrc();
await conf.save();
}
Expand Down
8 changes: 4 additions & 4 deletions packages/ionic/src/commands/cordova/prepare.ts
Expand Up @@ -60,7 +60,7 @@ You may wish to use ${input('ionic cordova prepare')} if you run your project wi
}

async run(inputs: CommandLineInputs, options: CommandLineOptions): Promise<void> {
const { loadConfigXml } = await import('../../lib/integrations/cordova/config');
const { loadCordovaConfig } = await import('../../lib/integrations/cordova/config');
const { getPlatforms } = await import('../../lib/integrations/cordova/project');
const [ platform ] = inputs;

Expand All @@ -77,11 +77,11 @@ You may wish to use ${input('ionic cordova prepare')} if you run your project wi
),
});
} else {
const conf = await loadConfigXml(this.integration);
const conf = await loadCordovaConfig(this.integration);
const platforms = await getPlatforms(this.integration.root);
const engines = conf.getPlatformEngines();
const configuredPlatforms = conf.getConfiguredPlatforms();

if (engines.length === 0 && platforms.length === 0) {
if (configuredPlatforms.length === 0 && platforms.length === 0) {
this.env.log.warn(
`No platforms added to this project. Cannot prepare native platforms without any installed.\n` +
`Run ${input('ionic cordova platform add <platform>')} to add native platforms.`
Expand Down
4 changes: 2 additions & 2 deletions packages/ionic/src/commands/cordova/resources.ts
Expand Up @@ -126,7 +126,7 @@ Cordova reference documentation:
}

async runResourceServer(platform: string | undefined, options: CommandLineOptions): Promise<void> {
const { loadConfigXml } = await import('../../lib/integrations/cordova/config');
const { loadCordovaConfig } = await import('../../lib/integrations/cordova/config');
const { addResourcesToConfigXml, createImgDestinationDirectories, findMostSpecificSourceImage, getImageResources, getSourceImages, transformResourceImage, uploadSourceImage } = await import('../../lib/integrations/cordova/resources');

const { force } = options;
Expand All @@ -138,7 +138,7 @@ Cordova reference documentation:

// await this.checkForPlatformInstallation(platform, { promptToInstall: true });

const conf = await loadConfigXml(this.integration);
const conf = await loadCordovaConfig(this.integration);
const buildPlatforms = platform ? [platform] : await this.getBuildPlatforms();

if (buildPlatforms.length === 0) {
Expand Down
7 changes: 4 additions & 3 deletions packages/ionic/src/commands/cordova/run.ts
Expand Up @@ -7,7 +7,6 @@ import { CommandInstanceInfo, CommandLineInputs, CommandLineOptions, CommandMeta
import { COMMON_BUILD_COMMAND_OPTIONS } from '../../lib/build';
import { input, strong, weak } from '../../lib/color';
import { FatalException, RunnerException } from '../../lib/errors';
import { loadConfigXml } from '../../lib/integrations/cordova/config';
import { getPackagePath } from '../../lib/integrations/cordova/project';
import { filterArgumentsForCordova, generateOptionsForCordovaBuild } from '../../lib/integrations/cordova/utils';
import { SUPPORTED_PLATFORMS, checkNativeRun, createNativeRunArgs, createNativeRunListArgs, getNativeTargets, runNativeRun } from '../../lib/native-run';
Expand Down Expand Up @@ -248,7 +247,8 @@ Just like with ${input('ionic cordova build')}, you can pass additional options
}

protected async runServeDeploy(inputs: CommandLineInputs, options: CommandLineOptions): Promise<void> {
const conf = await loadConfigXml(this.integration);
const { loadCordovaConfig } = await import('../../lib/integrations/cordova/config');
const conf = await loadCordovaConfig(this.integration);
const metadata = await this.getMetadata();

if (!this.project) {
Expand Down Expand Up @@ -306,7 +306,8 @@ Just like with ${input('ionic cordova build')}, you can pass additional options
}

protected async runBuildDeploy(inputs: CommandLineInputs, options: CommandLineOptions): Promise<void> {
const conf = await loadConfigXml(this.integration);
const { loadCordovaConfig } = await import('../../lib/integrations/cordova/config');
const conf = await loadCordovaConfig(this.integration);
const metadata = await this.getMetadata();

if (!this.project) {
Expand Down
4 changes: 2 additions & 2 deletions packages/ionic/src/commands/monitoring/syncmaps.ts
Expand Up @@ -39,7 +39,7 @@ By default, ${input('ionic monitoring syncmaps')} will upload the sourcemap file
}

async run(inputs: CommandLineInputs, options: CommandLineOptions): Promise<void> {
const { loadConfigXml } = await import('../../lib/integrations/cordova/config');
const { loadCordovaConfig } = await import('../../lib/integrations/cordova/config');

if (!this.project) {
throw new FatalException(`Cannot run ${input('ionic monitoring syncmaps')} outside a project directory.`);
Expand All @@ -52,7 +52,7 @@ By default, ${input('ionic monitoring syncmaps')} will upload the sourcemap file
const doBuild = options.build ? true : false;

const cordova = this.project.requireIntegration('cordova');
const conf = await loadConfigXml(cordova);
const conf = await loadCordovaConfig(cordova);
const cordovaInfo = conf.getProjectInfo();

const appVersion = cordovaInfo.version;
Expand Down
2 changes: 1 addition & 1 deletion packages/ionic/src/definitions.ts
Expand Up @@ -49,7 +49,7 @@ export interface CordovaPackageJson extends PackageJson {
cordova: {
platforms: string[];
plugins: {
[key: string]: {};
[key: string]: unknown;
};
};
}
Expand Down
2 changes: 1 addition & 1 deletion packages/ionic/src/guards.ts
Expand Up @@ -21,7 +21,7 @@ export function isCordovaPackageJson(obj: any): obj is CordovaPackageJson {
return obj &&
typeof obj.name === 'string' &&
typeof obj.cordova === 'object' &&
typeof obj.cordova.platforms === 'object' &&
Array.isArray(obj.cordova.platforms) &&
typeof obj.cordova.plugins === 'object';
}

Expand Down
11 changes: 5 additions & 6 deletions packages/ionic/src/lib/doctor/ailments/index.ts
Expand Up @@ -6,7 +6,7 @@ import { TreatableAilment } from '../../../definitions';
import { AppClient } from '../../app';
import { ancillary, input, strong } from '../../color';
import { getIonicRemote, isRepoInitialized } from '../../git';
import { loadConfigXml } from '../../integrations/cordova/config';
import { loadCordovaConfig } from '../../integrations/cordova/config';
import { getPlatforms } from '../../integrations/cordova/project';
import { pkgManagerArgs } from '../../utils/npm';

Expand Down Expand Up @@ -219,11 +219,10 @@ export class UnsavedCordovaPlatforms extends Ailment {
}

const platforms = await getPlatforms(cordova.root);
const conf = await loadConfigXml(cordova);
const engines = conf.getPlatformEngines();
const engineNames = new Set([...engines.map(e => e.name)]);
const conf = await loadCordovaConfig(cordova);
const configuredPlatforms = new Set([...conf.getConfiguredPlatforms().map(e => e.name)]);

const configXmlDiff = platforms.filter(p => !engineNames.has(p));
const configXmlDiff = platforms.filter(p => !configuredPlatforms.has(p));

return configXmlDiff.length > 0;
}
Expand Down Expand Up @@ -252,7 +251,7 @@ export class DefaultCordovaBundleIdUsed extends Ailment {
return false;
}

const conf = await loadConfigXml(cordova);
const conf = await loadCordovaConfig(cordova);

return conf.getBundleId() === 'io.ionic.starter';
}
Expand Down
112 changes: 61 additions & 51 deletions packages/ionic/src/lib/integrations/cordova/config.ts
@@ -1,80 +1,99 @@
import { prettyPath } from '@ionic/cli-framework/utils/format';
import { readPackageJsonFile } from '@ionic/cli-framework/utils/node';
import { readFile, writeFile } from '@ionic/utils-fs';
import * as Debug from 'debug';
import * as et from 'elementtree';
import * as path from 'path';

import { ProjectIntegration, ResourcesPlatform } from '../../../definitions';
import { CordovaPackageJson, ProjectIntegration, ResourcesPlatform } from '../../../definitions';
import { isCordovaPackageJson } from '../../../guards';
import { failure, input, strong } from '../../color';
import { FatalException } from '../../errors';
import { shortid } from '../../utils/uuid';

const debug = Debug('ionic:lib:integrations:cordova:config');

export interface PlatformEngine {
export interface ConfiguredPlatform {
name: string;
spec: string;
[key: string]: string;
spec?: string;
}

export class ConfigXml {
export class ConfigConfig {
protected _doc?: et.ElementTree;
protected _pkg?: CordovaPackageJson;
protected _sessionid?: string;
protected saving = false;

constructor(readonly filePath: string) {}
constructor(readonly configXmlPath: string, readonly packageJsonPath: string) {}

get doc() {
get doc(): et.ElementTree {
if (!this._doc) {
throw new Error('No doc loaded.');
}

return this._doc;
}

get sessionid() {
get pkg(): CordovaPackageJson {
if (!this._pkg) {
throw new Error('No package.json loaded.');
}

return this._pkg;
}

get sessionid(): string {
if (!this._sessionid) {
throw new Error('No doc loaded.');
}

return this._sessionid;
}

static async load(filePath: string): Promise<ConfigXml> {
if (!filePath) {
throw new Error('Must supply file path.');
static async load(configXmlPath: string, packageJsonPath: string): Promise<ConfigConfig> {
if (!configXmlPath || !packageJsonPath) {
throw new Error('Must supply file paths for config.xml and package.json.');
}

const conf = new ConfigXml(filePath);
const conf = new ConfigConfig(configXmlPath, packageJsonPath);
await conf.reload();

return conf;
}

async reload(): Promise<void> {
const configFileContents = await readFile(this.filePath, { encoding: 'utf8' });
protected async reload(): Promise<void> {
const configXml = await readFile(this.configXmlPath, { encoding: 'utf8' });

if (!configFileContents) {
if (!configXml) {
throw new Error(`Cannot load empty config.xml file.`);
}

try {
this._doc = et.parse(configFileContents);
this._doc = et.parse(configXml);
this._sessionid = shortid();
} catch (e) {
throw new Error(`Cannot parse config.xml file: ${e.stack ? e.stack : e}`);
}

const packageJson = await readPackageJsonFile(this.packageJsonPath);

if (isCordovaPackageJson(packageJson)) {
this._pkg = packageJson;
} else {
this._pkg = { ...packageJson, cordova: { platforms: [], plugins: {} } };
debug('Invalid package.json for Cordova. Missing or invalid Cordova entries in %O', this.packageJsonPath);
}
}

async save(): Promise<void> {
if (!this.saving) {
this.saving = true;
await writeFile(this.filePath, this.write(), { encoding: 'utf8' });
await writeFile(this.configXmlPath, this.write(), { encoding: 'utf8' });
this.saving = false;
}
}

setName(name: string) {
setName(name: string): void {
const root = this.doc.getroot();
let nameNode = root.find('name');

Expand All @@ -85,12 +104,12 @@ export class ConfigXml {
nameNode.text = name;
}

setBundleId(bundleId: string) {
setBundleId(bundleId: string): void {
const root = this.doc.getroot();
root.set('id', bundleId);
}

getBundleId() {
getBundleId(): string | undefined {
const root = this.doc.getroot();
return root.get('id');
}
Expand All @@ -99,7 +118,7 @@ export class ConfigXml {
* Update config.xml content src to be a dev server url. As part of this
* backup the original content src for a reset to occur at a later time.
*/
writeContentSrc(newSrc: string) {
writeContentSrc(newSrc: string): void {
const root = this.doc.getroot();
let contentElement = root.find('content');

Expand Down Expand Up @@ -190,25 +209,16 @@ export class ConfigXml {
return { id, name, version };
}

getPlatformEngines(): PlatformEngine[] {
const root = this.doc.getroot();
const engines = root.findall('engine');
getConfiguredPlatforms(): ConfiguredPlatform[] {
const deps: { [key: string]: string | undefined; } = { ...this.pkg.devDependencies, ...this.pkg.dependencies };

return engines.map(engine => this.engineElementToPlatformEngine(engine));
return this.pkg.cordova.platforms.map(platform => ({
name: platform,
spec: deps[`cordova-${platform}`],
}));
}

getPlatformEngine(platform: string): PlatformEngine | undefined {
const root = this.doc.getroot();
const engine = root.find(`engine[@name='${platform}']`);

if (!engine) {
return undefined;
}

return this.engineElementToPlatformEngine(engine);
}

async ensurePlatformImages(platform: string, resourcesPlatform: ResourcesPlatform) {
ensurePlatformImages(platform: string, resourcesPlatform: ResourcesPlatform): void {
const root = this.doc.getroot();
const orientation = this.getPreference('Orientation') || 'default';

Expand Down Expand Up @@ -253,7 +263,7 @@ export class ConfigXml {
}
}

async ensureSplashScreenPreferences() {
ensureSplashScreenPreferences(): void {
const root = this.doc.getroot();

let splashScreenPrefElement = root.find(`preference[@name='SplashScreen']`);
Expand Down Expand Up @@ -281,28 +291,28 @@ export class ConfigXml {

return contents;
}

protected engineElementToPlatformEngine(engine: et.Element): PlatformEngine {
const name = engine.get('name');
const spec = engine.get('spec');

return { name: name ? name : '', spec: spec ? spec : '', ...engine.attrib };
}
}

export async function loadConfigXml(integration: Required<ProjectIntegration>): Promise<ConfigXml> {
const filePath = path.resolve(integration.root, 'config.xml');
debug(`Using config.xml: ${filePath}`);
export async function loadCordovaConfig(integration: Required<ProjectIntegration>): Promise<ConfigConfig> {
const configXmlPath = path.resolve(integration.root, 'config.xml');
const packageJsonPath = path.resolve(integration.root, 'package.json');

debug('Loading Cordova Config (config.xml: %O, package.json: %O)', configXmlPath, packageJsonPath);

try {
return await ConfigXml.load(filePath);
return await ConfigConfig.load(configXmlPath, packageJsonPath);
} catch (e) {
const msg = e.code === 'ENOENT'
? `Cordova ${strong('config.xml')} file not found.\n\nYou can re-add the Cordova integration with the following command: ${input('ionic integrations enable cordova --add')}`
? (
`Could not find necessary file(s): ${strong('config.xml')}, ${strong('package.json')}.\n\n` +
` - ${strong(prettyPath(configXmlPath))}\n` +
` - ${strong(prettyPath(packageJsonPath))}\n\n` +
`You can re-add the Cordova integration with the following command: ${input('ionic integrations enable cordova --add')}`
)
: failure(e.stack ? e.stack : e);

throw new FatalException(
`Cannot load ${strong(prettyPath(filePath))}\n` +
`Cannot load Cordova config.\n` +
`${msg}`
);
}
Expand Down
4 changes: 2 additions & 2 deletions packages/ionic/src/lib/integrations/cordova/index.ts
Expand Up @@ -170,10 +170,10 @@ export class Integration extends BaseIntegration<ProjectIntegration> {
}

async personalize({ name, packageId }: ProjectPersonalizationDetails) {
const { loadConfigXml } = await import('./config');
const { loadCordovaConfig } = await import('./config');

const integration = this.e.project.requireIntegration('cordova');
const conf = await loadConfigXml(integration);
const conf = await loadCordovaConfig(integration);

conf.setName(name);

Expand Down

0 comments on commit 286917f

Please sign in to comment.