@simplysm/sd-cli 12.8.20 → 12.8.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/entry/sd-cli-cordova.d.ts +30 -0
- package/dist/entry/sd-cli-cordova.js +307 -246
- package/dist/entry/sd-cli-cordova.js.map +1 -1
- package/dist/entry/sd-cli-project.d.ts +1 -1
- package/dist/entry/sd-cli-project.js +8 -9
- package/dist/entry/sd-cli-project.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/pkg-builders/client/sd-ng.bundler.d.ts +15 -1
- package/dist/pkg-builders/client/sd-ng.bundler.js +60 -70
- package/dist/pkg-builders/client/sd-ng.bundler.js.map +1 -1
- package/dist/pkg-builders/client/sd-ng.plugin-creator.js +49 -29
- package/dist/pkg-builders/client/sd-ng.plugin-creator.js.map +1 -1
- package/dist/pkg-builders/lib/sd-ts-lib.builder.js +7 -4
- package/dist/pkg-builders/lib/sd-ts-lib.builder.js.map +1 -1
- package/dist/pkg-builders/server/sd-server.bundler.js +11 -10
- package/dist/pkg-builders/server/sd-server.bundler.js.map +1 -1
- package/dist/ts-compiler/sd-ts-compiler.d.ts +25 -2
- package/dist/ts-compiler/sd-ts-compiler.js +306 -575
- package/dist/ts-compiler/sd-ts-compiler.js.map +1 -1
- package/dist/ts-compiler/sd-ts-dependency-analyzer.d.ts +6 -0
- package/dist/ts-compiler/sd-ts-dependency-analyzer.js +141 -0
- package/dist/ts-compiler/sd-ts-dependency-analyzer.js.map +1 -0
- package/dist/types/ts-compiler.types.d.ts +12 -7
- package/dist/types/worker.types.d.ts +13 -0
- package/dist/utils/sd-cli-performance-time.js +1 -1
- package/package.json +6 -8
- package/src/entry/sd-cli-cordova.ts +393 -280
- package/src/entry/sd-cli-project.ts +11 -23
- package/src/index.ts +1 -0
- package/src/pkg-builders/client/sd-ng.bundler.ts +67 -69
- package/src/pkg-builders/client/sd-ng.plugin-creator.ts +47 -27
- package/src/pkg-builders/lib/sd-ts-lib.builder.ts +14 -7
- package/src/pkg-builders/server/sd-server.bundler.ts +22 -12
- package/src/ts-compiler/sd-ts-compiler.ts +379 -704
- package/src/ts-compiler/sd-ts-dependency-analyzer.ts +185 -0
- package/src/types/ts-compiler.types.ts +11 -6
- package/src/types/worker.types.ts +7 -6
- package/src/utils/sd-cli-performance-time.ts +1 -1
|
@@ -1,23 +1,62 @@
|
|
|
1
1
|
import * as path from "path";
|
|
2
2
|
import { FsUtils, SdLogger, SdProcess } from "@simplysm/sd-core-node";
|
|
3
|
-
import
|
|
4
|
-
import JSZip from "jszip";
|
|
5
|
-
// const BIN_PATH = path.resolve(process.cwd(), "node_modules/.bin/cordova.cmd");
|
|
3
|
+
import { SdZip, XmlConvert } from "@simplysm/sd-core-common";
|
|
6
4
|
export class SdCliCordova {
|
|
7
5
|
constructor(_opt) {
|
|
8
6
|
this._opt = _opt;
|
|
7
|
+
// 상수 정의
|
|
8
|
+
this.CORDOVA_DIR_NAME = ".cordova";
|
|
9
|
+
this.PLATFORMS_DIR_NAME = "platforms";
|
|
10
|
+
this.WWW_DIR_NAME = "www";
|
|
11
|
+
this.PLUGINS_DIR_NAME = "plugins";
|
|
12
|
+
this.PLUGINS_FETCH_FILE = "fetch.json";
|
|
13
|
+
this.ANDROID_PLATFORM_VERSION = "12.0.0";
|
|
14
|
+
this.ANDROID_SDK_VERSION = "33";
|
|
15
|
+
this.KEYSTORE_FILE_NAME = "android.keystore";
|
|
16
|
+
this.CONFIG_XML_FILE_NAME = "config.xml";
|
|
17
|
+
this.CONFIG_XML_BACKUP_FILE_NAME = "config.xml.bak";
|
|
18
|
+
this.BUILD_JSON_FILE_NAME = "build.json";
|
|
19
|
+
this.ANDROID_SIGNING_PROP_PATH = "platforms/android/release-signing.properties";
|
|
20
|
+
this.ICON_DIR_PATH = "res/icons";
|
|
21
|
+
this.SPLASH_SCREEN_DIR_PATH = "res/screen/android";
|
|
22
|
+
this.SPLASH_SCREEN_XML_FILE = "splashscreen.xml";
|
|
9
23
|
this._logger = SdLogger.get(["simplysm", "sd-cli", "SdCliCordova"]);
|
|
10
24
|
this._platforms = Object.keys(this._opt.config.platform ?? { browser: {} });
|
|
11
25
|
this._npmConfig = FsUtils.readJson(path.resolve(this._opt.pkgPath, "package.json"));
|
|
12
|
-
// this._logger = Logger.get(["simplysm", "sd-cli", this.constructor.name, this._npmConfig.name]);
|
|
13
26
|
}
|
|
14
27
|
async _execAsync(cmd, cwd) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
28
|
+
try {
|
|
29
|
+
this._logger.debug(`실행 명령: ${cmd}`);
|
|
30
|
+
const msg = await SdProcess.spawnAsync(cmd, { cwd });
|
|
31
|
+
this._logger.debug(`실행 결과: ${msg}`);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
this._logger.error(`명령 실행 실패: ${cmd}`);
|
|
35
|
+
this._logger.error(`오류: ${err instanceof Error ? err.message : String(err)}`);
|
|
36
|
+
throw err;
|
|
37
|
+
}
|
|
18
38
|
}
|
|
19
39
|
async initializeAsync() {
|
|
20
|
-
const cordovaPath = path.resolve(this._opt.pkgPath,
|
|
40
|
+
const cordovaPath = path.resolve(this._opt.pkgPath, this.CORDOVA_DIR_NAME);
|
|
41
|
+
// 1. Cordova 프로젝트 초기화
|
|
42
|
+
await this._initializeCordovaProjectAsync(cordovaPath);
|
|
43
|
+
// 2. 플랫폼 관리
|
|
44
|
+
await this._managePlatformsAsync(cordovaPath);
|
|
45
|
+
// 3. 플러그인 관리
|
|
46
|
+
await this._managePluginsAsync(cordovaPath);
|
|
47
|
+
// 4. 안드로이드 서명 설정
|
|
48
|
+
this._setupAndroidSign(cordovaPath);
|
|
49
|
+
// 5. 빌드 설정 파일 생성
|
|
50
|
+
this._createBuildConfig(cordovaPath);
|
|
51
|
+
// 6. 아이콘 및 스플래시 스크린 설정
|
|
52
|
+
this._setupIconAndSplashScreen(cordovaPath);
|
|
53
|
+
// 7. XML 설정 구성
|
|
54
|
+
this._configureXml(cordovaPath);
|
|
55
|
+
// 8. 각 플랫폼 www 준비
|
|
56
|
+
await this._execAsync(`npx cordova prepare`, cordovaPath);
|
|
57
|
+
}
|
|
58
|
+
// 1. Cordova 프로젝트 초기화
|
|
59
|
+
async _initializeCordovaProjectAsync(cordovaPath) {
|
|
21
60
|
if (FsUtils.exists(cordovaPath)) {
|
|
22
61
|
this._logger.log("이미 생성되어있는 '.cordova'를 사용합니다.");
|
|
23
62
|
}
|
|
@@ -25,112 +64,118 @@ export class SdCliCordova {
|
|
|
25
64
|
await this._execAsync(`npx cordova telemetry on`, this._opt.pkgPath);
|
|
26
65
|
// 프로젝트 생성
|
|
27
66
|
await this._execAsync(`npx cordova create "${cordovaPath}" "${this._opt.config.appId}" "${this._opt.config.appName}"`, process.cwd());
|
|
28
|
-
// volta
|
|
29
|
-
// await this._execAsync(`volta pin node@18`, cordovaPath);
|
|
30
|
-
// package.json 수정
|
|
31
|
-
/*const npmConfig = FsUtil.readJson(path.resolve(cordovaPath, "package.json"));
|
|
32
|
-
npmConfig.volta = {
|
|
33
|
-
node: process.version.substring(1)
|
|
34
|
-
};
|
|
35
|
-
FsUtil.writeJson(path.resolve(cordovaPath, "package.json"), npmConfig);*/
|
|
36
67
|
}
|
|
37
68
|
// platforms 폴더 혹시 없으면 생성
|
|
38
|
-
FsUtils.mkdirs(path.resolve(cordovaPath,
|
|
69
|
+
FsUtils.mkdirs(path.resolve(cordovaPath, this.PLATFORMS_DIR_NAME));
|
|
39
70
|
// www 폴더 혹시 없으면 생성
|
|
40
|
-
FsUtils.mkdirs(path.resolve(cordovaPath,
|
|
71
|
+
FsUtils.mkdirs(path.resolve(cordovaPath, this.WWW_DIR_NAME));
|
|
72
|
+
}
|
|
73
|
+
// 2. 플랫폼 관리
|
|
74
|
+
async _managePlatformsAsync(cordovaPath) {
|
|
75
|
+
const alreadyPlatforms = FsUtils.readdir(path.resolve(cordovaPath, this.PLATFORMS_DIR_NAME));
|
|
41
76
|
// 미설치 빌드 플랫폼 신규 생성
|
|
42
|
-
const alreadyPlatforms = FsUtils.readdir(path.resolve(cordovaPath, "platforms"));
|
|
43
77
|
for (const platform of this._platforms) {
|
|
44
|
-
if (
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
await this._execAsync(`npx cordova platform add ${platform}`, cordovaPath);
|
|
51
|
-
}
|
|
78
|
+
if (alreadyPlatforms.includes(platform))
|
|
79
|
+
continue;
|
|
80
|
+
if (platform === "android") {
|
|
81
|
+
await this._execAsync(`npx cordova platform add ${platform}@${this.ANDROID_PLATFORM_VERSION}`, cordovaPath);
|
|
52
82
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
/*for (const alreadyPlatform of alreadyPlatforms) {
|
|
56
|
-
if (!this._platforms.includes(alreadyPlatform)) {
|
|
57
|
-
await this._execAsync(`${BIN_PATH} platform remove ${alreadyPlatform}`, cordovaPath);
|
|
58
|
-
}
|
|
59
|
-
}*/
|
|
60
|
-
// 설치된 미사용 플러그인 삭제
|
|
61
|
-
const pluginsFetch = FsUtils.exists(path.resolve(cordovaPath, "plugins/fetch.json"))
|
|
62
|
-
? FsUtils.readJson(path.resolve(cordovaPath, "plugins/fetch.json"))
|
|
63
|
-
: undefined;
|
|
64
|
-
const alreadyPlugins = [];
|
|
65
|
-
if (pluginsFetch != null) {
|
|
66
|
-
for (const key of Object.keys(pluginsFetch)) {
|
|
67
|
-
alreadyPlugins.push({
|
|
68
|
-
name: key,
|
|
69
|
-
id: pluginsFetch[key].source.id,
|
|
70
|
-
});
|
|
83
|
+
else {
|
|
84
|
+
await this._execAsync(`npx cordova platform add ${platform}`, cordovaPath);
|
|
71
85
|
}
|
|
72
86
|
}
|
|
87
|
+
}
|
|
88
|
+
// 3. 플러그인 관리
|
|
89
|
+
async _managePluginsAsync(cordovaPath) {
|
|
90
|
+
const pluginsFetchPath = path.resolve(cordovaPath, this.PLUGINS_DIR_NAME, this.PLUGINS_FETCH_FILE);
|
|
91
|
+
const pluginsFetch = FsUtils.exists(pluginsFetchPath)
|
|
92
|
+
? FsUtils.readJson(pluginsFetchPath)
|
|
93
|
+
: {};
|
|
94
|
+
const alreadyPlugins = Object.keys(pluginsFetch).map(key => ({
|
|
95
|
+
name: key,
|
|
96
|
+
id: pluginsFetch[key].source.id,
|
|
97
|
+
dependencies: pluginsFetch[key].dependencies,
|
|
98
|
+
}));
|
|
73
99
|
const usePlugins = (this._opt.config.plugins ?? []).distinct();
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
100
|
+
// 사용하지 않는 플러그인 제거 및 새 플러그인 설치 - 의존성 때문에 순차 처리
|
|
101
|
+
await this._removeUnusedPluginsAsync(cordovaPath, alreadyPlugins, usePlugins);
|
|
102
|
+
await this._installNewPluginsAsync(cordovaPath, alreadyPlugins, usePlugins);
|
|
103
|
+
}
|
|
104
|
+
async _removeUnusedPluginsAsync(cordovaPath, alreadyPlugins, usePlugins) {
|
|
105
|
+
for (const alreadyPlugin of alreadyPlugins) {
|
|
106
|
+
// 사용하지 않는 플러그인 제거 시 의존성 검사
|
|
107
|
+
const isPluginUsed = usePlugins.some(usePlugin => usePlugin === alreadyPlugin.id || usePlugin === alreadyPlugin.name);
|
|
108
|
+
if (!isPluginUsed) {
|
|
109
|
+
const isDependencyExists = alreadyPlugins.some(plugin => plugin.dependencies?.includes(alreadyPlugin.name));
|
|
110
|
+
if (!isDependencyExists) {
|
|
111
|
+
await this._execAsync(`npx cordova plugin remove ${alreadyPlugin.name}`, cordovaPath);
|
|
112
|
+
}
|
|
81
113
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
}*/
|
|
88
|
-
// 미설치 플러그인들 설치
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async _installNewPluginsAsync(cordovaPath, alreadyPlugins, usePlugins) {
|
|
117
|
+
// 병렬로 플러그인을 설치하면 충돌이 발생할 수 있으므로 순차 처리
|
|
89
118
|
for (const usePlugin of usePlugins) {
|
|
90
|
-
|
|
119
|
+
const isPluginAlreadyInstalled = alreadyPlugins.some(plugin => usePlugin === plugin.id || usePlugin === plugin.name);
|
|
120
|
+
if (!isPluginAlreadyInstalled) {
|
|
91
121
|
await this._execAsync(`npx cordova plugin add ${usePlugin}`, cordovaPath);
|
|
92
122
|
}
|
|
93
123
|
}
|
|
94
|
-
|
|
124
|
+
}
|
|
125
|
+
// 4. 안드로이드 서명 설정
|
|
126
|
+
_setupAndroidSign(cordovaPath) {
|
|
127
|
+
const keystorePath = path.resolve(cordovaPath, this.KEYSTORE_FILE_NAME);
|
|
128
|
+
const signingPropsPath = path.resolve(cordovaPath, this.ANDROID_SIGNING_PROP_PATH);
|
|
95
129
|
if (this._opt.config.platform?.android?.sign) {
|
|
96
|
-
FsUtils.copy(path.resolve(this._opt.pkgPath, this._opt.config.platform.android.sign.keystore),
|
|
130
|
+
FsUtils.copy(path.resolve(this._opt.pkgPath, this._opt.config.platform.android.sign.keystore), keystorePath);
|
|
97
131
|
}
|
|
98
132
|
else {
|
|
99
|
-
FsUtils.remove(
|
|
133
|
+
FsUtils.remove(keystorePath);
|
|
100
134
|
// SIGN을 안쓸경우 아래 파일이 생성되어 있으면 오류남
|
|
101
|
-
FsUtils.remove(
|
|
135
|
+
FsUtils.remove(signingPropsPath);
|
|
102
136
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
:
|
|
119
|
-
|
|
137
|
+
}
|
|
138
|
+
// 5. 빌드 설정 파일 생성
|
|
139
|
+
_createBuildConfig(cordovaPath) {
|
|
140
|
+
const buildJsonPath = path.resolve(cordovaPath, this.BUILD_JSON_FILE_NAME);
|
|
141
|
+
const keystorePath = path.resolve(cordovaPath, this.KEYSTORE_FILE_NAME);
|
|
142
|
+
const androidConfig = this._opt.config.platform?.android
|
|
143
|
+
? {
|
|
144
|
+
android: {
|
|
145
|
+
release: {
|
|
146
|
+
packageType: this._opt.config.platform.android.bundle ? "bundle" : "apk",
|
|
147
|
+
...(this._opt.config.platform.android.sign
|
|
148
|
+
? {
|
|
149
|
+
keystore: keystorePath,
|
|
150
|
+
storePassword: this._opt.config.platform.android.sign.storePassword,
|
|
151
|
+
alias: this._opt.config.platform.android.sign.alias,
|
|
152
|
+
password: this._opt.config.platform.android.sign.password,
|
|
153
|
+
keystoreType: this._opt.config.platform.android.sign.keystoreType,
|
|
154
|
+
}
|
|
155
|
+
: {}),
|
|
120
156
|
},
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
|
|
157
|
+
},
|
|
158
|
+
}
|
|
159
|
+
: {};
|
|
160
|
+
FsUtils.writeJson(buildJsonPath, androidConfig);
|
|
161
|
+
}
|
|
162
|
+
// 6. 아이콘 및 스플래시 스크린 설정
|
|
163
|
+
_setupIconAndSplashScreen(cordovaPath) {
|
|
164
|
+
const iconDirPath = path.resolve(cordovaPath, this.ICON_DIR_PATH);
|
|
165
|
+
const splashScreenPath = path.resolve(cordovaPath, this.SPLASH_SCREEN_DIR_PATH);
|
|
166
|
+
const splashScreenXmlPath = path.resolve(splashScreenPath, this.SPLASH_SCREEN_XML_FILE);
|
|
124
167
|
// ICON 파일 복사
|
|
125
168
|
if (this._opt.config.icon != null) {
|
|
126
|
-
FsUtils.
|
|
169
|
+
FsUtils.mkdirs(iconDirPath);
|
|
170
|
+
FsUtils.copy(path.resolve(this._opt.pkgPath, this._opt.config.icon), path.resolve(iconDirPath, path.basename(this._opt.config.icon)));
|
|
127
171
|
}
|
|
128
172
|
else {
|
|
129
|
-
FsUtils.remove(
|
|
173
|
+
FsUtils.remove(iconDirPath);
|
|
130
174
|
}
|
|
131
175
|
// SplashScreen 파일 생성
|
|
132
176
|
if (this._opt.config.platform?.android && this._opt.config.icon != null) {
|
|
133
|
-
FsUtils.
|
|
177
|
+
FsUtils.mkdirs(splashScreenPath);
|
|
178
|
+
FsUtils.writeFile(splashScreenXmlPath, `
|
|
134
179
|
<?xml version="1.0" encoding="utf-8"?>
|
|
135
180
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
|
136
181
|
<item
|
|
@@ -140,194 +185,204 @@ export class SdCliCordova {
|
|
|
140
185
|
android:gravity="center" />
|
|
141
186
|
</layer-list>`.trim());
|
|
142
187
|
}
|
|
188
|
+
}
|
|
189
|
+
// 7. XML 설정 구성
|
|
190
|
+
_configureXml(cordovaPath) {
|
|
143
191
|
// CONFIG: 초기값 백업
|
|
144
|
-
const configFilePath = path.resolve(cordovaPath,
|
|
145
|
-
const configBackFilePath = path.resolve(cordovaPath,
|
|
192
|
+
const configFilePath = path.resolve(cordovaPath, this.CONFIG_XML_FILE_NAME);
|
|
193
|
+
const configBackFilePath = path.resolve(cordovaPath, this.CONFIG_XML_BACKUP_FILE_NAME);
|
|
146
194
|
if (!FsUtils.exists(configBackFilePath)) {
|
|
147
195
|
FsUtils.copy(configFilePath, configBackFilePath);
|
|
148
196
|
}
|
|
149
197
|
// CONFIG: 초기값 읽기
|
|
150
198
|
const configFileContent = FsUtils.readFile(configBackFilePath);
|
|
151
|
-
const configXml =
|
|
152
|
-
// CONFIG:
|
|
199
|
+
const configXml = XmlConvert.parse(configFileContent);
|
|
200
|
+
// CONFIG: 기본 설정
|
|
201
|
+
this._configureBasicXmlSettings(configXml);
|
|
202
|
+
// CONFIG: 안드로이드 설정
|
|
203
|
+
if (this._opt.config.platform?.android) {
|
|
204
|
+
this._configureAndroidXmlSettings(configXml);
|
|
205
|
+
}
|
|
206
|
+
// CONFIG: 파일 새로 쓰기
|
|
207
|
+
const configResultContent = XmlConvert.stringify(configXml, {
|
|
208
|
+
format: true
|
|
209
|
+
});
|
|
210
|
+
FsUtils.writeFile(configFilePath, configResultContent);
|
|
211
|
+
}
|
|
212
|
+
_configureBasicXmlSettings(configXml) {
|
|
213
|
+
// 버전 설정
|
|
153
214
|
configXml.widget.$.version = this._npmConfig.version;
|
|
154
|
-
//
|
|
215
|
+
// ICON 설정
|
|
155
216
|
if (this._opt.config.icon != null) {
|
|
156
|
-
configXml
|
|
217
|
+
configXml.widget.icon = [
|
|
157
218
|
{
|
|
158
219
|
$: {
|
|
159
|
-
src:
|
|
160
|
-
+ path.basename(this._opt.config.icon),
|
|
220
|
+
src: `${this.ICON_DIR_PATH}/${path.basename(this._opt.config.icon)}`,
|
|
161
221
|
},
|
|
162
222
|
},
|
|
163
223
|
];
|
|
164
224
|
}
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
configXml
|
|
168
|
-
configXml
|
|
169
|
-
configXml
|
|
170
|
-
configXml
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
225
|
+
// 접근허용 세팅
|
|
226
|
+
configXml.widget.content = [{ $: { src: "http://localhost/index.html" } }];
|
|
227
|
+
configXml.widget.access = [{ $: { origin: "*" } }];
|
|
228
|
+
configXml.widget["allow-navigation"] = [{ $: { href: "*" } }];
|
|
229
|
+
configXml.widget["allow-intent"] = [{ $: { href: "*" } }];
|
|
230
|
+
configXml.widget.preference = [{ $: { name: "MixedContentMode", value: "1" } }];
|
|
231
|
+
}
|
|
232
|
+
_configureAndroidXmlSettings(configXml) {
|
|
233
|
+
configXml.widget.$["xmlns:android"] = "http://schemas.android.com/apk/res/android";
|
|
234
|
+
configXml.widget.$["xmlns:tools"] = "http://schemas.android.com/tools";
|
|
235
|
+
configXml.widget.platform = configXml.widget.platform ?? [];
|
|
236
|
+
const androidPlatform = {
|
|
237
|
+
"$": {
|
|
238
|
+
name: "android",
|
|
239
|
+
},
|
|
240
|
+
"preference": [
|
|
241
|
+
{
|
|
242
|
+
$: {
|
|
243
|
+
name: "AndroidWindowSplashScreenAnimatedIcon",
|
|
244
|
+
value: `${this.SPLASH_SCREEN_DIR_PATH}/${this.SPLASH_SCREEN_XML_FILE}`,
|
|
245
|
+
},
|
|
181
246
|
},
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
247
|
+
],
|
|
248
|
+
"edit-config": [
|
|
249
|
+
{
|
|
250
|
+
$: {
|
|
251
|
+
file: "app/src/main/AndroidManifest.xml",
|
|
252
|
+
mode: "merge",
|
|
253
|
+
target: "/manifest",
|
|
188
254
|
},
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
file: "app/src/main/AndroidManifest.xml",
|
|
194
|
-
mode: "merge",
|
|
195
|
-
target: "/manifest",
|
|
196
|
-
},
|
|
197
|
-
manifest: [
|
|
198
|
-
{
|
|
199
|
-
$: {
|
|
200
|
-
"xmlns:tools": "http://schemas.android.com/tools",
|
|
201
|
-
},
|
|
255
|
+
manifest: [
|
|
256
|
+
{
|
|
257
|
+
$: {
|
|
258
|
+
"xmlns:tools": "http://schemas.android.com/tools",
|
|
202
259
|
},
|
|
203
|
-
],
|
|
204
|
-
},
|
|
205
|
-
{
|
|
206
|
-
$: {
|
|
207
|
-
file: "app/src/main/AndroidManifest.xml",
|
|
208
|
-
mode: "merge",
|
|
209
|
-
target: "/manifest/application",
|
|
210
260
|
},
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
: {},
|
|
219
|
-
},
|
|
220
|
-
},
|
|
221
|
-
],
|
|
261
|
+
],
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
$: {
|
|
265
|
+
file: "app/src/main/AndroidManifest.xml",
|
|
266
|
+
mode: "merge",
|
|
267
|
+
target: "/manifest/application",
|
|
222
268
|
},
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
269
|
+
application: [
|
|
270
|
+
{
|
|
271
|
+
$: {
|
|
272
|
+
"android:usesCleartextTraffic": "true",
|
|
273
|
+
...this._opt.config.platform.android.config
|
|
274
|
+
? Object.keys(this._opt.config.platform.android.config)
|
|
275
|
+
.toObject(key => "android:" + key, key => this._opt.config.platform.android.config[key])
|
|
276
|
+
: {},
|
|
277
|
+
},
|
|
231
278
|
},
|
|
279
|
+
],
|
|
280
|
+
},
|
|
281
|
+
],
|
|
282
|
+
};
|
|
283
|
+
// SDK 버전 설정
|
|
284
|
+
if (this._opt.config.platform.android.sdkVersion != null) {
|
|
285
|
+
androidPlatform.preference.push(...[
|
|
286
|
+
{
|
|
287
|
+
$: {
|
|
288
|
+
name: "android-maxSdkVersion",
|
|
289
|
+
value: `${this._opt.config.platform.android.sdkVersion}`,
|
|
232
290
|
},
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
$: {
|
|
294
|
+
name: "android-minSdkVersion",
|
|
295
|
+
value: `${this._opt.config.platform.android.sdkVersion}`,
|
|
238
296
|
},
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
$: {
|
|
300
|
+
name: "android-targetSdkVersion",
|
|
301
|
+
value: `${this._opt.config.platform.android.sdkVersion}`,
|
|
244
302
|
},
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
$: {
|
|
306
|
+
name: "android-compileSdkVersion",
|
|
307
|
+
value: this.ANDROID_SDK_VERSION,
|
|
250
308
|
},
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
309
|
+
},
|
|
310
|
+
]);
|
|
311
|
+
}
|
|
312
|
+
// 권한 설정
|
|
313
|
+
if (this._opt.config.platform.android.permissions) {
|
|
314
|
+
androidPlatform["config-file"] = androidPlatform["config-file"] ?? [];
|
|
315
|
+
androidPlatform["config-file"].push({
|
|
316
|
+
"$": {
|
|
317
|
+
target: "AndroidManifest.xml",
|
|
318
|
+
parent: "/*",
|
|
319
|
+
},
|
|
320
|
+
"uses-permission": this._opt.config.platform.android.permissions.map((perm) => ({
|
|
321
|
+
$: {
|
|
322
|
+
"android:name": `android.permission.${perm.name}`,
|
|
323
|
+
...(perm.maxSdkVersion != null
|
|
324
|
+
? {
|
|
325
|
+
"android:maxSdkVersion": `${perm.maxSdkVersion}`,
|
|
326
|
+
}
|
|
327
|
+
: {}),
|
|
328
|
+
...(perm.ignore != null
|
|
329
|
+
? {
|
|
330
|
+
"tools:ignore": `${perm.ignore}`,
|
|
331
|
+
}
|
|
332
|
+
: {}),
|
|
259
333
|
},
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
"android:name": `android.permission.${perm.name}`,
|
|
263
|
-
...(perm.maxSdkVersion != null
|
|
264
|
-
? {
|
|
265
|
-
"android:maxSdkVersion": `${perm.maxSdkVersion}`,
|
|
266
|
-
}
|
|
267
|
-
: {}),
|
|
268
|
-
...(perm.ignore != null
|
|
269
|
-
? {
|
|
270
|
-
"tools:ignore": `${perm.ignore}`,
|
|
271
|
-
}
|
|
272
|
-
: {}),
|
|
273
|
-
},
|
|
274
|
-
})),
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
configXml["widget"]["platform"].push(androidPlatform);
|
|
334
|
+
})),
|
|
335
|
+
});
|
|
278
336
|
}
|
|
279
|
-
|
|
280
|
-
const configResultContent = new xml2js.Builder().buildObject(configXml);
|
|
281
|
-
FsUtils.writeFile(configFilePath, configResultContent);
|
|
282
|
-
//android.json의 undefined 문제 해결
|
|
283
|
-
/*const androidJsonFilePath = path.resolve(cordovaPath, "platforms/android/android.json");
|
|
284
|
-
if (FsUtil.exists(androidJsonFilePath)) {
|
|
285
|
-
const androidConf = FsUtil.readJson(androidJsonFilePath);
|
|
286
|
-
if (androidConf.config_munge.files["undefined"] != null) {
|
|
287
|
-
delete androidConf.config_munge.files["undefined"];
|
|
288
|
-
}
|
|
289
|
-
FsUtil.writeJson(androidJsonFilePath, androidConf, { space: 2 });
|
|
290
|
-
}*/
|
|
291
|
-
// 각 플랫폼 www 준비
|
|
292
|
-
await this._execAsync(`npx cordova prepare`, cordovaPath);
|
|
337
|
+
configXml.widget.platform.push(androidPlatform);
|
|
293
338
|
}
|
|
294
339
|
async buildAsync(outPath) {
|
|
295
|
-
const cordovaPath = path.resolve(this._opt.pkgPath,
|
|
296
|
-
// 실행
|
|
340
|
+
const cordovaPath = path.resolve(this._opt.pkgPath, this.CORDOVA_DIR_NAME);
|
|
341
|
+
// 빌드 실행 - 병렬 처리로 개선
|
|
297
342
|
const buildType = this._opt.config.debug ? "debug" : "release";
|
|
298
|
-
|
|
299
|
-
|
|
343
|
+
// 모든 플랫폼 동시에 빌드
|
|
344
|
+
await Promise.all(this._platforms.map(platform => this._execAsync(`npx cordova build ${platform} --${buildType}`, cordovaPath)));
|
|
345
|
+
// 결과물 복사 및 ZIP 파일 생성 - 병렬 처리
|
|
346
|
+
await Promise.all(Object.keys(this._opt.config.platform ?? {}).map(async (platform) => {
|
|
347
|
+
await this._processBuildOutputAsync(cordovaPath, outPath, platform, buildType);
|
|
348
|
+
}));
|
|
349
|
+
}
|
|
350
|
+
async _processBuildOutputAsync(cordovaPath, outPath, platform, buildType) {
|
|
351
|
+
const targetOutPath = path.resolve(outPath, platform);
|
|
352
|
+
// 결과물 복사: ANDROID
|
|
353
|
+
if (platform === "android") {
|
|
354
|
+
this._copyAndroidBuildOutput(cordovaPath, targetOutPath, buildType);
|
|
300
355
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
356
|
+
// 자동업데이트를 위한 파일 생성
|
|
357
|
+
await this._createUpdateZipAsync(cordovaPath, outPath, platform);
|
|
358
|
+
}
|
|
359
|
+
_copyAndroidBuildOutput(cordovaPath, targetOutPath, buildType) {
|
|
360
|
+
const apkFileName = this._opt.config.platform.android.sign
|
|
361
|
+
? `app-${buildType}.apk`
|
|
362
|
+
: `app-${buildType}-unsigned.apk`;
|
|
363
|
+
const latestDistApkFileName = path.basename(`${this._opt.config.appName}${this._opt.config.platform.android.sign
|
|
364
|
+
? ""
|
|
365
|
+
: "-unsigned"}-latest.apk`);
|
|
366
|
+
FsUtils.mkdirs(targetOutPath);
|
|
367
|
+
FsUtils.copy(path.resolve(cordovaPath, "platforms/android/app/build/outputs/apk", buildType, apkFileName), path.resolve(targetOutPath, latestDistApkFileName));
|
|
368
|
+
}
|
|
369
|
+
async _createUpdateZipAsync(cordovaPath, outPath, platform) {
|
|
370
|
+
const zip = new SdZip();
|
|
371
|
+
const wwwPath = path.resolve(cordovaPath, this.WWW_DIR_NAME);
|
|
372
|
+
const platformWwwPath = path.resolve(cordovaPath, this.PLATFORMS_DIR_NAME, platform, "platform_www");
|
|
373
|
+
this._addFilesToZip(zip, wwwPath);
|
|
374
|
+
this._addFilesToZip(zip, platformWwwPath);
|
|
375
|
+
// ZIP 파일 생성
|
|
376
|
+
const updateDirPath = path.resolve(outPath, platform, "updates");
|
|
377
|
+
FsUtils.mkdirs(updateDirPath);
|
|
378
|
+
FsUtils.writeFile(path.resolve(updateDirPath, this._npmConfig.version + ".zip"), await zip.compressAsync());
|
|
379
|
+
}
|
|
380
|
+
_addFilesToZip(zip, dirPath) {
|
|
381
|
+
const files = FsUtils.glob(path.resolve(dirPath, "**/*"), { nodir: true });
|
|
382
|
+
for (const file of files) {
|
|
383
|
+
const relFilePath = path.relative(dirPath, file);
|
|
384
|
+
const fileBuffer = FsUtils.readFileBuffer(file);
|
|
385
|
+
zip.write(relFilePath, new Uint8Array(fileBuffer));
|
|
331
386
|
}
|
|
332
387
|
}
|
|
333
388
|
static async runWebviewOnDeviceAsync(opt) {
|
|
@@ -335,7 +390,13 @@ export class SdCliCordova {
|
|
|
335
390
|
if (opt.url !== undefined) {
|
|
336
391
|
FsUtils.remove(path.resolve(cordovaPath, "www"));
|
|
337
392
|
FsUtils.mkdirs(path.resolve(cordovaPath, "www"));
|
|
338
|
-
FsUtils.writeFile(path.resolve(cordovaPath, "www/index.html"), `
|
|
393
|
+
FsUtils.writeFile(path.resolve(cordovaPath, "www/index.html"), `
|
|
394
|
+
'${opt.url}'로 이동중...
|
|
395
|
+
<script>
|
|
396
|
+
setTimeout(function () {
|
|
397
|
+
window.location.href = "${opt.url.replace(/\/$/, "")}/${opt.pkgName}/cordova/";
|
|
398
|
+
}, 3000);
|
|
399
|
+
</script>`.trim());
|
|
339
400
|
}
|
|
340
401
|
await SdProcess.spawnAsync(`npx cordova run ${opt.platform} --device`, { cwd: cordovaPath });
|
|
341
402
|
}
|