@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,13 +1,28 @@
|
|
|
1
1
|
import * as path from "path";
|
|
2
2
|
import { FsUtils, SdLogger, SdProcess } from "@simplysm/sd-core-node";
|
|
3
|
-
import xml2js from "xml2js";
|
|
4
|
-
import JSZip from "jszip";
|
|
5
3
|
import { INpmConfig } from "../types/common-configs.types";
|
|
6
4
|
import { ISdClientBuilderCordovaConfig } from "../types/config.types";
|
|
7
|
-
|
|
8
|
-
// const BIN_PATH = path.resolve(process.cwd(), "node_modules/.bin/cordova.cmd");
|
|
5
|
+
import { SdZip, XmlConvert } from "@simplysm/sd-core-common";
|
|
9
6
|
|
|
10
7
|
export class SdCliCordova {
|
|
8
|
+
// 상수 정의
|
|
9
|
+
private readonly CORDOVA_DIR_NAME = ".cordova";
|
|
10
|
+
private readonly PLATFORMS_DIR_NAME = "platforms";
|
|
11
|
+
private readonly WWW_DIR_NAME = "www";
|
|
12
|
+
|
|
13
|
+
private readonly PLUGINS_DIR_NAME = "plugins";
|
|
14
|
+
private readonly PLUGINS_FETCH_FILE = "fetch.json";
|
|
15
|
+
private readonly ANDROID_PLATFORM_VERSION = "12.0.0";
|
|
16
|
+
private readonly ANDROID_SDK_VERSION = "33";
|
|
17
|
+
private readonly KEYSTORE_FILE_NAME = "android.keystore";
|
|
18
|
+
private readonly CONFIG_XML_FILE_NAME = "config.xml";
|
|
19
|
+
private readonly CONFIG_XML_BACKUP_FILE_NAME = "config.xml.bak";
|
|
20
|
+
private readonly BUILD_JSON_FILE_NAME = "build.json";
|
|
21
|
+
private readonly ANDROID_SIGNING_PROP_PATH = "platforms/android/release-signing.properties";
|
|
22
|
+
private readonly ICON_DIR_PATH = "res/icons";
|
|
23
|
+
private readonly SPLASH_SCREEN_DIR_PATH = "res/screen/android";
|
|
24
|
+
private readonly SPLASH_SCREEN_XML_FILE = "splashscreen.xml";
|
|
25
|
+
|
|
11
26
|
private _logger = SdLogger.get(["simplysm", "sd-cli", "SdCliCordova"]);
|
|
12
27
|
|
|
13
28
|
private _platforms: string[];
|
|
@@ -16,18 +31,51 @@ export class SdCliCordova {
|
|
|
16
31
|
constructor(private readonly _opt: { pkgPath: string; config: ISdClientBuilderCordovaConfig }) {
|
|
17
32
|
this._platforms = Object.keys(this._opt.config.platform ?? { browser: {} });
|
|
18
33
|
this._npmConfig = FsUtils.readJson(path.resolve(this._opt.pkgPath, "package.json"));
|
|
19
|
-
// this._logger = Logger.get(["simplysm", "sd-cli", this.constructor.name, this._npmConfig.name]);
|
|
20
34
|
}
|
|
21
35
|
|
|
22
36
|
private async _execAsync(cmd: string, cwd: string): Promise<void> {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
37
|
+
try {
|
|
38
|
+
this._logger.debug(`실행 명령: ${cmd}`);
|
|
39
|
+
const msg = await SdProcess.spawnAsync(cmd, { cwd });
|
|
40
|
+
this._logger.debug(`실행 결과: ${msg}`);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
this._logger.error(`명령 실행 실패: ${cmd}`);
|
|
44
|
+
this._logger.error(`오류: ${err instanceof Error ? err.message : String(err)}`);
|
|
45
|
+
throw err;
|
|
46
|
+
}
|
|
26
47
|
}
|
|
27
48
|
|
|
28
49
|
public async initializeAsync(): Promise<void> {
|
|
29
|
-
const cordovaPath = path.resolve(this._opt.pkgPath,
|
|
50
|
+
const cordovaPath = path.resolve(this._opt.pkgPath, this.CORDOVA_DIR_NAME);
|
|
51
|
+
|
|
52
|
+
// 1. Cordova 프로젝트 초기화
|
|
53
|
+
await this._initializeCordovaProjectAsync(cordovaPath);
|
|
54
|
+
|
|
55
|
+
// 2. 플랫폼 관리
|
|
56
|
+
await this._managePlatformsAsync(cordovaPath);
|
|
57
|
+
|
|
58
|
+
// 3. 플러그인 관리
|
|
59
|
+
await this._managePluginsAsync(cordovaPath);
|
|
60
|
+
|
|
61
|
+
// 4. 안드로이드 서명 설정
|
|
62
|
+
this._setupAndroidSign(cordovaPath);
|
|
63
|
+
|
|
64
|
+
// 5. 빌드 설정 파일 생성
|
|
65
|
+
this._createBuildConfig(cordovaPath);
|
|
66
|
+
|
|
67
|
+
// 6. 아이콘 및 스플래시 스크린 설정
|
|
68
|
+
this._setupIconAndSplashScreen(cordovaPath);
|
|
30
69
|
|
|
70
|
+
// 7. XML 설정 구성
|
|
71
|
+
this._configureXml(cordovaPath);
|
|
72
|
+
|
|
73
|
+
// 8. 각 플랫폼 www 준비
|
|
74
|
+
await this._execAsync(`npx cordova prepare`, cordovaPath);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 1. Cordova 프로젝트 초기화
|
|
78
|
+
private async _initializeCordovaProjectAsync(cordovaPath: string): Promise<void> {
|
|
31
79
|
if (FsUtils.exists(cordovaPath)) {
|
|
32
80
|
this._logger.log("이미 생성되어있는 '.cordova'를 사용합니다.");
|
|
33
81
|
}
|
|
@@ -39,135 +87,173 @@ export class SdCliCordova {
|
|
|
39
87
|
`npx cordova create "${cordovaPath}" "${this._opt.config.appId}" "${this._opt.config.appName}"`,
|
|
40
88
|
process.cwd(),
|
|
41
89
|
);
|
|
42
|
-
|
|
43
|
-
// volta
|
|
44
|
-
// await this._execAsync(`volta pin node@18`, cordovaPath);
|
|
45
|
-
|
|
46
|
-
// package.json 수정
|
|
47
|
-
/*const npmConfig = FsUtil.readJson(path.resolve(cordovaPath, "package.json"));
|
|
48
|
-
npmConfig.volta = {
|
|
49
|
-
node: process.version.substring(1)
|
|
50
|
-
};
|
|
51
|
-
FsUtil.writeJson(path.resolve(cordovaPath, "package.json"), npmConfig);*/
|
|
52
90
|
}
|
|
53
91
|
|
|
54
92
|
// platforms 폴더 혹시 없으면 생성
|
|
55
|
-
FsUtils.mkdirs(path.resolve(cordovaPath,
|
|
93
|
+
FsUtils.mkdirs(path.resolve(cordovaPath, this.PLATFORMS_DIR_NAME));
|
|
56
94
|
|
|
57
95
|
// www 폴더 혹시 없으면 생성
|
|
58
|
-
FsUtils.mkdirs(path.resolve(cordovaPath,
|
|
96
|
+
FsUtils.mkdirs(path.resolve(cordovaPath, this.WWW_DIR_NAME));
|
|
97
|
+
}
|
|
59
98
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
99
|
+
// 2. 플랫폼 관리
|
|
100
|
+
private async _managePlatformsAsync(cordovaPath: string): Promise<void> {
|
|
101
|
+
const alreadyPlatforms = FsUtils.readdir(path.resolve(cordovaPath, this.PLATFORMS_DIR_NAME));
|
|
102
|
+
|
|
103
|
+
// 미설치 빌드 플랫폼 신규 생성 - 병렬 처리
|
|
104
|
+
const platformInstallPromises = this._platforms
|
|
105
|
+
.filter(platform => !alreadyPlatforms.includes(platform))
|
|
106
|
+
.map(platform => {
|
|
65
107
|
if (platform === "android") {
|
|
66
|
-
|
|
108
|
+
return this._execAsync(
|
|
109
|
+
`npx cordova platform add ${platform}@${this.ANDROID_PLATFORM_VERSION}`,
|
|
110
|
+
cordovaPath,
|
|
111
|
+
);
|
|
67
112
|
}
|
|
68
113
|
else {
|
|
69
|
-
|
|
114
|
+
return this._execAsync(`npx cordova platform add ${platform}`, cordovaPath);
|
|
70
115
|
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
116
|
+
});
|
|
73
117
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (!this._platforms.includes(alreadyPlatform)) {
|
|
77
|
-
await this._execAsync(`${BIN_PATH} platform remove ${alreadyPlatform}`, cordovaPath);
|
|
78
|
-
}
|
|
79
|
-
}*/
|
|
118
|
+
await Promise.all(platformInstallPromises);
|
|
119
|
+
}
|
|
80
120
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
121
|
+
// 3. 플러그인 관리
|
|
122
|
+
private async _managePluginsAsync(cordovaPath: string): Promise<void> {
|
|
123
|
+
const pluginsFetchPath = path.resolve(
|
|
124
|
+
cordovaPath,
|
|
125
|
+
this.PLUGINS_DIR_NAME,
|
|
126
|
+
this.PLUGINS_FETCH_FILE,
|
|
127
|
+
);
|
|
128
|
+
const pluginsFetch = FsUtils.exists(pluginsFetchPath)
|
|
129
|
+
? FsUtils.readJson(pluginsFetchPath)
|
|
130
|
+
: {};
|
|
131
|
+
|
|
132
|
+
const alreadyPlugins: Array<{
|
|
133
|
+
name: string;
|
|
134
|
+
id: string;
|
|
135
|
+
dependencies?: string[]
|
|
136
|
+
}> = Object.keys(pluginsFetch).map(key => ({
|
|
137
|
+
name: key,
|
|
138
|
+
id: pluginsFetch[key].source.id,
|
|
139
|
+
dependencies: pluginsFetch[key].dependencies,
|
|
140
|
+
}));
|
|
85
141
|
|
|
86
|
-
const
|
|
142
|
+
const usePlugins = (this._opt.config.plugins ?? []).distinct();
|
|
87
143
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
id: pluginsFetch[key].source.id,
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
}
|
|
144
|
+
// 사용하지 않는 플러그인 제거 및 새 플러그인 설치 - 의존성 때문에 순차 처리
|
|
145
|
+
await this._removeUnusedPluginsAsync(cordovaPath, alreadyPlugins, usePlugins);
|
|
146
|
+
await this._installNewPluginsAsync(cordovaPath, alreadyPlugins, usePlugins);
|
|
147
|
+
}
|
|
96
148
|
|
|
97
|
-
|
|
149
|
+
private async _removeUnusedPluginsAsync(
|
|
150
|
+
cordovaPath: string,
|
|
151
|
+
alreadyPlugins: Array<{ name: string; id: string; dependencies?: string[] }>,
|
|
152
|
+
usePlugins: string[],
|
|
153
|
+
): Promise<void> {
|
|
154
|
+
for (const alreadyPlugin of alreadyPlugins) {
|
|
155
|
+
// 사용하지 않는 플러그인 제거 시 의존성 검사
|
|
156
|
+
const isPluginUsed = usePlugins.some(
|
|
157
|
+
usePlugin => usePlugin === alreadyPlugin.id || usePlugin === alreadyPlugin.name,
|
|
158
|
+
);
|
|
98
159
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (alreadyPlugin.name === usePlugin || alreadyPlugin.id === usePlugin) {
|
|
104
|
-
hasPlugin = true;
|
|
105
|
-
break;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
160
|
+
if (!isPluginUsed) {
|
|
161
|
+
const isDependencyExists = alreadyPlugins.some(
|
|
162
|
+
plugin => plugin.dependencies?.includes(alreadyPlugin.name),
|
|
163
|
+
);
|
|
108
164
|
|
|
109
|
-
|
|
110
|
-
|
|
165
|
+
if (!isDependencyExists) {
|
|
166
|
+
await this._execAsync(`npx cordova plugin remove ${alreadyPlugin.name}`, cordovaPath);
|
|
167
|
+
}
|
|
111
168
|
}
|
|
112
|
-
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
113
171
|
|
|
114
|
-
|
|
172
|
+
private async _installNewPluginsAsync(
|
|
173
|
+
cordovaPath: string,
|
|
174
|
+
alreadyPlugins: Array<{ name: string; id: string; dependencies?: string[] }>,
|
|
175
|
+
usePlugins: string[],
|
|
176
|
+
): Promise<void> {
|
|
177
|
+
// 병렬로 플러그인을 설치하면 충돌이 발생할 수 있으므로 순차 처리
|
|
115
178
|
for (const usePlugin of usePlugins) {
|
|
116
|
-
|
|
179
|
+
const isPluginAlreadyInstalled = alreadyPlugins.some(
|
|
180
|
+
plugin => usePlugin === plugin.id || usePlugin === plugin.name,
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
if (!isPluginAlreadyInstalled) {
|
|
117
184
|
await this._execAsync(`npx cordova plugin add ${usePlugin}`, cordovaPath);
|
|
118
185
|
}
|
|
119
186
|
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// 4. 안드로이드 서명 설정
|
|
190
|
+
private _setupAndroidSign(cordovaPath: string): void {
|
|
191
|
+
const keystorePath = path.resolve(cordovaPath, this.KEYSTORE_FILE_NAME);
|
|
192
|
+
const signingPropsPath = path.resolve(cordovaPath, this.ANDROID_SIGNING_PROP_PATH);
|
|
120
193
|
|
|
121
|
-
// ANDROID SIGN 파일 복사
|
|
122
194
|
if (this._opt.config.platform?.android?.sign) {
|
|
123
195
|
FsUtils.copy(
|
|
124
196
|
path.resolve(this._opt.pkgPath, this._opt.config.platform.android.sign.keystore),
|
|
125
|
-
|
|
197
|
+
keystorePath,
|
|
126
198
|
);
|
|
127
199
|
}
|
|
128
200
|
else {
|
|
129
|
-
FsUtils.remove(
|
|
201
|
+
FsUtils.remove(keystorePath);
|
|
130
202
|
// SIGN을 안쓸경우 아래 파일이 생성되어 있으면 오류남
|
|
131
|
-
FsUtils.remove(
|
|
203
|
+
FsUtils.remove(signingPropsPath);
|
|
132
204
|
}
|
|
205
|
+
}
|
|
133
206
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
:
|
|
150
|
-
|
|
207
|
+
// 5. 빌드 설정 파일 생성
|
|
208
|
+
private _createBuildConfig(cordovaPath: string): void {
|
|
209
|
+
const buildJsonPath = path.resolve(cordovaPath, this.BUILD_JSON_FILE_NAME);
|
|
210
|
+
const keystorePath = path.resolve(cordovaPath, this.KEYSTORE_FILE_NAME);
|
|
211
|
+
|
|
212
|
+
const androidConfig = this._opt.config.platform?.android
|
|
213
|
+
? {
|
|
214
|
+
android: {
|
|
215
|
+
release: {
|
|
216
|
+
packageType: this._opt.config.platform.android.bundle ? "bundle" : "apk",
|
|
217
|
+
...(this._opt.config.platform.android.sign
|
|
218
|
+
? {
|
|
219
|
+
keystore: keystorePath,
|
|
220
|
+
storePassword: this._opt.config.platform.android.sign.storePassword,
|
|
221
|
+
alias: this._opt.config.platform.android.sign.alias,
|
|
222
|
+
password: this._opt.config.platform.android.sign.password,
|
|
223
|
+
keystoreType: this._opt.config.platform.android.sign.keystoreType,
|
|
224
|
+
}
|
|
225
|
+
: {}),
|
|
151
226
|
},
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
|
|
227
|
+
},
|
|
228
|
+
}
|
|
229
|
+
: {};
|
|
230
|
+
|
|
231
|
+
FsUtils.writeJson(buildJsonPath, androidConfig);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// 6. 아이콘 및 스플래시 스크린 설정
|
|
235
|
+
private _setupIconAndSplashScreen(cordovaPath: string): void {
|
|
236
|
+
const iconDirPath = path.resolve(cordovaPath, this.ICON_DIR_PATH);
|
|
237
|
+
const splashScreenPath = path.resolve(cordovaPath, this.SPLASH_SCREEN_DIR_PATH);
|
|
238
|
+
const splashScreenXmlPath = path.resolve(splashScreenPath, this.SPLASH_SCREEN_XML_FILE);
|
|
155
239
|
|
|
156
240
|
// ICON 파일 복사
|
|
157
241
|
if (this._opt.config.icon != null) {
|
|
242
|
+
FsUtils.mkdirs(iconDirPath);
|
|
158
243
|
FsUtils.copy(
|
|
159
244
|
path.resolve(this._opt.pkgPath, this._opt.config.icon),
|
|
160
|
-
path.resolve(
|
|
245
|
+
path.resolve(iconDirPath, path.basename(this._opt.config.icon)),
|
|
161
246
|
);
|
|
162
247
|
}
|
|
163
248
|
else {
|
|
164
|
-
FsUtils.remove(
|
|
249
|
+
FsUtils.remove(iconDirPath);
|
|
165
250
|
}
|
|
166
251
|
|
|
167
252
|
// SplashScreen 파일 생성
|
|
168
253
|
if (this._opt.config.platform?.android && this._opt.config.icon != null) {
|
|
254
|
+
FsUtils.mkdirs(splashScreenPath);
|
|
169
255
|
FsUtils.writeFile(
|
|
170
|
-
|
|
256
|
+
splashScreenXmlPath,
|
|
171
257
|
`
|
|
172
258
|
<?xml version="1.0" encoding="utf-8"?>
|
|
173
259
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
|
@@ -179,240 +265,264 @@ export class SdCliCordova {
|
|
|
179
265
|
</layer-list>`.trim(),
|
|
180
266
|
);
|
|
181
267
|
}
|
|
268
|
+
}
|
|
182
269
|
|
|
270
|
+
// 7. XML 설정 구성
|
|
271
|
+
private _configureXml(cordovaPath: string) {
|
|
183
272
|
// CONFIG: 초기값 백업
|
|
184
|
-
const configFilePath = path.resolve(cordovaPath,
|
|
185
|
-
const configBackFilePath = path.resolve(cordovaPath,
|
|
273
|
+
const configFilePath = path.resolve(cordovaPath, this.CONFIG_XML_FILE_NAME);
|
|
274
|
+
const configBackFilePath = path.resolve(cordovaPath, this.CONFIG_XML_BACKUP_FILE_NAME);
|
|
275
|
+
|
|
186
276
|
if (!FsUtils.exists(configBackFilePath)) {
|
|
187
277
|
FsUtils.copy(configFilePath, configBackFilePath);
|
|
188
278
|
}
|
|
189
279
|
|
|
190
280
|
// CONFIG: 초기값 읽기
|
|
191
281
|
const configFileContent = FsUtils.readFile(configBackFilePath);
|
|
192
|
-
const configXml =
|
|
282
|
+
const configXml = XmlConvert.parse(configFileContent);
|
|
283
|
+
|
|
284
|
+
// CONFIG: 기본 설정
|
|
285
|
+
this._configureBasicXmlSettings(configXml);
|
|
286
|
+
|
|
287
|
+
// CONFIG: 안드로이드 설정
|
|
288
|
+
if (this._opt.config.platform?.android) {
|
|
289
|
+
this._configureAndroidXmlSettings(configXml);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// CONFIG: 파일 새로 쓰기
|
|
293
|
+
const configResultContent = XmlConvert.stringify(configXml);
|
|
294
|
+
FsUtils.writeFile(configFilePath, configResultContent);
|
|
295
|
+
}
|
|
193
296
|
|
|
194
|
-
|
|
297
|
+
private _configureBasicXmlSettings(configXml: any): void {
|
|
298
|
+
// 버전 설정
|
|
195
299
|
configXml.widget.$.version = this._npmConfig.version;
|
|
196
300
|
|
|
197
|
-
//
|
|
301
|
+
// ICON 설정
|
|
198
302
|
if (this._opt.config.icon != null) {
|
|
199
|
-
configXml
|
|
303
|
+
configXml.widget.icon = [
|
|
200
304
|
{
|
|
201
305
|
$: {
|
|
202
|
-
src:
|
|
203
|
-
+ path.basename(this._opt.config.icon),
|
|
306
|
+
src: `${this.ICON_DIR_PATH}/${path.basename(this._opt.config.icon)}`,
|
|
204
307
|
},
|
|
205
308
|
},
|
|
206
309
|
];
|
|
207
310
|
}
|
|
208
311
|
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
configXml
|
|
212
|
-
configXml
|
|
213
|
-
configXml
|
|
214
|
-
configXml
|
|
215
|
-
|
|
216
|
-
configXml["widget"]["preference"] = [{ $: { name: "MixedContentMode", value: "1" } }];
|
|
312
|
+
// 접근허용 세팅
|
|
313
|
+
configXml.widget.content = [{ $: { src: "http://localhost/index.html" } }];
|
|
314
|
+
configXml.widget.access = [{ $: { origin: "*" } }];
|
|
315
|
+
configXml.widget["allow-navigation"] = [{ $: { href: "*" } }];
|
|
316
|
+
configXml.widget["allow-intent"] = [{ $: { href: "*" } }];
|
|
317
|
+
configXml.widget.preference = [{ $: { name: "MixedContentMode", value: "1" } }];
|
|
318
|
+
}
|
|
217
319
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
configXml.widget.$["xmlns:tools"] = "http://schemas.android.com/tools";
|
|
320
|
+
private _configureAndroidXmlSettings(configXml: any): void {
|
|
321
|
+
configXml.widget.$["xmlns:android"] = "http://schemas.android.com/apk/res/android";
|
|
322
|
+
configXml.widget.$["xmlns:tools"] = "http://schemas.android.com/tools";
|
|
222
323
|
|
|
223
|
-
|
|
324
|
+
configXml.widget.platform = configXml.widget.platform ?? [];
|
|
224
325
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
},
|
|
235
|
-
},
|
|
236
|
-
],
|
|
237
|
-
"edit-config": [
|
|
238
|
-
{
|
|
239
|
-
$: {
|
|
240
|
-
file: "app/src/main/AndroidManifest.xml",
|
|
241
|
-
mode: "merge",
|
|
242
|
-
target: "/manifest",
|
|
243
|
-
},
|
|
244
|
-
manifest: [
|
|
245
|
-
{
|
|
246
|
-
$: {
|
|
247
|
-
"xmlns:tools": "http://schemas.android.com/tools",
|
|
248
|
-
},
|
|
249
|
-
},
|
|
250
|
-
],
|
|
326
|
+
const androidPlatform = {
|
|
327
|
+
"$": {
|
|
328
|
+
name: "android",
|
|
329
|
+
},
|
|
330
|
+
"preference": [
|
|
331
|
+
{
|
|
332
|
+
$: {
|
|
333
|
+
name: "AndroidWindowSplashScreenAnimatedIcon",
|
|
334
|
+
value: `${this.SPLASH_SCREEN_DIR_PATH}/${this.SPLASH_SCREEN_XML_FILE}`,
|
|
251
335
|
},
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
$: {
|
|
261
|
-
"android:usesCleartextTraffic": "true",
|
|
262
|
-
...this._opt.config.platform.android.config
|
|
263
|
-
? Object.keys(this._opt.config.platform.android.config)
|
|
264
|
-
.toObject(
|
|
265
|
-
key => "android:" + key,
|
|
266
|
-
key => this._opt.config.platform!.android!.config![key],
|
|
267
|
-
)
|
|
268
|
-
: {},
|
|
269
|
-
},
|
|
270
|
-
},
|
|
271
|
-
],
|
|
336
|
+
},
|
|
337
|
+
],
|
|
338
|
+
"edit-config": [
|
|
339
|
+
{
|
|
340
|
+
$: {
|
|
341
|
+
file: "app/src/main/AndroidManifest.xml",
|
|
342
|
+
mode: "merge",
|
|
343
|
+
target: "/manifest",
|
|
272
344
|
},
|
|
273
|
-
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
if (this._opt.config.platform.android.sdkVersion != null) {
|
|
277
|
-
androidPlatform.preference.push(
|
|
278
|
-
...[
|
|
345
|
+
manifest: [
|
|
279
346
|
{
|
|
280
347
|
$: {
|
|
281
|
-
|
|
282
|
-
value: `${this._opt.config.platform.android.sdkVersion}`,
|
|
283
|
-
},
|
|
284
|
-
},
|
|
285
|
-
{
|
|
286
|
-
$: {
|
|
287
|
-
name: "android-minSdkVersion",
|
|
288
|
-
value: `${this._opt.config.platform.android.sdkVersion}`,
|
|
289
|
-
},
|
|
290
|
-
},
|
|
291
|
-
{
|
|
292
|
-
$: {
|
|
293
|
-
name: "android-targetSdkVersion",
|
|
294
|
-
value: `${this._opt.config.platform.android.sdkVersion}`,
|
|
348
|
+
"xmlns:tools": "http://schemas.android.com/tools",
|
|
295
349
|
},
|
|
296
350
|
},
|
|
351
|
+
],
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
$: {
|
|
355
|
+
file: "app/src/main/AndroidManifest.xml",
|
|
356
|
+
mode: "merge",
|
|
357
|
+
target: "/manifest/application",
|
|
358
|
+
},
|
|
359
|
+
application: [
|
|
297
360
|
{
|
|
298
361
|
$: {
|
|
299
|
-
|
|
300
|
-
|
|
362
|
+
"android:usesCleartextTraffic": "true",
|
|
363
|
+
...this._opt.config.platform!.android!.config
|
|
364
|
+
? Object.keys(this._opt.config.platform!.android!.config)
|
|
365
|
+
.toObject(
|
|
366
|
+
key => "android:" + key,
|
|
367
|
+
key => this._opt.config.platform!.android!.config![key],
|
|
368
|
+
)
|
|
369
|
+
: {},
|
|
301
370
|
},
|
|
302
371
|
},
|
|
303
372
|
],
|
|
304
|
-
|
|
305
|
-
|
|
373
|
+
},
|
|
374
|
+
],
|
|
375
|
+
};
|
|
306
376
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
377
|
+
// SDK 버전 설정
|
|
378
|
+
if (this._opt.config.platform!.android!.sdkVersion != null) {
|
|
379
|
+
androidPlatform.preference.push(
|
|
380
|
+
...[
|
|
381
|
+
{
|
|
382
|
+
$: {
|
|
383
|
+
name: "android-maxSdkVersion",
|
|
384
|
+
value: `${this._opt.config.platform!.android!.sdkVersion}`,
|
|
385
|
+
},
|
|
313
386
|
},
|
|
314
|
-
|
|
387
|
+
{
|
|
315
388
|
$: {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
? {
|
|
319
|
-
"android:maxSdkVersion": `${perm.maxSdkVersion}`,
|
|
320
|
-
}
|
|
321
|
-
: {}),
|
|
322
|
-
...(perm.ignore != null
|
|
323
|
-
? {
|
|
324
|
-
"tools:ignore": `${perm.ignore}`,
|
|
325
|
-
}
|
|
326
|
-
: {}),
|
|
389
|
+
name: "android-minSdkVersion",
|
|
390
|
+
value: `${this._opt.config.platform!.android!.sdkVersion}`,
|
|
327
391
|
},
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
$: {
|
|
395
|
+
name: "android-targetSdkVersion",
|
|
396
|
+
value: `${this._opt.config.platform!.android!.sdkVersion}`,
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
$: {
|
|
401
|
+
name: "android-compileSdkVersion",
|
|
402
|
+
value: this.ANDROID_SDK_VERSION,
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
],
|
|
406
|
+
);
|
|
333
407
|
}
|
|
334
408
|
|
|
335
|
-
//
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
409
|
+
// 권한 설정
|
|
410
|
+
if (this._opt.config.platform!.android!.permissions) {
|
|
411
|
+
androidPlatform["config-file"] = androidPlatform["config-file"] ?? [];
|
|
412
|
+
androidPlatform["config-file"].push({
|
|
413
|
+
"$": {
|
|
414
|
+
target: "AndroidManifest.xml",
|
|
415
|
+
parent: "/*",
|
|
416
|
+
},
|
|
417
|
+
"uses-permission": this._opt.config.platform!.android!.permissions.map((perm) => ({
|
|
418
|
+
$: {
|
|
419
|
+
"android:name": `android.permission.${perm.name}`,
|
|
420
|
+
...(perm.maxSdkVersion != null
|
|
421
|
+
? {
|
|
422
|
+
"android:maxSdkVersion": `${perm.maxSdkVersion}`,
|
|
423
|
+
}
|
|
424
|
+
: {}),
|
|
425
|
+
...(perm.ignore != null
|
|
426
|
+
? {
|
|
427
|
+
"tools:ignore": `${perm.ignore}`,
|
|
428
|
+
}
|
|
429
|
+
: {}),
|
|
430
|
+
},
|
|
431
|
+
})),
|
|
432
|
+
});
|
|
433
|
+
}
|
|
348
434
|
|
|
349
|
-
|
|
350
|
-
await this._execAsync(`npx cordova prepare`, cordovaPath);
|
|
435
|
+
configXml.widget.platform.push(androidPlatform);
|
|
351
436
|
}
|
|
352
437
|
|
|
353
438
|
public async buildAsync(outPath: string): Promise<void> {
|
|
354
|
-
const cordovaPath = path.resolve(this._opt.pkgPath,
|
|
439
|
+
const cordovaPath = path.resolve(this._opt.pkgPath, this.CORDOVA_DIR_NAME);
|
|
355
440
|
|
|
356
|
-
// 실행
|
|
441
|
+
// 빌드 실행 - 병렬 처리로 개선
|
|
357
442
|
const buildType = this._opt.config.debug ? "debug" : "release";
|
|
358
|
-
|
|
359
|
-
|
|
443
|
+
|
|
444
|
+
// 모든 플랫폼 동시에 빌드
|
|
445
|
+
await Promise.all(this._platforms.map(platform =>
|
|
446
|
+
this._execAsync(`npx cordova build ${platform} --${buildType}`, cordovaPath),
|
|
447
|
+
));
|
|
448
|
+
|
|
449
|
+
// 결과물 복사 및 ZIP 파일 생성 - 병렬 처리
|
|
450
|
+
await Promise.all(Object.keys(this._opt.config.platform ?? {}).map(async platform => {
|
|
451
|
+
await this._processBuildOutputAsync(cordovaPath, outPath, platform, buildType);
|
|
452
|
+
}));
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
private async _processBuildOutputAsync(
|
|
456
|
+
cordovaPath: string,
|
|
457
|
+
outPath: string,
|
|
458
|
+
platform: string,
|
|
459
|
+
buildType: string,
|
|
460
|
+
): Promise<void> {
|
|
461
|
+
const targetOutPath = path.resolve(outPath, platform);
|
|
462
|
+
|
|
463
|
+
// 결과물 복사: ANDROID
|
|
464
|
+
if (platform === "android") {
|
|
465
|
+
this._copyAndroidBuildOutput(cordovaPath, targetOutPath, buildType);
|
|
360
466
|
}
|
|
361
467
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
// 결과물 복사: ANDROID
|
|
366
|
-
if (platform === "android") {
|
|
367
|
-
const apkFileName = this._opt.config.platform!.android!.sign
|
|
368
|
-
? `app-${buildType}.apk`
|
|
369
|
-
: `app-${buildType}-unsigned.apk`;
|
|
370
|
-
const latestDistApkFileName = path.basename(
|
|
371
|
-
`${this._opt.config.appName}${this._opt.config.platform!.android!.sign
|
|
372
|
-
? ""
|
|
373
|
-
: "-unsigned"}-latest.apk`,
|
|
374
|
-
);
|
|
375
|
-
FsUtils.mkdirs(targetOutPath);
|
|
376
|
-
FsUtils.copy(
|
|
377
|
-
path.resolve(
|
|
378
|
-
cordovaPath,
|
|
379
|
-
"platforms/android/app/build/outputs/apk",
|
|
380
|
-
buildType,
|
|
381
|
-
apkFileName,
|
|
382
|
-
),
|
|
383
|
-
path.resolve(targetOutPath, latestDistApkFileName),
|
|
384
|
-
);
|
|
385
|
-
}
|
|
468
|
+
// 자동업데이트를 위한 파일 생성
|
|
469
|
+
await this._createUpdateZipAsync(cordovaPath, outPath, platform);
|
|
470
|
+
}
|
|
386
471
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
"
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
const fileBuffer = FsUtils.readFileBuffer(platformWwwFile);
|
|
409
|
-
zip.file(relFilePath, fileBuffer);
|
|
410
|
-
}
|
|
472
|
+
private _copyAndroidBuildOutput(
|
|
473
|
+
cordovaPath: string,
|
|
474
|
+
targetOutPath: string,
|
|
475
|
+
buildType: string,
|
|
476
|
+
) {
|
|
477
|
+
const apkFileName = this._opt.config.platform!.android!.sign
|
|
478
|
+
? `app-${buildType}.apk`
|
|
479
|
+
: `app-${buildType}-unsigned.apk`;
|
|
480
|
+
|
|
481
|
+
const latestDistApkFileName = path.basename(
|
|
482
|
+
`${this._opt.config.appName}${this._opt.config.platform!.android!.sign
|
|
483
|
+
? ""
|
|
484
|
+
: "-unsigned"}-latest.apk`,
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
FsUtils.mkdirs(targetOutPath);
|
|
488
|
+
FsUtils.copy(
|
|
489
|
+
path.resolve(cordovaPath, "platforms/android/app/build/outputs/apk", buildType, apkFileName),
|
|
490
|
+
path.resolve(targetOutPath, latestDistApkFileName),
|
|
491
|
+
);
|
|
492
|
+
}
|
|
411
493
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
494
|
+
private async _createUpdateZipAsync(
|
|
495
|
+
cordovaPath: string,
|
|
496
|
+
outPath: string,
|
|
497
|
+
platform: string,
|
|
498
|
+
): Promise<void> {
|
|
499
|
+
const zip = new SdZip();
|
|
500
|
+
const wwwPath = path.resolve(cordovaPath, this.WWW_DIR_NAME);
|
|
501
|
+
const platformWwwPath = path.resolve(
|
|
502
|
+
cordovaPath,
|
|
503
|
+
this.PLATFORMS_DIR_NAME,
|
|
504
|
+
platform,
|
|
505
|
+
"platform_www",
|
|
506
|
+
);
|
|
507
|
+
|
|
508
|
+
this._addFilesToZip(zip, wwwPath);
|
|
509
|
+
this._addFilesToZip(zip, platformWwwPath);
|
|
510
|
+
|
|
511
|
+
// ZIP 파일 생성
|
|
512
|
+
const updateDirPath = path.resolve(outPath, platform, "updates");
|
|
513
|
+
FsUtils.mkdirs(updateDirPath);
|
|
514
|
+
FsUtils.writeFile(
|
|
515
|
+
path.resolve(updateDirPath, this._npmConfig.version + ".zip"),
|
|
516
|
+
await zip.compressAsync(),
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
private _addFilesToZip(zip: SdZip, dirPath: string) {
|
|
521
|
+
const files = FsUtils.glob(path.resolve(dirPath, "**/*"), { nodir: true });
|
|
522
|
+
for (const file of files) {
|
|
523
|
+
const relFilePath = path.relative(dirPath, file);
|
|
524
|
+
const fileBuffer = FsUtils.readFileBuffer(file);
|
|
525
|
+
zip.write(relFilePath, new Uint8Array(fileBuffer));
|
|
416
526
|
}
|
|
417
527
|
}
|
|
418
528
|
|
|
@@ -428,13 +538,16 @@ export class SdCliCordova {
|
|
|
428
538
|
FsUtils.mkdirs(path.resolve(cordovaPath, "www"));
|
|
429
539
|
FsUtils.writeFile(
|
|
430
540
|
path.resolve(cordovaPath, "www/index.html"),
|
|
431
|
-
`
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
541
|
+
`
|
|
542
|
+
'${opt.url}'로 이동중...
|
|
543
|
+
<script>
|
|
544
|
+
setTimeout(function () {
|
|
545
|
+
window.location.href = "${opt.url.replace(/\/$/, "")}/${opt.pkgName}/cordova/";
|
|
546
|
+
}, 3000);
|
|
547
|
+
</script>`.trim(),
|
|
435
548
|
);
|
|
436
549
|
}
|
|
437
550
|
|
|
438
551
|
await SdProcess.spawnAsync(`npx cordova run ${opt.platform} --device`, { cwd: cordovaPath });
|
|
439
552
|
}
|
|
440
|
-
}
|
|
553
|
+
}
|