Skip to content

Commit ab2d675

Browse files
committed
use platform definitions for which platforms to generate resources
1 parent 8106328 commit ab2d675

File tree

8 files changed

+148
-41
lines changed

8 files changed

+148
-41
lines changed

README.md

+10
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@ To generate resources with all the default options, just run:
2828
$ cordova-res
2929
```
3030

31+
`cordova-res` accepts a platform for the first argument. If specified, resources are generated only for that platform:
32+
33+
```bash
34+
$ cordova-res ios
35+
```
36+
37+
Otherwise, `cordova-res` looks for platforms in `config.xml` (e.g. `<platform name="ios">`) and generates resources only for them.
38+
39+
#### Documentation
40+
3141
See the help documentation on the command line with the `--help` flag.
3242

3343
```bash

src/__tests__/cli.ts

+41-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,50 @@
1-
import { generateRunOptions } from '../cli';
1+
import { Options } from '..';
2+
import { generateRunOptions, parseOptions } from '../cli';
23
import { Platform } from '../platform';
34

45
describe('cordova-res', () => {
56

67
describe('cli', () => {
78

9+
describe('parseOptions', () => {
10+
11+
const DEFAULT_OPTIONS: Options = {
12+
logstream: process.stdout,
13+
errstream: process.stderr,
14+
resourcesDirectory: 'resources',
15+
};
16+
17+
it('should parse default options with no arguments', () => {
18+
const result = parseOptions([]);
19+
expect(result).toEqual(DEFAULT_OPTIONS);
20+
});
21+
22+
it('should parse options for android', () => {
23+
const args = ['android'];
24+
const result = parseOptions(args);
25+
expect(result).toEqual({ ...DEFAULT_OPTIONS, platforms: { android: generateRunOptions(Platform.ANDROID, 'resources', args) } });
26+
});
27+
28+
it('should parse default options when the first argument is not a platform', () => {
29+
const args = ['--help', 'android'];
30+
const result = parseOptions(args);
31+
expect(result).toEqual(DEFAULT_OPTIONS);
32+
});
33+
34+
it('should accept --resources flag', () => {
35+
const args = ['--resources', 'res'];
36+
const result = parseOptions(args);
37+
expect(result).toEqual({ ...DEFAULT_OPTIONS, resourcesDirectory: 'res' });
38+
});
39+
40+
it('should log to stderr with --json flag', () => {
41+
const args = ['--json'];
42+
const result = parseOptions(args);
43+
expect(result).toEqual({ ...DEFAULT_OPTIONS, logstream: process.stderr });
44+
});
45+
46+
});
47+
848
describe('generateRunOptions', () => {
949

1050
it('should provide defaults with no args', async () => {

src/__tests__/config.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ describe('cordova-res', () => {
2525
const src = 'resources/icon.png';
2626
const container = et.Element('platform');
2727

28-
runResource('/path/to/config.xml', resource, container);
28+
runResource('/path/to/config.xml', container, resource);
2929

3030
const children = container.findall('icon');
3131
expect(children.length).toEqual(1);
@@ -38,7 +38,7 @@ describe('cordova-res', () => {
3838
const container = et.Element('platform');
3939
et.SubElement(container, 'icon', { src });
4040

41-
runResource('/path/to/config.xml', resource, container);
41+
runResource('/path/to/config.xml', container, resource);
4242

4343
const children = container.findall('icon');
4444
expect(children.length).toEqual(1);
@@ -51,7 +51,7 @@ describe('cordova-res', () => {
5151
const container = et.Element('platform');
5252
et.SubElement(container, 'icon', { src: 'resources\\icon.png' });
5353

54-
runResource('/path/to/config.xml', resource, container);
54+
runResource('/path/to/config.xml', container, resource);
5555

5656
const children = container.findall('icon');
5757
expect(children.length).toEqual(1);

src/cli.ts

+31-10
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,49 @@
1+
import et from 'elementtree';
2+
13
import { Options, PlatformOptions } from '.';
4+
import { getPlatforms } from './config';
25
import { BadInputError } from './error';
3-
import { AdaptiveIconResourceOptions, PLATFORMS, Platform, RunPlatformOptions, SimpleResourceOptions, validatePlatforms } from './platform';
6+
import { AdaptiveIconResourceOptions, Platform, RunPlatformOptions, SimpleResourceOptions, validatePlatforms } from './platform';
47
import { DEFAULT_RESOURCES_DIRECTORY, RESOURCE_TYPES, ResourceKey, ResourceType, Source, SourceType, validateResourceTypes } from './resources';
58
import { getOptionValue } from './utils/cli';
69

10+
export function getDirectory(): string {
11+
return process.cwd();
12+
}
13+
14+
export async function resolveOptions(args: ReadonlyArray<string>, directory: string, config?: et.ElementTree): Promise<Options> {
15+
const doc = config ? config.getroot() : undefined;
16+
const platformList = validatePlatforms(doc ? getPlatforms(doc) : []);
17+
const parsedOptions = parseOptions(args);
18+
const { resourcesDirectory = DEFAULT_RESOURCES_DIRECTORY } = parsedOptions;
19+
20+
return {
21+
...{ directory, ...platformList.length > 0 ? { platforms: generatePlatformOptions(platformList, resourcesDirectory, args) } : {} },
22+
...parsedOptions,
23+
};
24+
}
25+
726
export function parseOptions(args: ReadonlyArray<string>): Options {
8-
const platformArg = args[0] ? args[0] : undefined;
9-
const platformList = validatePlatforms(platformArg && !platformArg.startsWith('-') ? [platformArg] : PLATFORMS);
10-
const platforms: PlatformOptions = {};
11-
const resourcesDirectory = getOptionValue(args, '--resources', DEFAULT_RESOURCES_DIRECTORY);
1227
const json = args.includes('--json');
28+
const resourcesDirectory = getOptionValue(args, '--resources', DEFAULT_RESOURCES_DIRECTORY);
29+
const platformArg = args[0] ? args[0] : undefined;
30+
const platformList = validatePlatforms(platformArg && !platformArg.startsWith('-') ? [platformArg] : []);
1331

1432
return {
15-
directory: process.cwd(),
1633
resourcesDirectory,
1734
logstream: json ? process.stderr : process.stdout,
1835
errstream: process.stderr,
19-
platforms: platformList.reduce((acc, platform) => {
20-
acc[platform] = generateRunOptions(platform, resourcesDirectory, args);
21-
return acc;
22-
}, platforms),
36+
...platformList.length > 0 ? { platforms: generatePlatformOptions(platformList, resourcesDirectory, args) } : {},
2337
};
2438
}
2539

40+
export function generatePlatformOptions(platforms: ReadonlyArray<Platform>, resourcesDirectory: string, args: ReadonlyArray<string>): PlatformOptions {
41+
return platforms.reduce((acc, platform) => {
42+
acc[platform] = generateRunOptions(platform, resourcesDirectory, args);
43+
return acc;
44+
}, {} as PlatformOptions);
45+
}
46+
2647
export function generateRunOptions(platform: Platform, resourcesDirectory: string, args: ReadonlyArray<string>): RunPlatformOptions {
2748
const typeOption = getOptionValue(args, '--type');
2849
const types = validateResourceTypes(typeOption ? [typeOption] : RESOURCE_TYPES);

src/config.ts

+20-13
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@ import { ResolvedColorSource, ResolvedSource, ResourceNodeAttribute, ResourceNod
99

1010
const debug = Debug('cordova-res:config');
1111

12-
export async function run(configPath: string, resourcesDirectory: string, sources: ReadonlyArray<ResolvedSource>, resources: ReadonlyArray<GeneratedResource>, errstream?: NodeJS.WritableStream): Promise<void> {
12+
export function getConfigPath(directory: string): string {
13+
return pathlib.resolve(directory, 'config.xml');
14+
}
15+
16+
export async function run(configPath: string, resourcesDirectory: string, doc: et.ElementTree, sources: ReadonlyArray<ResolvedSource>, resources: ReadonlyArray<GeneratedResource>, errstream?: NodeJS.WritableStream): Promise<void> {
1317
const colors = sources.filter((source): source is ResolvedColorSource => source.type === SourceType.COLOR);
14-
const config = await read(configPath);
1518

16-
const androidPlatformElement = resolvePlatformElement(config.getroot(), Platform.ANDROID);
19+
const androidPlatformElement = resolvePlatformElement(doc.getroot(), Platform.ANDROID);
1720

1821
if (colors.length > 0) {
1922
debug('Color sources found--generating colors document.');
@@ -31,9 +34,7 @@ export async function run(configPath: string, resourcesDirectory: string, source
3134
resourceFileElement.set('target', '/app/src/main/res/values/colors.xml');
3235
}
3336

34-
runConfig(configPath, resources, config, errstream);
35-
36-
await write(configPath, config);
37+
runConfig(configPath, doc, resources, errstream);
3738
}
3839

3940
export async function resolveColorsDocument(colorsPath: string): Promise<et.ElementTree> {
@@ -69,9 +70,9 @@ export async function runColorsConfig(colorsPath: string, colors: ReadonlyArray<
6970
await write(colorsPath, colorsDocument);
7071
}
7172

72-
export function runConfig(configPath: string, resources: ReadonlyArray<GeneratedResource>, doc: et.ElementTree, errstream?: NodeJS.WritableStream): void {
73+
export function runConfig(configPath: string, doc: et.ElementTree, resources: ReadonlyArray<GeneratedResource>, errstream?: NodeJS.WritableStream): void {
7374
const root = doc.getroot();
74-
const orientationPreference = getPreference(doc, 'Orientation');
75+
const orientationPreference = getPreference(root, 'Orientation');
7576
debug('Orientation preference: %O', orientationPreference);
7677

7778
const orientation = orientationPreference || 'default';
@@ -89,7 +90,7 @@ export function runConfig(configPath: string, resources: ReadonlyArray<Generated
8990
filteredResources = filteredResources.filter(img => typeof img.target === 'string');
9091
}
9192
for (const resource of filteredResources) {
92-
runResource(configPath, resource, platformElement);
93+
runResource(configPath, platformElement, resource);
9394
}
9495
}
9596
}
@@ -98,7 +99,7 @@ export function conformPath(configPath: string, value: string | number): string
9899
return pathlib.relative(pathlib.dirname(configPath), value.toString()).replace(/\\/g, '/');
99100
}
100101

101-
export function runResource(configPath: string, resource: GeneratedResource, container: et.Element): void {
102+
export function runResource(configPath: string, container: et.Element, resource: GeneratedResource): void {
102103
const src = resource[resource.indexAttribute.key];
103104

104105
if (typeof src !== 'string') {
@@ -181,9 +182,15 @@ export async function write(path: string, doc: et.ElementTree): Promise<void> {
181182
await writeFile(path, contents, 'utf8');
182183
}
183184

184-
export function getPreference(doc: et.ElementTree, name: string): string | undefined {
185-
const root = doc.getroot();
186-
const preferenceElement = root.find(`preference[@name='${name}']`);
185+
export function getPlatforms(container: et.Element): string[] {
186+
const platformElements = container.findall('platform');
187+
const platforms = platformElements.map(el => el.get('name'));
188+
189+
return platforms.filter((p): p is string => typeof p === 'string');
190+
}
191+
192+
export function getPreference(container: et.Element, name: string): string | undefined {
193+
const preferenceElement = container.find(`preference[@name='${name}']`);
187194

188195
if (!preferenceElement) {
189196
return undefined;

src/help.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
const help = `
22
Usage: cordova-res [ios|android] [options]
33
4-
Generate Cordova resources for one or all platforms.
4+
Generate Cordova resources for native platforms.
5+
6+
This tool writes to 'config.xml' to register resources with Cordova. Valid
7+
platform definitions are required. See the Cordova docs[1] for more
8+
information.
59
610
By default, this tool will look for 'icon.(png|jpg)' and 'splash.(png|jpg)'
711
files inside 'resources/'. If an 'icon-foreground.(png|jpg)' file is found
@@ -18,6 +22,8 @@ const help = `
1822
'icon-background.(png|jpg)' file in 'resources/android/'. To use a color,
1923
specify a hex color, e.g. '--icon-background-source #FF0000'.
2024
25+
[1]: https://cordova.apache.org/docs/en/latest/config_ref/index.html#platform
26+
2127
Options:
2228
2329
--type <icon|splash|adaptive-icon> ... Only generate one type of resource

src/index.ts

+25-13
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { pathWritable } from '@ionic/utils-fs';
22
import Debug from 'debug';
3+
import et from 'elementtree';
34
import path from 'path';
45

5-
import { generateRunOptions, parseOptions } from './cli';
6-
import { run as runConfig } from './config';
6+
import { generateRunOptions, getDirectory, resolveOptions } from './cli';
7+
import { getConfigPath, read as readConfig, run as runConfig, write as writeConfig } from './config';
78
import { BaseError } from './error';
89
import { GeneratedResource, PLATFORMS, Platform, RunPlatformOptions, run as runPlatform } from './platform';
910
import { DEFAULT_RESOURCES_DIRECTORY, Density, Orientation, ResolvedSource, SourceType } from './resources';
11+
import { tryFn } from './utils/fn';
1012

1113
const debug = Debug('cordova-res');
1214

@@ -32,7 +34,7 @@ interface ResultSource {
3234
}
3335

3436
async function CordovaRes({
35-
directory = process.cwd(),
37+
directory = getDirectory(),
3638
resourcesDirectory = DEFAULT_RESOURCES_DIRECTORY,
3739
logstream = process.stdout,
3840
errstream = process.stderr,
@@ -42,13 +44,24 @@ async function CordovaRes({
4244
[Platform.WINDOWS]: generateRunOptions(Platform.WINDOWS, resourcesDirectory, []),
4345
},
4446
}: CordovaRes.Options = {}): Promise<Result> {
45-
const configPath = path.resolve(directory, 'config.xml');
47+
const configPath = getConfigPath(directory);
4648

4749
debug('Paths: (config: %O) (resources dir: %O)', configPath, resourcesDirectory);
4850

51+
let config: et.ElementTree | undefined;
4952
const resources: GeneratedResource[] = [];
5053
const sources: ResolvedSource[] = [];
5154

55+
if (await pathWritable(configPath)) {
56+
config = await readConfig(configPath);
57+
} else {
58+
debug('File missing/not writable: %O', configPath);
59+
60+
if (errstream) {
61+
errstream.write(`WARN: No config.xml file in directory. Skipping config.\n`);
62+
}
63+
}
64+
5265
for (const platform of PLATFORMS) {
5366
const platformOptions = platforms[platform];
5467

@@ -60,15 +73,11 @@ async function CordovaRes({
6073
}
6174
}
6275

63-
if (await pathWritable(configPath)) {
64-
await runConfig(configPath, resourcesDirectory, sources, resources, errstream);
65-
logstream.write(`Wrote to config.xml\n`);
66-
} else {
67-
debug('File missing/not writable: %O', configPath);
76+
if (config) {
77+
await runConfig(configPath, resourcesDirectory, config, sources, resources, errstream);
78+
await writeConfig(configPath, config);
6879

69-
if (errstream) {
70-
errstream.write(`WARN: No config.xml file in directory. Skipping config.\n`);
71-
}
80+
logstream.write(`Wrote to config.xml\n`);
7281
}
7382

7483
return {
@@ -162,7 +171,10 @@ namespace CordovaRes {
162171
}
163172

164173
try {
165-
const options = parseOptions(args);
174+
const directory = getDirectory();
175+
const configPath = getConfigPath(directory);
176+
const config = await tryFn(() => readConfig(configPath));
177+
const options = await resolveOptions(args, directory, config);
166178
const result = await run(options);
167179

168180
if (args.includes('--json')) {

src/utils/fn.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import Debug from 'debug';
2+
3+
const debug = Debug('cordova-res:utils:fn');
4+
5+
export async function tryFn<T>(fn: (() => Promise<T>)): Promise<T | undefined> {
6+
try {
7+
return await fn();
8+
} catch (e) {
9+
debug('Encountered error when trying function: %O', fn);
10+
}
11+
}

0 commit comments

Comments
 (0)