@simplysm/sd-cli 12.8.20 → 12.8.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/entry/sd-cli-cordova.d.ts +30 -0
- package/dist/entry/sd-cli-cordova.js +307 -246
- package/dist/entry/sd-cli-cordova.js.map +1 -1
- package/dist/entry/sd-cli-project.d.ts +1 -1
- package/dist/entry/sd-cli-project.js +8 -9
- package/dist/entry/sd-cli-project.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/pkg-builders/client/sd-ng.bundler.d.ts +15 -1
- package/dist/pkg-builders/client/sd-ng.bundler.js +60 -70
- package/dist/pkg-builders/client/sd-ng.bundler.js.map +1 -1
- package/dist/pkg-builders/client/sd-ng.plugin-creator.js +49 -29
- package/dist/pkg-builders/client/sd-ng.plugin-creator.js.map +1 -1
- package/dist/pkg-builders/lib/sd-ts-lib.builder.js +7 -4
- package/dist/pkg-builders/lib/sd-ts-lib.builder.js.map +1 -1
- package/dist/pkg-builders/server/sd-server.bundler.js +11 -10
- package/dist/pkg-builders/server/sd-server.bundler.js.map +1 -1
- package/dist/ts-compiler/sd-ts-compiler.d.ts +25 -2
- package/dist/ts-compiler/sd-ts-compiler.js +306 -575
- package/dist/ts-compiler/sd-ts-compiler.js.map +1 -1
- package/dist/ts-compiler/sd-ts-dependency-analyzer.d.ts +6 -0
- package/dist/ts-compiler/sd-ts-dependency-analyzer.js +141 -0
- package/dist/ts-compiler/sd-ts-dependency-analyzer.js.map +1 -0
- package/dist/types/ts-compiler.types.d.ts +12 -7
- package/dist/types/worker.types.d.ts +13 -0
- package/dist/utils/sd-cli-performance-time.js +1 -1
- package/package.json +6 -8
- package/src/entry/sd-cli-cordova.ts +393 -280
- package/src/entry/sd-cli-project.ts +11 -23
- package/src/index.ts +1 -0
- package/src/pkg-builders/client/sd-ng.bundler.ts +67 -69
- package/src/pkg-builders/client/sd-ng.plugin-creator.ts +47 -27
- package/src/pkg-builders/lib/sd-ts-lib.builder.ts +14 -7
- package/src/pkg-builders/server/sd-server.bundler.ts +22 -12
- package/src/ts-compiler/sd-ts-compiler.ts +379 -704
- package/src/ts-compiler/sd-ts-dependency-analyzer.ts +185 -0
- package/src/types/ts-compiler.types.ts +11 -6
- package/src/types/worker.types.ts +7 -6
- package/src/utils/sd-cli-performance-time.ts +1 -1
|
@@ -1,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,171 @@ 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
|
+
}
|
|
98
|
+
|
|
99
|
+
// 2. 플랫폼 관리
|
|
100
|
+
private async _managePlatformsAsync(cordovaPath: string): Promise<void> {
|
|
101
|
+
const alreadyPlatforms = FsUtils.readdir(path.resolve(cordovaPath, this.PLATFORMS_DIR_NAME));
|
|
59
102
|
|
|
60
103
|
// 미설치 빌드 플랫폼 신규 생성
|
|
61
|
-
const alreadyPlatforms = FsUtils.readdir(path.resolve(cordovaPath, "platforms"));
|
|
62
104
|
for (const platform of this._platforms) {
|
|
63
|
-
if (
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
105
|
+
if (alreadyPlatforms.includes(platform)) continue;
|
|
106
|
+
|
|
107
|
+
if (platform === "android") {
|
|
108
|
+
await this._execAsync(
|
|
109
|
+
`npx cordova platform add ${platform}@${this.ANDROID_PLATFORM_VERSION}`,
|
|
110
|
+
cordovaPath,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
await this._execAsync(`npx cordova platform add ${platform}`, cordovaPath);
|
|
71
115
|
}
|
|
72
116
|
}
|
|
117
|
+
}
|
|
73
118
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
119
|
+
// 3. 플러그인 관리
|
|
120
|
+
private async _managePluginsAsync(cordovaPath: string): Promise<void> {
|
|
121
|
+
const pluginsFetchPath = path.resolve(
|
|
122
|
+
cordovaPath,
|
|
123
|
+
this.PLUGINS_DIR_NAME,
|
|
124
|
+
this.PLUGINS_FETCH_FILE,
|
|
125
|
+
);
|
|
126
|
+
const pluginsFetch = FsUtils.exists(pluginsFetchPath)
|
|
127
|
+
? FsUtils.readJson(pluginsFetchPath)
|
|
128
|
+
: {};
|
|
129
|
+
|
|
130
|
+
const alreadyPlugins: Array<{
|
|
131
|
+
name: string;
|
|
132
|
+
id: string;
|
|
133
|
+
dependencies?: string[]
|
|
134
|
+
}> = Object.keys(pluginsFetch).map(key => ({
|
|
135
|
+
name: key,
|
|
136
|
+
id: pluginsFetch[key].source.id,
|
|
137
|
+
dependencies: pluginsFetch[key].dependencies,
|
|
138
|
+
}));
|
|
80
139
|
|
|
81
|
-
|
|
82
|
-
const pluginsFetch = FsUtils.exists(path.resolve(cordovaPath, "plugins/fetch.json"))
|
|
83
|
-
? FsUtils.readJson(path.resolve(cordovaPath, "plugins/fetch.json"))
|
|
84
|
-
: undefined;
|
|
140
|
+
const usePlugins = (this._opt.config.plugins ?? []).distinct();
|
|
85
141
|
|
|
86
|
-
|
|
142
|
+
// 사용하지 않는 플러그인 제거 및 새 플러그인 설치 - 의존성 때문에 순차 처리
|
|
143
|
+
await this._removeUnusedPluginsAsync(cordovaPath, alreadyPlugins, usePlugins);
|
|
144
|
+
await this._installNewPluginsAsync(cordovaPath, alreadyPlugins, usePlugins);
|
|
145
|
+
}
|
|
87
146
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
147
|
+
private async _removeUnusedPluginsAsync(
|
|
148
|
+
cordovaPath: string,
|
|
149
|
+
alreadyPlugins: Array<{ name: string; id: string; dependencies?: string[] }>,
|
|
150
|
+
usePlugins: string[],
|
|
151
|
+
): Promise<void> {
|
|
152
|
+
for (const alreadyPlugin of alreadyPlugins) {
|
|
153
|
+
// 사용하지 않는 플러그인 제거 시 의존성 검사
|
|
154
|
+
const isPluginUsed = usePlugins.some(
|
|
155
|
+
usePlugin => usePlugin === alreadyPlugin.id || usePlugin === alreadyPlugin.name,
|
|
156
|
+
);
|
|
96
157
|
|
|
97
|
-
|
|
158
|
+
if (!isPluginUsed) {
|
|
159
|
+
const isDependencyExists = alreadyPlugins.some(
|
|
160
|
+
plugin => plugin.dependencies?.includes(alreadyPlugin.name),
|
|
161
|
+
);
|
|
98
162
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
let hasPlugin = false;
|
|
102
|
-
for (const usePlugin of usePlugins) {
|
|
103
|
-
if (alreadyPlugin.name === usePlugin || alreadyPlugin.id === usePlugin) {
|
|
104
|
-
hasPlugin = true;
|
|
105
|
-
break;
|
|
163
|
+
if (!isDependencyExists) {
|
|
164
|
+
await this._execAsync(`npx cordova plugin remove ${alreadyPlugin.name}`, cordovaPath);
|
|
106
165
|
}
|
|
107
166
|
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
108
169
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
//
|
|
170
|
+
private async _installNewPluginsAsync(
|
|
171
|
+
cordovaPath: string,
|
|
172
|
+
alreadyPlugins: Array<{ name: string; id: string; dependencies?: string[] }>,
|
|
173
|
+
usePlugins: string[],
|
|
174
|
+
): Promise<void> {
|
|
175
|
+
// 병렬로 플러그인을 설치하면 충돌이 발생할 수 있으므로 순차 처리
|
|
115
176
|
for (const usePlugin of usePlugins) {
|
|
116
|
-
|
|
177
|
+
const isPluginAlreadyInstalled = alreadyPlugins.some(
|
|
178
|
+
plugin => usePlugin === plugin.id || usePlugin === plugin.name,
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
if (!isPluginAlreadyInstalled) {
|
|
117
182
|
await this._execAsync(`npx cordova plugin add ${usePlugin}`, cordovaPath);
|
|
118
183
|
}
|
|
119
184
|
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 4. 안드로이드 서명 설정
|
|
188
|
+
private _setupAndroidSign(cordovaPath: string): void {
|
|
189
|
+
const keystorePath = path.resolve(cordovaPath, this.KEYSTORE_FILE_NAME);
|
|
190
|
+
const signingPropsPath = path.resolve(cordovaPath, this.ANDROID_SIGNING_PROP_PATH);
|
|
120
191
|
|
|
121
|
-
// ANDROID SIGN 파일 복사
|
|
122
192
|
if (this._opt.config.platform?.android?.sign) {
|
|
123
193
|
FsUtils.copy(
|
|
124
194
|
path.resolve(this._opt.pkgPath, this._opt.config.platform.android.sign.keystore),
|
|
125
|
-
|
|
195
|
+
keystorePath,
|
|
126
196
|
);
|
|
127
197
|
}
|
|
128
198
|
else {
|
|
129
|
-
FsUtils.remove(
|
|
199
|
+
FsUtils.remove(keystorePath);
|
|
130
200
|
// SIGN을 안쓸경우 아래 파일이 생성되어 있으면 오류남
|
|
131
|
-
FsUtils.remove(
|
|
201
|
+
FsUtils.remove(signingPropsPath);
|
|
132
202
|
}
|
|
203
|
+
}
|
|
133
204
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
:
|
|
150
|
-
|
|
205
|
+
// 5. 빌드 설정 파일 생성
|
|
206
|
+
private _createBuildConfig(cordovaPath: string): void {
|
|
207
|
+
const buildJsonPath = path.resolve(cordovaPath, this.BUILD_JSON_FILE_NAME);
|
|
208
|
+
const keystorePath = path.resolve(cordovaPath, this.KEYSTORE_FILE_NAME);
|
|
209
|
+
|
|
210
|
+
const androidConfig = this._opt.config.platform?.android
|
|
211
|
+
? {
|
|
212
|
+
android: {
|
|
213
|
+
release: {
|
|
214
|
+
packageType: this._opt.config.platform.android.bundle ? "bundle" : "apk",
|
|
215
|
+
...(this._opt.config.platform.android.sign
|
|
216
|
+
? {
|
|
217
|
+
keystore: keystorePath,
|
|
218
|
+
storePassword: this._opt.config.platform.android.sign.storePassword,
|
|
219
|
+
alias: this._opt.config.platform.android.sign.alias,
|
|
220
|
+
password: this._opt.config.platform.android.sign.password,
|
|
221
|
+
keystoreType: this._opt.config.platform.android.sign.keystoreType,
|
|
222
|
+
}
|
|
223
|
+
: {}),
|
|
151
224
|
},
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
|
|
225
|
+
},
|
|
226
|
+
}
|
|
227
|
+
: {};
|
|
228
|
+
|
|
229
|
+
FsUtils.writeJson(buildJsonPath, androidConfig);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// 6. 아이콘 및 스플래시 스크린 설정
|
|
233
|
+
private _setupIconAndSplashScreen(cordovaPath: string): void {
|
|
234
|
+
const iconDirPath = path.resolve(cordovaPath, this.ICON_DIR_PATH);
|
|
235
|
+
const splashScreenPath = path.resolve(cordovaPath, this.SPLASH_SCREEN_DIR_PATH);
|
|
236
|
+
const splashScreenXmlPath = path.resolve(splashScreenPath, this.SPLASH_SCREEN_XML_FILE);
|
|
155
237
|
|
|
156
238
|
// ICON 파일 복사
|
|
157
239
|
if (this._opt.config.icon != null) {
|
|
240
|
+
FsUtils.mkdirs(iconDirPath);
|
|
158
241
|
FsUtils.copy(
|
|
159
242
|
path.resolve(this._opt.pkgPath, this._opt.config.icon),
|
|
160
|
-
path.resolve(
|
|
243
|
+
path.resolve(iconDirPath, path.basename(this._opt.config.icon)),
|
|
161
244
|
);
|
|
162
245
|
}
|
|
163
246
|
else {
|
|
164
|
-
FsUtils.remove(
|
|
247
|
+
FsUtils.remove(iconDirPath);
|
|
165
248
|
}
|
|
166
249
|
|
|
167
250
|
// SplashScreen 파일 생성
|
|
168
251
|
if (this._opt.config.platform?.android && this._opt.config.icon != null) {
|
|
252
|
+
FsUtils.mkdirs(splashScreenPath);
|
|
169
253
|
FsUtils.writeFile(
|
|
170
|
-
|
|
254
|
+
splashScreenXmlPath,
|
|
171
255
|
`
|
|
172
256
|
<?xml version="1.0" encoding="utf-8"?>
|
|
173
257
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
|
@@ -179,240 +263,266 @@ export class SdCliCordova {
|
|
|
179
263
|
</layer-list>`.trim(),
|
|
180
264
|
);
|
|
181
265
|
}
|
|
266
|
+
}
|
|
182
267
|
|
|
268
|
+
// 7. XML 설정 구성
|
|
269
|
+
private _configureXml(cordovaPath: string) {
|
|
183
270
|
// CONFIG: 초기값 백업
|
|
184
|
-
const configFilePath = path.resolve(cordovaPath,
|
|
185
|
-
const configBackFilePath = path.resolve(cordovaPath,
|
|
271
|
+
const configFilePath = path.resolve(cordovaPath, this.CONFIG_XML_FILE_NAME);
|
|
272
|
+
const configBackFilePath = path.resolve(cordovaPath, this.CONFIG_XML_BACKUP_FILE_NAME);
|
|
273
|
+
|
|
186
274
|
if (!FsUtils.exists(configBackFilePath)) {
|
|
187
275
|
FsUtils.copy(configFilePath, configBackFilePath);
|
|
188
276
|
}
|
|
189
277
|
|
|
190
278
|
// CONFIG: 초기값 읽기
|
|
191
279
|
const configFileContent = FsUtils.readFile(configBackFilePath);
|
|
192
|
-
const configXml =
|
|
280
|
+
const configXml = XmlConvert.parse(configFileContent);
|
|
281
|
+
|
|
282
|
+
// CONFIG: 기본 설정
|
|
283
|
+
this._configureBasicXmlSettings(configXml);
|
|
284
|
+
|
|
285
|
+
// CONFIG: 안드로이드 설정
|
|
286
|
+
if (this._opt.config.platform?.android) {
|
|
287
|
+
this._configureAndroidXmlSettings(configXml);
|
|
288
|
+
}
|
|
193
289
|
|
|
194
|
-
// CONFIG:
|
|
290
|
+
// CONFIG: 파일 새로 쓰기
|
|
291
|
+
const configResultContent = XmlConvert.stringify(configXml, {
|
|
292
|
+
format: true
|
|
293
|
+
});
|
|
294
|
+
FsUtils.writeFile(configFilePath, configResultContent);
|
|
295
|
+
}
|
|
296
|
+
|
|
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
|
-
...[
|
|
279
|
-
{
|
|
280
|
-
$: {
|
|
281
|
-
name: "android-maxSdkVersion",
|
|
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
|
-
},
|
|
345
|
+
manifest: [
|
|
291
346
|
{
|
|
292
347
|
$: {
|
|
293
|
-
|
|
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
|
-
|
|
468
|
+
// 자동업데이트를 위한 파일 생성
|
|
469
|
+
await this._createUpdateZipAsync(cordovaPath, outPath, platform);
|
|
470
|
+
}
|
|
364
471
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
+
}
|
|
386
493
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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
|
+
}
|
|
411
519
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
);
|
|
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
|
+
}
|