@simplysm/sd-cli 14.0.18 → 14.0.21

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.
Files changed (124) hide show
  1. package/dist/angular/vite-angular-plugin.d.ts +2 -0
  2. package/dist/angular/vite-angular-plugin.d.ts.map +1 -1
  3. package/dist/angular/vite-angular-plugin.js +57 -28
  4. package/dist/angular/vite-angular-plugin.js.map +1 -1
  5. package/dist/angular/vite-postcss-inline-plugin.d.ts.map +1 -1
  6. package/dist/angular/vite-postcss-inline-plugin.js +4 -1
  7. package/dist/angular/vite-postcss-inline-plugin.js.map +1 -1
  8. package/dist/capacitor/capacitor-android.d.ts +16 -0
  9. package/dist/capacitor/capacitor-android.d.ts.map +1 -0
  10. package/dist/capacitor/capacitor-android.js +289 -0
  11. package/dist/capacitor/capacitor-android.js.map +1 -0
  12. package/dist/capacitor/capacitor.d.ts +0 -50
  13. package/dist/capacitor/capacitor.d.ts.map +1 -1
  14. package/dist/capacitor/capacitor.js +16 -281
  15. package/dist/capacitor/capacitor.js.map +1 -1
  16. package/dist/commands/check.js +2 -2
  17. package/dist/commands/check.js.map +1 -1
  18. package/dist/commands/device.d.ts.map +1 -1
  19. package/dist/commands/device.js +3 -2
  20. package/dist/commands/device.js.map +1 -1
  21. package/dist/commands/lint.d.ts +1 -42
  22. package/dist/commands/lint.d.ts.map +1 -1
  23. package/dist/commands/lint.js +1 -151
  24. package/dist/commands/lint.js.map +1 -1
  25. package/dist/commands/publish.d.ts.map +1 -1
  26. package/dist/commands/publish.js +2 -1
  27. package/dist/commands/publish.js.map +1 -1
  28. package/dist/commands/typecheck.d.ts +3 -40
  29. package/dist/commands/typecheck.d.ts.map +1 -1
  30. package/dist/commands/typecheck.js +3 -232
  31. package/dist/commands/typecheck.js.map +1 -1
  32. package/dist/electron/electron.d.ts.map +1 -1
  33. package/dist/electron/electron.js +20 -8
  34. package/dist/electron/electron.js.map +1 -1
  35. package/dist/orchestrators/DevWatchOrchestrator.d.ts +1 -0
  36. package/dist/orchestrators/DevWatchOrchestrator.d.ts.map +1 -1
  37. package/dist/orchestrators/DevWatchOrchestrator.js +16 -0
  38. package/dist/orchestrators/DevWatchOrchestrator.js.map +1 -1
  39. package/dist/orchestrators/TypecheckOrchestrator.d.ts +74 -0
  40. package/dist/orchestrators/TypecheckOrchestrator.d.ts.map +1 -0
  41. package/dist/orchestrators/TypecheckOrchestrator.js +285 -0
  42. package/dist/orchestrators/TypecheckOrchestrator.js.map +1 -0
  43. package/dist/sd-cli.js +6 -1
  44. package/dist/sd-cli.js.map +1 -1
  45. package/dist/utils/lint-core.d.ts +43 -0
  46. package/dist/utils/lint-core.d.ts.map +1 -0
  47. package/dist/utils/lint-core.js +154 -0
  48. package/dist/utils/lint-core.js.map +1 -0
  49. package/dist/utils/lint-utils.d.ts +1 -1
  50. package/dist/utils/lint-utils.d.ts.map +1 -1
  51. package/dist/utils/server-production-files.d.ts +22 -0
  52. package/dist/utils/server-production-files.d.ts.map +1 -0
  53. package/dist/utils/server-production-files.js +162 -0
  54. package/dist/utils/server-production-files.js.map +1 -0
  55. package/dist/utils/vite-config.d.ts +1 -1
  56. package/dist/utils/vite-config.d.ts.map +1 -1
  57. package/dist/utils/vite-config.js +76 -26
  58. package/dist/utils/vite-config.js.map +1 -1
  59. package/dist/utils/vite-scope-watch-plugin.d.ts.map +1 -1
  60. package/dist/utils/vite-scope-watch-plugin.js +7 -1
  61. package/dist/utils/vite-scope-watch-plugin.js.map +1 -1
  62. package/dist/workers/lint.worker.d.ts +1 -1
  63. package/dist/workers/lint.worker.d.ts.map +1 -1
  64. package/dist/workers/lint.worker.js +1 -1
  65. package/dist/workers/lint.worker.js.map +1 -1
  66. package/dist/workers/server-build.worker.d.ts.map +1 -1
  67. package/dist/workers/server-build.worker.js +11 -161
  68. package/dist/workers/server-build.worker.js.map +1 -1
  69. package/dist/workers/server-runtime.worker.d.ts.map +1 -1
  70. package/dist/workers/server-runtime.worker.js +15 -0
  71. package/dist/workers/server-runtime.worker.js.map +1 -1
  72. package/package.json +9 -7
  73. package/src/angular/vite-angular-plugin.ts +88 -34
  74. package/src/angular/vite-postcss-inline-plugin.ts +5 -1
  75. package/src/capacitor/capacitor-android.ts +368 -0
  76. package/src/capacitor/capacitor.ts +18 -363
  77. package/src/commands/check.ts +2 -2
  78. package/src/commands/device.ts +3 -2
  79. package/src/commands/lint.ts +1 -201
  80. package/src/commands/publish.ts +2 -1
  81. package/src/commands/typecheck.ts +7 -292
  82. package/src/electron/electron.ts +15 -8
  83. package/src/orchestrators/DevWatchOrchestrator.ts +18 -0
  84. package/src/orchestrators/TypecheckOrchestrator.ts +364 -0
  85. package/src/sd-cli.ts +6 -1
  86. package/src/utils/lint-core.ts +205 -0
  87. package/src/utils/lint-utils.ts +1 -1
  88. package/src/utils/server-production-files.ts +186 -0
  89. package/src/utils/vite-config.ts +83 -27
  90. package/src/utils/vite-scope-watch-plugin.ts +6 -1
  91. package/src/workers/lint.worker.ts +1 -1
  92. package/src/workers/server-build.worker.ts +10 -185
  93. package/src/workers/server-runtime.worker.ts +15 -0
  94. package/tests/angular/linker-disk-cache.spec.ts +31 -25
  95. package/tests/angular/vite-angular-plugin-hmr-fallback.spec.ts +15 -15
  96. package/tests/angular/vite-angular-plugin-hmr.spec.ts +9 -9
  97. package/tests/angular/vite-angular-plugin-legacy-watch.spec.ts +108 -0
  98. package/tests/angular/vite-angular-plugin-lint.spec.ts +4 -4
  99. package/tests/angular/vite-angular-plugin-scss-hmr.spec.ts +10 -15
  100. package/tests/angular/vite-angular-plugin.spec.ts +80 -15
  101. package/tests/angular/vite-postcss-inline-plugin.spec.ts +10 -0
  102. package/tests/capacitor/capacitor-android-exports.verify.md +11 -0
  103. package/tests/capacitor/capacitor-android.spec.ts +219 -0
  104. package/tests/capacitor/capacitor-build.spec.ts +17 -21
  105. package/tests/capacitor/capacitor-icon.spec.ts +17 -19
  106. package/tests/capacitor/capacitor-init.spec.ts +18 -14
  107. package/tests/capacitor/capacitor-run.spec.ts +10 -24
  108. package/tests/capacitor/capacitor-workspace.spec.ts +30 -25
  109. package/tests/commands/check.spec.ts +2 -2
  110. package/tests/commands/device.spec.ts +12 -7
  111. package/tests/commands/lint.spec.ts +33 -194
  112. package/tests/commands/publish-set.verify.md +7 -0
  113. package/tests/electron/electron-symlink-cleanup.verify.md +8 -0
  114. package/tests/electron/electron.spec.ts +27 -2
  115. package/tests/orchestrators/dist-delete-watcher.verify.md +10 -0
  116. package/tests/orchestrators/typecheck-orchestrator.spec.ts +180 -0
  117. package/tests/sd-cli-catch-all.verify.md +7 -0
  118. package/tests/utils/lint-core-import-paths.verify.md +10 -0
  119. package/tests/utils/lint-core.spec.ts +188 -0
  120. package/tests/utils/server-production-files-import-paths.verify.md +14 -0
  121. package/tests/utils/vite-config.spec.ts +255 -133
  122. package/tests/utils/vite-scope-watch-plugin.spec.ts +22 -0
  123. package/tests/workers/server-build-context-dispose.verify.md +8 -0
  124. package/tests/workers/server-runtime-worker.spec.ts +48 -4
