@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.
Files changed (40) hide show
  1. package/dist/entry/sd-cli-cordova.d.ts +30 -0
  2. package/dist/entry/sd-cli-cordova.js +307 -246
  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/pkg-builders/server/sd-server.bundler.js +11 -10
  18. package/dist/pkg-builders/server/sd-server.bundler.js.map +1 -1
  19. package/dist/ts-compiler/sd-ts-compiler.d.ts +25 -2
  20. package/dist/ts-compiler/sd-ts-compiler.js +306 -575
  21. package/dist/ts-compiler/sd-ts-compiler.js.map +1 -1
  22. package/dist/ts-compiler/sd-ts-dependency-analyzer.d.ts +6 -0
  23. package/dist/ts-compiler/sd-ts-dependency-analyzer.js +141 -0
  24. package/dist/ts-compiler/sd-ts-dependency-analyzer.js.map +1 -0
  25. package/dist/types/ts-compiler.types.d.ts +12 -7
  26. package/dist/types/worker.types.d.ts +13 -0
  27. package/dist/utils/sd-cli-performance-time.js +1 -1
  28. package/package.json +6 -8
  29. package/src/entry/sd-cli-cordova.ts +393 -280
  30. package/src/entry/sd-cli-project.ts +11 -23
  31. package/src/index.ts +1 -0
  32. package/src/pkg-builders/client/sd-ng.bundler.ts +67 -69
  33. package/src/pkg-builders/client/sd-ng.plugin-creator.ts +47 -27
  34. package/src/pkg-builders/lib/sd-ts-lib.builder.ts +14 -7
  35. package/src/pkg-builders/server/sd-server.bundler.ts +22 -12
  36. package/src/ts-compiler/sd-ts-compiler.ts +379 -704
  37. package/src/ts-compiler/sd-ts-dependency-analyzer.ts +185 -0
  38. package/src/types/ts-compiler.types.ts +11 -6
  39. package/src/types/worker.types.ts +7 -6
  40. 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,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, "platforms"));
69
+ FsUtils.mkdirs(path.resolve(cordovaPath, this.PLATFORMS_DIR_NAME));
39
70
  // www 폴더 혹시 없으면 생성
40
- FsUtils.mkdirs(path.resolve(cordovaPath, "www"));
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 (!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
- }
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
- // 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;
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
- if (!hasPlugin) {
85
- await this._execAsync(`${BIN_PATH} plugin remove ${alreadyPlugin.name}`, cordovaPath);
86
- }
87
- }*/
88
- // 미설치 플러그인들 설치
114
+ }
115
+ }
116
+ async _installNewPluginsAsync(cordovaPath, alreadyPlugins, usePlugins) {
117
+ // 병렬로 플러그인을 설치하면 충돌이 발생할 수 있으므로 순차 처리
89
118
  for (const usePlugin of usePlugins) {
90
- if (!alreadyPlugins.some((item) => usePlugin === item.id || usePlugin === item.name)) {
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
- // ANDROID SIGN 파일 복사
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), path.resolve(cordovaPath, "android.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(path.resolve(cordovaPath, "android.keystore"));
133
+ FsUtils.remove(keystorePath);
100
134
  // SIGN을 안쓸경우 아래 파일이 생성되어 있으면 오류남
101
- FsUtils.remove(path.resolve(cordovaPath, "platforms/android/release-signing.properties"));
135
+ FsUtils.remove(signingPropsPath);
102
136
  }
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
- },
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.copy(path.resolve(this._opt.pkgPath, this._opt.config.icon), path.resolve(cordovaPath, "res/icons", path.basename(this._opt.config.icon)));
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(path.resolve(cordovaPath, "res/icons"));
173
+ FsUtils.remove(iconDirPath);
130
174
  }
131
175
  // SplashScreen 파일 생성
132
176
  if (this._opt.config.platform?.android && this._opt.config.icon != null) {
133
- FsUtils.writeFile(path.resolve(cordovaPath, "res/screen/android/splashscreen.xml"), `
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, "config.xml");
145
- const configBackFilePath = path.resolve(cordovaPath, "config.xml.bak");
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 = await xml2js.parseStringPromise(configFileContent);
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
- // CONFIG: ICON 설정
215
+ // ICON 설정
155
216
  if (this._opt.config.icon != null) {
156
- configXml["widget"]["icon"] = [
217
+ configXml.widget.icon = [
157
218
  {
158
219
  $: {
159
- src: "res/icons/"
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
- // 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",
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
- "preference": [
183
- {
184
- $: {
185
- name: "AndroidWindowSplashScreenAnimatedIcon",
186
- value: "res/screen/android/splashscreen.xml",
187
- },
247
+ ],
248
+ "edit-config": [
249
+ {
250
+ $: {
251
+ file: "app/src/main/AndroidManifest.xml",
252
+ mode: "merge",
253
+ target: "/manifest",
188
254
  },
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
- },
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
- 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
- ],
261
+ ],
262
+ },
263
+ {
264
+ $: {
265
+ file: "app/src/main/AndroidManifest.xml",
266
+ mode: "merge",
267
+ target: "/manifest/application",
222
268
  },
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}`,
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
- name: "android-minSdkVersion",
236
- value: `${this._opt.config.platform.android.sdkVersion}`,
237
- },
291
+ },
292
+ {
293
+ $: {
294
+ name: "android-minSdkVersion",
295
+ value: `${this._opt.config.platform.android.sdkVersion}`,
238
296
  },
239
- {
240
- $: {
241
- name: "android-targetSdkVersion",
242
- value: `${this._opt.config.platform.android.sdkVersion}`,
243
- },
297
+ },
298
+ {
299
+ $: {
300
+ name: "android-targetSdkVersion",
301
+ value: `${this._opt.config.platform.android.sdkVersion}`,
244
302
  },
245
- {
246
- $: {
247
- name: "android-compileSdkVersion",
248
- value: `33`,
249
- },
303
+ },
304
+ {
305
+ $: {
306
+ name: "android-compileSdkVersion",
307
+ value: this.ANDROID_SDK_VERSION,
250
308
  },
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: "/*",
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
- "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);
334
+ })),
335
+ });
278
336
  }
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);
337
+ configXml.widget.platform.push(androidPlatform);
293
338
  }
294
339
  async buildAsync(outPath) {
295
- const cordovaPath = path.resolve(this._opt.pkgPath, ".cordova");
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
- for (const platform of this._platforms) {
299
- await this._execAsync(`npx cordova build ${platform} --${buildType}`, cordovaPath);
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
- 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" }));
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"), `'${opt.url}'로 이동중... <script>setTimeout(function () {window.location.href = "${opt.url.replace(/\/$/, "")}/${opt.pkgName}/cordova/"}, 3000);</script>`.trim());
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
  }