@simplysm/sd-cli 12.8.20 → 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.
Files changed (37) hide show
  1. package/dist/entry/sd-cli-cordova.d.ts +30 -0
  2. package/dist/entry/sd-cli-cordova.js +309 -249
  3. package/dist/entry/sd-cli-cordova.js.map +1 -1
  4. package/dist/entry/sd-cli-project.d.ts +1 -1
  5. package/dist/entry/sd-cli-project.js +8 -9
  6. package/dist/entry/sd-cli-project.js.map +1 -1
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.js +1 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/pkg-builders/client/sd-ng.bundler.d.ts +15 -1
  11. package/dist/pkg-builders/client/sd-ng.bundler.js +60 -70
  12. package/dist/pkg-builders/client/sd-ng.bundler.js.map +1 -1
  13. package/dist/pkg-builders/client/sd-ng.plugin-creator.js +49 -29
  14. package/dist/pkg-builders/client/sd-ng.plugin-creator.js.map +1 -1
  15. package/dist/pkg-builders/lib/sd-ts-lib.builder.js +7 -4
  16. package/dist/pkg-builders/lib/sd-ts-lib.builder.js.map +1 -1
  17. package/dist/ts-compiler/sd-ts-compiler.d.ts +24 -2
  18. package/dist/ts-compiler/sd-ts-compiler.js +267 -575
  19. package/dist/ts-compiler/sd-ts-compiler.js.map +1 -1
  20. package/dist/ts-compiler/sd-ts-dependency-analyzer.d.ts +10 -0
  21. package/dist/ts-compiler/sd-ts-dependency-analyzer.js +140 -0
  22. package/dist/ts-compiler/sd-ts-dependency-analyzer.js.map +1 -0
  23. package/dist/types/ts-compiler.types.d.ts +12 -7
  24. package/dist/types/worker.types.d.ts +13 -0
  25. package/dist/utils/sd-cli-performance-time.js +1 -1
  26. package/package.json +5 -7
  27. package/src/entry/sd-cli-cordova.ts +394 -281
  28. package/src/entry/sd-cli-project.ts +11 -23
  29. package/src/index.ts +1 -0
  30. package/src/pkg-builders/client/sd-ng.bundler.ts +70 -69
  31. package/src/pkg-builders/client/sd-ng.plugin-creator.ts +47 -27
  32. package/src/pkg-builders/lib/sd-ts-lib.builder.ts +14 -7
  33. package/src/ts-compiler/sd-ts-compiler.ts +334 -705
  34. package/src/ts-compiler/sd-ts-dependency-analyzer.ts +176 -0
  35. package/src/types/ts-compiler.types.ts +11 -6
  36. package/src/types/worker.types.ts +7 -6
  37. 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 xml2js from "xml2js";
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
- this._logger.debug(cmd);
16
- const msg = await SdProcess.spawnAsync(cmd, { cwd });
17
- this._logger.debug(msg);
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, ".cordova");
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, "platforms"));
69
+ FsUtils.mkdirs(path.resolve(cordovaPath, this.PLATFORMS_DIR_NAME));
39
70
  // www 폴더 혹시 없으면 생성
