@simplysm/sd-cli 12.16.3 → 12.16.5
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/SdCliCapacitor.d.ts +12 -8
- package/dist/entry/SdCliCapacitor.js +132 -51
- package/package.json +5 -5
- package/src/entry/SdCliCapacitor.ts +167 -50
|
@@ -15,20 +15,24 @@ export declare class SdCliCapacitor {
|
|
|
15
15
|
private static _execAsync;
|
|
16
16
|
initializeAsync(): Promise<void>;
|
|
17
17
|
private _initializeCapacitorProjectAsync;
|
|
18
|
-
private
|
|
19
|
-
private
|
|
18
|
+
private _syncVersionAsync;
|
|
19
|
+
private _createCapacitorConfigAsync;
|
|
20
20
|
private _managePlatformsAsync;
|
|
21
21
|
private _managePluginsAsync;
|
|
22
|
-
private
|
|
22
|
+
private _setupAndroidSignAsync;
|
|
23
23
|
private _setupIconAndSplashScreenAsync;
|
|
24
|
-
private
|
|
25
|
-
private
|
|
26
|
-
private
|
|
27
|
-
private
|
|
24
|
+
private _configureAndroidNativeAsync;
|
|
25
|
+
private _configureAndroidGradlePropertiesAsync;
|
|
26
|
+
private _findJava21;
|
|
27
|
+
private _configureSdkPathAsync;
|
|
28
|
+
private _configureAndroidManifestAsync;
|
|
29
|
+
private _configureAndroidBuildGradleAsync;
|
|
30
|
+
private _configureAndroidStringsAsync;
|
|
28
31
|
buildAsync(outPath: string): Promise<void>;
|
|
29
32
|
private _buildPlatformAsync;
|
|
30
33
|
private _buildAndroidAsync;
|
|
31
|
-
private
|
|
34
|
+
private _copyAndroidBuildOutputAsync;
|
|
35
|
+
private _findAndroidSdk;
|
|
32
36
|
static runWebviewOnDeviceAsync(opt: {
|
|
33
37
|
platform: string;
|
|
34
38
|
package: string;
|
|
@@ -9,7 +9,7 @@ export class SdCliCapacitor {
|
|
|
9
9
|
this._CONFIG_FILE_NAME = "capacitor.config.ts";
|
|
10
10
|
this._KEYSTORE_FILE_NAME = "android.keystore";
|
|
11
11
|
this._ICON_DIR_PATH = "resources";
|
|
12
|
-
this._platforms = Object.keys(this._opt.config.platform ?? {
|
|
12
|
+
this._platforms = Object.keys(this._opt.config.platform ?? {});
|
|
13
13
|
this._npmConfig = FsUtils.readJson(path.resolve(this._opt.pkgPath, "package.json"));
|
|
14
14
|
}
|
|
15
15
|
static { this._logger = SdLogger.get(["simplysm", "sd-cli", "SdCliCapacitor"]); }
|
|
@@ -23,18 +23,18 @@ export class SdCliCapacitor {
|
|
|
23
23
|
// 1. Capacitor 프로젝트 초기화
|
|
24
24
|
await this._initializeCapacitorProjectAsync(capacitorPath);
|
|
25
25
|
// 2. Capacitor 설정 파일 생성
|
|
26
|
-
this.
|
|
26
|
+
await this._createCapacitorConfigAsync(capacitorPath);
|
|
27
27
|
// 3. 플랫폼 관리
|
|
28
28
|
await this._managePlatformsAsync(capacitorPath);
|
|
29
29
|
// 4. 플러그인 관리
|
|
30
30
|
await this._managePluginsAsync(capacitorPath);
|
|
31
31
|
// 5. 안드로이드 서명 설정
|
|
32
|
-
this.
|
|
32
|
+
await this._setupAndroidSignAsync(capacitorPath);
|
|
33
33
|
// 6. 아이콘 및 스플래시 스크린 설정
|
|
34
34
|
await this._setupIconAndSplashScreenAsync(capacitorPath);
|
|
35
35
|
// 7. Android 네이티브 설정 (AndroidManifest.xml, build.gradle 등)
|
|
36
36
|
if (this._platforms.includes("android")) {
|
|
37
|
-
this.
|
|
37
|
+
await this._configureAndroidNativeAsync(capacitorPath);
|
|
38
38
|
}
|
|
39
39
|
// 8. 웹 자산 동기화
|
|
40
40
|
await SdCliCapacitor._execAsync("npx", ["cap", "sync"], capacitorPath);
|
|
@@ -44,44 +44,56 @@ export class SdCliCapacitor {
|
|
|
44
44
|
if (FsUtils.exists(capacitorPath)) {
|
|
45
45
|
SdCliCapacitor._logger.log("이미 생성되어있는 '.capacitor'를 사용합니다.");
|
|
46
46
|
// 버전 동기화
|
|
47
|
-
this.
|
|
47
|
+
await this._syncVersionAsync(capacitorPath);
|
|
48
48
|
}
|
|
49
49
|
else {
|
|
50
|
-
FsUtils.
|
|
50
|
+
await FsUtils.mkdirsAsync(capacitorPath);
|
|
51
51
|
// package.json 생성
|
|
52
|
+
const projNpmConfig = await FsUtils.readJsonAsync(path.resolve(this._opt.pkgPath, "../../package.json"));
|
|
52
53
|
const pkgJson = {
|
|
53
54
|
name: this._opt.config.appId,
|
|
54
55
|
version: this._npmConfig.version,
|
|
55
56
|
private: true,
|
|
57
|
+
volta: projNpmConfig.volta,
|
|
56
58
|
dependencies: {
|
|
57
59
|
"@capacitor/core": "^7.0.0",
|
|
58
60
|
},
|
|
59
61
|
devDependencies: {
|
|
60
62
|
"@capacitor/cli": "^7.0.0",
|
|
61
63
|
"@capacitor/assets": "^3.0.0",
|
|
64
|
+
...this._platforms.toObject((item) => `@capacitor/${item}`, () => "^7.0.0"),
|
|
62
65
|
},
|
|
63
66
|
};
|
|
64
|
-
FsUtils.
|
|
67
|
+
await FsUtils.writeJsonAsync(path.resolve(capacitorPath, "package.json"), pkgJson, {
|
|
68
|
+
space: 2,
|
|
69
|
+
});
|
|
70
|
+
// .yarnrc.yml 작성
|
|
71
|
+
await FsUtils.writeFileAsync(path.resolve(capacitorPath, ".yarnrc.yml"), "nodeLinker: node-modules");
|
|
72
|
+
// yarn.lock 작성
|
|
73
|
+
await FsUtils.writeFileAsync(path.resolve(capacitorPath, "yarn.lock"), "");
|
|
65
74
|
// yarn install
|
|
66
75
|
await SdCliCapacitor._execAsync("yarn", ["install"], capacitorPath);
|
|
67
76
|
// capacitor init
|
|
68
77
|
await SdCliCapacitor._execAsync("npx", ["cap", "init", this._opt.config.appName, this._opt.config.appId], capacitorPath);
|
|
69
78
|
}
|
|
79
|
+
// www/index.html 생성
|
|
80
|
+
const wwwPath = path.resolve(capacitorPath, "www");
|
|
81
|
+
await FsUtils.writeFileAsync(path.resolve(wwwPath, "index.html"), "<!DOCTYPE html><html><head></head><body></body></html>");
|
|
70
82
|
}
|
|
71
83
|
// 버전 동기화
|
|
72
|
-
|
|
84
|
+
async _syncVersionAsync(capacitorPath) {
|
|
73
85
|
const pkgJsonPath = path.resolve(capacitorPath, "package.json");
|
|
74
86
|
if (FsUtils.exists(pkgJsonPath)) {
|
|
75
|
-
const pkgJson = FsUtils.
|
|
87
|
+
const pkgJson = (await FsUtils.readJsonAsync(pkgJsonPath));
|
|
76
88
|
if (pkgJson.version !== this._npmConfig.version) {
|
|
77
89
|
pkgJson.version = this._npmConfig.version;
|
|
78
|
-
FsUtils.
|
|
90
|
+
await FsUtils.writeJsonAsync(pkgJsonPath, pkgJson, { space: 2 });
|
|
79
91
|
SdCliCapacitor._logger.log(`버전 동기화: ${this._npmConfig.version}`);
|
|
80
92
|
}
|
|
81
93
|
}
|
|
82
94
|
}
|
|
83
95
|
// 2. Capacitor 설정 파일 생성
|
|
84
|
-
|
|
96
|
+
async _createCapacitorConfigAsync(capacitorPath) {
|
|
85
97
|
const configFilePath = path.resolve(capacitorPath, this._CONFIG_FILE_NAME);
|
|
86
98
|
// 플러그인 옵션 생성
|
|
87
99
|
const pluginOptions = {};
|
|
@@ -114,7 +126,7 @@ export class SdCliCapacitor {
|
|
|
114
126
|
|
|
115
127
|
export default config;
|
|
116
128
|
`;
|
|
117
|
-
FsUtils.
|
|
129
|
+
await FsUtils.writeFileAsync(configFilePath, configContent);
|
|
118
130
|
}
|
|
119
131
|
// 3. 플랫폼 관리
|
|
120
132
|
async _managePlatformsAsync(capacitorPath) {
|
|
@@ -127,7 +139,7 @@ export class SdCliCapacitor {
|
|
|
127
139
|
// 4. 플러그인 관리
|
|
128
140
|
async _managePluginsAsync(capacitorPath) {
|
|
129
141
|
const pkgJsonPath = path.resolve(capacitorPath, "package.json");
|
|
130
|
-
const pkgJson = FsUtils.
|
|
142
|
+
const pkgJson = (await FsUtils.readJsonAsync(pkgJsonPath));
|
|
131
143
|
const currentDeps = Object.keys(pkgJson.dependencies ?? {});
|
|
132
144
|
const usePlugins = Object.keys(this._opt.config.plugins ?? {});
|
|
133
145
|
// 사용하지 않는 플러그인 제거
|
|
@@ -154,24 +166,23 @@ export class SdCliCapacitor {
|
|
|
154
166
|
// 새 플러그인 설치
|
|
155
167
|
for (const plugin of usePlugins) {
|
|
156
168
|
if (!currentDeps.includes(plugin)) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
169
|
+
await SdCliCapacitor._execAsync("yarn", ["add", plugin], capacitorPath);
|
|
170
|
+
SdCliCapacitor._logger.log(`플러그인 설치: ${plugin}`);
|
|
171
|
+
/*try {
|
|
172
|
+
} catch {
|
|
173
|
+
SdCliCapacitor._logger.warn(`플러그인 설치 실패: ${plugin}`);
|
|
174
|
+
}*/
|
|
164
175
|
}
|
|
165
176
|
}
|
|
166
177
|
}
|
|
167
178
|
// 5. 안드로이드 서명 설정
|
|
168
|
-
|
|
179
|
+
async _setupAndroidSignAsync(capacitorPath) {
|
|
169
180
|
const keystorePath = path.resolve(capacitorPath, this._KEYSTORE_FILE_NAME);
|
|
170
181
|
if (this._opt.config.platform?.android?.sign) {
|
|
171
|
-
FsUtils.
|
|
182
|
+
await FsUtils.copyAsync(path.resolve(this._opt.pkgPath, this._opt.config.platform.android.sign.keystore), keystorePath);
|
|
172
183
|
}
|
|
173
184
|
else {
|
|
174
|
-
FsUtils.
|
|
185
|
+
await FsUtils.removeAsync(keystorePath);
|
|
175
186
|
}
|
|
176
187
|
}
|
|
177
188
|
// 6. 아이콘 및 스플래시 스크린 설정
|
|
@@ -179,11 +190,11 @@ export class SdCliCapacitor {
|
|
|
179
190
|
const iconDirPath = path.resolve(capacitorPath, this._ICON_DIR_PATH);
|
|
180
191
|
// ICON 파일 복사
|
|
181
192
|
if (this._opt.config.icon != null) {
|
|
182
|
-
FsUtils.
|
|
193
|
+
await FsUtils.mkdirsAsync(iconDirPath);
|
|
183
194
|
const iconSource = path.resolve(this._opt.pkgPath, this._opt.config.icon);
|
|
184
195
|
// icon.png, splash.png 둘 다 같은 파일 사용
|
|
185
|
-
FsUtils.
|
|
186
|
-
FsUtils.
|
|
196
|
+
await FsUtils.copyAsync(iconSource, path.resolve(iconDirPath, "icon.png"));
|
|
197
|
+
await FsUtils.copyAsync(iconSource, path.resolve(iconDirPath, "splash.png"));
|
|
187
198
|
// @capacitor/assets로 아이콘/스플래시 리사이징
|
|
188
199
|
try {
|
|
189
200
|
await SdCliCapacitor._execAsync("npx", ["@capacitor/assets", "generate", "--android"], capacitorPath);
|
|
@@ -193,28 +204,73 @@ export class SdCliCapacitor {
|
|
|
193
204
|
}
|
|
194
205
|
}
|
|
195
206
|
else {
|
|
196
|
-
FsUtils.
|
|
207
|
+
await FsUtils.removeAsync(iconDirPath);
|
|
197
208
|
}
|
|
198
209
|
}
|
|
199
210
|
// 7. Android 네이티브 설정
|
|
200
|
-
|
|
211
|
+
async _configureAndroidNativeAsync(capacitorPath) {
|
|
201
212
|
const androidPath = path.resolve(capacitorPath, "android");
|
|
202
213
|
if (!FsUtils.exists(androidPath)) {
|
|
203
214
|
return;
|
|
204
215
|
}
|
|
216
|
+
// JAVA_HOME 찾기
|
|
217
|
+
await this._configureAndroidGradlePropertiesAsync(androidPath);
|
|
218
|
+
// local.properties 생성
|
|
219
|
+
await this._configureSdkPathAsync(androidPath);
|
|
205
220
|
// AndroidManifest.xml 수정
|
|
206
|
-
this.
|
|
221
|
+
await this._configureAndroidManifestAsync(androidPath);
|
|
207
222
|
// build.gradle 수정 (필요시)
|
|
208
|
-
this.
|
|
223
|
+
await this._configureAndroidBuildGradleAsync(androidPath);
|
|
209
224
|
// strings.xml 앱 이름 수정
|
|
210
|
-
this.
|
|
225
|
+
await this._configureAndroidStringsAsync(androidPath);
|
|
226
|
+
}
|
|
227
|
+
async _configureAndroidGradlePropertiesAsync(androidPath) {
|
|
228
|
+
const gradlePropsPath = path.resolve(androidPath, "gradle.properties");
|
|
229
|
+
if (!FsUtils.exists(gradlePropsPath)) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
let content = await FsUtils.readFileAsync(gradlePropsPath);
|
|
233
|
+
// Java 21 경로 자동 탐색
|
|
234
|
+
const java21Path = this._findJava21();
|
|
235
|
+
if (java21Path != null && !content.includes("org.gradle.java.home")) {
|
|
236
|
+
content += `\norg.gradle.java.home=${java21Path.replace(/\\/g, "\\\\")}\n`;
|
|
237
|
+
FsUtils.writeFile(gradlePropsPath, content);
|
|
238
|
+
}
|
|
211
239
|
}
|
|
212
|
-
|
|
240
|
+
_findJava21() {
|
|
241
|
+
const patterns = [
|
|
242
|
+
"C:/Program Files/Amazon Corretto/jdk21*",
|
|
243
|
+
"C:/Program Files/Eclipse Adoptium/jdk-21*",
|
|
244
|
+
"C:/Program Files/Java/jdk-21*",
|
|
245
|
+
"C:/Program Files/Microsoft/jdk-21*",
|
|
246
|
+
];
|
|
247
|
+
for (const pattern of patterns) {
|
|
248
|
+
const matches = FsUtils.glob(pattern);
|
|
249
|
+
if (matches.length > 0) {
|
|
250
|
+
// 가장 최신 버전 선택 (정렬 후 마지막)
|
|
251
|
+
return matches.sort().at(-1);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return undefined;
|
|
255
|
+
}
|
|
256
|
+
async _configureSdkPathAsync(androidPath) {
|
|
257
|
+
// local.properties 생성
|
|
258
|
+
const localPropsPath = path.resolve(androidPath, "local.properties");
|
|
259
|
+
if (FsUtils.exists(localPropsPath)) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
// SDK 경로 탐색 (Cordova 방식과 유사)
|
|
263
|
+
const sdkPath = this._findAndroidSdk();
|
|
264
|
+
if (sdkPath != null) {
|
|
265
|
+
await FsUtils.writeFileAsync(localPropsPath, `sdk.dir=${sdkPath.replace(/\\/g, "/")}\n`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
async _configureAndroidManifestAsync(androidPath) {
|
|
213
269
|
const manifestPath = path.resolve(androidPath, "app/src/main/AndroidManifest.xml");
|
|
214
270
|
if (!FsUtils.exists(manifestPath)) {
|
|
215
271
|
return;
|
|
216
272
|
}
|
|
217
|
-
let manifestContent = FsUtils.
|
|
273
|
+
let manifestContent = await FsUtils.readFileAsync(manifestPath);
|
|
218
274
|
// usesCleartextTraffic 설정
|
|
219
275
|
if (!manifestContent.includes("android:usesCleartextTraffic")) {
|
|
220
276
|
manifestContent = manifestContent.replace("<application", '<application android:usesCleartextTraffic="true"');
|
|
@@ -244,14 +300,14 @@ export class SdCliCapacitor {
|
|
|
244
300
|
}
|
|
245
301
|
}
|
|
246
302
|
}
|
|
247
|
-
FsUtils.
|
|
303
|
+
await FsUtils.writeFileAsync(manifestPath, manifestContent);
|
|
248
304
|
}
|
|
249
|
-
|
|
305
|
+
async _configureAndroidBuildGradleAsync(androidPath) {
|
|
250
306
|
const buildGradlePath = path.resolve(androidPath, "app/build.gradle");
|
|
251
307
|
if (!FsUtils.exists(buildGradlePath)) {
|
|
252
308
|
return;
|
|
253
309
|
}
|
|
254
|
-
let gradleContent = FsUtils.
|
|
310
|
+
let gradleContent = await FsUtils.readFileAsync(buildGradlePath);
|
|
255
311
|
// versionName, versionCode 설정
|
|
256
312
|
const version = this._npmConfig.version;
|
|
257
313
|
const versionParts = version.split(".");
|
|
@@ -292,19 +348,19 @@ export class SdCliCapacitor {
|
|
|
292
348
|
gradleContent = gradleContent.replace(/(buildTypes\s*\{[\s\S]*?release\s*\{)/, `$1\n signingConfig signingConfigs.release`);
|
|
293
349
|
}
|
|
294
350
|
}
|
|
295
|
-
FsUtils.
|
|
351
|
+
await FsUtils.writeFileAsync(buildGradlePath, gradleContent);
|
|
296
352
|
}
|
|
297
|
-
|
|
353
|
+
async _configureAndroidStringsAsync(androidPath) {
|
|
298
354
|
const stringsPath = path.resolve(androidPath, "app/src/main/res/values/strings.xml");
|
|
299
355
|
if (!FsUtils.exists(stringsPath)) {
|
|
300
356
|
return;
|
|
301
357
|
}
|
|
302
|
-
let stringsContent = FsUtils.
|
|
358
|
+
let stringsContent = await FsUtils.readFileAsync(stringsPath);
|
|
303
359
|
stringsContent = stringsContent.replace(/<string name="app_name">[^<]+<\/string>/, `<string name="app_name">${this._opt.config.appName}</string>`);
|
|
304
360
|
stringsContent = stringsContent.replace(/<string name="title_activity_main">[^<]+<\/string>/, `<string name="title_activity_main">${this._opt.config.appName}</string>`);
|
|
305
361
|
stringsContent = stringsContent.replace(/<string name="package_name">[^<]+<\/string>/, `<string name="package_name">${this._opt.config.appId}</string>`);
|
|
306
362
|
stringsContent = stringsContent.replace(/<string name="custom_url_scheme">[^<]+<\/string>/, `<string name="custom_url_scheme">${this._opt.config.appId}</string>`);
|
|
307
|
-
FsUtils.
|
|
363
|
+
await FsUtils.writeFileAsync(stringsPath, stringsContent);
|
|
308
364
|
}
|
|
309
365
|
async buildAsync(outPath) {
|
|
310
366
|
const capacitorPath = path.resolve(this._opt.pkgPath, this._CAPACITOR_DIR_NAME);
|
|
@@ -340,9 +396,9 @@ export class SdCliCapacitor {
|
|
|
340
396
|
const gradleCmd = process.platform === "win32" ? "gradlew.bat" : "./gradlew";
|
|
341
397
|
await SdCliCapacitor._execAsync(gradleCmd, [gradleTask, "--no-daemon"], androidPath);
|
|
342
398
|
// 빌드 결과물 복사
|
|
343
|
-
this.
|
|
399
|
+
await this._copyAndroidBuildOutputAsync(androidPath, targetOutPath, buildType);
|
|
344
400
|
}
|
|
345
|
-
|
|
401
|
+
async _copyAndroidBuildOutputAsync(androidPath, targetOutPath, buildType) {
|
|
346
402
|
const isBundle = this._opt.config.platform?.android?.bundle;
|
|
347
403
|
const isSigned = !!this._opt.config.platform?.android?.sign;
|
|
348
404
|
const ext = isBundle ? "aab" : "apk";
|
|
@@ -357,21 +413,41 @@ export class SdCliCapacitor {
|
|
|
357
413
|
return;
|
|
358
414
|
}
|
|
359
415
|
const outputFileName = `${this._opt.config.appName}${isSigned ? "" : "-unsigned"}-latest.${ext}`;
|
|
360
|
-
FsUtils.
|
|
361
|
-
FsUtils.
|
|
416
|
+
await FsUtils.mkdirsAsync(targetOutPath);
|
|
417
|
+
await FsUtils.copyAsync(actualPath, path.resolve(targetOutPath, outputFileName));
|
|
362
418
|
const updatesPath = path.resolve(targetOutPath, "updates");
|
|
363
|
-
FsUtils.
|
|
364
|
-
FsUtils.
|
|
419
|
+
await FsUtils.mkdirsAsync(updatesPath);
|
|
420
|
+
await FsUtils.copyAsync(actualPath, path.resolve(updatesPath, `${this._npmConfig.version}.${ext}`));
|
|
421
|
+
}
|
|
422
|
+
_findAndroidSdk() {
|
|
423
|
+
// 1. 환경변수 확인
|
|
424
|
+
const fromEnv = process.env["ANDROID_HOME"] ?? process.env["ANDROID_SDK_ROOT"];
|
|
425
|
+
if (fromEnv != null && FsUtils.exists(fromEnv)) {
|
|
426
|
+
return fromEnv;
|
|
427
|
+
}
|
|
428
|
+
// 2. 일반적인 설치 경로 탐색
|
|
429
|
+
const candidates = [
|
|
430
|
+
path.resolve(process.env["LOCALAPPDATA"] ?? "", "Android/Sdk"),
|
|
431
|
+
path.resolve(process.env["HOME"] ?? "", "Android/Sdk"),
|
|
432
|
+
"C:/Program Files/Android/Sdk",
|
|
433
|
+
"C:/Android/Sdk",
|
|
434
|
+
];
|
|
435
|
+
for (const candidate of candidates) {
|
|
436
|
+
if (FsUtils.exists(candidate)) {
|
|
437
|
+
return candidate;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return undefined;
|
|
365
441
|
}
|
|
366
442
|
static async runWebviewOnDeviceAsync(opt) {
|
|
367
|
-
const projNpmConf = FsUtils.
|
|
368
|
-
const allPkgPaths = projNpmConf.workspaces.
|
|
443
|
+
const projNpmConf = (await FsUtils.readJsonAsync(path.resolve(process.cwd(), "package.json")));
|
|
444
|
+
const allPkgPaths = await projNpmConf.workspaces.mapManyAsync(async (item) => await FsUtils.globAsync(PathUtils.posix(process.cwd(), item)));
|
|
369
445
|
const capacitorPath = path.resolve(allPkgPaths.single((item) => item.endsWith(opt.package)), ".capacitor");
|
|
370
446
|
if (opt.url !== undefined) {
|
|
371
447
|
// capacitor.config.ts의 server.url 설정 업데이트
|
|
372
448
|
const configPath = path.resolve(capacitorPath, "capacitor.config.ts");
|
|
373
449
|
if (FsUtils.exists(configPath)) {
|
|
374
|
-
let configContent = FsUtils.
|
|
450
|
+
let configContent = await FsUtils.readFileAsync(configPath);
|
|
375
451
|
const serverUrl = `${opt.url.replace(/\/$/, "")}/${opt.package}/capacitor/`;
|
|
376
452
|
// 기존 url 설정이 있으면 교체, 없으면 server 블록 첫 줄에 추가
|
|
377
453
|
if (configContent.includes("url:")) {
|
|
@@ -380,11 +456,16 @@ export class SdCliCapacitor {
|
|
|
380
456
|
else if (configContent.includes("server:")) {
|
|
381
457
|
configContent = configContent.replace(/server:\s*\{/, `server: {\n url: "${serverUrl}",`);
|
|
382
458
|
}
|
|
383
|
-
FsUtils.
|
|
459
|
+
await FsUtils.writeFileAsync(configPath, configContent);
|
|
384
460
|
}
|
|
385
461
|
}
|
|
386
462
|
// cap sync 후 run
|
|
387
463
|
await this._execAsync("npx", ["cap", "sync", opt.platform], capacitorPath);
|
|
388
|
-
|
|
464
|
+
try {
|
|
465
|
+
await this._execAsync("npx", ["cap", "run", opt.platform], capacitorPath);
|
|
466
|
+
}
|
|
467
|
+
catch {
|
|
468
|
+
await SdProcess.spawnAsync("adb", ["kill-server"]);
|
|
469
|
+
}
|
|
389
470
|
}
|
|
390
471
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simplysm/sd-cli",
|
|
3
|
-
"version": "12.16.
|
|
3
|
+
"version": "12.16.5",
|
|
4
4
|
"description": "심플리즘 패키지 - CLI",
|
|
5
5
|
"author": "김석래",
|
|
6
6
|
"repository": {
|
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
"@angular/compiler-cli": "^20.3.15",
|
|
18
18
|
"@anthropic-ai/sdk": "^0.71.2",
|
|
19
19
|
"@electron/rebuild": "^4.0.2",
|
|
20
|
-
"@simplysm/sd-core-common": "12.16.
|
|
21
|
-
"@simplysm/sd-core-node": "12.16.
|
|
22
|
-
"@simplysm/sd-service-server": "12.16.
|
|
23
|
-
"@simplysm/sd-storage": "12.16.
|
|
20
|
+
"@simplysm/sd-core-common": "12.16.5",
|
|
21
|
+
"@simplysm/sd-core-node": "12.16.5",
|
|
22
|
+
"@simplysm/sd-service-server": "12.16.5",
|
|
23
|
+
"@simplysm/sd-storage": "12.16.5",
|
|
24
24
|
"browserslist": "^4.28.1",
|
|
25
25
|
"cordova": "^13.0.0",
|
|
26
26
|
"electron": "^33.4.11",
|
|
@@ -18,7 +18,7 @@ export class SdCliCapacitor {
|
|
|
18
18
|
private readonly _npmConfig: INpmConfig;
|
|
19
19
|
|
|
20
20
|
constructor(private readonly _opt: { pkgPath: string; config: ISdClientBuilderCapacitorConfig }) {
|
|
21
|
-
this._platforms = Object.keys(this._opt.config.platform ?? {
|
|
21
|
+
this._platforms = Object.keys(this._opt.config.platform ?? {});
|
|
22
22
|
this._npmConfig = FsUtils.readJson(path.resolve(this._opt.pkgPath, "package.json"));
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -37,7 +37,7 @@ export class SdCliCapacitor {
|
|
|
37
37
|
await this._initializeCapacitorProjectAsync(capacitorPath);
|
|
38
38
|
|
|
39
39
|
// 2. Capacitor 설정 파일 생성
|
|
40
|
-
this.
|
|
40
|
+
await this._createCapacitorConfigAsync(capacitorPath);
|
|
41
41
|
|
|
42
42
|
// 3. 플랫폼 관리
|
|
43
43
|
await this._managePlatformsAsync(capacitorPath);
|
|
@@ -46,14 +46,14 @@ export class SdCliCapacitor {
|
|
|
46
46
|
await this._managePluginsAsync(capacitorPath);
|
|
47
47
|
|
|
48
48
|
// 5. 안드로이드 서명 설정
|
|
49
|
-
this.
|
|
49
|
+
await this._setupAndroidSignAsync(capacitorPath);
|
|
50
50
|
|
|
51
51
|
// 6. 아이콘 및 스플래시 스크린 설정
|
|
52
52
|
await this._setupIconAndSplashScreenAsync(capacitorPath);
|
|
53
53
|
|
|
54
54
|
// 7. Android 네이티브 설정 (AndroidManifest.xml, build.gradle 등)
|
|
55
55
|
if (this._platforms.includes("android")) {
|
|
56
|
-
this.
|
|
56
|
+
await this._configureAndroidNativeAsync(capacitorPath);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
// 8. 웹 자산 동기화
|
|
@@ -66,24 +66,43 @@ export class SdCliCapacitor {
|
|
|
66
66
|
SdCliCapacitor._logger.log("이미 생성되어있는 '.capacitor'를 사용합니다.");
|
|
67
67
|
|
|
68
68
|
// 버전 동기화
|
|
69
|
-
this.
|
|
69
|
+
await this._syncVersionAsync(capacitorPath);
|
|
70
70
|
} else {
|
|
71
|
-
FsUtils.
|
|
71
|
+
await FsUtils.mkdirsAsync(capacitorPath);
|
|
72
72
|
|
|
73
73
|
// package.json 생성
|
|
74
|
+
const projNpmConfig = await FsUtils.readJsonAsync(
|
|
75
|
+
path.resolve(this._opt.pkgPath, "../../package.json"),
|
|
76
|
+
);
|
|
74
77
|
const pkgJson = {
|
|
75
78
|
name: this._opt.config.appId,
|
|
76
79
|
version: this._npmConfig.version,
|
|
77
80
|
private: true,
|
|
81
|
+
volta: projNpmConfig.volta,
|
|
78
82
|
dependencies: {
|
|
79
83
|
"@capacitor/core": "^7.0.0",
|
|
80
84
|
},
|
|
81
85
|
devDependencies: {
|
|
82
86
|
"@capacitor/cli": "^7.0.0",
|
|
83
87
|
"@capacitor/assets": "^3.0.0",
|
|
88
|
+
...this._platforms.toObject(
|
|
89
|
+
(item) => `@capacitor/${item}`,
|
|
90
|
+
() => "^7.0.0",
|
|
91
|
+
),
|
|
84
92
|
},
|
|
85
93
|
};
|
|
86
|
-
FsUtils.
|
|
94
|
+
await FsUtils.writeJsonAsync(path.resolve(capacitorPath, "package.json"), pkgJson, {
|
|
95
|
+
space: 2,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// .yarnrc.yml 작성
|
|
99
|
+
await FsUtils.writeFileAsync(
|
|
100
|
+
path.resolve(capacitorPath, ".yarnrc.yml"),
|
|
101
|
+
"nodeLinker: node-modules",
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// yarn.lock 작성
|
|
105
|
+
await FsUtils.writeFileAsync(path.resolve(capacitorPath, "yarn.lock"), "");
|
|
87
106
|
|
|
88
107
|
// yarn install
|
|
89
108
|
await SdCliCapacitor._execAsync("yarn", ["install"], capacitorPath);
|
|
@@ -95,25 +114,32 @@ export class SdCliCapacitor {
|
|
|
95
114
|
capacitorPath,
|
|
96
115
|
);
|
|
97
116
|
}
|
|
117
|
+
|
|
118
|
+
// www/index.html 생성
|
|
119
|
+
const wwwPath = path.resolve(capacitorPath, "www");
|
|
120
|
+
await FsUtils.writeFileAsync(
|
|
121
|
+
path.resolve(wwwPath, "index.html"),
|
|
122
|
+
"<!DOCTYPE html><html><head></head><body></body></html>",
|
|
123
|
+
);
|
|
98
124
|
}
|
|
99
125
|
|
|
100
126
|
// 버전 동기화
|
|
101
|
-
private
|
|
127
|
+
private async _syncVersionAsync(capacitorPath: string) {
|
|
102
128
|
const pkgJsonPath = path.resolve(capacitorPath, "package.json");
|
|
103
129
|
|
|
104
130
|
if (FsUtils.exists(pkgJsonPath)) {
|
|
105
|
-
const pkgJson = FsUtils.
|
|
131
|
+
const pkgJson = (await FsUtils.readJsonAsync(pkgJsonPath)) as INpmConfig;
|
|
106
132
|
|
|
107
133
|
if (pkgJson.version !== this._npmConfig.version) {
|
|
108
134
|
pkgJson.version = this._npmConfig.version;
|
|
109
|
-
FsUtils.
|
|
135
|
+
await FsUtils.writeJsonAsync(pkgJsonPath, pkgJson, { space: 2 });
|
|
110
136
|
SdCliCapacitor._logger.log(`버전 동기화: ${this._npmConfig.version}`);
|
|
111
137
|
}
|
|
112
138
|
}
|
|
113
139
|
}
|
|
114
140
|
|
|
115
141
|
// 2. Capacitor 설정 파일 생성
|
|
116
|
-
private
|
|
142
|
+
private async _createCapacitorConfigAsync(capacitorPath: string) {
|
|
117
143
|
const configFilePath = path.resolve(capacitorPath, this._CONFIG_FILE_NAME);
|
|
118
144
|
|
|
119
145
|
// 플러그인 옵션 생성
|
|
@@ -151,7 +177,7 @@ export class SdCliCapacitor {
|
|
|
151
177
|
export default config;
|
|
152
178
|
`;
|
|
153
179
|
|
|
154
|
-
FsUtils.
|
|
180
|
+
await FsUtils.writeFileAsync(configFilePath, configContent);
|
|
155
181
|
}
|
|
156
182
|
|
|
157
183
|
// 3. 플랫폼 관리
|
|
@@ -166,7 +192,7 @@ export class SdCliCapacitor {
|
|
|
166
192
|
// 4. 플러그인 관리
|
|
167
193
|
private async _managePluginsAsync(capacitorPath: string): Promise<void> {
|
|
168
194
|
const pkgJsonPath = path.resolve(capacitorPath, "package.json");
|
|
169
|
-
const pkgJson = FsUtils.
|
|
195
|
+
const pkgJson = (await FsUtils.readJsonAsync(pkgJsonPath)) as INpmConfig;
|
|
170
196
|
const currentDeps = Object.keys(pkgJson.dependencies ?? {});
|
|
171
197
|
|
|
172
198
|
const usePlugins = Object.keys(this._opt.config.plugins ?? {});
|
|
@@ -198,27 +224,27 @@ export class SdCliCapacitor {
|
|
|
198
224
|
// 새 플러그인 설치
|
|
199
225
|
for (const plugin of usePlugins) {
|
|
200
226
|
if (!currentDeps.includes(plugin)) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
227
|
+
await SdCliCapacitor._execAsync("yarn", ["add", plugin], capacitorPath);
|
|
228
|
+
SdCliCapacitor._logger.log(`플러그인 설치: ${plugin}`);
|
|
229
|
+
/*try {
|
|
204
230
|
} catch {
|
|
205
231
|
SdCliCapacitor._logger.warn(`플러그인 설치 실패: ${plugin}`);
|
|
206
|
-
}
|
|
232
|
+
}*/
|
|
207
233
|
}
|
|
208
234
|
}
|
|
209
235
|
}
|
|
210
236
|
|
|
211
237
|
// 5. 안드로이드 서명 설정
|
|
212
|
-
private
|
|
238
|
+
private async _setupAndroidSignAsync(capacitorPath: string) {
|
|
213
239
|
const keystorePath = path.resolve(capacitorPath, this._KEYSTORE_FILE_NAME);
|
|
214
240
|
|
|
215
241
|
if (this._opt.config.platform?.android?.sign) {
|
|
216
|
-
FsUtils.
|
|
242
|
+
await FsUtils.copyAsync(
|
|
217
243
|
path.resolve(this._opt.pkgPath, this._opt.config.platform.android.sign.keystore),
|
|
218
244
|
keystorePath,
|
|
219
245
|
);
|
|
220
246
|
} else {
|
|
221
|
-
FsUtils.
|
|
247
|
+
await FsUtils.removeAsync(keystorePath);
|
|
222
248
|
}
|
|
223
249
|
}
|
|
224
250
|
|
|
@@ -228,13 +254,13 @@ export class SdCliCapacitor {
|
|
|
228
254
|
|
|
229
255
|
// ICON 파일 복사
|
|
230
256
|
if (this._opt.config.icon != null) {
|
|
231
|
-
FsUtils.
|
|
257
|
+
await FsUtils.mkdirsAsync(iconDirPath);
|
|
232
258
|
|
|
233
259
|
const iconSource = path.resolve(this._opt.pkgPath, this._opt.config.icon);
|
|
234
260
|
|
|
235
261
|
// icon.png, splash.png 둘 다 같은 파일 사용
|
|
236
|
-
FsUtils.
|
|
237
|
-
FsUtils.
|
|
262
|
+
await FsUtils.copyAsync(iconSource, path.resolve(iconDirPath, "icon.png"));
|
|
263
|
+
await FsUtils.copyAsync(iconSource, path.resolve(iconDirPath, "splash.png"));
|
|
238
264
|
|
|
239
265
|
// @capacitor/assets로 아이콘/스플래시 리사이징
|
|
240
266
|
try {
|
|
@@ -247,36 +273,93 @@ export class SdCliCapacitor {
|
|
|
247
273
|
SdCliCapacitor._logger.warn("아이콘 리사이징 실패, 기본 아이콘 사용");
|
|
248
274
|
}
|
|
249
275
|
} else {
|
|
250
|
-
FsUtils.
|
|
276
|
+
await FsUtils.removeAsync(iconDirPath);
|
|
251
277
|
}
|
|
252
278
|
}
|
|
253
279
|
|
|
254
280
|
// 7. Android 네이티브 설정
|
|
255
|
-
private
|
|
281
|
+
private async _configureAndroidNativeAsync(capacitorPath: string) {
|
|
256
282
|
const androidPath = path.resolve(capacitorPath, "android");
|
|
257
283
|
|
|
258
284
|
if (!FsUtils.exists(androidPath)) {
|
|
259
285
|
return;
|
|
260
286
|
}
|
|
261
287
|
|
|
288
|
+
// JAVA_HOME 찾기
|
|
289
|
+
await this._configureAndroidGradlePropertiesAsync(androidPath);
|
|
290
|
+
|
|
291
|
+
// local.properties 생성
|
|
292
|
+
await this._configureSdkPathAsync(androidPath);
|
|
293
|
+
|
|
262
294
|
// AndroidManifest.xml 수정
|
|
263
|
-
this.
|
|
295
|
+
await this._configureAndroidManifestAsync(androidPath);
|
|
264
296
|
|
|
265
297
|
// build.gradle 수정 (필요시)
|
|
266
|
-
this.
|
|
298
|
+
await this._configureAndroidBuildGradleAsync(androidPath);
|
|
267
299
|
|
|
268
300
|
// strings.xml 앱 이름 수정
|
|
269
|
-
this.
|
|
301
|
+
await this._configureAndroidStringsAsync(androidPath);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
private async _configureAndroidGradlePropertiesAsync(androidPath: string) {
|
|
305
|
+
const gradlePropsPath = path.resolve(androidPath, "gradle.properties");
|
|
306
|
+
|
|
307
|
+
if (!FsUtils.exists(gradlePropsPath)) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
let content = await FsUtils.readFileAsync(gradlePropsPath);
|
|
312
|
+
|
|
313
|
+
// Java 21 경로 자동 탐색
|
|
314
|
+
const java21Path = this._findJava21();
|
|
315
|
+
if (java21Path != null && !content.includes("org.gradle.java.home")) {
|
|
316
|
+
content += `\norg.gradle.java.home=${java21Path.replace(/\\/g, "\\\\")}\n`;
|
|
317
|
+
FsUtils.writeFile(gradlePropsPath, content);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
private _findJava21(): string | undefined {
|
|
322
|
+
const patterns = [
|
|
323
|
+
"C:/Program Files/Amazon Corretto/jdk21*",
|
|
324
|
+
"C:/Program Files/Eclipse Adoptium/jdk-21*",
|
|
325
|
+
"C:/Program Files/Java/jdk-21*",
|
|
326
|
+
"C:/Program Files/Microsoft/jdk-21*",
|
|
327
|
+
];
|
|
328
|
+
|
|
329
|
+
for (const pattern of patterns) {
|
|
330
|
+
const matches = FsUtils.glob(pattern);
|
|
331
|
+
if (matches.length > 0) {
|
|
332
|
+
// 가장 최신 버전 선택 (정렬 후 마지막)
|
|
333
|
+
return matches.sort().at(-1);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return undefined;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
private async _configureSdkPathAsync(androidPath: string) {
|
|
341
|
+
// local.properties 생성
|
|
342
|
+
const localPropsPath = path.resolve(androidPath, "local.properties");
|
|
343
|
+
|
|
344
|
+
if (FsUtils.exists(localPropsPath)) {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// SDK 경로 탐색 (Cordova 방식과 유사)
|
|
349
|
+
const sdkPath = this._findAndroidSdk();
|
|
350
|
+
if (sdkPath != null) {
|
|
351
|
+
await FsUtils.writeFileAsync(localPropsPath, `sdk.dir=${sdkPath.replace(/\\/g, "/")}\n`);
|
|
352
|
+
}
|
|
270
353
|
}
|
|
271
354
|
|
|
272
|
-
private
|
|
355
|
+
private async _configureAndroidManifestAsync(androidPath: string) {
|
|
273
356
|
const manifestPath = path.resolve(androidPath, "app/src/main/AndroidManifest.xml");
|
|
274
357
|
|
|
275
358
|
if (!FsUtils.exists(manifestPath)) {
|
|
276
359
|
return;
|
|
277
360
|
}
|
|
278
361
|
|
|
279
|
-
let manifestContent = FsUtils.
|
|
362
|
+
let manifestContent = await FsUtils.readFileAsync(manifestPath);
|
|
280
363
|
|
|
281
364
|
// usesCleartextTraffic 설정
|
|
282
365
|
if (!manifestContent.includes("android:usesCleartextTraffic")) {
|
|
@@ -319,17 +402,17 @@ export class SdCliCapacitor {
|
|
|
319
402
|
}
|
|
320
403
|
}
|
|
321
404
|
|
|
322
|
-
FsUtils.
|
|
405
|
+
await FsUtils.writeFileAsync(manifestPath, manifestContent);
|
|
323
406
|
}
|
|
324
407
|
|
|
325
|
-
private
|
|
408
|
+
private async _configureAndroidBuildGradleAsync(androidPath: string) {
|
|
326
409
|
const buildGradlePath = path.resolve(androidPath, "app/build.gradle");
|
|
327
410
|
|
|
328
411
|
if (!FsUtils.exists(buildGradlePath)) {
|
|
329
412
|
return;
|
|
330
413
|
}
|
|
331
414
|
|
|
332
|
-
let gradleContent = FsUtils.
|
|
415
|
+
let gradleContent = await FsUtils.readFileAsync(buildGradlePath);
|
|
333
416
|
|
|
334
417
|
// versionName, versionCode 설정
|
|
335
418
|
const version = this._npmConfig.version;
|
|
@@ -384,17 +467,17 @@ export class SdCliCapacitor {
|
|
|
384
467
|
}
|
|
385
468
|
}
|
|
386
469
|
|
|
387
|
-
FsUtils.
|
|
470
|
+
await FsUtils.writeFileAsync(buildGradlePath, gradleContent);
|
|
388
471
|
}
|
|
389
472
|
|
|
390
|
-
private
|
|
473
|
+
private async _configureAndroidStringsAsync(androidPath: string) {
|
|
391
474
|
const stringsPath = path.resolve(androidPath, "app/src/main/res/values/strings.xml");
|
|
392
475
|
|
|
393
476
|
if (!FsUtils.exists(stringsPath)) {
|
|
394
477
|
return;
|
|
395
478
|
}
|
|
396
479
|
|
|
397
|
-
let stringsContent = FsUtils.
|
|
480
|
+
let stringsContent = await FsUtils.readFileAsync(stringsPath);
|
|
398
481
|
stringsContent = stringsContent.replace(
|
|
399
482
|
/<string name="app_name">[^<]+<\/string>/,
|
|
400
483
|
`<string name="app_name">${this._opt.config.appName}</string>`,
|
|
@@ -412,7 +495,7 @@ export class SdCliCapacitor {
|
|
|
412
495
|
`<string name="custom_url_scheme">${this._opt.config.appId}</string>`,
|
|
413
496
|
);
|
|
414
497
|
|
|
415
|
-
FsUtils.
|
|
498
|
+
await FsUtils.writeFileAsync(stringsPath, stringsContent);
|
|
416
499
|
}
|
|
417
500
|
|
|
418
501
|
async buildAsync(outPath: string): Promise<void> {
|
|
@@ -470,14 +553,14 @@ export class SdCliCapacitor {
|
|
|
470
553
|
await SdCliCapacitor._execAsync(gradleCmd, [gradleTask, "--no-daemon"], androidPath);
|
|
471
554
|
|
|
472
555
|
// 빌드 결과물 복사
|
|
473
|
-
this.
|
|
556
|
+
await this._copyAndroidBuildOutputAsync(androidPath, targetOutPath, buildType);
|
|
474
557
|
}
|
|
475
558
|
|
|
476
|
-
private
|
|
559
|
+
private async _copyAndroidBuildOutputAsync(
|
|
477
560
|
androidPath: string,
|
|
478
561
|
targetOutPath: string,
|
|
479
562
|
buildType: string,
|
|
480
|
-
)
|
|
563
|
+
) {
|
|
481
564
|
const isBundle = this._opt.config.platform?.android?.bundle;
|
|
482
565
|
const isSigned = !!this._opt.config.platform?.android?.sign;
|
|
483
566
|
|
|
@@ -510,12 +593,39 @@ export class SdCliCapacitor {
|
|
|
510
593
|
|
|
511
594
|
const outputFileName = `${this._opt.config.appName}${isSigned ? "" : "-unsigned"}-latest.${ext}`;
|
|
512
595
|
|
|
513
|
-
FsUtils.
|
|
514
|
-
FsUtils.
|
|
596
|
+
await FsUtils.mkdirsAsync(targetOutPath);
|
|
597
|
+
await FsUtils.copyAsync(actualPath, path.resolve(targetOutPath, outputFileName));
|
|
515
598
|
|
|
516
599
|
const updatesPath = path.resolve(targetOutPath, "updates");
|
|
517
|
-
FsUtils.
|
|
518
|
-
FsUtils.
|
|
600
|
+
await FsUtils.mkdirsAsync(updatesPath);
|
|
601
|
+
await FsUtils.copyAsync(
|
|
602
|
+
actualPath,
|
|
603
|
+
path.resolve(updatesPath, `${this._npmConfig.version}.${ext}`),
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
private _findAndroidSdk(): string | undefined {
|
|
608
|
+
// 1. 환경변수 확인
|
|
609
|
+
const fromEnv = process.env["ANDROID_HOME"] ?? process.env["ANDROID_SDK_ROOT"];
|
|
610
|
+
if (fromEnv != null && FsUtils.exists(fromEnv)) {
|
|
611
|
+
return fromEnv;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// 2. 일반적인 설치 경로 탐색
|
|
615
|
+
const candidates = [
|
|
616
|
+
path.resolve(process.env["LOCALAPPDATA"] ?? "", "Android/Sdk"),
|
|
617
|
+
path.resolve(process.env["HOME"] ?? "", "Android/Sdk"),
|
|
618
|
+
"C:/Program Files/Android/Sdk",
|
|
619
|
+
"C:/Android/Sdk",
|
|
620
|
+
];
|
|
621
|
+
|
|
622
|
+
for (const candidate of candidates) {
|
|
623
|
+
if (FsUtils.exists(candidate)) {
|
|
624
|
+
return candidate;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
return undefined;
|
|
519
629
|
}
|
|
520
630
|
|
|
521
631
|
static async runWebviewOnDeviceAsync(opt: {
|
|
@@ -523,9 +633,11 @@ export class SdCliCapacitor {
|
|
|
523
633
|
package: string;
|
|
524
634
|
url?: string;
|
|
525
635
|
}): Promise<void> {
|
|
526
|
-
const projNpmConf = FsUtils.
|
|
527
|
-
|
|
528
|
-
|
|
636
|
+
const projNpmConf = (await FsUtils.readJsonAsync(
|
|
637
|
+
path.resolve(process.cwd(), "package.json"),
|
|
638
|
+
)) as INpmConfig;
|
|
639
|
+
const allPkgPaths = await projNpmConf.workspaces!.mapManyAsync(
|
|
640
|
+
async (item) => await FsUtils.globAsync(PathUtils.posix(process.cwd(), item)),
|
|
529
641
|
);
|
|
530
642
|
|
|
531
643
|
const capacitorPath = path.resolve(
|
|
@@ -537,7 +649,7 @@ export class SdCliCapacitor {
|
|
|
537
649
|
// capacitor.config.ts의 server.url 설정 업데이트
|
|
538
650
|
const configPath = path.resolve(capacitorPath, "capacitor.config.ts");
|
|
539
651
|
if (FsUtils.exists(configPath)) {
|
|
540
|
-
let configContent = FsUtils.
|
|
652
|
+
let configContent = await FsUtils.readFileAsync(configPath);
|
|
541
653
|
const serverUrl = `${opt.url.replace(/\/$/, "")}/${opt.package}/capacitor/`;
|
|
542
654
|
|
|
543
655
|
// 기존 url 설정이 있으면 교체, 없으면 server 블록 첫 줄에 추가
|
|
@@ -549,12 +661,17 @@ export class SdCliCapacitor {
|
|
|
549
661
|
`server: {\n url: "${serverUrl}",`,
|
|
550
662
|
);
|
|
551
663
|
}
|
|
552
|
-
FsUtils.
|
|
664
|
+
await FsUtils.writeFileAsync(configPath, configContent);
|
|
553
665
|
}
|
|
554
666
|
}
|
|
555
667
|
|
|
556
668
|
// cap sync 후 run
|
|
557
669
|
await this._execAsync("npx", ["cap", "sync", opt.platform], capacitorPath);
|
|
558
|
-
|
|
670
|
+
|
|
671
|
+
try {
|
|
672
|
+
await this._execAsync("npx", ["cap", "run", opt.platform], capacitorPath);
|
|
673
|
+
} catch {
|
|
674
|
+
await SdProcess.spawnAsync("adb", ["kill-server"]);
|
|
675
|
+
}
|
|
559
676
|
}
|
|
560
677
|
}
|