@simplysm/sd-cli 12.16.11 → 12.16.13
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 +1 -3
- package/dist/entry/SdCliCapacitor.js +47 -57
- package/dist/pkg-builders/client/SdNgBundler.js +1 -1
- package/dist/pkg-builders/commons/SdWorkerPathPlugin.js +0 -1
- package/package.json +5 -5
- package/src/entry/SdCliCapacitor.ts +74 -68
- package/src/pkg-builders/client/SdNgBundler.ts +1 -1
- package/src/pkg-builders/commons/SdWorkerPathPlugin.ts +0 -1
|
@@ -4,7 +4,7 @@ export declare class SdCliCapacitor {
|
|
|
4
4
|
private readonly _CAPACITOR_DIR_NAME;
|
|
5
5
|
private readonly _CONFIG_FILE_NAME;
|
|
6
6
|
private readonly _KEYSTORE_FILE_NAME;
|
|
7
|
-
private readonly
|
|
7
|
+
private readonly _ICON_DIR_NAME;
|
|
8
8
|
private readonly _platforms;
|
|
9
9
|
private readonly _npmConfig;
|
|
10
10
|
constructor(_opt: {
|
|
@@ -15,7 +15,6 @@ export declare class SdCliCapacitor {
|
|
|
15
15
|
private static _execAsync;
|
|
16
16
|
initializeAsync(): Promise<void>;
|
|
17
17
|
private _initializeCapacitorProjectAsync;
|
|
18
|
-
private _syncVersionAsync;
|
|
19
18
|
private _createCapacitorConfigAsync;
|
|
20
19
|
private _managePlatformsAsync;
|
|
21
20
|
private _managePluginsAsync;
|
|
@@ -33,7 +32,6 @@ export declare class SdCliCapacitor {
|
|
|
33
32
|
private _configureAndroidBuildGradleAsync;
|
|
34
33
|
private _configureAndroidStringsAsync;
|
|
35
34
|
buildAsync(outPath: string): Promise<void>;
|
|
36
|
-
private _buildPlatformAsync;
|
|
37
35
|
private _buildAndroidAsync;
|
|
38
36
|
private _copyAndroidBuildOutputAsync;
|
|
39
37
|
private _findAndroidSdk;
|
|
@@ -9,7 +9,7 @@ export class SdCliCapacitor {
|
|
|
9
9
|
this._CAPACITOR_DIR_NAME = ".capacitor";
|
|
10
10
|
this._CONFIG_FILE_NAME = "capacitor.config.ts";
|
|
11
11
|
this._KEYSTORE_FILE_NAME = "android.keystore";
|
|
12
|
-
this.
|
|
12
|
+
this._ICON_DIR_NAME = "resources";
|
|
13
13
|
this._platforms = Object.keys(this._opt.config.platform ?? {});
|
|
14
14
|
this._npmConfig = FsUtils.readJson(path.resolve(this._opt.pkgPath, "package.json"));
|
|
15
15
|
}
|
|
@@ -47,13 +47,23 @@ export class SdCliCapacitor {
|
|
|
47
47
|
}
|
|
48
48
|
// 1. Capacitor 프로젝트 초기화
|
|
49
49
|
async _initializeCapacitorProjectAsync(capacitorPath) {
|
|
50
|
-
|
|
50
|
+
const wwwPath = path.resolve(capacitorPath, "www");
|
|
51
|
+
if (FsUtils.exists(wwwPath)) {
|
|
51
52
|
SdCliCapacitor._logger.log("이미 생성되어있는 '.capacitor'를 사용합니다.");
|
|
52
53
|
// 버전 동기화
|
|
53
|
-
|
|
54
|
+
const pkgJsonPath = path.resolve(capacitorPath, "package.json");
|
|
55
|
+
if (FsUtils.exists(pkgJsonPath)) {
|
|
56
|
+
const pkgJson = (await FsUtils.readJsonAsync(pkgJsonPath));
|
|
57
|
+
if (pkgJson.version !== this._npmConfig.version) {
|
|
58
|
+
pkgJson.version = this._npmConfig.version;
|
|
59
|
+
await FsUtils.writeJsonAsync(pkgJsonPath, pkgJson, { space: 2 });
|
|
60
|
+
SdCliCapacitor._logger.debug(`버전 동기화: ${this._npmConfig.version}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
54
63
|
return false;
|
|
55
64
|
}
|
|
56
|
-
|
|
65
|
+
// www 폴더 생성
|
|
66
|
+
await FsUtils.mkdirsAsync(wwwPath);
|
|
57
67
|
// package.json 생성
|
|
58
68
|
const projNpmConfig = await FsUtils.readJsonAsync(path.resolve(this._opt.pkgPath, "../../package.json"));
|
|
59
69
|
const pkgJson = {
|
|
@@ -76,29 +86,17 @@ export class SdCliCapacitor {
|
|
|
76
86
|
});
|
|
77
87
|
// .yarnrc.yml 작성
|
|
78
88
|
await FsUtils.writeFileAsync(path.resolve(capacitorPath, ".yarnrc.yml"), "nodeLinker: node-modules");
|
|
79
|
-
// yarn.lock 작성
|
|
89
|
+
// 빈 yarn.lock 작성
|
|
80
90
|
await FsUtils.writeFileAsync(path.resolve(capacitorPath, "yarn.lock"), "");
|
|
81
91
|
// yarn install
|
|
92
|
+
await SdCliCapacitor._execAsync("yarn", ["dlx", "npm-check-updates", "-u", "--target", "semver"], capacitorPath);
|
|
82
93
|
await SdCliCapacitor._execAsync("yarn", ["install"], capacitorPath);
|
|
83
94
|
// capacitor init
|
|
84
95
|
await SdCliCapacitor._execAsync("npx", ["cap", "init", this._opt.config.appName, this._opt.config.appId], capacitorPath);
|
|
85
96
|
// www/index.html 생성
|
|
86
|
-
const wwwPath = path.resolve(capacitorPath, "www");
|
|
87
97
|
await FsUtils.writeFileAsync(path.resolve(wwwPath, "index.html"), "<!DOCTYPE html><html><head></head><body></body></html>");
|
|
88
98
|
return true;
|
|
89
99
|
}
|
|
90
|
-
// 버전 동기화
|
|
91
|
-
async _syncVersionAsync(capacitorPath) {
|
|
92
|
-
const pkgJsonPath = path.resolve(capacitorPath, "package.json");
|
|
93
|
-
if (FsUtils.exists(pkgJsonPath)) {
|
|
94
|
-
const pkgJson = (await FsUtils.readJsonAsync(pkgJsonPath));
|
|
95
|
-
if (pkgJson.version !== this._npmConfig.version) {
|
|
96
|
-
pkgJson.version = this._npmConfig.version;
|
|
97
|
-
await FsUtils.writeJsonAsync(pkgJsonPath, pkgJson, { space: 2 });
|
|
98
|
-
SdCliCapacitor._logger.debug(`버전 동기화: ${this._npmConfig.version}`);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
100
|
// 2. Capacitor 설정 파일 생성
|
|
103
101
|
async _createCapacitorConfigAsync(capacitorPath) {
|
|
104
102
|
const configFilePath = path.resolve(capacitorPath, this._CONFIG_FILE_NAME);
|
|
@@ -204,21 +202,14 @@ export class SdCliCapacitor {
|
|
|
204
202
|
await FsUtils.removeAsync(keystorePath);
|
|
205
203
|
}
|
|
206
204
|
}
|
|
207
|
-
// 6. 아이콘 및 스플래시 스크린 설정
|
|
208
205
|
async _setupIconAndSplashScreenAsync(capacitorPath) {
|
|
209
|
-
const resourcesDirPath = path.resolve(capacitorPath, this.
|
|
206
|
+
const resourcesDirPath = path.resolve(capacitorPath, this._ICON_DIR_NAME);
|
|
210
207
|
if (this._opt.config.icon != null) {
|
|
211
208
|
await FsUtils.mkdirsAsync(resourcesDirPath);
|
|
212
209
|
const iconSource = path.resolve(this._opt.pkgPath, this._opt.config.icon);
|
|
213
|
-
//
|
|
214
|
-
// 로고 크기 약 60% - safe zone(61%) 내에서 최대한 크게
|
|
210
|
+
// 아이콘만 생성
|
|
215
211
|
const logoPath = path.resolve(resourcesDirPath, "logo.png");
|
|
216
212
|
await this._createCenteredImageAsync(iconSource, logoPath, 1024, 0.6);
|
|
217
|
-
// splash.png: 2732x2732
|
|
218
|
-
// 로고 크기 약 35% - 화면 중앙에 적당한 크기로
|
|
219
|
-
const splashPath = path.resolve(resourcesDirPath, "splash.png");
|
|
220
|
-
await this._createCenteredImageAsync(iconSource, splashPath, 2732, 0.35);
|
|
221
|
-
// 기존 아이콘 삭제 (겹침 방지)
|
|
222
213
|
await this._cleanupExistingIconsAsync(capacitorPath);
|
|
223
214
|
try {
|
|
224
215
|
await SdCliCapacitor._execAsync("npx", [
|
|
@@ -231,8 +222,8 @@ export class SdCliCapacitor {
|
|
|
231
222
|
"#ffffff",
|
|
232
223
|
], capacitorPath);
|
|
233
224
|
}
|
|
234
|
-
catch {
|
|
235
|
-
SdCliCapacitor._logger.warn("아이콘 생성 실패, 기본 아이콘 사용");
|
|
225
|
+
catch (e) {
|
|
226
|
+
SdCliCapacitor._logger.warn("아이콘 생성 실패, 기본 아이콘 사용", e);
|
|
236
227
|
}
|
|
237
228
|
}
|
|
238
229
|
else {
|
|
@@ -262,13 +253,23 @@ export class SdCliCapacitor {
|
|
|
262
253
|
const androidResPath = path.resolve(capacitorPath, "android/app/src/main/res");
|
|
263
254
|
if (!FsUtils.exists(androidResPath))
|
|
264
255
|
return;
|
|
256
|
+
// mipmap 폴더의 모든 ic_launcher 관련 파일 삭제 (png + xml)
|
|
265
257
|
const mipmapDirs = await FsUtils.globAsync(path.resolve(androidResPath, "mipmap-*"));
|
|
266
258
|
for (const dir of mipmapDirs) {
|
|
267
|
-
const iconFiles = await FsUtils.globAsync(path.resolve(dir, "ic_launcher
|
|
259
|
+
const iconFiles = await FsUtils.globAsync(path.resolve(dir, "ic_launcher*")); // 확장자 제거
|
|
268
260
|
for (const file of iconFiles) {
|
|
269
261
|
await FsUtils.removeAsync(file);
|
|
270
262
|
}
|
|
271
263
|
}
|
|
264
|
+
// drawable 폴더의 splash/icon 관련 파일도 삭제
|
|
265
|
+
const drawableDirs = await FsUtils.globAsync(path.resolve(androidResPath, "drawable*"));
|
|
266
|
+
for (const dir of drawableDirs) {
|
|
267
|
+
const splashFiles = await FsUtils.globAsync(path.resolve(dir, "splash*"));
|
|
268
|
+
const iconFiles = await FsUtils.globAsync(path.resolve(dir, "ic_launcher*"));
|
|
269
|
+
for (const file of [...splashFiles, ...iconFiles]) {
|
|
270
|
+
await FsUtils.removeAsync(file);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
272
273
|
}
|
|
273
274
|
// 7. Android 네이티브 설정
|
|
274
275
|
async _configureAndroidNativeAsync(capacitorPath) {
|
|
@@ -295,10 +296,15 @@ export class SdCliCapacitor {
|
|
|
295
296
|
return;
|
|
296
297
|
}
|
|
297
298
|
let stylesContent = await FsUtils.readFileAsync(stylesPath);
|
|
298
|
-
// Edge-to-Edge
|
|
299
|
+
// Edge-to-Edge 비활성화만
|
|
299
300
|
if (!stylesContent.includes("android:windowOptOutEdgeToEdgeEnforcement")) {
|
|
300
|
-
stylesContent = stylesContent.replace(/(<style[^>]*AppTheme[^>]*>)/, `$1\n <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>`);
|
|
301
|
+
stylesContent = stylesContent.replace(/(<style[^>]*name="AppTheme"[^>]*>)/, `$1\n <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>`);
|
|
301
302
|
}
|
|
303
|
+
// NoActionBarLaunch를 단순 NoActionBar로
|
|
304
|
+
stylesContent = stylesContent.replace(/(<style\s+name="AppTheme\.NoActionBarLaunch"\s+parent=")[^"]+(")/, `$1Theme.AppCompat.Light.NoActionBar$2`);
|
|
305
|
+
// splash 관련 전부 제거
|
|
306
|
+
stylesContent = stylesContent.replace(/\s*<item name="android:background">@drawable\/splash<\/item>/g, "");
|
|
307
|
+
stylesContent = stylesContent.replace(/\s*<item name="android:windowSplashScreen[^"]*">[^<]*<\/item>/g, "");
|
|
302
308
|
await FsUtils.writeFileAsync(stylesPath, stylesContent);
|
|
303
309
|
}
|
|
304
310
|
async _configureAndroidGradlePropertiesAsync(androidPath) {
|
|
@@ -416,7 +422,7 @@ export class SdCliCapacitor {
|
|
|
416
422
|
// Signing 설정
|
|
417
423
|
const signConfig = this._opt.config.platform?.android?.sign;
|
|
418
424
|
if (signConfig) {
|
|
419
|
-
const keystoreRelativePath =
|
|
425
|
+
const keystoreRelativePath = `../../${this._KEYSTORE_FILE_NAME}`;
|
|
420
426
|
const keystoreType = signConfig.keystoreType ?? "jks";
|
|
421
427
|
// signingConfigs 블록 추가
|
|
422
428
|
if (!gradleContent.includes("signingConfigs")) {
|
|
@@ -424,15 +430,15 @@ export class SdCliCapacitor {
|
|
|
424
430
|
signingConfigs {
|
|
425
431
|
release {
|
|
426
432
|
storeFile file("${keystoreRelativePath}")
|
|
427
|
-
storePassword
|
|
428
|
-
keyAlias
|
|
429
|
-
keyPassword
|
|
433
|
+
storePassword '${signConfig.storePassword}'
|
|
434
|
+
keyAlias '${signConfig.alias}'
|
|
435
|
+
keyPassword '${signConfig.password}'
|
|
430
436
|
storeType "${keystoreType}"
|
|
431
437
|
}
|
|
432
438
|
}
|
|
433
439
|
`;
|
|
434
440
|
// android { 블록 내부에 추가
|
|
435
|
-
gradleContent = gradleContent.replace(/(android\s*\{)/, `$
|
|
441
|
+
gradleContent = gradleContent.replace(/(android\s*\{)/, (match) => `${match}${signingConfigsBlock}`);
|
|
436
442
|
}
|
|
437
443
|
// buildTypes.release에 signingConfig 추가
|
|
438
444
|
if (!gradleContent.includes("signingConfig signingConfigs.release")) {
|
|
@@ -460,34 +466,18 @@ export class SdCliCapacitor {
|
|
|
460
466
|
await Promise.all(this._platforms.map(async (platform) => {
|
|
461
467
|
// 해당 플랫폼만 copy
|
|
462
468
|
await SdCliCapacitor._execAsync("npx", ["cap", "copy", platform], capacitorPath);
|
|
463
|
-
|
|
469
|
+
if (platform === "android") {
|
|
470
|
+
await this._buildAndroidAsync(capacitorPath, outPath, buildType);
|
|
471
|
+
}
|
|
464
472
|
}));
|
|
465
473
|
}
|
|
466
|
-
async _buildPlatformAsync(capacitorPath, outPath, platform, buildType) {
|
|
467
|
-
if (platform === "android") {
|
|
468
|
-
await this._buildAndroidAsync(capacitorPath, outPath, buildType);
|
|
469
|
-
}
|
|
470
|
-
// iOS 지원 시 추가
|
|
471
|
-
}
|
|
472
474
|
async _buildAndroidAsync(capacitorPath, outPath, buildType) {
|
|
473
475
|
const androidPath = path.resolve(capacitorPath, "android");
|
|
474
476
|
const targetOutPath = path.resolve(outPath, "android");
|
|
475
|
-
// Gradle wrapper로 빌드
|
|
476
477
|
const isBundle = this._opt.config.platform?.android?.bundle;
|
|
477
478
|
const gradleTask = buildType === "release" ? (isBundle ? "bundleRelease" : "assembleRelease") : "assembleDebug";
|
|
478
|
-
// gradlew 실행 권한 부여 (Linux/Mac)
|
|
479
|
-
const gradlewPath = path.resolve(androidPath, "gradlew");
|
|
480
|
-
if (FsUtils.exists(gradlewPath)) {
|
|
481
|
-
try {
|
|
482
|
-
await SdCliCapacitor._execAsync("chmod", ["+x", "gradlew"], androidPath);
|
|
483
|
-
}
|
|
484
|
-
catch {
|
|
485
|
-
// Windows에서는 무시
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
479
|
// Gradle 빌드 실행
|
|
489
|
-
|
|
490
|
-
await SdCliCapacitor._execAsync(gradleCmd, [gradleTask, "--no-daemon"], androidPath);
|
|
480
|
+
await SdCliCapacitor._execAsync("cmd", ["/c", "gradlew.bat", gradleTask, "--no-daemon"], androidPath);
|
|
491
481
|
// 빌드 결과물 복사
|
|
492
482
|
await this._copyAndroidBuildOutputAsync(androidPath, targetOutPath, buildType);
|
|
493
483
|
}
|
|
@@ -435,7 +435,7 @@ export class SdNgBundler {
|
|
|
435
435
|
...(this._conf.builderType === "electron"
|
|
436
436
|
? []
|
|
437
437
|
: [nodeStdLibBrowserPlugin(nodeStdLibBrowser)]),
|
|
438
|
-
SdWorkerPathPlugin(
|
|
438
|
+
SdWorkerPathPlugin(this._outputPath),
|
|
439
439
|
// {
|
|
440
440
|
// name: "log-circular",
|
|
441
441
|
// setup(build) {
|
|
@@ -24,7 +24,6 @@ export function SdWorkerPathPlugin(outdir) {
|
|
|
24
24
|
return match;
|
|
25
25
|
}
|
|
26
26
|
// 2. 출력될 워커 파일명 결정 (캐싱 및 중복 방지를 위해 해시 사용 권장)
|
|
27
|
-
// 예: dist/workers/protocol.worker-X7A8B9.js
|
|
28
27
|
const fileContent = await FsUtils.readFileBufferAsync(resolvedWorkerPath);
|
|
29
28
|
const hash = HashUtils.get(fileContent).substring(0, 8);
|
|
30
29
|
const workerBaseName = path.basename(resolvedWorkerPath, path.extname(resolvedWorkerPath));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simplysm/sd-cli",
|
|
3
|
-
"version": "12.16.
|
|
3
|
+
"version": "12.16.13",
|
|
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.13",
|
|
21
|
+
"@simplysm/sd-core-node": "12.16.13",
|
|
22
|
+
"@simplysm/sd-service-server": "12.16.13",
|
|
23
|
+
"@simplysm/sd-storage": "12.16.13",
|
|
24
24
|
"browserslist": "^4.28.1",
|
|
25
25
|
"cordova": "^13.0.0",
|
|
26
26
|
"electron": "^33.4.11",
|
|
@@ -10,7 +10,7 @@ export class SdCliCapacitor {
|
|
|
10
10
|
private readonly _CAPACITOR_DIR_NAME = ".capacitor";
|
|
11
11
|
private readonly _CONFIG_FILE_NAME = "capacitor.config.ts";
|
|
12
12
|
private readonly _KEYSTORE_FILE_NAME = "android.keystore";
|
|
13
|
-
private readonly
|
|
13
|
+
private readonly _ICON_DIR_NAME = "resources";
|
|
14
14
|
|
|
15
15
|
// private readonly _ANDROID_DIR_NAME = "android";
|
|
16
16
|
// private readonly _WWW_DIR_NAME = "www";
|
|
@@ -67,16 +67,28 @@ export class SdCliCapacitor {
|
|
|
67
67
|
|
|
68
68
|
// 1. Capacitor 프로젝트 초기화
|
|
69
69
|
private async _initializeCapacitorProjectAsync(capacitorPath: string): Promise<boolean> {
|
|
70
|
-
|
|
70
|
+
const wwwPath = path.resolve(capacitorPath, "www");
|
|
71
|
+
|
|
72
|
+
if (FsUtils.exists(wwwPath)) {
|
|
71
73
|
SdCliCapacitor._logger.log("이미 생성되어있는 '.capacitor'를 사용합니다.");
|
|
72
74
|
|
|
73
75
|
// 버전 동기화
|
|
74
|
-
|
|
76
|
+
const pkgJsonPath = path.resolve(capacitorPath, "package.json");
|
|
77
|
+
|
|
78
|
+
if (FsUtils.exists(pkgJsonPath)) {
|
|
79
|
+
const pkgJson = (await FsUtils.readJsonAsync(pkgJsonPath)) as INpmConfig;
|
|
80
|
+
|
|
81
|
+
if (pkgJson.version !== this._npmConfig.version) {
|
|
82
|
+
pkgJson.version = this._npmConfig.version;
|
|
83
|
+
await FsUtils.writeJsonAsync(pkgJsonPath, pkgJson, { space: 2 });
|
|
84
|
+
SdCliCapacitor._logger.debug(`버전 동기화: ${this._npmConfig.version}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
75
87
|
return false;
|
|
76
88
|
}
|
|
77
89
|
|
|
78
|
-
|
|
79
|
-
await FsUtils.mkdirsAsync(
|
|
90
|
+
// www 폴더 생성
|
|
91
|
+
await FsUtils.mkdirsAsync(wwwPath);
|
|
80
92
|
|
|
81
93
|
// package.json 생성
|
|
82
94
|
const projNpmConfig = await FsUtils.readJsonAsync(
|
|
@@ -110,10 +122,15 @@ export class SdCliCapacitor {
|
|
|
110
122
|
"nodeLinker: node-modules",
|
|
111
123
|
);
|
|
112
124
|
|
|
113
|
-
// yarn.lock 작성
|
|
125
|
+
// 빈 yarn.lock 작성
|
|
114
126
|
await FsUtils.writeFileAsync(path.resolve(capacitorPath, "yarn.lock"), "");
|
|
115
127
|
|
|
116
128
|
// yarn install
|
|
129
|
+
await SdCliCapacitor._execAsync(
|
|
130
|
+
"yarn",
|
|
131
|
+
["dlx", "npm-check-updates", "-u", "--target", "semver"],
|
|
132
|
+
capacitorPath,
|
|
133
|
+
);
|
|
117
134
|
await SdCliCapacitor._execAsync("yarn", ["install"], capacitorPath);
|
|
118
135
|
|
|
119
136
|
// capacitor init
|
|
@@ -124,7 +141,6 @@ export class SdCliCapacitor {
|
|
|
124
141
|
);
|
|
125
142
|
|
|
126
143
|
// www/index.html 생성
|
|
127
|
-
const wwwPath = path.resolve(capacitorPath, "www");
|
|
128
144
|
await FsUtils.writeFileAsync(
|
|
129
145
|
path.resolve(wwwPath, "index.html"),
|
|
130
146
|
"<!DOCTYPE html><html><head></head><body></body></html>",
|
|
@@ -133,21 +149,6 @@ export class SdCliCapacitor {
|
|
|
133
149
|
return true;
|
|
134
150
|
}
|
|
135
151
|
|
|
136
|
-
// 버전 동기화
|
|
137
|
-
private async _syncVersionAsync(capacitorPath: string) {
|
|
138
|
-
const pkgJsonPath = path.resolve(capacitorPath, "package.json");
|
|
139
|
-
|
|
140
|
-
if (FsUtils.exists(pkgJsonPath)) {
|
|
141
|
-
const pkgJson = (await FsUtils.readJsonAsync(pkgJsonPath)) as INpmConfig;
|
|
142
|
-
|
|
143
|
-
if (pkgJson.version !== this._npmConfig.version) {
|
|
144
|
-
pkgJson.version = this._npmConfig.version;
|
|
145
|
-
await FsUtils.writeJsonAsync(pkgJsonPath, pkgJson, { space: 2 });
|
|
146
|
-
SdCliCapacitor._logger.debug(`버전 동기화: ${this._npmConfig.version}`);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
152
|
// 2. Capacitor 설정 파일 생성
|
|
152
153
|
private async _createCapacitorConfigAsync(capacitorPath: string) {
|
|
153
154
|
const configFilePath = path.resolve(capacitorPath, this._CONFIG_FILE_NAME);
|
|
@@ -272,26 +273,18 @@ export class SdCliCapacitor {
|
|
|
272
273
|
}
|
|
273
274
|
}
|
|
274
275
|
|
|
275
|
-
// 6. 아이콘 및 스플래시 스크린 설정
|
|
276
276
|
private async _setupIconAndSplashScreenAsync(capacitorPath: string): Promise<void> {
|
|
277
|
-
const resourcesDirPath = path.resolve(capacitorPath, this.
|
|
277
|
+
const resourcesDirPath = path.resolve(capacitorPath, this._ICON_DIR_NAME);
|
|
278
278
|
|
|
279
279
|
if (this._opt.config.icon != null) {
|
|
280
280
|
await FsUtils.mkdirsAsync(resourcesDirPath);
|
|
281
281
|
|
|
282
282
|
const iconSource = path.resolve(this._opt.pkgPath, this._opt.config.icon);
|
|
283
283
|
|
|
284
|
-
//
|
|
285
|
-
// 로고 크기 약 60% - safe zone(61%) 내에서 최대한 크게
|
|
284
|
+
// 아이콘만 생성
|
|
286
285
|
const logoPath = path.resolve(resourcesDirPath, "logo.png");
|
|
287
286
|
await this._createCenteredImageAsync(iconSource, logoPath, 1024, 0.6);
|
|
288
287
|
|
|
289
|
-
// splash.png: 2732x2732
|
|
290
|
-
// 로고 크기 약 35% - 화면 중앙에 적당한 크기로
|
|
291
|
-
const splashPath = path.resolve(resourcesDirPath, "splash.png");
|
|
292
|
-
await this._createCenteredImageAsync(iconSource, splashPath, 2732, 0.35);
|
|
293
|
-
|
|
294
|
-
// 기존 아이콘 삭제 (겹침 방지)
|
|
295
288
|
await this._cleanupExistingIconsAsync(capacitorPath);
|
|
296
289
|
|
|
297
290
|
try {
|
|
@@ -308,8 +301,8 @@ export class SdCliCapacitor {
|
|
|
308
301
|
],
|
|
309
302
|
capacitorPath,
|
|
310
303
|
);
|
|
311
|
-
} catch {
|
|
312
|
-
SdCliCapacitor._logger.warn("아이콘 생성 실패, 기본 아이콘 사용");
|
|
304
|
+
} catch (e) {
|
|
305
|
+
SdCliCapacitor._logger.warn("아이콘 생성 실패, 기본 아이콘 사용", e);
|
|
313
306
|
}
|
|
314
307
|
} else {
|
|
315
308
|
await FsUtils.removeAsync(resourcesDirPath);
|
|
@@ -347,13 +340,24 @@ export class SdCliCapacitor {
|
|
|
347
340
|
|
|
348
341
|
if (!FsUtils.exists(androidResPath)) return;
|
|
349
342
|
|
|
343
|
+
// mipmap 폴더의 모든 ic_launcher 관련 파일 삭제 (png + xml)
|
|
350
344
|
const mipmapDirs = await FsUtils.globAsync(path.resolve(androidResPath, "mipmap-*"));
|
|
351
345
|
for (const dir of mipmapDirs) {
|
|
352
|
-
const iconFiles = await FsUtils.globAsync(path.resolve(dir, "ic_launcher
|
|
346
|
+
const iconFiles = await FsUtils.globAsync(path.resolve(dir, "ic_launcher*")); // 확장자 제거
|
|
353
347
|
for (const file of iconFiles) {
|
|
354
348
|
await FsUtils.removeAsync(file);
|
|
355
349
|
}
|
|
356
350
|
}
|
|
351
|
+
|
|
352
|
+
// drawable 폴더의 splash/icon 관련 파일도 삭제
|
|
353
|
+
const drawableDirs = await FsUtils.globAsync(path.resolve(androidResPath, "drawable*"));
|
|
354
|
+
for (const dir of drawableDirs) {
|
|
355
|
+
const splashFiles = await FsUtils.globAsync(path.resolve(dir, "splash*"));
|
|
356
|
+
const iconFiles = await FsUtils.globAsync(path.resolve(dir, "ic_launcher*"));
|
|
357
|
+
for (const file of [...splashFiles, ...iconFiles]) {
|
|
358
|
+
await FsUtils.removeAsync(file);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
357
361
|
}
|
|
358
362
|
|
|
359
363
|
// 7. Android 네이티브 설정
|
|
@@ -392,14 +396,30 @@ export class SdCliCapacitor {
|
|
|
392
396
|
|
|
393
397
|
let stylesContent = await FsUtils.readFileAsync(stylesPath);
|
|
394
398
|
|
|
395
|
-
// Edge-to-Edge
|
|
399
|
+
// Edge-to-Edge 비활성화만
|
|
396
400
|
if (!stylesContent.includes("android:windowOptOutEdgeToEdgeEnforcement")) {
|
|
397
401
|
stylesContent = stylesContent.replace(
|
|
398
|
-
/(<style[^>]*AppTheme[^>]*>)/,
|
|
402
|
+
/(<style[^>]*name="AppTheme"[^>]*>)/,
|
|
399
403
|
`$1\n <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>`,
|
|
400
404
|
);
|
|
401
405
|
}
|
|
402
406
|
|
|
407
|
+
// NoActionBarLaunch를 단순 NoActionBar로
|
|
408
|
+
stylesContent = stylesContent.replace(
|
|
409
|
+
/(<style\s+name="AppTheme\.NoActionBarLaunch"\s+parent=")[^"]+(")/,
|
|
410
|
+
`$1Theme.AppCompat.Light.NoActionBar$2`,
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
// splash 관련 전부 제거
|
|
414
|
+
stylesContent = stylesContent.replace(
|
|
415
|
+
/\s*<item name="android:background">@drawable\/splash<\/item>/g,
|
|
416
|
+
"",
|
|
417
|
+
);
|
|
418
|
+
stylesContent = stylesContent.replace(
|
|
419
|
+
/\s*<item name="android:windowSplashScreen[^"]*">[^<]*<\/item>/g,
|
|
420
|
+
"",
|
|
421
|
+
);
|
|
422
|
+
|
|
403
423
|
await FsUtils.writeFileAsync(stylesPath, stylesContent);
|
|
404
424
|
}
|
|
405
425
|
|
|
@@ -560,7 +580,7 @@ export class SdCliCapacitor {
|
|
|
560
580
|
// Signing 설정
|
|
561
581
|
const signConfig = this._opt.config.platform?.android?.sign;
|
|
562
582
|
if (signConfig) {
|
|
563
|
-
const keystoreRelativePath =
|
|
583
|
+
const keystoreRelativePath = `../../${this._KEYSTORE_FILE_NAME}`;
|
|
564
584
|
const keystoreType = signConfig.keystoreType ?? "jks";
|
|
565
585
|
|
|
566
586
|
// signingConfigs 블록 추가
|
|
@@ -569,15 +589,18 @@ export class SdCliCapacitor {
|
|
|
569
589
|
signingConfigs {
|
|
570
590
|
release {
|
|
571
591
|
storeFile file("${keystoreRelativePath}")
|
|
572
|
-
storePassword
|
|
573
|
-
keyAlias
|
|
574
|
-
keyPassword
|
|
592
|
+
storePassword '${signConfig.storePassword}'
|
|
593
|
+
keyAlias '${signConfig.alias}'
|
|
594
|
+
keyPassword '${signConfig.password}'
|
|
575
595
|
storeType "${keystoreType}"
|
|
576
596
|
}
|
|
577
597
|
}
|
|
578
598
|
`;
|
|
579
599
|
// android { 블록 내부에 추가
|
|
580
|
-
gradleContent = gradleContent.replace(
|
|
600
|
+
gradleContent = gradleContent.replace(
|
|
601
|
+
/(android\s*\{)/,
|
|
602
|
+
(match) => `${match}${signingConfigsBlock}`,
|
|
603
|
+
);
|
|
581
604
|
}
|
|
582
605
|
|
|
583
606
|
// buildTypes.release에 signingConfig 추가
|
|
@@ -629,23 +652,14 @@ export class SdCliCapacitor {
|
|
|
629
652
|
this._platforms.map(async (platform) => {
|
|
630
653
|
// 해당 플랫폼만 copy
|
|
631
654
|
await SdCliCapacitor._execAsync("npx", ["cap", "copy", platform], capacitorPath);
|
|
632
|
-
|
|
655
|
+
|
|
656
|
+
if (platform === "android") {
|
|
657
|
+
await this._buildAndroidAsync(capacitorPath, outPath, buildType);
|
|
658
|
+
}
|
|
633
659
|
}),
|
|
634
660
|
);
|
|
635
661
|
}
|
|
636
662
|
|
|
637
|
-
private async _buildPlatformAsync(
|
|
638
|
-
capacitorPath: string,
|
|
639
|
-
outPath: string,
|
|
640
|
-
platform: string,
|
|
641
|
-
buildType: string,
|
|
642
|
-
): Promise<void> {
|
|
643
|
-
if (platform === "android") {
|
|
644
|
-
await this._buildAndroidAsync(capacitorPath, outPath, buildType);
|
|
645
|
-
}
|
|
646
|
-
// iOS 지원 시 추가
|
|
647
|
-
}
|
|
648
|
-
|
|
649
663
|
private async _buildAndroidAsync(
|
|
650
664
|
capacitorPath: string,
|
|
651
665
|
outPath: string,
|
|
@@ -654,24 +668,16 @@ export class SdCliCapacitor {
|
|
|
654
668
|
const androidPath = path.resolve(capacitorPath, "android");
|
|
655
669
|
const targetOutPath = path.resolve(outPath, "android");
|
|
656
670
|
|
|
657
|
-
// Gradle wrapper로 빌드
|
|
658
671
|
const isBundle = this._opt.config.platform?.android?.bundle;
|
|
659
672
|
const gradleTask =
|
|
660
673
|
buildType === "release" ? (isBundle ? "bundleRelease" : "assembleRelease") : "assembleDebug";
|
|
661
674
|
|
|
662
|
-
// gradlew 실행 권한 부여 (Linux/Mac)
|
|
663
|
-
const gradlewPath = path.resolve(androidPath, "gradlew");
|
|
664
|
-
if (FsUtils.exists(gradlewPath)) {
|
|
665
|
-
try {
|
|
666
|
-
await SdCliCapacitor._execAsync("chmod", ["+x", "gradlew"], androidPath);
|
|
667
|
-
} catch {
|
|
668
|
-
// Windows에서는 무시
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
|
|
672
675
|
// Gradle 빌드 실행
|
|
673
|
-
|
|
674
|
-
|
|
676
|
+
await SdCliCapacitor._execAsync(
|
|
677
|
+
"cmd",
|
|
678
|
+
["/c", "gradlew.bat", gradleTask, "--no-daemon"],
|
|
679
|
+
androidPath,
|
|
680
|
+
);
|
|
675
681
|
|
|
676
682
|
// 빌드 결과물 복사
|
|
677
683
|
await this._copyAndroidBuildOutputAsync(androidPath, targetOutPath, buildType);
|
|
@@ -560,7 +560,7 @@ export class SdNgBundler {
|
|
|
560
560
|
...(this._conf.builderType === "electron"
|
|
561
561
|
? []
|
|
562
562
|
: [nodeStdLibBrowserPlugin(nodeStdLibBrowser)]),
|
|
563
|
-
SdWorkerPathPlugin(
|
|
563
|
+
SdWorkerPathPlugin(this._outputPath),
|
|
564
564
|
// {
|
|
565
565
|
// name: "log-circular",
|
|
566
566
|
// setup(build) {
|
|
@@ -33,7 +33,6 @@ export function SdWorkerPathPlugin(outdir: string): esbuild.Plugin {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
// 2. 출력될 워커 파일명 결정 (캐싱 및 중복 방지를 위해 해시 사용 권장)
|
|
36
|
-
// 예: dist/workers/protocol.worker-X7A8B9.js
|
|
37
36
|
const fileContent = await FsUtils.readFileBufferAsync(resolvedWorkerPath);
|
|
38
37
|
const hash = HashUtils.get(fileContent).substring(0, 8);
|
|
39
38
|
const workerBaseName = path.basename(
|