@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.
@@ -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 _ICON_DIR_PATH;
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._ICON_DIR_PATH = "resources";
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
- if (FsUtils.exists(path.resolve(capacitorPath, "www"))) {
50
+ const wwwPath = path.resolve(capacitorPath, "www");
51
+ if (FsUtils.exists(wwwPath)) {
51
52
  SdCliCapacitor._logger.log("이미 생성되어있는 '.capacitor'를 사용합니다.");
52
53
  // 버전 동기화
53
- await this._syncVersionAsync(capacitorPath);
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
- await FsUtils.mkdirsAsync(capacitorPath);
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._ICON_DIR_PATH);
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
- // logo.png: 1024x1024 (Easy Mode)
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*.png"));
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 = `../${this._KEYSTORE_FILE_NAME}`;
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 "${signConfig.storePassword}"
429
- keyAlias "${signConfig.alias}"
430
- keyPassword "${signConfig.password}"
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*\{)/, `$1${signingConfigsBlock}`);
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
- await this._buildPlatformAsync(capacitorPath, outPath, platform, buildType);
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
- const gradleCmd = process.platform === "win32" ? "gradlew.bat" : "./gradlew";
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(path.resolve(this._opt.pkgPath, "dist")),
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.12",
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.12",
21
- "@simplysm/sd-core-node": "12.16.12",
22
- "@simplysm/sd-service-server": "12.16.12",
23
- "@simplysm/sd-storage": "12.16.12",
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 _ICON_DIR_PATH = "resources";
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
- if (FsUtils.exists(path.resolve(capacitorPath, "www"))) {
70
+ const wwwPath = path.resolve(capacitorPath, "www");
71
+
72
+ if (FsUtils.exists(wwwPath)) {
71
73
  SdCliCapacitor._logger.log("이미 생성되어있는 '.capacitor'를 사용합니다.");
72
74
 
73
75
  // 버전 동기화
74
- await this._syncVersionAsync(capacitorPath);
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(capacitorPath);
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._ICON_DIR_PATH);
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
- // logo.png: 1024x1024 (Easy Mode)
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*.png"));
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 = `../${this._KEYSTORE_FILE_NAME}`;
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 "${signConfig.storePassword}"
574
- keyAlias "${signConfig.alias}"
575
- keyPassword "${signConfig.password}"
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(/(android\s*\{)/, `$1${signingConfigsBlock}`);
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
- await this._buildPlatformAsync(capacitorPath, outPath, platform, buildType);
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
- const gradleCmd = process.platform === "win32" ? "gradlew.bat" : "./gradlew";
675
- await SdCliCapacitor._execAsync(gradleCmd, [gradleTask, "--no-daemon"], androidPath);
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(path.resolve(this._opt.pkgPath, "dist")),
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(