Skip to content

Commit 286917f

Browse files
committed
fix(cordova): rely on package.json for plugins/platforms
closes #3984 fixes #4013
1 parent f4c7fc2 commit 286917f

File tree

11 files changed

+88
-78
lines changed

11 files changed

+88
-78
lines changed

packages/ionic/src/commands/cordova/base.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export abstract class CordovaCommand extends Command {
112112
`See ${strong(`https://ionicframework.com/docs/${this.project.type}/overview`)} for detailed information.\n`
113113
);
114114
}
115-
const { loadConfigXml } = await import('../../lib/integrations/cordova/config');
115+
const { loadCordovaConfig } = await import('../../lib/integrations/cordova/config');
116116

117117
await this.checkCordova(runinfo);
118118

@@ -130,7 +130,7 @@ export abstract class CordovaCommand extends Command {
130130
}
131131
}
132132

133-
const conf = await loadConfigXml(this.integration);
133+
const conf = await loadCordovaConfig(this.integration);
134134
conf.resetContentSrc();
135135
await conf.save();
136136
}

packages/ionic/src/commands/cordova/prepare.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ You may wish to use ${input('ionic cordova prepare')} if you run your project wi
6060
}
6161

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

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

84-
if (engines.length === 0 && platforms.length === 0) {
84+
if (configuredPlatforms.length === 0 && platforms.length === 0) {
8585
this.env.log.warn(
8686
`No platforms added to this project. Cannot prepare native platforms without any installed.\n` +
8787
`Run ${input('ionic cordova platform add <platform>')} to add native platforms.`

packages/ionic/src/commands/cordova/resources.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ Cordova reference documentation:
126126
}
127127

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

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

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

141-
const conf = await loadConfigXml(this.integration);
141+
const conf = await loadCordovaConfig(this.integration);
142142
const buildPlatforms = platform ? [platform] : await this.getBuildPlatforms();
143143

144144
if (buildPlatforms.length === 0) {

packages/ionic/src/commands/cordova/run.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { CommandInstanceInfo, CommandLineInputs, CommandLineOptions, CommandMeta
77
import { COMMON_BUILD_COMMAND_OPTIONS } from '../../lib/build';
88
import { input, strong, weak } from '../../lib/color';
99
import { FatalException, RunnerException } from '../../lib/errors';
10-
import { loadConfigXml } from '../../lib/integrations/cordova/config';
1110
import { getPackagePath } from '../../lib/integrations/cordova/project';
1211
import { filterArgumentsForCordova, generateOptionsForCordovaBuild } from '../../lib/integrations/cordova/utils';
1312
import { SUPPORTED_PLATFORMS, checkNativeRun, createNativeRunArgs, createNativeRunListArgs, getNativeTargets, runNativeRun } from '../../lib/native-run';
@@ -248,7 +247,8 @@ Just like with ${input('ionic cordova build')}, you can pass additional options
248247
}
249248

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

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

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

312313
if (!this.project) {

packages/ionic/src/commands/monitoring/syncmaps.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ By default, ${input('ionic monitoring syncmaps')} will upload the sourcemap file
3939
}
4040

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

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

5454
const cordova = this.project.requireIntegration('cordova');
55-
const conf = await loadConfigXml(cordova);
55+
const conf = await loadCordovaConfig(cordova);
5656
const cordovaInfo = conf.getProjectInfo();
5757

5858
const appVersion = cordovaInfo.version;

packages/ionic/src/definitions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export interface CordovaPackageJson extends PackageJson {
4949
cordova: {
5050
platforms: string[];
5151
plugins: {
52-
[key: string]: {};
52+
[key: string]: unknown;
5353
};
5454
};
5555
}

packages/ionic/src/guards.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export function isCordovaPackageJson(obj: any): obj is CordovaPackageJson {
2121
return obj &&
2222
typeof obj.name === 'string' &&
2323
typeof obj.cordova === 'object' &&
24-
typeof obj.cordova.platforms === 'object' &&
24+
Array.isArray(obj.cordova.platforms) &&
2525
typeof obj.cordova.plugins === 'object';
2626
}
2727

packages/ionic/src/lib/doctor/ailments/index.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { TreatableAilment } from '../../../definitions';
66
import { AppClient } from '../../app';
77
import { ancillary, input, strong } from '../../color';
88
import { getIonicRemote, isRepoInitialized } from '../../git';
9-
import { loadConfigXml } from '../../integrations/cordova/config';
9+
import { loadCordovaConfig } from '../../integrations/cordova/config';
1010
import { getPlatforms } from '../../integrations/cordova/project';
1111
import { pkgManagerArgs } from '../../utils/npm';
1212

@@ -219,11 +219,10 @@ export class UnsavedCordovaPlatforms extends Ailment {
219219
}
220220

221221
const platforms = await getPlatforms(cordova.root);
222-
const conf = await loadConfigXml(cordova);
223-
const engines = conf.getPlatformEngines();
224-
const engineNames = new Set([...engines.map(e => e.name)]);
222+
const conf = await loadCordovaConfig(cordova);
223+
const configuredPlatforms = new Set([...conf.getConfiguredPlatforms().map(e => e.name)]);
225224

226-
const configXmlDiff = platforms.filter(p => !engineNames.has(p));
225+
const configXmlDiff = platforms.filter(p => !configuredPlatforms.has(p));
227226

228227
return configXmlDiff.length > 0;
229228
}
@@ -252,7 +251,7 @@ export class DefaultCordovaBundleIdUsed extends Ailment {
252251
return false;
253252
}
254253

255-
const conf = await loadConfigXml(cordova);
254+
const conf = await loadCordovaConfig(cordova);
256255

257256
return conf.getBundleId() === 'io.ionic.starter';
258257
}

packages/ionic/src/lib/integrations/cordova/config.ts

Lines changed: 61 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,99 @@
11
import { prettyPath } from '@ionic/cli-framework/utils/format';
2+
import { readPackageJsonFile } from '@ionic/cli-framework/utils/node';
23
import { readFile, writeFile } from '@ionic/utils-fs';
34
import * as Debug from 'debug';
45
import * as et from 'elementtree';
56
import * as path from 'path';
67

7-
import { ProjectIntegration, ResourcesPlatform } from '../../../definitions';
8+
import { CordovaPackageJson, ProjectIntegration, ResourcesPlatform } from '../../../definitions';
9+
import { isCordovaPackageJson } from '../../../guards';
810
import { failure, input, strong } from '../../color';
911
import { FatalException } from '../../errors';
1012
import { shortid } from '../../utils/uuid';
1113

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

14-
export interface PlatformEngine {
16+
export interface ConfiguredPlatform {
1517
name: string;
16-
spec: string;
17-
[key: string]: string;
18+
spec?: string;
1819
}
1920

20-
export class ConfigXml {
21+
export class ConfigConfig {
2122
protected _doc?: et.ElementTree;
23+
protected _pkg?: CordovaPackageJson;
2224
protected _sessionid?: string;
2325
protected saving = false;
2426

25-
constructor(readonly filePath: string) {}
27+
constructor(readonly configXmlPath: string, readonly packageJsonPath: string) {}
2628

27-
get doc() {
29+
get doc(): et.ElementTree {
2830
if (!this._doc) {
2931
throw new Error('No doc loaded.');
3032
}
3133

3234
return this._doc;
3335
}
3436

35-
get sessionid() {
37+
get pkg(): CordovaPackageJson {
38+
if (!this._pkg) {
39+
throw new Error('No package.json loaded.');
40+
}
41+
42+
return this._pkg;
43+
}
44+
45+
get sessionid(): string {
3646
if (!this._sessionid) {
3747
throw new Error('No doc loaded.');
3848
}
3949

4050
return this._sessionid;
4151
}
4252

43-
static async load(filePath: string): Promise<ConfigXml> {
44-
if (!filePath) {
45-
throw new Error('Must supply file path.');
53+
static async load(configXmlPath: string, packageJsonPath: string): Promise<ConfigConfig> {
54+
if (!configXmlPath || !packageJsonPath) {
55+
throw new Error('Must supply file paths for config.xml and package.json.');
4656
}
4757

48-
const conf = new ConfigXml(filePath);
58+
const conf = new ConfigConfig(configXmlPath, packageJsonPath);
4959
await conf.reload();
5060

5161
return conf;
5262
}
5363

54-
async reload(): Promise<void> {
55-
const configFileContents = await readFile(this.filePath, { encoding: 'utf8' });
64+
protected async reload(): Promise<void> {
65+
const configXml = await readFile(this.configXmlPath, { encoding: 'utf8' });
5666

57-
if (!configFileContents) {
67+
if (!configXml) {
5868
throw new Error(`Cannot load empty config.xml file.`);
5969
}
6070

6171
try {
62-
this._doc = et.parse(configFileContents);
72+
this._doc = et.parse(configXml);
6373
this._sessionid = shortid();
6474
} catch (e) {
6575
throw new Error(`Cannot parse config.xml file: ${e.stack ? e.stack : e}`);
6676
}
77+
78+
const packageJson = await readPackageJsonFile(this.packageJsonPath);
79+
80+
if (isCordovaPackageJson(packageJson)) {
81+
this._pkg = packageJson;
82+
} else {
83+
this._pkg = { ...packageJson, cordova: { platforms: [], plugins: {} } };
84+
debug('Invalid package.json for Cordova. Missing or invalid Cordova entries in %O', this.packageJsonPath);
85+
}
6786
}
6887

6988
async save(): Promise<void> {
7089
if (!this.saving) {
7190
this.saving = true;
72-
await writeFile(this.filePath, this.write(), { encoding: 'utf8' });
91+
await writeFile(this.configXmlPath, this.write(), { encoding: 'utf8' });
7392
this.saving = false;
7493
}
7594
}
7695

77-
setName(name: string) {
96+
setName(name: string): void {
7897
const root = this.doc.getroot();
7998
let nameNode = root.find('name');
8099

@@ -85,12 +104,12 @@ export class ConfigXml {
85104
nameNode.text = name;
86105
}
87106

88-
setBundleId(bundleId: string) {
107+
setBundleId(bundleId: string): void {
89108
const root = this.doc.getroot();
90109
root.set('id', bundleId);
91110
}
92111

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

@@ -190,25 +209,16 @@ export class ConfigXml {
190209
return { id, name, version };
191210
}
192211

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

197-
return engines.map(engine => this.engineElementToPlatformEngine(engine));
215+
return this.pkg.cordova.platforms.map(platform => ({
216+
name: platform,
217+
spec: deps[`cordova-${platform}`],
218+
}));
198219
}
199220

200-
getPlatformEngine(platform: string): PlatformEngine | undefined {
201-
const root = this.doc.getroot();
202-
const engine = root.find(`engine[@name='${platform}']`);
203-
204-
if (!engine) {
205-
return undefined;
206-
}
207-
208-
return this.engineElementToPlatformEngine(engine);
209-
}
210-
211-
async ensurePlatformImages(platform: string, resourcesPlatform: ResourcesPlatform) {
221+
ensurePlatformImages(platform: string, resourcesPlatform: ResourcesPlatform): void {
212222
const root = this.doc.getroot();
213223
const orientation = this.getPreference('Orientation') || 'default';
214224

@@ -253,7 +263,7 @@ export class ConfigXml {
253263
}
254264
}
255265

256-
async ensureSplashScreenPreferences() {
266+
ensureSplashScreenPreferences(): void {
257267
const root = this.doc.getroot();
258268

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

282292
return contents;
283293
}
284-
285-
protected engineElementToPlatformEngine(engine: et.Element): PlatformEngine {
286-
const name = engine.get('name');
287-
const spec = engine.get('spec');
288-
289-
return { name: name ? name : '', spec: spec ? spec : '', ...engine.attrib };
290-
}
291294
}
292295

293-
export async function loadConfigXml(integration: Required<ProjectIntegration>): Promise<ConfigXml> {
294-
const filePath = path.resolve(integration.root, 'config.xml');
295-
debug(`Using config.xml: ${filePath}`);
296+
export async function loadCordovaConfig(integration: Required<ProjectIntegration>): Promise<ConfigConfig> {
297+
const configXmlPath = path.resolve(integration.root, 'config.xml');
298+
const packageJsonPath = path.resolve(integration.root, 'package.json');
299+
300+
debug('Loading Cordova Config (config.xml: %O, package.json: %O)', configXmlPath, packageJsonPath);
296301

297302
try {
298-
return await ConfigXml.load(filePath);
303+
return await ConfigConfig.load(configXmlPath, packageJsonPath);
299304
} catch (e) {
300305
const msg = e.code === 'ENOENT'
301-
? `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')}`
306+
? (
307+
`Could not find necessary file(s): ${strong('config.xml')}, ${strong('package.json')}.\n\n` +
308+
` - ${strong(prettyPath(configXmlPath))}\n` +
309+
` - ${strong(prettyPath(packageJsonPath))}\n\n` +
310+
`You can re-add the Cordova integration with the following command: ${input('ionic integrations enable cordova --add')}`
311+
)
302312
: failure(e.stack ? e.stack : e);
303313

304314
throw new FatalException(
305-
`Cannot load ${strong(prettyPath(filePath))}\n` +
315+
`Cannot load Cordova config.\n` +
306316
`${msg}`
307317
);
308318
}

packages/ionic/src/lib/integrations/cordova/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,10 @@ export class Integration extends BaseIntegration<ProjectIntegration> {
170170
}
171171

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

175175
const integration = this.e.project.requireIntegration('cordova');
176-
const conf = await loadConfigXml(integration);
176+
const conf = await loadCordovaConfig(integration);
177177

178178
conf.setName(name);
179179

0 commit comments

Comments
 (0)