Skip to content

Commit 58fb49e

Browse files
authored
feat: --copy for copying generated resources to native projects (#85)
1 parent 78c12a0 commit 58fb49e

File tree

5 files changed

+208
-0
lines changed

5 files changed

+208
-0
lines changed

src/__tests__/cli.ts

+5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ describe('cordova-res', () => {
1212
logstream: process.stdout,
1313
errstream: process.stderr,
1414
resourcesDirectory: 'resources',
15+
nativeProject: {
16+
enabled: false,
17+
androidProjectDirectory: '',
18+
iosProjectDirectory: '',
19+
},
1520
};
1621

1722
it('should parse default options with no arguments', () => {

src/cli.ts

+7
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import et from 'elementtree';
33
import { Options, PlatformOptions } from '.';
44
import { getPlatforms } from './config';
55
import { BadInputError } from './error';
6+
import { NativeProject } from './native';
67
import { AdaptiveIconResourceOptions, Platform, RunPlatformOptions, SimpleResourceOptions, filterSupportedPlatforms, validatePlatforms } from './platform';
78
import { DEFAULT_RESOURCES_DIRECTORY, RESOURCE_TYPES, ResourceKey, ResourceType, Source, SourceType, validateResourceTypes } from './resources';
89
import { getOptionValue } from './utils/cli';
@@ -28,12 +29,18 @@ export function parseOptions(args: readonly string[]): Options {
2829
const resourcesDirectory = getOptionValue(args, '--resources', DEFAULT_RESOURCES_DIRECTORY);
2930
const platformArg = args[0] ? args[0] : undefined;
3031
const platformList = validatePlatforms(platformArg && !platformArg.startsWith('-') ? [platformArg] : []);
32+
const nativeProject: Readonly<NativeProject> = {
33+
enabled: args.includes('--copy'),
34+
androidProjectDirectory: getOptionValue(args, '--android-project', ''),
35+
iosProjectDirectory: getOptionValue(args, '--ios-project', ''),
36+
};
3137

3238
return {
3339
resourcesDirectory,
3440
logstream: json ? process.stderr : process.stdout,
3541
errstream: process.stderr,
3642
...platformList.length > 0 ? { platforms: generatePlatformOptions(platformList, resourcesDirectory, args) } : {},
43+
nativeProject,
3744
};
3845
}
3946

src/help.ts

+4
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ const help = `
3434
--icon-foreground-source <path> ...... Use file for foreground of adaptive icon
3535
--icon-background-source <path|hex> .. Use file or color for background of adaptive icon
3636
37+
--copy ............................... Enable process of copying generated resources to native projects
38+
--android-project <path> ............. Use specified directory for Android native project (default: 'android')
39+
--ios-project <path> ................. Use specified directory for iOS native project (default: 'ios')
40+
3741
-h, --help ........................... Print help for the platform, then quit
3842
--version ............................ Print version, then quit
3943
--verbose ............................ Print verbose output to stderr

src/index.ts

+16
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import path from 'path';
66
import { generateRunOptions, getDirectory, resolveOptions } from './cli';
77
import { getConfigPath, read as readConfig, run as runConfig, write as writeConfig } from './config';
88
import { BaseError } from './error';
9+
import { NativeProject, copyToNativeProject } from './native';
910
import { GeneratedResource, PLATFORMS, Platform, RunPlatformOptions, run as runPlatform } from './platform';
1011
import { DEFAULT_RESOURCES_DIRECTORY, Density, Orientation, ResolvedSource, SourceType } from './resources';
1112
import { tryFn } from './utils/fn';
@@ -43,6 +44,11 @@ async function CordovaRes({
4344
[Platform.IOS]: generateRunOptions(Platform.IOS, resourcesDirectory, []),
4445
[Platform.WINDOWS]: generateRunOptions(Platform.WINDOWS, resourcesDirectory, []),
4546
},
47+
nativeProject = {
48+
enabled: false,
49+
androidProjectDirectory: '',
50+
iosProjectDirectory: '',
51+
},
4652
}: CordovaRes.Options = {}): Promise<Result> {
4753
const configPath = getConfigPath(directory);
4854

@@ -70,6 +76,9 @@ async function CordovaRes({
7076
logstream.write(`Generated ${platformResult.resources.length} resources for ${platform}\n`);
7177
resources.push(...platformResult.resources);
7278
sources.push(...platformResult.sources);
79+
if (nativeProject.enabled) {
80+
await copyToNativeProject(platform, nativeProject, logstream);
81+
}
7382
}
7483
}
7584

@@ -156,6 +165,13 @@ namespace CordovaRes {
156165
* provided, resources are generated in an explicit, opt-in manner.
157166
*/
158167
readonly platforms?: Readonly<PlatformOptions>;
168+
169+
/**
170+
* Specify target native project directory.
171+
*
172+
* This is for copying all generated resources directly.
173+
*/
174+
readonly nativeProject?: Readonly<NativeProject>;
159175
}
160176

161177
export async function runCommandLine(args: readonly string[]): Promise<void> {

src/native.ts

+176
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import { copy } from '@ionic/utils-fs';
2+
import Debug from 'debug';
3+
import path from 'path';
4+
5+
import { Platform } from './platform';
6+
7+
export interface NativeProject {
8+
enabled?: boolean;
9+
androidProjectDirectory?: string;
10+
iosProjectDirectory?: string;
11+
}
12+
interface ProcessItem {
13+
source: string;
14+
target: string;
15+
}
16+
17+
const debug = Debug('cordova-res:native');
18+
19+
const SOURCE_IOS_ICON = 'resources/ios/icon/';
20+
const SOURCE_IOS_SPLASH = 'resources/ios/splash/';
21+
22+
const TARGET_IOS_ICON = '/App/App/Assets.xcassets/AppIcon.appiconset/';
23+
const TARGET_IOS_SPLASH = '/App/App/Assets.xcassets/Splash.imageset/';
24+
25+
const SOURCE_ANDROID_ICON = 'resources/android/icon/';
26+
const SOURCE_ANDROID_SPLASH = 'resources/android/splash/';
27+
28+
const TARGET_ANDROID_ICON = '/app/src/main/res/';
29+
const TARGET_ANDROID_SPLASH = '/app/src/main/res/';
30+
31+
const IOS_ICONS: readonly ProcessItem[] = [
32+
{ source: 'icon-20.png', target: '[email protected]' },
33+
{ source: '[email protected]', target: '[email protected]' },
34+
{ source: '[email protected]', target: '[email protected]' },
35+
{ source: '[email protected]', target: '[email protected]' },
36+
{ source: 'icon-29.png', target: '[email protected]' },
37+
{ source: '[email protected]', target: '[email protected]' },
38+
{ source: '[email protected]', target: '[email protected]' },
39+
{ source: '[email protected]', target: '[email protected]' },
40+
{ source: 'icon-40.png', target: '[email protected]' },
41+
{ source: '[email protected]', target: '[email protected]' },
42+
{ source: '[email protected]', target: '[email protected]' },
43+
{ source: '[email protected]', target: '[email protected]' },
44+
{ source: '[email protected]', target: '[email protected]' },
45+
{ source: '[email protected]', target: '[email protected]' },
46+
{ source: 'icon-76.png', target: '[email protected]' },
47+
{ source: '[email protected]', target: '[email protected]' },
48+
{ source: '[email protected]', target: '[email protected]' },
49+
{ source: 'icon-1024.png', target: '[email protected]' },
50+
];
51+
const IOS_SPLASHES: readonly ProcessItem[] = [
52+
{ source: 'Default-Portrait@~ipadpro.png', target: 'splash-2732x2732.png' },
53+
{ source: 'Default-Portrait@~ipadpro.png', target: 'splash-2732x2732-1.png' },
54+
{ source: 'Default-Portrait@~ipadpro.png', target: 'splash-2732x2732-2.png' },
55+
];
56+
57+
const ANDROID_ICONS: readonly ProcessItem[] = [
58+
{ source: 'drawable-ldpi-icon.png', target: 'drawable-hdpi-icon.png' },
59+
{ source: 'drawable-mdpi-icon.png', target: 'mipmap-mdpi/ic_launcher.png' },
60+
{
61+
source: 'drawable-mdpi-icon.png',
62+
target: 'mipmap-mdpi/ic_launcher_round.png',
63+
},
64+
{
65+
source: 'drawable-mdpi-icon.png',
66+
target: 'mipmap-mdpi/ic_launcher_foreground.png',
67+
},
68+
{ source: 'drawable-hdpi-icon.png', target: 'mipmap-hdpi/ic_launcher.png' },
69+
{
70+
source: 'drawable-hdpi-icon.png',
71+
target: 'mipmap-hdpi/ic_launcher_round.png',
72+
},
73+
{
74+
source: 'drawable-hdpi-icon.png',
75+
target: 'mipmap-hdpi/ic_launcher_foreground.png',
76+
},
77+
{ source: 'drawable-xhdpi-icon.png', target: 'mipmap-xhdpi/ic_launcher.png' },
78+
{
79+
source: 'drawable-xhdpi-icon.png',
80+
target: 'mipmap-xhdpi/ic_launcher_round.png',
81+
},
82+
{
83+
source: 'drawable-xhdpi-icon.png',
84+
target: 'mipmap-xhdpi/ic_launcher_foreground.png',
85+
},
86+
{
87+
source: 'drawable-xxhdpi-icon.png',
88+
target: 'mipmap-xxhdpi/ic_launcher.png',
89+
},
90+
{
91+
source: 'drawable-xxhdpi-icon.png',
92+
target: 'mipmap-xxhdpi/ic_launcher_round.png',
93+
},
94+
{
95+
source: 'drawable-xxhdpi-icon.png',
96+
target: 'mipmap-xxhdpi/ic_launcher_foreground.png',
97+
},
98+
{
99+
source: 'drawable-xxxhdpi-icon.png',
100+
target: 'mipmap-xxxhdpi/ic_launcher.png',
101+
},
102+
{
103+
source: 'drawable-xxxhdpi-icon.png',
104+
target: 'mipmap-xxxhdpi/ic_launcher_round.png',
105+
},
106+
{
107+
source: 'drawable-xxxhdpi-icon.png',
108+
target: 'mipmap-xxxhdpi/ic_launcher_foreground.png',
109+
},
110+
];
111+
const ANDROID_SPLASHES: readonly ProcessItem[] = [
112+
{ source: 'drawable-land-mdpi-screen.png', target: 'drawable/splash.png' },
113+
{
114+
source: 'drawable-land-mdpi-screen.png',
115+
target: 'drawable-land-mdpi/splash.png',
116+
},
117+
{
118+
source: 'drawable-land-hdpi-screen.png',
119+
target: 'drawable-land-hdpi/splash.png',
120+
},
121+
{
122+
source: 'drawable-land-xhdpi-screen.png',
123+
target: 'drawable-land-xhdpi/splash.png',
124+
},
125+
{
126+
source: 'drawable-land-xxhdpi-screen.png',
127+
target: 'drawable-land-xxhdpi/splash.png',
128+
},
129+
{
130+
source: 'drawable-land-xxxhdpi-screen.png',
131+
target: 'drawable-land-xxxhdpi/splash.png',
132+
},
133+
{
134+
source: 'drawable-port-mdpi-screen.png',
135+
target: 'drawable-port-mdpi/splash.png',
136+
},
137+
{
138+
source: 'drawable-port-hdpi-screen.png',
139+
target: 'drawable-port-hdpi/splash.png',
140+
},
141+
{
142+
source: 'drawable-port-xhdpi-screen.png',
143+
target: 'drawable-port-xhdpi/splash.png',
144+
},
145+
{
146+
source: 'drawable-port-xxhdpi-screen.png',
147+
target: 'drawable-port-xxhdpi/splash.png',
148+
},
149+
{
150+
source: 'drawable-port-xxxhdpi-screen.png',
151+
target: 'drawable-port-xxxhdpi/splash.png',
152+
},
153+
];
154+
155+
async function copyImages(sourcePath: string, targetPath: string, images: readonly ProcessItem[]) {
156+
await Promise.all(images.map(async item => {
157+
const source = path.join(sourcePath, item.source);
158+
const target = path.join(targetPath, item.target);
159+
await copy(source, target);
160+
debug('Copied resource item from %s to %s', source, target);
161+
}));
162+
}
163+
164+
export async function copyToNativeProject(platform: Platform, nativeProject: NativeProject, logstream: NodeJS.WritableStream) {
165+
if (platform === Platform.IOS) {
166+
const iosProjectDirectory = nativeProject.iosProjectDirectory || 'ios';
167+
await copyImages(SOURCE_IOS_ICON, path.join(iosProjectDirectory, TARGET_IOS_ICON), IOS_ICONS);
168+
await copyImages(SOURCE_IOS_SPLASH, path.join(iosProjectDirectory, TARGET_IOS_SPLASH), IOS_SPLASHES);
169+
logstream.write(`Copied ${IOS_ICONS.length + IOS_SPLASHES.length} resource items to iOS project`);
170+
} else if (platform === Platform.ANDROID) {
171+
const androidProjectDirectory = nativeProject.androidProjectDirectory || 'android';
172+
await copyImages(SOURCE_ANDROID_ICON, path.join(androidProjectDirectory, TARGET_ANDROID_ICON), ANDROID_ICONS);
173+
await copyImages(SOURCE_ANDROID_SPLASH, path.join(androidProjectDirectory, TARGET_ANDROID_SPLASH), ANDROID_SPLASHES);
174+
logstream.write(`Copied ${ANDROID_ICONS.length + ANDROID_SPLASHES.length} resource items to Android project`);
175+
}
176+
}

0 commit comments

Comments
 (0)