40
- FsUtils.mkdirs(path.resolve(cordovaPath, "www"));
41
- // 미설치 빌드 플랫폼 신규 생성
42
- const alreadyPlatforms = FsUtils.readdir(path.resolve(cordovaPath, "platforms"));
43
- for (const platform of this._platforms) {
44
- if (!alreadyPlatforms.includes(platform)) {
45
- // await this._execAsync(`${BIN_PATH} platform add ${platform}`, cordovaPath);
46
- if (platform === "android") {
47
- await this._execAsync(`npx cordova platform add ${platform}@12.0.0`, cordovaPath);
48
- }
49
- else {
50
- await this._execAsync(`npx cordova platform add ${platform}`, cordovaPath);
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
- // TODO: Dependency에 의해 설치된 플러그인 삭제되면 안됨 android.json의 installed_plugin으로 변경하면 될지도?
75
- /*for (const alreadyPlugin of alreadyPlugins) {
76
- let hasPlugin = false;
77
- for (const usePlugin of usePlugins) {
78
- if (alreadyPlugin.name === usePlugin || alreadyPlugin.id === usePlugin) {
79
- hasPlugin = true;
80
- break;
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
- if (!hasPlugin) {
85
- await this._execAsync(`${BIN_PATH} plugin remove ${alreadyPlugin.name}`, cordovaPath);
86
- }
87
- }*/
88
- // 미설치 플러그인들 설치
115
+ }
116
+ }
117
+ async _installNewPluginsAsync(cordovaPath, alreadyPlugins, usePlugins) {
118
+ // 병렬로 플러그인을 설치하면 충돌이 발생할 수 있으므로 순차 처리
89
119
  for (const usePlugin of usePlugins) {
90
- if (!alreadyPlugins.some((item) => usePlugin === item.id || usePlugin === item.name)) {
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
- // ANDROID SIGN 파일 복사
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), path.resolve(cordovaPath, "android.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(path.resolve(cordovaPath, "android.keystore"));
134
+ FsUtils.remove(keystorePath);
100
135
  // SIGN을 안쓸경우 아래 파일이 생성되어 있으면 오류남
101
- FsUtils.remove(path.resolve(cordovaPath, "platforms/android/release-signing.properties"));
136
+ FsUtils.remove(signingPropsPath);
102
137
  }
103
- // 빌드 옵션 파일 생성
104
- FsUtils.writeJson(path.resolve(cordovaPath, "build.json"), {
105
- ...(this._opt.config.platform?.android
106
- ? {
107
- android: {
108
- release: {
109
- packageType: this._opt.config.platform.android.bundle ? "bundle" : "apk",
110
- ...(this._opt.config.platform.android.sign
111
- ? {
112
- keystore: path.resolve(cordovaPath, "android.keystore"),
113
- storePassword: this._opt.config.platform.android.sign.storePassword,
114
- alias: this._opt.config.platform.android.sign.alias,
115
- password: this._opt.config.platform.android.sign.password,
116
- keystoreType: this._opt.config.platform.android.sign.keystoreType,
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.copy(path.resolve(this._opt.pkgPath, this._opt.config.icon), path.resolve(cordovaPath, "res/icons", path.basename(this._opt.config.icon)));
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(path.resolve(cordovaPath, "res/icons"));
174
+ FsUtils.remove(iconDirPath);
130
175
  }
131
176
  // SplashScreen 파일 생성
132
177
  if (this._opt.config.platform?.android && this._opt.config.icon != null) {
133
- FsUtils.writeFile(path.resolve(cordovaPath, "res/screen/android/splashscreen.xml"), `
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, "config.xml");
145
- const configBackFilePath = path.resolve(cordovaPath, "config.xml.bak");
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 = await xml2js.parseStringPromise(configFileContent);
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
- // CONFIG: ICON 설정
214
+ // ICON 설정
155
215
  if (this._opt.config.icon != null) {
156
- configXml["widget"]["icon"] = [
216
+ configXml.widget.icon = [
157
217
  {
158
218
  $: {
159
- src: "res/icons/"
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
- // CONFIG: 접근허용 세팅
166
- // configXml["widget"]["content"] = [{ $: { src: "index.html" } }];
167
- configXml["widget"]["content"] = [{ $: { src: "http://localhost/index.html" } }];
168
- configXml["widget"]["access"] = [{ $: { origin: "*" } }];
169
- configXml["widget"]["allow-navigation"] = [{ $: { href: "*" } }];
170
- configXml["widget"]["allow-intent"] = [{ $: { href: "*" } }];
171
- // configXml["widget"]["preference"] = [{ $: { name: "Scheme", value: "http" } }];
172
- configXml["widget"]["preference"] = [{ $: { name: "MixedContentMode", value: "1" } }];
173
- // CONFIG: ANDROID usesCleartextTraffic 설정 및 splashscreen 파일 설정
174
- if (this._opt.config.platform?.android) {
175
- configXml.widget.$["xmlns:android"] = "http://schemas.android.com/apk/res/android";
176
- configXml.widget.$["xmlns:tools"] = "http://schemas.android.com/tools";
177
- configXml["widget"]["platform"] = configXml["widget"]["platform"] ?? [];
178
- const androidPlatform = {
179
- "$": {
180
- name: "android",
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
- "preference": [
183
- {
184
- $: {
185
- name: "AndroidWindowSplashScreenAnimatedIcon",
186
- value: "res/screen/android/splashscreen.xml",
187
- },
246
+ ],
247
+ "edit-config": [
248
+ {
249
+ $: {
250
+ file: "app/src/main/AndroidManifest.xml",
251
+ mode: "merge",
252
+ target: "/manifest",
188
253
  },
189
- ],
190
- "edit-config": [
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
- application: [
212
- {
213
- $: {
214
- "android:usesCleartextTraffic": "true",
215
- ...this._opt.config.platform.android.config
216
- ? Object.keys(this._opt.config.platform.android.config)
217
- .toObject(key => "android:" + key, key => this._opt.config.platform.android.config[key])
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
- if (this._opt.config.platform.android.sdkVersion != null) {
226
- androidPlatform.preference.push(...[
227
- {
228
- $: {
229
- name: "android-maxSdkVersion",
230
- value: `${this._opt.config.platform.android.sdkVersion}`,
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
- name: "android-minSdkVersion",
236
- value: `${this._opt.config.platform.android.sdkVersion}`,
237
- },
290
+ },
291
+ {
292
+ $: {
293
+ name: "android-minSdkVersion",
294
+ value: `${this._opt.config.platform.android.sdkVersion}`,
238
295
  },
239
- {
240
- $: {
241
- name: "android-targetSdkVersion",
242
- value: `${this._opt.config.platform.android.sdkVersion}`,
243
- },
296
+ },
297
+ {
298
+ $: {
299
+ name: "android-targetSdkVersion",
300
+ value: `${this._opt.config.platform.android.sdkVersion}`,
244
301
  },
245
- {
246
- $: {
247
- name: "android-compileSdkVersion",
248
- value: `33`,
249
- },
302
+ },
303
+ {
304
+ $: {
305
+ name: "android-compileSdkVersion",
306
+ value: this.ANDROID_SDK_VERSION,
250
307
  },
251
- ]);
252
- }
253
- if (this._opt.config.platform.android.permissions) {
254
- androidPlatform["config-file"] = androidPlatform["config-file"] ?? [];
255
- androidPlatform["config-file"].push({
256
- "$": {
257
- target: "AndroidManifest.xml",
258
- parent: "/*",
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
- "uses-permission": this._opt.config.platform.android.permissions.map((perm) => ({
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
- // CONFIG: 파일 새로 쓰기
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, ".cordova");
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
- for (const platform of this._platforms) {
299
- await this._execAsync(`npx cordova build ${platform} --${buildType}`, cordovaPath);
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
- for (const platform of Object.keys(this._opt.config.platform ?? {})) {
302
- const targetOutPath = path.resolve(outPath, platform);
303
- // 결과물 복사: ANDROID
304
- if (platform === "android") {
305
- const apkFileName = this._opt.config.platform.android.sign
306
- ? `app-${buildType}.apk`
307
- : `app-${buildType}-unsigned.apk`;
308
- const latestDistApkFileName = path.basename(`${this._opt.config.appName}${this._opt.config.platform.android.sign
309
- ? ""
310
- : "-unsigned"}-latest.apk`);
311
- FsUtils.mkdirs(targetOutPath);
312
- FsUtils.copy(path.resolve(cordovaPath, "platforms/android/app/build/outputs/apk", buildType, apkFileName), path.resolve(targetOutPath, latestDistApkFileName));
313
- }
314
- // 자동업데이트를 위한 파일 쓰기 (ZIP)
315
- const zip = new JSZip();
316
- const wwwFiles = FsUtils.glob(path.resolve(cordovaPath, "www/**/*"), { nodir: true });
317
- for (const wwwFile of wwwFiles) {
318
- const relFilePath = path.relative(path.resolve(cordovaPath, "www"), wwwFile);
319
- const fileBuffer = FsUtils.readFileBuffer(wwwFile);
320
- zip.file(relFilePath, fileBuffer);
321
- }
322
- const platformWwwFiles = FsUtils.glob(path.resolve(cordovaPath, "platforms", platform, "platform_www/**/*"), {
323
- nodir: true,
324
- });
325
- for (const platformWwwFile of platformWwwFiles) {
326
- const relFilePath = path.relative(path.resolve(cordovaPath, "platforms", platform, "platform_www"), platformWwwFile);
327
- const fileBuffer = FsUtils.readFileBuffer(platformWwwFile);
328
- zip.file(relFilePath, fileBuffer);
329
- }
330
- FsUtils.writeFile(path.resolve(path.resolve(outPath, platform, "updates"), this._npmConfig.version + ".zip"), await zip.generateAsync({ type: "nodebuffer" }));
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"), `'${opt.url}'로 이동중... <script>setTimeout(function () {window.location.href = "${opt.url.replace(/\/$/, "")}/${opt.pkgName}/cordova/"}, 3000);</script>`.trim());
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
  }