@@ -1,12 +1,11 @@
1
1
  import fs from "node:fs";
2
2
  import { existsSync } from "node:fs";
3
3
  import path from "path";
4
- import { symlink } from "fs/promises";
5
4
  import { createRequire } from "module";
6
5
  import { cpx, fsx, pathx } from "@simplysm/core-node";
7
- import { env } from "@simplysm/core-common";
8
6
  import { consola, LogLevels } from "consola";
9
7
  import type { NpmConfig, SdCapacitorConfig } from "../sd-config.types.js";
8
+ import { configureAndroid, findAndroidSdk, findJava21 } from "./capacitor-android.js";
10
9
 
11
10
  /**
12
11
  * 설정 검증 에러
@@ -127,7 +126,7 @@ export class Capacitor {
127
126
  // 5. Android 네이티브 설정 구성
128
127
  if (this._platforms.includes("android")) {
129
128
  Capacitor._logger.debug("Android 네이티브 설정 시작");
130
- await this._configureAndroid();
129
+ await configureAndroid(this._capPath, this._config, this._npmConfig);
131
130
  Capacitor._logger.debug("Android 네이티브 설정 완료");
132
131
  }
133
132
 
@@ -215,7 +214,7 @@ export class Capacitor {
215
214
  */
216
215
  private async _validateTools(): Promise<void> {
217
216
  // Android SDK 확인
218
- const sdkPath = await this._findAndroidSdk();
217
+ const sdkPath = await findAndroidSdk();
219
218
  if (sdkPath == null) {
220
219
  throw new Error(
221
220
  "Android SDK를 찾을 수 없습니다.\n" +
@@ -227,7 +226,7 @@ export class Capacitor {
227
226
 
228
227
  // Java 확인 (android 플랫폼인 경우에만)
229
228
  if (this._platforms.includes("android")) {
230
- const javaPath = await this._findJava21();
229
+ const javaPath = await findJava21();
231
230
  if (javaPath == null) {
232
231
  Capacitor._logger.warn(
233
232
  "Java 21을 찾을 수 없습니다. Gradle이 내장 JDK를 사용하거나 빌드가 실패할 수 있습니다.",
@@ -247,14 +246,12 @@ export class Capacitor {
247
246
  */
248
247
  private async _initCap(): Promise<boolean> {
249
248
  Capacitor._logger.debug("package.json 설정 시작");
250
- const { depChanged, workspacePlugins } = await this._setupNpmConf();
249
+ const depChanged = await this._setupNpmConf();
251
250
  const nodeModulesExists = await fsx.exists(pathx.posixResolve(this._capPath, "node_modules"));
252
251
  Capacitor._logger.debug(`depChanged: ${depChanged}, nodeModulesExists: ${nodeModulesExists}`);
253
252
 
254
253
  if (!depChanged && nodeModulesExists) {
255
- // 의존성 미변경이어도 workspace 플러그인 symlink는 항상 갱신
256
- Capacitor._logger.debug("의존성 변경 없음, workspace 플러그인 symlink만 갱신");
257
- await this._linkWorkspacePlugins(workspacePlugins);
254
+ Capacitor._logger.debug("의존성 변경 없음");
258
255
  return false;
259
256
  }
260
257
 
@@ -270,11 +267,6 @@ export class Capacitor {
270
267
  await this._exec("pnpm", ["approve-builds", "--all"], this._capPath);
271
268
  Capacitor._logger.debug("pnpm install 완료");
272
269
 
273
- // workspace 플러그인 symlink
274
- Capacitor._logger.debug("workspace 플러그인 symlink 시작");
275
- await this._linkWorkspacePlugins(workspacePlugins);
276
- Capacitor._logger.debug("workspace 플러그인 symlink 완료");
277
-
278
270
  // 멱등성: capacitor.config.ts가 없을 때만 cap init 실행
279
271
  const configPath = pathx.posixResolve(this._capPath, "capacitor.config.ts");
280
272
  if (!(await fsx.exists(configPath))) {
@@ -300,7 +292,7 @@ export class Capacitor {
300
292
  /**
301
293
  * package.json 설정
302
294
  */
303
- private async _setupNpmConf(): Promise<{ depChanged: boolean; workspacePlugins: string[] }> {
295
+ private async _setupNpmConf(): Promise<boolean> {
304
296
  const projNpmConfigPath = pathx.posixResolve(this._findWorkspaceRoot(), "package.json");
305
297
 
306
298
  // 루트 package.json 존재 확인
@@ -358,16 +350,17 @@ export class Capacitor {
358
350
  }
359
351
  }
360
352
 
361
- // 새 플러그인 추가 (workspace:* 플러그인은 분리)
362
- const workspacePlugins: string[] = [];
353
+ // 새 플러그인 추가
354
+ const pkgRequire = createRequire(pathx.posixResolve(this._pkgPath, "package.json"));
363
355
  for (const plugin of usePlugins) {
364
356
  const version = mainDeps[plugin] ?? "*";
365
357
  if (typeof version === "string" && version.startsWith("workspace:")) {
366
- // workspace 플러그인은 package.json에 추가하지 않고 symlink로 처리
367
- workspacePlugins.push(plugin);
368
- // 이전에 추가되어 있었으면 제거
369
- delete capNpmConf.dependencies[plugin];
370
- Capacitor._logger.debug(`workspace 플러그인 (symlink 예정): ${plugin}`);
358
+ // workspace 플러그인은 link: 프로토콜로 실제 경로를 지정
359
+ const pluginPkgJsonPath = pkgRequire.resolve(`${plugin}/package.json`);
360
+ const pluginDir = path.dirname(pluginPkgJsonPath);
361
+ const relativePath = path.relative(this._capPath, pluginDir).replace(/\\/g, "/");
362
+ capNpmConf.dependencies[plugin] = `link:${relativePath}`;
363
+ Capacitor._logger.debug(`workspace 플러그인 (link): ${plugin} → ${relativePath}`);
371
364
  } else if (!(plugin in capNpmConf.dependencies)) {
372
365
  capNpmConf.dependencies[plugin] = version;
373
366
  Capacitor._logger.debug(`플러그인 추가: ${plugin}@${version}`);
@@ -388,12 +381,11 @@ export class Capacitor {
388
381
  await fsx.writeJson(capNpmConfPath, capNpmConf, { space: 2 });
389
382
 
390
383
  // 의존성 변경 여부 확인
391
- const isChanged =
384
+ return (
392
385
  orgCapNpmConf.volta !== capNpmConf.volta ||
393
386
  JSON.stringify(orgCapNpmConf.dependencies) !== JSON.stringify(capNpmConf.dependencies) ||
394
- JSON.stringify(orgCapNpmConf.devDependencies) !== JSON.stringify(capNpmConf.devDependencies);
395
-
396
- return { depChanged: isChanged, workspacePlugins };
387
+ JSON.stringify(orgCapNpmConf.devDependencies) !== JSON.stringify(capNpmConf.devDependencies)
388
+ );
397
389
  }
398
390
 
399
391
  /**
@@ -517,319 +509,6 @@ export default config;
517
509
 
518
510
  //#endregion
519
511
 
520
- //#region Private - Android 설정
521
-
522
- /**
523
- * Android 네이티브 설정 구성
524
- */
525
- private async _configureAndroid(): Promise<void> {
526
- const androidPath = pathx.posixResolve(this._capPath, "android");
527
-
528
- // Android 디렉토리 존재 확인
529
- if (!(await fsx.exists(androidPath))) {
530
- throw new Error(`Android 프로젝트 디렉토리를 찾을 수 없습니다: ${androidPath}`);
531
- }
532
-
533
- Capacitor._logger.debug("JAVA_HOME 설정 시작");
534
- await this._configureAndroidJavaHomePath(androidPath);
535
- Capacitor._logger.debug("JAVA_HOME 설정 완료");
536
-
537
- Capacitor._logger.debug("Android SDK 경로 설정 시작");
538
- await this._configureAndroidSdkPath(androidPath);
539
- Capacitor._logger.debug("Android SDK 경로 설정 완료");
540
-
541
- Capacitor._logger.debug("AndroidManifest.xml 설정 시작");
542
- await this._configureAndroidManifest(androidPath);
543
- Capacitor._logger.debug("AndroidManifest.xml 설정 완료");
544
-
545
- Capacitor._logger.debug("루트 build.gradle Kotlin 플러그인 설정 시작");
546
- await this._configureAndroidRootBuildGradle(androidPath);
547
- Capacitor._logger.debug("루트 build.gradle Kotlin 플러그인 설정 완료");
548
-
549
- Capacitor._logger.debug("build.gradle 설정 시작");
550
- await this._configureAndroidBuildGradle(androidPath);
551
- Capacitor._logger.debug("build.gradle 설정 완료");
552
-
553
- Capacitor._logger.debug("styles.xml 설정 시작");
554
- await this._configureAndroidStyles(androidPath);
555
- Capacitor._logger.debug("styles.xml 설정 완료");
556
- }
557
-
558
- /**
559
- * JAVA_HOME 경로 설정 (gradle.properties)
560
- */
561
- private async _configureAndroidJavaHomePath(androidPath: string): Promise<void> {
562
- const gradlePropsPath = pathx.posixResolve(androidPath, "gradle.properties");
563
-
564
- if (!(await fsx.exists(gradlePropsPath))) {
565
- Capacitor._logger.warn(`gradle.properties 파일을 찾을 수 없습니다: ${gradlePropsPath}`);
566
- return;
567
- }
568
-
569
- let content = await fsx.read(gradlePropsPath);
570
-
571
- const java21Path = await this._findJava21();
572
- if (java21Path != null && !content.includes("org.gradle.java.home")) {
573
- // Windows 경로 이스케이프
574
- const escapedPath = java21Path.replace(/\\/g, "\\\\");
575
- content += `\norg.gradle.java.home=${escapedPath}\n`;
576
- await fsx.write(gradlePropsPath, content);
577
- }
578
- }
579
-
580
- /**
581
- * Java 21 경로 자동 탐색
582
- */
583
- private async _findJava21(): Promise<string | undefined> {
584
- const patterns = [
585
- "C:/Program Files/Amazon Corretto/jdk21*",
586
- "C:/Program Files/Eclipse Adoptium/jdk-21*",
587
- "C:/Program Files/Java/jdk-21*",
588
- "C:/Program Files/Microsoft/jdk-21*",
589
- "/usr/lib/jvm/java-21*",
590
- "/usr/lib/jvm/temurin-21*",
591
- ];
592
-
593
- for (const pattern of patterns) {
594
- const matches = await fsx.glob(pattern);
595
- if (matches.length > 0) {
596
- return matches.sort().at(-1);
597
- }
598
- }
599
-
600
- return undefined;
601
- }
602
-
603
- /**
604
- * Android SDK 경로 설정 (local.properties)
605
- */
606
- private async _configureAndroidSdkPath(androidPath: string): Promise<void> {
607
- const localPropsPath = pathx.posixResolve(androidPath, "local.properties");
608
-
609
- const sdkPath = await this._findAndroidSdk();
610
- if (sdkPath != null) {
611
- // Gradle 호환: 항상 forward slash 사용
612
- await fsx.write(localPropsPath, `sdk.dir=${pathx.posix(sdkPath)}\n`);
613
- } else {
614
- throw new Error(
615
- "Android SDK를 찾을 수 없습니다.\n" +
616
- "1. Android Studio를 설치하거나\n" +
617
- "2. ANDROID_HOME 또는 ANDROID_SDK_ROOT 환경 변수를 설정하세요.",
618
- );
619
- }
620
- }
621
-
622
- /**
623
- * Android SDK 경로 탐색
624
- */
625
- private async _findAndroidSdk(): Promise<string | undefined> {
626
- const androidHome =
627
- env("ANDROID_HOME") ??
628
- env("ANDROID_SDK_ROOT");
629
- if (androidHome != null && (await fsx.exists(androidHome))) {
630
- return androidHome;
631
- }
632
-
633
- const candidates = [
634
- pathx.posixResolve(env("LOCALAPPDATA") ?? "", "Android/Sdk"),
635
- pathx.posixResolve(env("HOME") ?? "", "Android/Sdk"),
636
- "C:/Program Files/Android/Sdk",
637
- "C:/Android/Sdk",
638
- ];
639
-
640
- for (const candidate of candidates) {
641
- if (await fsx.exists(candidate)) {
642
- return candidate;
643
- }
644
- }
645
-
646
- return undefined;
647
- }
648
-
649
- /**
650
- * AndroidManifest.xml 설정
651
- *
652
- * 주의: Capacitor가 생성하는 초기 XML 포맷에 의존하는 정규식 기반 수정.
653
- * XML 구조가 변경되면 정규식이 실패할 수 있음.
654
- */
655
- private async _configureAndroidManifest(androidPath: string): Promise<void> {
656
- const manifestPath = pathx.posixResolve(androidPath, "app/src/main/AndroidManifest.xml");
657
-
658
- if (!(await fsx.exists(manifestPath))) {
659
- throw new Error(`AndroidManifest.xml 파일을 찾을 수 없습니다: ${manifestPath}`);
660
- }
661
-
662
- let content = await fsx.read(manifestPath);
663
-
664
- // usesCleartextTraffic 설정
665
- if (!content.includes("android:usesCleartextTraffic")) {
666
- content = content.replace("<application", '<application android:usesCleartextTraffic="true"');
667
- }
668
-
669
- // 추가 권한 설정
670
- const permissions = this._config.platform?.android?.permissions ?? [];
671
- for (const perm of permissions) {
672
- const permTag = `<uses-permission android:name="android.permission.${perm.name}"`;
673
- if (!content.includes(permTag)) {
674
- const maxSdkAttr =
675
- perm.maxSdkVersion != null ? ` android:maxSdkVersion="${perm.maxSdkVersion}"` : "";
676
- const ignoreAttr = perm.ignore != null ? ` tools:ignore="${perm.ignore}"` : "";
677
- const permLine = ` ${permTag}${maxSdkAttr}${ignoreAttr} />\n`;
678
-
679
- if (perm.ignore != null && !content.includes("xmlns:tools=")) {
680
- content = content.replace(
681
- "<manifest xmlns:android",
682
- '<manifest xmlns:tools="http://schemas.android.com/tools" xmlns:android',
683
- );
684
- }
685
-
686
- content = content.replace("</manifest>", `${permLine}</manifest>`);
687
- }
688
- }
689
-
690
- // 추가 application 속성 설정
691
- const appConfig = this._config.platform?.android?.config;
692
- if (appConfig) {
693
- for (const [key, value] of Object.entries(appConfig)) {
694
- const attr = `android:${key}="${value}"`;
695
- if (!content.includes(`android:${key}=`)) {
696
- content = content.replace("<application", `<application ${attr}`);
697
- }
698
- }
699
- }
700
-
701
- // intentFilters 설정
702
- const intentFilters = this._config.platform?.android?.intentFilters ?? [];
703
- for (const filter of intentFilters) {
704
- const filterKey = filter.action ?? filter.category ?? "";
705
- if (filterKey && !content.includes(filterKey)) {
706
- const actionLine = filter.action != null ? `<action android:name="${filter.action}"/>` : "";
707
- const categoryLine =
708
- filter.category != null ? `<category android:name="${filter.category}"/>` : "";
709
-
710
- content = content.replace(
711
- /(<activity[\s\S]*?android:name="\.MainActivity"[\s\S]*?>)/,
712
- `$1
713
- <intent-filter>
714
- ${actionLine}
715
- ${categoryLine}
716
- </intent-filter>`,
717
- );
718
- }
719
- }
720
-
721
- await fsx.write(manifestPath, content);
722
- }
723
-
724
- /**
725
- * 루트 build.gradle에 Kotlin Gradle 플러그인 classpath 추가
726
- */
727
- private async _configureAndroidRootBuildGradle(androidPath: string): Promise<void> {
728
- const rootBuildGradlePath = pathx.posixResolve(androidPath, "build.gradle");
729
-
730
- if (!(await fsx.exists(rootBuildGradlePath))) {
731
- Capacitor._logger.warn(`루트 build.gradle 파일을 찾을 수 없습니다: ${rootBuildGradlePath}`);
732
- return;
733
- }
734
-
735
- let content = await fsx.read(rootBuildGradlePath);
736
-
737
- if (!content.includes("kotlin-gradle-plugin")) {
738
- content = content.replace(
739
- /classpath 'com\.android\.tools\.build:gradle:[^']+'/,
740
- `$&\n classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.20'`,
741
- );
742
- await fsx.write(rootBuildGradlePath, content);
743
- }
744
- }
745
-
746
- /**
747
- * build.gradle 수정 (서명 설정 제외)
748
- */
749
- private async _configureAndroidBuildGradle(androidPath: string): Promise<void> {
750
- const buildGradlePath = pathx.posixResolve(androidPath, "app/build.gradle");
751
-
752
- if (!(await fsx.exists(buildGradlePath))) {
753
- throw new Error(`build.gradle 파일을 찾을 수 없습니다: ${buildGradlePath}`);
754
- }
755
-
756
- let content = await fsx.read(buildGradlePath);
757
-
758
- // versionName, versionCode 설정
759
- const version = this._npmConfig.version;
760
- const cleanVersion = version.replace(/-.*$/, "");
761
- const versionParts = cleanVersion.split(".");
762
- const versionCode =
763
- parseInt(versionParts[0] ?? "0") * 10000 +
764
- parseInt(versionParts[1] ?? "0") * 100 +
765
- parseInt(versionParts[2] ?? "0");
766
-
767
- content = content.replace(/versionCode \d+/, `versionCode ${versionCode}`);
768
- content = content.replace(/versionName "[^"]+"/, `versionName "${version}"`);
769
-
770
- // SDK 버전 설정
771
- if (this._config.platform?.android?.sdkVersion != null) {
772
- const sdkVersion = this._config.platform.android.sdkVersion;
773
- content = content.replace(/minSdkVersion .+/, `minSdkVersion ${sdkVersion}`);
774
- content = content.replace(/targetSdkVersion .+/, `targetSdkVersion ${sdkVersion}`);
775
- } else {
776
- content = content.replace(/minSdkVersion .+/, `minSdkVersion rootProject.ext.minSdkVersion`);
777
- content = content.replace(
778
- /targetSdkVersion .+/,
779
- `targetSdkVersion rootProject.ext.targetSdkVersion`,
780
- );
781
- }
782
-
783
- await fsx.write(buildGradlePath, content);
784
- }
785
-
786
- /**
787
- * styles.xml의 스플래시 테마 수정
788
- *
789
- * 1. Theme.SplashScreen parent → Theme.AppCompat.DayNight.NoActionBar
790
- * Theme.SplashScreen은 android:windowBackground에 compat_splash_screen을 설정하여
791
- * android:background(@drawable/splash)와 이중 표시를 발생시킨다.
792
- * installSplashScreen()을 호출하지 않으므로 Theme.SplashScreen 기능이 불필요하다.
793
- *
794
- * 2. android:background → android:windowBackground
795
- * android:background는 View 레벨 속성으로 AppCompat 뷰 계층의 여러 View에 상속되어
796
- * 동일한 splash 로고가 다중 레이어에 중복 렌더링된다.
797
- * android:windowBackground는 Window의 DecorView에만 적용되어 단일 렌더링을 보장한다.
798
- */
799
- private async _configureAndroidStyles(androidPath: string): Promise<void> {
800
- const stylesPath = pathx.posixResolve(androidPath, "app/src/main/res/values/styles.xml");
801
-
802
- if (!(await fsx.exists(stylesPath))) {
803
- Capacitor._logger.warn(`styles.xml 파일을 찾을 수 없습니다: ${stylesPath}`);
804
- return;
805
- }
806
-
807
- let content = await fsx.read(stylesPath);
808
- let changed = false;
809
-
810
- if (content.includes('parent="Theme.SplashScreen"')) {
811
- content = content.replace(
812
- 'parent="Theme.SplashScreen"',
813
- 'parent="Theme.AppCompat.DayNight.NoActionBar"',
814
- );
815
- changed = true;
816
- }
817
-
818
- if (content.includes('"android:background">@drawable/splash')) {
819
- content = content.replace(
820
- '"android:background">@drawable/splash',
821
- '"android:windowBackground">@drawable/splash',
822
- );
823
- changed = true;
824
- }
825
-
826
- if (changed) {
827
- await fsx.write(stylesPath, content);
828
- }
829
- }
830
-
831
- //#endregion
832
-
833
512
  //#region Public — 기기 실행
834
513
 
835
514
  /**
@@ -1087,30 +766,6 @@ export default config;
1087
766
  * workspace:* 플러그인을 .capacitor/node_modules/에 symlink로 연결한다.
1088
767
  * cap sync는 플러그인의 android/ 네이티브 코드만 필요하므로 JS 의존성 resolve 불필요.
1089
768
  */
1090
- private async _linkWorkspacePlugins(plugins: string[]): Promise<void> {
1091
- if (plugins.length === 0) return;
1092
-
1093
- const require = createRequire(pathx.posixResolve(this._pkgPath, "package.json"));
1094
-
1095
- for (const plugin of plugins) {
1096
- const pluginPkgJsonPath = require.resolve(`${plugin}/package.json`);
1097
- const pluginDir = path.dirname(pluginPkgJsonPath);
1098
-
1099
- const linkPath = pathx.posixResolve(this._capPath, "node_modules", ...plugin.split("/"));
1100
-
1101
- // scope 디렉토리 생성 (예: @simplysm/)
1102
- await fsx.mkdir(path.dirname(linkPath));
1103
-
1104
- // 기존 symlink가 있으면 삭제
1105
- if (await fsx.exists(linkPath)) {
1106
- await fsx.rm(linkPath);
1107
- }
1108
-
1109
- await symlink(pluginDir, linkPath, "junction");
1110
- Capacitor._logger.debug(`workspace 플러그인 symlink: ${plugin} → ${pluginDir}`);
1111
- }
1112
- }
1113
-
1114
769
  /**
1115
770
  * pnpm-workspace.yaml이 있는 워크스페이스 루트 디렉토리를 찾는다.
1116
771
  */
@@ -1,7 +1,7 @@
1
1
  import { cpx } from "@simplysm/core-node";
2
2
  import { err as errNs } from "@simplysm/core-common";
3
- import { executeTypecheck, type TypecheckResult } from "./typecheck";
4
- import { executeLint, type LintResult } from "./lint";
3
+ import { executeTypecheck, type TypecheckResult } from "../orchestrators/TypecheckOrchestrator";
4
+ import { executeLint, type LintResult } from "../utils/lint-core";
5
5
  import { consola } from "consola";
6
6
  import { validateTargets, discoverWorkspacePackages } from "../utils/package-utils";
7
7
  import { runLintInWorker } from "../utils/lint-utils";
@@ -64,8 +64,9 @@ export async function runDevice(options: DeviceOptions): Promise<void> {
64
64
  if (typeof clientConfig.server === "number") {
65
65
  serverUrl = `http://localhost:${clientConfig.server}/${targetName}/`;
66
66
  } else {
67
- // server가 패키지명(string)인 경우: .dev-port 파일에서 포트 자동 탐지
68
- const portFile = path.join(pkgDir, "dist", ".dev-port");
67
+ // server가 패키지명(string)인 경우: 서버 패키지의 .dev-port 파일에서 포트 자동 탐지
68
+ const serverPkgDir = pathx.posixResolve(cwd, "packages", clientConfig.server);
69
+ const portFile = path.join(serverPkgDir, "dist", ".dev-port");
69
70
  let portStr: string;
70
71
  try {
71
72
  portStr = fs.readFileSync(portFile, "utf-8").trim();