@simplysm/sd-cli 12.8.18 → 12.8.21
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 +309 -249
- 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/ts-compiler/sd-ts-compiler.d.ts +24 -2
- package/dist/ts-compiler/sd-ts-compiler.js +267 -575
- package/dist/ts-compiler/sd-ts-compiler.js.map +1 -1
- package/dist/ts-compiler/sd-ts-dependency-analyzer.d.ts +10 -0
- package/dist/ts-compiler/sd-ts-dependency-analyzer.js +140 -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 +10 -12
- package/src/entry/sd-cli-cordova.ts +394 -281
- package/src/entry/sd-cli-project.ts +11 -23
- package/src/index.ts +1 -0
- package/src/pkg-builders/client/sd-ng.bundler.ts +70 -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/ts-compiler/sd-ts-compiler.ts +334 -705
- package/src/ts-compiler/sd-ts-dependency-analyzer.ts +176 -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,119 @@ 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,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
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));
|
|
76
|
+
// 미설치 빌드 플랫폼 신규 생성 - 병렬 처리
|
|
77
|
+
const platformInstallPromises = this._platforms
|
|
78
|
+
.filter(platform => !alreadyPlatforms.includes(platform))
|
|
79
|
+
.map(platform => {
|
|
80
|
+
if (platform === "android") {
|
|
81
|
+
return 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
|
+
return this._execAsync(`npx cordova platform add ${platform}`, cordovaPath);
|
|
71
85
|
}
|
|
72
|
-
}
|
|
86
|
+
});
|
|
87
|
+
await Promise.all(platformInstallPromises);
|
|
88
|
+
}
|
|
89
|
+
// 3. 플러그인 관리
|
|
90
|
+
async _managePluginsAsync(cordovaPath) {
|
|
91
|
+
const pluginsFetchPath = path.resolve(cordovaPath, this.PLUGINS_DIR_NAME, this.PLUGINS_FETCH_FILE);
|
|
92
|
+
const pluginsFetch = FsUtils.exists(pluginsFetchPath)
|
|
93
|
+
? FsUtils.readJson(pluginsFetchPath)
|
|
94
|
+
: {};
|
|
95
|
+
const alreadyPlugins = Object.keys(pluginsFetch).map(key => ({
|
|
96
|
+
name: key,
|
|
97
|
+
id: pluginsFetch[key].source.id,
|
|
98
|
+
dependencies: pluginsFetch[key].dependencies,
|
|
99
|
+
}));
|
|
73
100
|
const usePlugins = (this._opt.config.plugins ?? []).distinct();
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
101
|
+
// 사용하지 않는 플러그인 제거 및 새 플러그인 설치 - 의존성 때문에 순차 처리
|
|
102
|
+
await this._removeUnusedPluginsAsync(cordovaPath, alreadyPlugins, usePlugins);
|
|
103
|
+
await this._installNewPluginsAsync(cordovaPath, alreadyPlugins, usePlugins);
|
|
104
|
+
}
|
|
105
|
+
async _removeUnusedPluginsAsync(cordovaPath, alreadyPlugins, usePlugins) {
|
|
106
|
+
for (const alreadyPlugin of alreadyPlugins) {
|
|
107
|
+
// 사용하지 않는 플러그인 제거 시 의존성 검사
|
|
108
|
+
const isPluginUsed = usePlugins.some(usePlugin => usePlugin === alreadyPlugin.id || usePlugin === alreadyPlugin.name);
|
|
109
|
+
if (!isPluginUsed) {
|
|
110
|
+
const isDependencyExists = alreadyPlugins.some(plugin => plugin.dependencies?.includes(alreadyPlugin.name));
|
|
111
|
+
if (!isDependencyExists) {
|
|
112
|
+
await this._execAsync(`npx cordova plugin remove ${alreadyPlugin.name}`, cordovaPath);
|
|
113
|
+
}
|
|
81
114
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
}*/
|
|
88
|
-
// 미설치 플러그인들 설치
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async _installNewPluginsAsync(cordovaPath, alreadyPlugins, usePlugins) {
|
|
118
|
+
// 병렬로 플러그인을 설치하면 충돌이 발생할 수 있으므로 순차 처리
|
|
89
119
|
for (const usePlugin of usePlugins) {
|
|
90
|
-
|
|
120
|
+
const isPluginAlreadyInstalled = alreadyPlugins.some(plugin => usePlugin === plugin.id || usePlugin === plugin.name);
|
|
121
|
+
if (!isPluginAlreadyInstalled) {
|
|
91
122
|
await this._execAsync(`npx cordova plugin add ${usePlugin}`, cordovaPath);
|
|
92
123
|
}
|
|
93
124
|
}
|
|
94
|
-
|
|
125
|
+
}
|
|
126
|
+
// 4. 안드로이드 서명 설정
|
|
127
|
+
_setupAndroidSign(cordovaPath) {
|
|
128
|
+
const keystorePath = path.resolve(cordovaPath, this.KEYSTORE_FILE_NAME);
|
|
129
|
+
const signingPropsPath = path.resolve(cordovaPath, this.ANDROID_SIGNING_PROP_PATH);
|
|
95
130
|
if (this._opt.config.platform?.android?.sign) {
|
|
96
|
-
FsUtils.copy(path.resolve(this._opt.pkgPath, this._opt.config.platform.android.sign.keystore),
|
|
131
|
+
FsUtils.copy(path.resolve(this._opt.pkgPath, this._opt.config.platform.android.sign.keystore), keystorePath);
|
|
97
132
|
}
|
|
98
133
|
else {
|
|
99
|
-
FsUtils.remove(
|
|
134
|
+
FsUtils.remove(keystorePath);
|
|
100
135
|
// SIGN을 안쓸경우 아래 파일이 생성되어 있으면 오류남
|
|
101
|
-
FsUtils.remove(
|
|
136
|
+
FsUtils.remove(signingPropsPath);
|
|
102
137
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
:
|
|
119
|
-
|
|
138
|
+
}
|
|
139
|
+
// 5. 빌드 설정 파일 생성
|
|
140
|
+
_createBuildConfig(cordovaPath) {
|
|
141
|
+
const buildJsonPath = path.resolve(cordovaPath, this.BUILD_JSON_FILE_NAME);
|
|
142
|
+
const keystorePath = path.resolve(cordovaPath, this.KEYSTORE_FILE_NAME);
|
|
143
|
+
const androidConfig = this._opt.config.platform?.android
|
|
144
|
+
? {
|
|
145
|
+
android: {
|
|
146
|
+
release: {
|
|
147
|
+
packageType: this._opt.config.platform.android.bundle ? "bundle" : "apk",
|
|
148
|
+
...(this._opt.config.platform.android.sign
|
|
149
|
+
? {
|
|
150
|
+
keystore: keystorePath,
|
|
151
|
+
storePassword: this._opt.config.platform.android.sign.storePassword,
|
|
152
|
+
alias: this._opt.config.platform.android.sign.alias,
|
|
153
|
+
password: this._opt.config.platform.android.sign.password,
|
|
154
|
+
keystoreType: this._opt.config.platform.android.sign.keystoreType,
|
|
155
|
+
}
|
|
156
|
+
: {}),
|
|
120
157
|
},
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
|
|
158
|
+
},
|
|
159
|
+
}
|
|
160
|
+
: {};
|
|
161
|
+
FsUtils.writeJson(buildJsonPath, androidConfig);
|
|
162
|
+
}
|
|
163
|
+
// 6. 아이콘 및 스플래시 스크린 설정
|
|
164
|
+
_setupIconAndSplashScreen(cordovaPath) {
|
|
165
|
+
const iconDirPath = path.resolve(cordovaPath, this.ICON_DIR_PATH);
|
|
166
|
+
const splashScreenPath = path.resolve(cordovaPath, this.SPLASH_SCREEN_DIR_PATH);
|
|
167
|
+
const splashScreenXmlPath = path.resolve(splashScreenPath, this.SPLASH_SCREEN_XML_FILE);
|
|
124
168
|
// ICON 파일 복사
|
|
125
169
|
if (this._opt.config.icon != null) {
|
|
126
|
-
FsUtils.
|
|
170
|
+
FsUtils.mkdirs(iconDirPath);
|
|
171
|
+
FsUtils.copy(path.resolve(this._opt.pkgPath, this._opt.config.icon), path.resolve(iconDirPath, path.basename(this._opt.config.icon)));
|
|
127
172
|
}
|
|
128
173
|
else {
|
|
129
|
-
FsUtils.remove(
|
|
174
|
+
FsUtils.remove(iconDirPath);
|
|
130
175
|
}
|
|
131
176
|
// SplashScreen 파일 생성
|
|
132
177
|
if (this._opt.config.platform?.android && this._opt.config.icon != null) {
|
|
133
|
-
FsUtils.
|
|
178
|
+
FsUtils.mkdirs(splashScreenPath);
|
|
179
|
+
FsUtils.writeFile(splashScreenXmlPath, `
|
|
134
180
|
<?xml version="1.0" encoding="utf-8"?>
|
|
135
181
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
|
136
182
|
<item
|
|
@@ -140,194 +186,202 @@ export class SdCliCordova {
|
|
|
140
186
|
android:gravity="center" />
|
|
141
187
|
</layer-list>`.trim());
|
|
142
188
|
}
|
|
189
|
+
}
|
|
190
|
+
// 7. XML 설정 구성
|
|
191
|
+
_configureXml(cordovaPath) {
|
|
143
192
|
// CONFIG: 초기값 백업
|
|
144
|
-
const configFilePath = path.resolve(cordovaPath,
|
|
145
|
-
const configBackFilePath = path.resolve(cordovaPath,
|
|
193
|
+
const configFilePath = path.resolve(cordovaPath, this.CONFIG_XML_FILE_NAME);
|
|
194
|
+
const configBackFilePath = path.resolve(cordovaPath, this.CONFIG_XML_BACKUP_FILE_NAME);
|
|
146
195
|
if (!FsUtils.exists(configBackFilePath)) {
|
|
147
196
|
FsUtils.copy(configFilePath, configBackFilePath);
|
|
148
197
|
}
|
|
149
198
|
// CONFIG: 초기값 읽기
|
|
150
199
|
const configFileContent = FsUtils.readFile(configBackFilePath);
|
|
151
|
-
const configXml =
|
|
152
|
-
// CONFIG:
|
|
200
|
+
const configXml = XmlConvert.parse(configFileContent);
|
|
201
|
+
// CONFIG: 기본 설정
|
|
202
|
+
this._configureBasicXmlSettings(configXml);
|
|
203
|
+
// CONFIG: 안드로이드 설정
|
|
204
|
+
if (this._opt.config.platform?.android) {
|
|
205
|
+
this._configureAndroidXmlSettings(configXml);
|
|
206
|
+
}
|
|
207
|
+
// CONFIG: 파일 새로 쓰기
|
|
208
|
+
const configResultContent = XmlConvert.stringify(configXml);
|
|
209
|
+
FsUtils.writeFile(configFilePath, configResultContent);
|
|
210
|
+
}
|
|
211
|
+
_configureBasicXmlSettings(configXml) {
|
|
212
|
+
// 버전 설정
|
|
153
213
|
configXml.widget.$.version = this._npmConfig.version;
|
|
154
|
-
//
|
|
214
|
+
// ICON 설정
|
|
155
215
|
if (this._opt.config.icon != null) {
|
|
156
|
-
configXml
|
|
216
|
+
configXml.widget.icon = [
|
|
157
217
|
{
|
|
158
218
|
$: {
|
|
159
|
-
src:
|
|
160
|
-
+ path.basename(this._opt.config.icon),
|
|
219
|
+
src: `${this.ICON_DIR_PATH}/${path.basename(this._opt.config.icon)}`,
|
|
161
220
|
},
|
|
162
221
|
},
|
|
163
222
|
];
|
|
164
223
|
}
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
configXml
|
|
168
|
-
configXml
|
|
169
|
-
configXml
|
|
170
|
-
configXml
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
224
|
+
// 접근허용 세팅
|
|
225
|
+
configXml.widget.content = [{ $: { src: "http://localhost/index.html" } }];
|
|
226
|
+
configXml.widget.access = [{ $: { origin: "*" } }];
|
|
227
|
+
configXml.widget["allow-navigation"] = [{ $: { href: "*" } }];
|
|
228
|
+
configXml.widget["allow-intent"] = [{ $: { href: "*" } }];
|
|
229
|
+
configXml.widget.preference = [{ $: { name: "MixedContentMode", value: "1" } }];
|
|
230
|
+
}
|
|
231
|
+
_configureAndroidXmlSettings(configXml) {
|
|
232
|
+
configXml.widget.$["xmlns:android"] = "http://schemas.android.com/apk/res/android";
|
|
233
|
+
configXml.widget.$["xmlns:tools"] = "http://schemas.android.com/tools";
|
|
234
|
+
configXml.widget.platform = configXml.widget.platform ?? [];
|
|
235
|
+
const androidPlatform = {
|
|
236
|
+
"$": {
|
|
237
|
+
name: "android",
|
|
238
|
+
},
|
|
239
|
+
"preference": [
|
|
240
|
+
{
|
|
241
|
+
$: {
|
|
242
|
+
name: "AndroidWindowSplashScreenAnimatedIcon",
|
|
243
|
+
value: `${this.SPLASH_SCREEN_DIR_PATH}/${this.SPLASH_SCREEN_XML_FILE}`,
|
|
244
|
+
},
|
|
181
245
|
},
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
246
|
+
],
|
|
247
|
+
"edit-config": [
|
|
248
|
+
{
|
|
249
|
+
$: {
|
|
250
|
+
file: "app/src/main/AndroidManifest.xml",
|
|
251
|
+
mode: "merge",
|
|
252
|
+
target: "/manifest",
|
|
188
253
|
},
|
|
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
|
-
},
|
|
254
|
+
manifest: [
|
|
255
|
+
{
|
|
256
|
+
$: {
|
|
257
|
+
"xmlns:tools": "http://schemas.android.com/tools",
|
|
202
258
|
},
|
|
203
|
-
],
|
|
204
|
-
},
|
|
205
|
-
{
|
|
206
|
-
$: {
|
|
207
|
-
file: "app/src/main/AndroidManifest.xml",
|
|
208
|
-
mode: "merge",
|
|
209
|
-
target: "/manifest/application",
|
|
210
259
|
},
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
: {},
|
|
219
|
-
},
|
|
220
|
-
},
|
|
221
|
-
],
|
|
260
|
+
],
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
$: {
|
|
264
|
+
file: "app/src/main/AndroidManifest.xml",
|
|
265
|
+
mode: "merge",
|
|
266
|
+
target: "/manifest/application",
|
|
222
267
|
},
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
268
|
+
application: [
|
|
269
|
+
{
|
|
270
|
+
$: {
|
|
271
|
+
"android:usesCleartextTraffic": "true",
|
|
272
|
+
...this._opt.config.platform.android.config
|
|
273
|
+
? Object.keys(this._opt.config.platform.android.config)
|
|
274
|
+
.toObject(key => "android:" + key, key => this._opt.config.platform.android.config[key])
|
|
275
|
+
: {},
|
|
276
|
+
},
|
|
231
277
|
},
|
|
278
|
+
],
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
};
|
|
282
|
+
// SDK 버전 설정
|
|
283
|
+
if (this._opt.config.platform.android.sdkVersion != null) {
|
|
284
|
+
androidPlatform.preference.push(...[
|
|
285
|
+
{
|
|
286
|
+
$: {
|
|
287
|
+
name: "android-maxSdkVersion",
|
|
288
|
+
value: `${this._opt.config.platform.android.sdkVersion}`,
|
|
232
289
|
},
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
$: {
|
|
293
|
+
name: "android-minSdkVersion",
|
|
294
|
+
value: `${this._opt.config.platform.android.sdkVersion}`,
|
|
238
295
|
},
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
$: {
|
|
299
|
+
name: "android-targetSdkVersion",
|
|
300
|
+
value: `${this._opt.config.platform.android.sdkVersion}`,
|
|
244
301
|
},
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
$: {
|
|
305
|
+
name: "android-compileSdkVersion",
|
|
306
|
+
value: this.ANDROID_SDK_VERSION,
|
|
250
307
|
},
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
308
|
+
},
|
|
309
|
+
]);
|
|
310
|
+
}
|
|
311
|
+
// 권한 설정
|
|
312
|
+
if (this._opt.config.platform.android.permissions) {
|
|
313
|
+
androidPlatform["config-file"] = androidPlatform["config-file"] ?? [];
|
|
314
|
+
androidPlatform["config-file"].push({
|
|
315
|
+
"$": {
|
|
316
|
+
target: "AndroidManifest.xml",
|
|
317
|
+
parent: "/*",
|
|
318
|
+
},
|
|
319
|
+
"uses-permission": this._opt.config.platform.android.permissions.map((perm) => ({
|
|
320
|
+
$: {
|
|
321
|
+
"android:name": `android.permission.${perm.name}`,
|
|
322
|
+
...(perm.maxSdkVersion != null
|
|
323
|
+
? {
|
|
324
|
+
"android:maxSdkVersion": `${perm.maxSdkVersion}`,
|
|
325
|
+
}
|
|
326
|
+
: {}),
|
|
327
|
+
...(perm.ignore != null
|
|
328
|
+
? {
|
|
329
|
+
"tools:ignore": `${perm.ignore}`,
|
|
330
|
+
}
|
|
331
|
+
: {}),
|
|
259
332
|
},
|
|
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);
|
|
333
|
+
})),
|
|
334
|
+
});
|
|
278
335
|
}
|
|
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);
|
|
336
|
+
configXml.widget.platform.push(androidPlatform);
|
|
293
337
|
}
|
|
294
338
|
async buildAsync(outPath) {
|
|
295
|
-
const cordovaPath = path.resolve(this._opt.pkgPath,
|
|
296
|
-
// 실행
|
|
339
|
+
const cordovaPath = path.resolve(this._opt.pkgPath, this.CORDOVA_DIR_NAME);
|
|
340
|
+
// 빌드 실행 - 병렬 처리로 개선
|
|
297
341
|
const buildType = this._opt.config.debug ? "debug" : "release";
|
|
298
|
-
|
|
299
|
-
|
|
342
|
+
// 모든 플랫폼 동시에 빌드
|
|
343
|
+
await Promise.all(this._platforms.map(platform => this._execAsync(`npx cordova build ${platform} --${buildType}`, cordovaPath)));
|
|
344
|
+
// 결과물 복사 및 ZIP 파일 생성 - 병렬 처리
|
|
345
|
+
await Promise.all(Object.keys(this._opt.config.platform ?? {}).map(async (platform) => {
|
|
346
|
+
await this._processBuildOutputAsync(cordovaPath, outPath, platform, buildType);
|
|
347
|
+
}));
|
|
348
|
+
}
|
|
349
|
+
async _processBuildOutputAsync(cordovaPath, outPath, platform, buildType) {
|
|
350
|
+
const targetOutPath = path.resolve(outPath, platform);
|
|
351
|
+
// 결과물 복사: ANDROID
|
|
352
|
+
if (platform === "android") {
|
|
353
|
+
this._copyAndroidBuildOutput(cordovaPath, targetOutPath, buildType);
|
|
300
354
|
}
|
|
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
|
-
|
|
355
|
+
// 자동업데이트를 위한 파일 생성
|
|
356
|
+
await this._createUpdateZipAsync(cordovaPath, outPath, platform);
|
|
357
|
+
}
|
|
358
|
+
_copyAndroidBuildOutput(cordovaPath, targetOutPath, buildType) {
|
|
359
|
+
const apkFileName = this._opt.config.platform.android.sign
|
|
360
|
+
? `app-${buildType}.apk`
|
|
361
|
+
: `app-${buildType}-unsigned.apk`;
|
|
362
|
+
const latestDistApkFileName = path.basename(`${this._opt.config.appName}${this._opt.config.platform.android.sign
|
|
363
|
+
? ""
|
|
364
|
+
: "-unsigned"}-latest.apk`);
|
|
365
|
+
FsUtils.mkdirs(targetOutPath);
|
|
366
|
+
FsUtils.copy(path.resolve(cordovaPath, "platforms/android/app/build/outputs/apk", buildType, apkFileName), path.resolve(targetOutPath, latestDistApkFileName));
|
|
367
|
+
}
|
|
368
|
+
async _createUpdateZipAsync(cordovaPath, outPath, platform) {
|
|
369
|
+
const zip = new SdZip();
|
|
370
|
+
const wwwPath = path.resolve(cordovaPath, this.WWW_DIR_NAME);
|
|
371
|
+
const platformWwwPath = path.resolve(cordovaPath, this.PLATFORMS_DIR_NAME, platform, "platform_www");
|
|
372
|
+
this._addFilesToZip(zip, wwwPath);
|
|
373
|
+
this._addFilesToZip(zip, platformWwwPath);
|
|
374
|
+
// ZIP 파일 생성
|
|
375
|
+
const updateDirPath = path.resolve(outPath, platform, "updates");
|
|
376
|
+
FsUtils.mkdirs(updateDirPath);
|
|
377
|
+
FsUtils.writeFile(path.resolve(updateDirPath, this._npmConfig.version + ".zip"), await zip.compressAsync());
|
|
378
|
+
}
|
|
379
|
+
_addFilesToZip(zip, dirPath) {
|
|
380
|
+
const files = FsUtils.glob(path.resolve(dirPath, "**/*"), { nodir: true });
|
|
381
|
+
for (const file of files) {
|
|
382
|
+
const relFilePath = path.relative(dirPath, file);
|
|
383
|
+
const fileBuffer = FsUtils.readFileBuffer(file);
|
|
384
|
+
zip.write(relFilePath, new Uint8Array(fileBuffer));
|
|
331
385
|
}
|
|
332
386
|
}
|
|
333
387
|
static async runWebviewOnDeviceAsync(opt) {
|
|
@@ -335,7 +389,13 @@ export class SdCliCordova {
|
|
|
335
389
|
if (opt.url !== undefined) {
|
|
336
390
|
FsUtils.remove(path.resolve(cordovaPath, "www"));
|
|
337
391
|
FsUtils.mkdirs(path.resolve(cordovaPath, "www"));
|
|
338
|
-
FsUtils.writeFile(path.resolve(cordovaPath, "www/index.html"), `
|
|
392
|
+
FsUtils.writeFile(path.resolve(cordovaPath, "www/index.html"), `
|
|
393
|
+
'${opt.url}'로 이동중...
|
|
394
|
+
<script>
|
|
395
|
+
setTimeout(function () {
|
|
396
|
+
window.location.href = "${opt.url.replace(/\/$/, "")}/${opt.pkgName}/cordova/";
|
|
397
|
+
}, 3000);
|
|
398
|
+
</script>`.trim());
|
|
339
399
|
}
|
|
340
400
|
await SdProcess.spawnAsync(`npx cordova run ${opt.platform} --device`, { cwd: cordovaPath });
|
|
341
401
|
}
|