@simplysm/sd-cli 12.16.12 → 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 -58
- 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 -69
- 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 = {
|
|
@@ -64,7 +74,6 @@ export class SdCliCapacitor {
|
|
|
64
74
|
dependencies: {
|
|
65
75
|
"@capacitor/core": "^7.0.0",
|
|
66
76
|
"@capacitor/app": "^7.0.0",
|
|
67
|
-
"@capacitor/splash-screen": "^7.0.0",
|
|
68
77
|
},
|
|
69
78
|
devDependencies: {
|
|
70
79
|
"@capacitor/cli": "^7.0.0",
|
|
@@ -77,29 +86,17 @@ export class SdCliCapacitor {
|
|
|
77
86
|
});
|
|
78
87
|
// .yarnrc.yml 작성
|
|
79
88
|
await FsUtils.writeFileAsync(path.resolve(capacitorPath, ".yarnrc.yml"), "nodeLinker: node-modules");
|
|
80
|
-
// yarn.lock 작성
|
|
89
|
+
// 빈 yarn.lock 작성
|
|
81
90
|
await FsUtils.writeFileAsync(path.resolve(capacitorPath, "yarn.lock"), "");
|
|
82
91
|
// yarn install
|
|
92
|
+
await SdCliCapacitor._execAsync("yarn", ["dlx", "npm-check-updates", "-u", "--target", "semver"], capacitorPath);
|
|
83
93
|
await SdCliCapacitor._execAsync("yarn", ["install"], capacitorPath);
|
|
84
94
|
// capacitor init
|
|
85
95
|
await SdCliCapacitor._execAsync("npx", ["cap", "init", this._opt.config.appName, this._opt.config.appId], capacitorPath);
|
|
86
96
|
// www/index.html 생성
|
|
87
|
-
const wwwPath = path.resolve(capacitorPath, "www");
|
|
88
97
|
await FsUtils.writeFileAsync(path.resolve(wwwPath, "index.html"), "<!DOCTYPE html><html><head></head><body></body></html>");
|
|
89
98
|
return true;
|
|
90
99
|
}
|
|
91
|
-
// 버전 동기화
|
|
92
|
-
async _syncVersionAsync(capacitorPath) {
|
|
93
|
-
const pkgJsonPath = path.resolve(capacitorPath, "package.json");
|
|
94
|
-
if (FsUtils.exists(pkgJsonPath)) {
|
|
95
|
-
const pkgJson = (await FsUtils.readJsonAsync(pkgJsonPath));
|
|
96
|
-
if (pkgJson.version !== this._npmConfig.version) {
|
|
97
|
-
pkgJson.version = this._npmConfig.version;
|
|
98
|
-
await FsUtils.writeJsonAsync(pkgJsonPath, pkgJson, { space: 2 });
|
|
99
|
-
SdCliCapacitor._logger.debug(`버전 동기화: ${this._npmConfig.version}`);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
100
|
// 2. Capacitor 설정 파일 생성
|
|
104
101
|
async _createCapacitorConfigAsync(capacitorPath) {
|
|
105
102
|
const configFilePath = path.resolve(capacitorPath, this._CONFIG_FILE_NAME);
|
|
@@ -205,21 +202,14 @@ export class SdCliCapacitor {
|
|
|
205
202
|
await FsUtils.removeAsync(keystorePath);
|
|
206
203
|
}
|
|
207
204
|
}
|
|
208
|
-
// 6. 아이콘 및 스플래시 스크린 설정
|
|
209
205
|
async _setupIconAndSplashScreenAsync(capacitorPath) {
|
|
210
|
-
const resourcesDirPath = path.resolve(capacitorPath, this.
|
|
206
|
+
const resourcesDirPath = path.resolve(capacitorPath, this._ICON_DIR_NAME);
|
|
211
207
|
if (this._opt.config.icon != null) {
|
|
212
208
|
await FsUtils.mkdirsAsync(resourcesDirPath);
|
|
213
209
|
const iconSource = path.resolve(this._opt.pkgPath, this._opt.config.icon);
|
|
214
|
-
//
|
|
215
|
-
// 로고 크기 약 60% - safe zone(61%) 내에서 최대한 크게
|
|
210
|
+
// 아이콘만 생성
|
|
216
211
|
const logoPath = path.resolve(resourcesDirPath, "logo.png");
|
|
217
212
|
await this._createCenteredImageAsync(iconSource, logoPath, 1024, 0.6);
|
|
218
|
-
// splash.png: 2732x2732
|
|
219
|
-
// 로고 크기 약 35% - 화면 중앙에 적당한 크기로
|
|
220
|
-
const splashPath = path.resolve(resourcesDirPath, "splash.png");
|
|
221
|
-
await this._createCenteredImageAsync(iconSource, splashPath, 2732, 0.35);
|
|
222
|
-
// 기존 아이콘 삭제 (겹침 방지)
|
|
223
213
|
await this._cleanupExistingIconsAsync(capacitorPath);
|
|
224
214
|
try {
|
|
225
215
|
await SdCliCapacitor._execAsync("npx", [
|
|
@@ -232,8 +222,8 @@ export class SdCliCapacitor {
|
|
|
232
222
|
"#ffffff",
|
|
233
223
|
], capacitorPath);
|
|
234
224
|
}
|
|
235
|
-
catch {
|
|
236
|
-
SdCliCapacitor._logger.warn("아이콘 생성 실패, 기본 아이콘 사용");
|
|
225
|
+
catch (e) {
|
|
226
|
+
SdCliCapacitor._logger.warn("아이콘 생성 실패, 기본 아이콘 사용", e);
|
|
237
227
|
}
|
|
238
228
|
}
|
|
239
229
|
else {
|
|
@@ -263,13 +253,23 @@ export class SdCliCapacitor {
|
|
|
263
253
|
const androidResPath = path.resolve(capacitorPath, "android/app/src/main/res");
|
|
264
254
|
if (!FsUtils.exists(androidResPath))
|
|
265
255
|
return;
|
|
256
|
+
// mipmap 폴더의 모든 ic_launcher 관련 파일 삭제 (png + xml)
|
|
266
257
|
const mipmapDirs = await FsUtils.globAsync(path.resolve(androidResPath, "mipmap-*"));
|
|
267
258
|
for (const dir of mipmapDirs) {
|
|
268
|
-
const iconFiles = await FsUtils.globAsync(path.resolve(dir, "ic_launcher
|
|
259
|
+
const iconFiles = await FsUtils.globAsync(path.resolve(dir, "ic_launcher*")); // 확장자 제거
|
|
269
260
|
for (const file of iconFiles) {
|
|
270
261
|
await FsUtils.removeAsync(file);
|
|
271
262
|
}
|
|
272
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
|
+
}
|
|
273
273
|
}
|
|
274
274
|
// 7. Android 네이티브 설정
|
|
275
275
|
async _configureAndroidNativeAsync(capacitorPath) {
|
|
@@ -296,10 +296,15 @@ export class SdCliCapacitor {
|
|
|
296
296
|
return;
|
|
297
297
|
}
|
|
298
298
|
let stylesContent = await FsUtils.readFileAsync(stylesPath);
|
|
299
|
-
// Edge-to-Edge
|
|
299
|
+
// Edge-to-Edge 비활성화만
|
|
300
300
|
if (!stylesContent.includes("android:windowOptOutEdgeToEdgeEnforcement")) {
|
|
301
|
-
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>`);
|
|
302
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, "");
|
|
303
308
|
await FsUtils.writeFileAsync(stylesPath, stylesContent);
|
|
304
309
|
}
|
|
305
310
|
async _configureAndroidGradlePropertiesAsync(androidPath) {
|
|
@@ -417,7 +422,7 @@ export class SdCliCapacitor {
|
|
|
417
422
|
// Signing 설정
|
|
418
423
|
const signConfig = this._opt.config.platform?.android?.sign;
|
|
419
424
|
if (signConfig) {
|
|
420
|
-
const keystoreRelativePath =
|
|
425
|
+
const keystoreRelativePath = `../../${this._KEYSTORE_FILE_NAME}`;
|
|
421
426
|
const keystoreType = signConfig.keystoreType ?? "jks";
|
|
422
427
|
// signingConfigs 블록 추가
|
|
423
428
|
if (!gradleContent.includes("signingConfigs")) {
|
|
@@ -425,15 +430,15 @@ export class SdCliCapacitor {
|
|
|
425
430
|
signingConfigs {
|
|
426
431
|
release {
|
|
427
432
|
storeFile file("${keystoreRelativePath}")
|
|
428
|
-
storePassword
|
|
429
|
-
keyAlias
|
|
430
|
-
keyPassword
|
|
433
|
+
storePassword '${signConfig.storePassword}'
|
|
434
|
+
keyAlias '${signConfig.alias}'
|
|
435
|
+
keyPassword '${signConfig.password}'
|
|
431
436
|
storeType "${keystoreType}"
|
|
432
437
|
}
|
|
433
438
|
}
|
|
434
439
|
`;
|
|
435
440
|
// android { 블록 내부에 추가
|
|
436
|
-
gradleContent = gradleContent.replace(/(android\s*\{)/, `$
|
|
441
|
+
gradleContent = gradleContent.replace(/(android\s*\{)/, (match) => `${match}${signingConfigsBlock}`);
|
|
437
442
|
}
|
|
438
443
|
// buildTypes.release에 signingConfig 추가
|
|
439
444
|
if (!gradleContent.includes("signingConfig signingConfigs.release")) {
|
|
@@ -461,34 +466,18 @@ export class SdCliCapacitor {
|
|
|
461
466
|
await Promise.all(this._platforms.map(async (platform) => {
|
|
462
467
|
// 해당 플랫폼만 copy
|
|
463
468
|
await SdCliCapacitor._execAsync("npx", ["cap", "copy", platform], capacitorPath);
|
|
464
|
-
|
|
469
|
+
if (platform === "android") {
|
|
470
|
+
await this._buildAndroidAsync(capacitorPath, outPath, buildType);
|
|
471
|
+
}
|
|
465
472
|
}));
|
|
466
473
|
}
|
|
467
|
-
async _buildPlatformAsync(capacitorPath, outPath, platform, buildType) {
|
|
468
|
-
if (platform === "android") {
|
|
469
|
-
await this._buildAndroidAsync(capacitorPath, outPath, buildType);
|
|
470
|
-
}
|
|
471
|
-
// iOS 지원 시 추가
|
|
472
|
-
}
|
|
473
474
|
async _buildAndroidAsync(capacitorPath, outPath, buildType) {
|
|
474
475
|
const androidPath = path.resolve(capacitorPath, "android");
|
|
475
476
|
const targetOutPath = path.resolve(outPath, "android");
|
|
476
|
-
// Gradle wrapper로 빌드
|
|
477
477
|
const isBundle = this._opt.config.platform?.android?.bundle;
|
|
478
478
|
const gradleTask = buildType === "release" ? (isBundle ? "bundleRelease" : "assembleRelease") : "assembleDebug";
|
|
479
|
-
// gradlew 실행 권한 부여 (Linux/Mac)
|
|
480
|
-
const gradlewPath = path.resolve(androidPath, "gradlew");
|
|
481
|
-
if (FsUtils.exists(gradlewPath)) {
|
|
482
|
-
try {
|
|
483
|
-
await SdCliCapacitor._execAsync("chmod", ["+x", "gradlew"], androidPath);
|
|
484
|
-
}
|
|
485
|
-
catch {
|
|
486
|
-
// Windows에서는 무시
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
479
|
// Gradle 빌드 실행
|
|
490
|
-
|
|
491
|
-
await SdCliCapacitor._execAsync(gradleCmd, [gradleTask, "--no-daemon"], androidPath);
|
|
480
|
+
await SdCliCapacitor._execAsync("cmd", ["/c", "gradlew.bat", gradleTask, "--no-daemon"], androidPath);
|
|
492
481
|
// 빌드 결과물 복사
|
|
493
482
|
await this._copyAndroidBuildOutputAsync(androidPath, targetOutPath, buildType);
|
|
494
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(
|
|
@@ -90,7 +102,6 @@ export class SdCliCapacitor {
|
|
|
90
102
|
dependencies: {
|
|
91
103
|
"@capacitor/core": "^7.0.0",
|
|
92
104
|
"@capacitor/app": "^7.0.0",
|
|
93
|
-
"@capacitor/splash-screen": "^7.0.0",
|
|
94
105
|
},
|
|
95
106
|
devDependencies: {
|
|
96
107
|
"@capacitor/cli": "^7.0.0",
|
|
@@ -111,10 +122,15 @@ export class SdCliCapacitor {
|
|
|
111
122
|
"nodeLinker: node-modules",
|
|
112
123
|
);
|
|
113
124
|
|
|
114
|
-
// yarn.lock 작성
|
|
125
|
+
// 빈 yarn.lock 작성
|
|
115
126
|
await FsUtils.writeFileAsync(path.resolve(capacitorPath, "yarn.lock"), "");
|
|
116
127
|
|
|
117
128
|
// yarn install
|
|
129
|
+
await SdCliCapacitor._execAsync(
|
|
130
|
+
"yarn",
|
|
131
|
+
["dlx", "npm-check-updates", "-u", "--target", "semver"],
|
|
132
|
+
capacitorPath,
|
|
133
|
+
);
|
|
118
134
|
await SdCliCapacitor._execAsync("yarn", ["install"], capacitorPath);
|
|
119
135
|
|
|
120
136
|
// capacitor init
|
|
@@ -125,7 +141,6 @@ export class SdCliCapacitor {
|
|
|
125
141
|
);
|
|
126
142
|
|
|
127
143
|
// www/index.html 생성
|
|
128
|
-
const wwwPath = path.resolve(capacitorPath, "www");
|
|
129
144
|
await FsUtils.writeFileAsync(
|
|
130
145
|
path.resolve(wwwPath, "index.html"),
|
|
131
146
|
"<!DOCTYPE html><html><head></head><body></body></html>",
|
|
@@ -134,21 +149,6 @@ export class SdCliCapacitor {
|
|
|
134
149
|
return true;
|
|
135
150
|
}
|
|
136
151
|
|
|
137
|
-
// 버전 동기화
|
|
138
|
-
private async _syncVersionAsync(capacitorPath: string) {
|
|
139
|
-
const pkgJsonPath = path.resolve(capacitorPath, "package.json");
|
|
140
|
-
|
|
141
|
-
if (FsUtils.exists(pkgJsonPath)) {
|
|
142
|
-
const pkgJson = (await FsUtils.readJsonAsync(pkgJsonPath)) as INpmConfig;
|
|
143
|
-
|
|
144
|
-
if (pkgJson.version !== this._npmConfig.version) {
|
|
145
|
-
pkgJson.version = this._npmConfig.version;
|
|
146
|
-
await FsUtils.writeJsonAsync(pkgJsonPath, pkgJson, { space: 2 });
|
|
147
|
-
SdCliCapacitor._logger.debug(`버전 동기화: ${this._npmConfig.version}`);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
152
|
// 2. Capacitor 설정 파일 생성
|
|
153
153
|
private async _createCapacitorConfigAsync(capacitorPath: string) {
|
|
154
154
|
const configFilePath = path.resolve(capacitorPath, this._CONFIG_FILE_NAME);
|
|
@@ -273,26 +273,18 @@ export class SdCliCapacitor {
|
|
|
273
273
|
}
|
|
274
274
|
}
|
|
275
275
|
|
|
276
|
-
// 6. 아이콘 및 스플래시 스크린 설정
|
|
277
276
|
private async _setupIconAndSplashScreenAsync(capacitorPath: string): Promise<void> {
|
|
278
|
-
const resourcesDirPath = path.resolve(capacitorPath, this.
|
|
277
|
+
const resourcesDirPath = path.resolve(capacitorPath, this._ICON_DIR_NAME);
|
|
279
278
|
|
|
280
279
|
if (this._opt.config.icon != null) {
|
|
281
280
|
await FsUtils.mkdirsAsync(resourcesDirPath);
|
|
282
281
|
|
|
283
282
|
const iconSource = path.resolve(this._opt.pkgPath, this._opt.config.icon);
|
|
284
283
|
|
|
285
|
-
//
|
|
286
|
-
// 로고 크기 약 60% - safe zone(61%) 내에서 최대한 크게
|
|
284
|
+
// 아이콘만 생성
|
|
287
285
|
const logoPath = path.resolve(resourcesDirPath, "logo.png");
|
|
288
286
|
await this._createCenteredImageAsync(iconSource, logoPath, 1024, 0.6);
|
|
289
287
|
|
|
290
|
-
// splash.png: 2732x2732
|
|
291
|
-
// 로고 크기 약 35% - 화면 중앙에 적당한 크기로
|
|
292
|
-
const splashPath = path.resolve(resourcesDirPath, "splash.png");
|
|
293
|
-
await this._createCenteredImageAsync(iconSource, splashPath, 2732, 0.35);
|
|
294
|
-
|
|
295
|
-
// 기존 아이콘 삭제 (겹침 방지)
|
|
296
288
|
await this._cleanupExistingIconsAsync(capacitorPath);
|
|
297
289
|
|
|
298
290
|
try {
|
|
@@ -309,8 +301,8 @@ export class SdCliCapacitor {
|
|
|
309
301
|
],
|
|
310
302
|
capacitorPath,
|
|
311
303
|
);
|
|
312
|
-
} catch {
|
|
313
|
-
SdCliCapacitor._logger.warn("아이콘 생성 실패, 기본 아이콘 사용");
|
|
304
|
+
} catch (e) {
|
|
305
|
+
SdCliCapacitor._logger.warn("아이콘 생성 실패, 기본 아이콘 사용", e);
|
|
314
306
|
}
|
|
315
307
|
} else {
|
|
316
308
|
await FsUtils.removeAsync(resourcesDirPath);
|
|
@@ -348,13 +340,24 @@ export class SdCliCapacitor {
|
|
|
348
340
|
|
|
349
341
|
if (!FsUtils.exists(androidResPath)) return;
|
|
350
342
|
|
|
343
|
+
// mipmap 폴더의 모든 ic_launcher 관련 파일 삭제 (png + xml)
|
|
351
344
|
const mipmapDirs = await FsUtils.globAsync(path.resolve(androidResPath, "mipmap-*"));
|
|
352
345
|
for (const dir of mipmapDirs) {
|
|
353
|
-
const iconFiles = await FsUtils.globAsync(path.resolve(dir, "ic_launcher
|
|
346
|
+
const iconFiles = await FsUtils.globAsync(path.resolve(dir, "ic_launcher*")); // 확장자 제거
|
|
354
347
|
for (const file of iconFiles) {
|
|
355
348
|
await FsUtils.removeAsync(file);
|
|
356
349
|
}
|
|
357
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
|
+
}
|
|
358
361
|
}
|
|
359
362
|
|
|
360
363
|
// 7. Android 네이티브 설정
|
|
@@ -393,14 +396,30 @@ export class SdCliCapacitor {
|
|
|
393
396
|
|
|
394
397
|
let stylesContent = await FsUtils.readFileAsync(stylesPath);
|
|
395
398
|
|
|
396
|
-
// Edge-to-Edge
|
|
399
|
+
// Edge-to-Edge 비활성화만
|
|
397
400
|
if (!stylesContent.includes("android:windowOptOutEdgeToEdgeEnforcement")) {
|
|
398
401
|
stylesContent = stylesContent.replace(
|
|
399
|
-
/(<style[^>]*AppTheme[^>]*>)/,
|
|
402
|
+
/(<style[^>]*name="AppTheme"[^>]*>)/,
|
|
400
403
|
`$1\n <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>`,
|
|
401
404
|
);
|
|
402
405
|
}
|
|
403
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
|
+
|
|
404
423
|
await FsUtils.writeFileAsync(stylesPath, stylesContent);
|
|
405
424
|
}
|
|
406
425
|
|
|
@@ -561,7 +580,7 @@ export class SdCliCapacitor {
|
|
|
561
580
|
// Signing 설정
|
|
562
581
|
const signConfig = this._opt.config.platform?.android?.sign;
|
|
563
582
|
if (signConfig) {
|
|
564
|
-
const keystoreRelativePath =
|
|
583
|
+
const keystoreRelativePath = `../../${this._KEYSTORE_FILE_NAME}`;
|
|
565
584
|
const keystoreType = signConfig.keystoreType ?? "jks";
|
|
566
585
|
|
|
567
586
|
// signingConfigs 블록 추가
|
|
@@ -570,15 +589,18 @@ export class SdCliCapacitor {
|
|
|
570
589
|
signingConfigs {
|
|
571
590
|
release {
|
|
572
591
|
storeFile file("${keystoreRelativePath}")
|
|
573
|
-
storePassword
|
|
574
|
-
keyAlias
|
|
575
|
-
keyPassword
|
|
592
|
+
storePassword '${signConfig.storePassword}'
|
|
593
|
+
keyAlias '${signConfig.alias}'
|
|
594
|
+
keyPassword '${signConfig.password}'
|
|
576
595
|
storeType "${keystoreType}"
|
|
577
596
|
}
|
|
578
597
|
}
|
|
579
598
|
`;
|
|
580
599
|
// android { 블록 내부에 추가
|
|
581
|
-
gradleContent = gradleContent.replace(
|
|
600
|
+
gradleContent = gradleContent.replace(
|
|
601
|
+
/(android\s*\{)/,
|
|
602
|
+
(match) => `${match}${signingConfigsBlock}`,
|
|
603
|
+
);
|
|
582
604
|
}
|
|
583
605
|
|
|
584
606
|
// buildTypes.release에 signingConfig 추가
|
|
@@ -630,23 +652,14 @@ export class SdCliCapacitor {
|
|
|
630
652
|
this._platforms.map(async (platform) => {
|
|
631
653
|
// 해당 플랫폼만 copy
|
|
632
654
|
await SdCliCapacitor._execAsync("npx", ["cap", "copy", platform], capacitorPath);
|
|
633
|
-
|
|
655
|
+
|
|
656
|
+
if (platform === "android") {
|
|
657
|
+
await this._buildAndroidAsync(capacitorPath, outPath, buildType);
|
|
658
|
+
}
|
|
634
659
|
}),
|
|
635
660
|
);
|
|
636
661
|
}
|
|
637
662
|
|
|
638
|
-
private async _buildPlatformAsync(
|
|
639
|
-
capacitorPath: string,
|
|
640
|
-
outPath: string,
|
|
641
|
-
platform: string,
|
|
642
|
-
buildType: string,
|
|
643
|
-
): Promise<void> {
|
|
644
|
-
if (platform === "android") {
|
|
645
|
-
await this._buildAndroidAsync(capacitorPath, outPath, buildType);
|
|
646
|
-
}
|
|
647
|
-
// iOS 지원 시 추가
|
|
648
|
-
}
|
|
649
|
-
|
|
650
663
|
private async _buildAndroidAsync(
|
|
651
664
|
capacitorPath: string,
|
|
652
665
|
outPath: string,
|
|
@@ -655,24 +668,16 @@ export class SdCliCapacitor {
|
|
|
655
668
|
const androidPath = path.resolve(capacitorPath, "android");
|
|
656
669
|
const targetOutPath = path.resolve(outPath, "android");
|
|
657
670
|
|
|
658
|
-
// Gradle wrapper로 빌드
|
|
659
671
|
const isBundle = this._opt.config.platform?.android?.bundle;
|
|
660
672
|
const gradleTask =
|
|
661
673
|
buildType === "release" ? (isBundle ? "bundleRelease" : "assembleRelease") : "assembleDebug";
|
|
662
674
|
|
|
663
|
-
// gradlew 실행 권한 부여 (Linux/Mac)
|
|
664
|
-
const gradlewPath = path.resolve(androidPath, "gradlew");
|
|
665
|
-
if (FsUtils.exists(gradlewPath)) {
|
|
666
|
-
try {
|
|
667
|
-
await SdCliCapacitor._execAsync("chmod", ["+x", "gradlew"], androidPath);
|
|
668
|
-
} catch {
|
|
669
|
-
// Windows에서는 무시
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
|
|
673
675
|
// Gradle 빌드 실행
|
|
674
|
-
|
|
675
|
-
|
|
676
|
+
await SdCliCapacitor._execAsync(
|
|
677
|
+
"cmd",
|
|
678
|
+
["/c", "gradlew.bat", gradleTask, "--no-daemon"],
|
|
679
|
+
androidPath,
|
|
680
|
+
);
|
|
676
681
|
|
|
677
682
|
// 빌드 결과물 복사
|
|
678
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(
|