@simplysm/sd-cli 14.0.19 → 14.0.22
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/angular/vite-postcss-inline-plugin.d.ts.map +1 -1
- package/dist/angular/vite-postcss-inline-plugin.js +4 -1
- package/dist/angular/vite-postcss-inline-plugin.js.map +1 -1
- package/dist/capacitor/capacitor-android.d.ts +16 -0
- package/dist/capacitor/capacitor-android.d.ts.map +1 -0
- package/dist/capacitor/capacitor-android.js +289 -0
- package/dist/capacitor/capacitor-android.js.map +1 -0
- package/dist/capacitor/capacitor.d.ts +0 -49
- package/dist/capacitor/capacitor.d.ts.map +1 -1
- package/dist/capacitor/capacitor.js +4 -244
- package/dist/capacitor/capacitor.js.map +1 -1
- package/dist/commands/check.js +2 -2
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/lint.d.ts +1 -42
- package/dist/commands/lint.d.ts.map +1 -1
- package/dist/commands/lint.js +1 -151
- package/dist/commands/lint.js.map +1 -1
- package/dist/commands/publish.d.ts.map +1 -1
- package/dist/commands/publish.js +2 -1
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/typecheck.d.ts +3 -40
- package/dist/commands/typecheck.d.ts.map +1 -1
- package/dist/commands/typecheck.js +3 -232
- package/dist/commands/typecheck.js.map +1 -1
- package/dist/electron/electron.js +11 -4
- package/dist/electron/electron.js.map +1 -1
- package/dist/engines/ViteEngine.js +1 -1
- package/dist/engines/ViteEngine.js.map +1 -1
- package/dist/engines/types.d.ts +2 -0
- package/dist/engines/types.d.ts.map +1 -1
- package/dist/infra/ResultCollector.d.ts +2 -2
- package/dist/infra/ResultCollector.d.ts.map +1 -1
- package/dist/infra/ResultCollector.js +1 -1
- package/dist/orchestrators/DevWatchOrchestrator.d.ts +2 -1
- package/dist/orchestrators/DevWatchOrchestrator.d.ts.map +1 -1
- package/dist/orchestrators/DevWatchOrchestrator.js +28 -16
- package/dist/orchestrators/DevWatchOrchestrator.js.map +1 -1
- package/dist/orchestrators/TypecheckOrchestrator.d.ts +74 -0
- package/dist/orchestrators/TypecheckOrchestrator.d.ts.map +1 -0
- package/dist/orchestrators/TypecheckOrchestrator.js +285 -0
- package/dist/orchestrators/TypecheckOrchestrator.js.map +1 -0
- package/dist/sd-cli.js +6 -1
- package/dist/sd-cli.js.map +1 -1
- package/dist/utils/lint-core.d.ts +43 -0
- package/dist/utils/lint-core.d.ts.map +1 -0
- package/dist/utils/lint-core.js +154 -0
- package/dist/utils/lint-core.js.map +1 -0
- package/dist/utils/lint-utils.d.ts +1 -1
- package/dist/utils/lint-utils.d.ts.map +1 -1
- package/dist/utils/output-utils.d.ts +2 -2
- package/dist/utils/output-utils.d.ts.map +1 -1
- package/dist/utils/output-utils.js.map +1 -1
- package/dist/utils/server-production-files.d.ts +22 -0
- package/dist/utils/server-production-files.d.ts.map +1 -0
- package/dist/utils/server-production-files.js +162 -0
- package/dist/utils/server-production-files.js.map +1 -0
- package/dist/utils/vite-config.js.map +1 -1
- package/dist/workers/client.worker.d.ts.map +1 -1
- package/dist/workers/client.worker.js +7 -1
- package/dist/workers/client.worker.js.map +1 -1
- package/dist/workers/lint.worker.d.ts +1 -1
- package/dist/workers/lint.worker.d.ts.map +1 -1
- package/dist/workers/lint.worker.js +1 -1
- package/dist/workers/lint.worker.js.map +1 -1
- package/dist/workers/server-build.worker.d.ts.map +1 -1
- package/dist/workers/server-build.worker.js +12 -161
- package/dist/workers/server-build.worker.js.map +1 -1
- package/package.json +4 -4
- package/src/angular/vite-postcss-inline-plugin.ts +5 -1
- package/src/capacitor/capacitor-android.ts +368 -0
- package/src/capacitor/capacitor.ts +4 -317
- package/src/commands/check.ts +2 -2
- package/src/commands/lint.ts +1 -201
- package/src/commands/publish.ts +2 -1
- package/src/commands/typecheck.ts +7 -292
- package/src/electron/electron.ts +4 -4
- package/src/engines/ViteEngine.ts +1 -1
- package/src/engines/types.ts +3 -0
- package/src/infra/ResultCollector.ts +2 -2
- package/src/orchestrators/DevWatchOrchestrator.ts +35 -20
- package/src/orchestrators/TypecheckOrchestrator.ts +364 -0
- package/src/sd-cli.ts +6 -1
- package/src/utils/lint-core.ts +205 -0
- package/src/utils/lint-utils.ts +1 -1
- package/src/utils/output-utils.ts +3 -3
- package/src/utils/server-production-files.ts +186 -0
- package/src/utils/vite-config.ts +1 -1
- package/src/workers/client.worker.ts +7 -1
- package/src/workers/lint.worker.ts +1 -1
- package/src/workers/server-build.worker.ts +11 -185
- package/tests/angular/vite-postcss-inline-plugin.spec.ts +10 -0
- package/tests/capacitor/capacitor-android-exports.verify.md +11 -0
- package/tests/capacitor/capacitor-android.spec.ts +219 -0
- package/tests/capacitor/capacitor-build.spec.ts +17 -21
- package/tests/capacitor/capacitor-icon.spec.ts +17 -19
- package/tests/capacitor/capacitor-init.spec.ts +18 -14
- package/tests/capacitor/capacitor-run.spec.ts +10 -24
- package/tests/capacitor/capacitor-workspace.spec.ts +10 -15
- package/tests/commands/check.spec.ts +2 -2
- package/tests/commands/lint.spec.ts +33 -194
- package/tests/commands/publish-set.verify.md +7 -0
- package/tests/electron/electron-symlink-cleanup.verify.md +8 -0
- package/tests/engines/vite-engine.spec.ts +29 -0
- package/tests/infra/result-collector.spec.ts +11 -0
- package/tests/orchestrators/dev-watch-orchestrator.spec.ts +70 -0
- package/tests/orchestrators/dist-delete-watcher.verify.md +10 -0
- package/tests/orchestrators/typecheck-orchestrator.spec.ts +180 -0
- package/tests/sd-cli-catch-all.verify.md +7 -0
- package/tests/utils/lint-core-import-paths.verify.md +10 -0
- package/tests/utils/lint-core.spec.ts +188 -0
- package/tests/utils/server-production-files-import-paths.verify.md +14 -0
- package/tests/workers/client-worker.spec.ts +92 -0
- package/tests/workers/server-build-context-dispose.verify.md +8 -0
- package/tests/workers/server-build-worker.spec.ts +39 -0
|
@@ -3,9 +3,9 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { createRequire } from "module";
|
|
5
5
|
import { cpx, fsx, pathx } from "@simplysm/core-node";
|
|
6
|
-
import { env } from "@simplysm/core-common";
|
|
7
6
|
import { consola, LogLevels } from "consola";
|
|
8
7
|
import type { NpmConfig, SdCapacitorConfig } from "../sd-config.types.js";
|
|
8
|
+
import { configureAndroid, findAndroidSdk, findJava21 } from "./capacitor-android.js";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* 설정 검증 에러
|
|
@@ -126,7 +126,7 @@ export class Capacitor {
|
|
|
126
126
|
// 5. Android 네이티브 설정 구성
|
|
127
127
|
if (this._platforms.includes("android")) {
|
|
128
128
|
Capacitor._logger.debug("Android 네이티브 설정 시작");
|
|
129
|
-
await this.
|
|
129
|
+
await configureAndroid(this._capPath, this._config, this._npmConfig);
|
|
130
130
|
Capacitor._logger.debug("Android 네이티브 설정 완료");
|
|
131
131
|
}
|
|
132
132
|
|
|
@@ -214,7 +214,7 @@ export class Capacitor {
|
|
|
214
214
|
*/
|
|
215
215
|
private async _validateTools(): Promise<void> {
|
|
216
216
|
// Android SDK 확인
|
|
217
|
-
const sdkPath = await
|
|
217
|
+
const sdkPath = await findAndroidSdk();
|
|
218
218
|
if (sdkPath == null) {
|
|
219
219
|
throw new Error(
|
|
220
220
|
"Android SDK를 찾을 수 없습니다.\n" +
|
|
@@ -226,7 +226,7 @@ export class Capacitor {
|
|
|
226
226
|
|
|
227
227
|
// Java 확인 (android 플랫폼인 경우에만)
|
|
228
228
|
if (this._platforms.includes("android")) {
|
|
229
|
-
const javaPath = await
|
|
229
|
+
const javaPath = await findJava21();
|
|
230
230
|
if (javaPath == null) {
|
|
231
231
|
Capacitor._logger.warn(
|
|
232
232
|
"Java 21을 찾을 수 없습니다. Gradle이 내장 JDK를 사용하거나 빌드가 실패할 수 있습니다.",
|
|
@@ -509,319 +509,6 @@ export default config;
|
|
|
509
509
|
|
|
510
510
|
//#endregion
|
|
511
511
|
|
|
512
|
-
//#region Private - Android 설정
|
|
513
|
-
|
|
514
|
-
/**
|
|
515
|
-
* Android 네이티브 설정 구성
|
|
516
|
-
*/
|
|
517
|
-
private async _configureAndroid(): Promise<void> {
|
|
518
|
-
const androidPath = pathx.posixResolve(this._capPath, "android");
|
|
519
|
-
|
|
520
|
-
// Android 디렉토리 존재 확인
|
|
521
|
-
if (!(await fsx.exists(androidPath))) {
|
|
522
|
-
throw new Error(`Android 프로젝트 디렉토리를 찾을 수 없습니다: ${androidPath}`);
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
Capacitor._logger.debug("JAVA_HOME 설정 시작");
|
|
526
|
-
await this._configureAndroidJavaHomePath(androidPath);
|
|
527
|
-
Capacitor._logger.debug("JAVA_HOME 설정 완료");
|
|
528
|
-
|
|
529
|
-
Capacitor._logger.debug("Android SDK 경로 설정 시작");
|
|
530
|
-
await this._configureAndroidSdkPath(androidPath);
|
|
531
|
-
Capacitor._logger.debug("Android SDK 경로 설정 완료");
|
|
532
|
-
|
|
533
|
-
Capacitor._logger.debug("AndroidManifest.xml 설정 시작");
|
|
534
|
-
await this._configureAndroidManifest(androidPath);
|
|
535
|
-
Capacitor._logger.debug("AndroidManifest.xml 설정 완료");
|
|
536
|
-
|
|
537
|
-
Capacitor._logger.debug("루트 build.gradle Kotlin 플러그인 설정 시작");
|
|
538
|
-
await this._configureAndroidRootBuildGradle(androidPath);
|
|
539
|
-
Capacitor._logger.debug("루트 build.gradle Kotlin 플러그인 설정 완료");
|
|
540
|
-
|
|
541
|
-
Capacitor._logger.debug("build.gradle 설정 시작");
|
|
542
|
-
await this._configureAndroidBuildGradle(androidPath);
|
|
543
|
-
Capacitor._logger.debug("build.gradle 설정 완료");
|
|
544
|
-
|
|
545
|
-
Capacitor._logger.debug("styles.xml 설정 시작");
|
|
546
|
-
await this._configureAndroidStyles(androidPath);
|
|
547
|
-
Capacitor._logger.debug("styles.xml 설정 완료");
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
/**
|
|
551
|
-
* JAVA_HOME 경로 설정 (gradle.properties)
|
|
552
|
-
*/
|
|
553
|
-
private async _configureAndroidJavaHomePath(androidPath: string): Promise<void> {
|
|
554
|
-
const gradlePropsPath = pathx.posixResolve(androidPath, "gradle.properties");
|
|
555
|
-
|
|
556
|
-
if (!(await fsx.exists(gradlePropsPath))) {
|
|
557
|
-
Capacitor._logger.warn(`gradle.properties 파일을 찾을 수 없습니다: ${gradlePropsPath}`);
|
|
558
|
-
return;
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
let content = await fsx.read(gradlePropsPath);
|
|
562
|
-
|
|
563
|
-
const java21Path = await this._findJava21();
|
|
564
|
-
if (java21Path != null && !content.includes("org.gradle.java.home")) {
|
|
565
|
-
// Windows 경로 이스케이프
|
|
566
|
-
const escapedPath = java21Path.replace(/\\/g, "\\\\");
|
|
567
|
-
content += `\norg.gradle.java.home=${escapedPath}\n`;
|
|
568
|
-
await fsx.write(gradlePropsPath, content);
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
/**
|
|
573
|
-
* Java 21 경로 자동 탐색
|
|
574
|
-
*/
|
|
575
|
-
private async _findJava21(): Promise<string | undefined> {
|
|
576
|
-
const patterns = [
|
|
577
|
-
"C:/Program Files/Amazon Corretto/jdk21*",
|
|
578
|
-
"C:/Program Files/Eclipse Adoptium/jdk-21*",
|
|
579
|
-
"C:/Program Files/Java/jdk-21*",
|
|
580
|
-
"C:/Program Files/Microsoft/jdk-21*",
|
|
581
|
-
"/usr/lib/jvm/java-21*",
|
|
582
|
-
"/usr/lib/jvm/temurin-21*",
|
|
583
|
-
];
|
|
584
|
-
|
|
585
|
-
for (const pattern of patterns) {
|
|
586
|
-
const matches = await fsx.glob(pattern);
|
|
587
|
-
if (matches.length > 0) {
|
|
588
|
-
return matches.sort().at(-1);
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
return undefined;
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
/**
|
|
596
|
-
* Android SDK 경로 설정 (local.properties)
|
|
597
|
-
*/
|
|
598
|
-
private async _configureAndroidSdkPath(androidPath: string): Promise<void> {
|
|
599
|
-
const localPropsPath = pathx.posixResolve(androidPath, "local.properties");
|
|
600
|
-
|
|
601
|
-
const sdkPath = await this._findAndroidSdk();
|
|
602
|
-
if (sdkPath != null) {
|
|
603
|
-
// Gradle 호환: 항상 forward slash 사용
|
|
604
|
-
await fsx.write(localPropsPath, `sdk.dir=${pathx.posix(sdkPath)}\n`);
|
|
605
|
-
} else {
|
|
606
|
-
throw new Error(
|
|
607
|
-
"Android SDK를 찾을 수 없습니다.\n" +
|
|
608
|
-
"1. Android Studio를 설치하거나\n" +
|
|
609
|
-
"2. ANDROID_HOME 또는 ANDROID_SDK_ROOT 환경 변수를 설정하세요.",
|
|
610
|
-
);
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
/**
|
|
615
|
-
* Android SDK 경로 탐색
|
|
616
|
-
*/
|
|
617
|
-
private async _findAndroidSdk(): Promise<string | undefined> {
|
|
618
|
-
const androidHome =
|
|
619
|
-
env("ANDROID_HOME") ??
|
|
620
|
-
env("ANDROID_SDK_ROOT");
|
|
621
|
-
if (androidHome != null && (await fsx.exists(androidHome))) {
|
|
622
|
-
return androidHome;
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
const candidates = [
|
|
626
|
-
pathx.posixResolve(env("LOCALAPPDATA") ?? "", "Android/Sdk"),
|
|
627
|
-
pathx.posixResolve(env("HOME") ?? "", "Android/Sdk"),
|
|
628
|
-
"C:/Program Files/Android/Sdk",
|
|
629
|
-
"C:/Android/Sdk",
|
|
630
|
-
];
|
|
631
|
-
|
|
632
|
-
for (const candidate of candidates) {
|
|
633
|
-
if (await fsx.exists(candidate)) {
|
|
634
|
-
return candidate;
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
return undefined;
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
/**
|
|
642
|
-
* AndroidManifest.xml 설정
|
|
643
|
-
*
|
|
644
|
-
* 주의: Capacitor가 생성하는 초기 XML 포맷에 의존하는 정규식 기반 수정.
|
|
645
|
-
* XML 구조가 변경되면 정규식이 실패할 수 있음.
|
|
646
|
-
*/
|
|
647
|
-
private async _configureAndroidManifest(androidPath: string): Promise<void> {
|
|
648
|
-
const manifestPath = pathx.posixResolve(androidPath, "app/src/main/AndroidManifest.xml");
|
|
649
|
-
|
|
650
|
-
if (!(await fsx.exists(manifestPath))) {
|
|
651
|
-
throw new Error(`AndroidManifest.xml 파일을 찾을 수 없습니다: ${manifestPath}`);
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
let content = await fsx.read(manifestPath);
|
|
655
|
-
|
|
656
|
-
// usesCleartextTraffic 설정
|
|
657
|
-
if (!content.includes("android:usesCleartextTraffic")) {
|
|
658
|
-
content = content.replace("<application", '<application android:usesCleartextTraffic="true"');
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
// 추가 권한 설정
|
|
662
|
-
const permissions = this._config.platform?.android?.permissions ?? [];
|
|
663
|
-
for (const perm of permissions) {
|
|
664
|
-
const permTag = `<uses-permission android:name="android.permission.${perm.name}"`;
|
|
665
|
-
if (!content.includes(permTag)) {
|
|
666
|
-
const maxSdkAttr =
|
|
667
|
-
perm.maxSdkVersion != null ? ` android:maxSdkVersion="${perm.maxSdkVersion}"` : "";
|
|
668
|
-
const ignoreAttr = perm.ignore != null ? ` tools:ignore="${perm.ignore}"` : "";
|
|
669
|
-
const permLine = ` ${permTag}${maxSdkAttr}${ignoreAttr} />\n`;
|
|
670
|
-
|
|
671
|
-
if (perm.ignore != null && !content.includes("xmlns:tools=")) {
|
|
672
|
-
content = content.replace(
|
|
673
|
-
"<manifest xmlns:android",
|
|
674
|
-
'<manifest xmlns:tools="http://schemas.android.com/tools" xmlns:android',
|
|
675
|
-
);
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
content = content.replace("</manifest>", `${permLine}</manifest>`);
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
// 추가 application 속성 설정
|
|
683
|
-
const appConfig = this._config.platform?.android?.config;
|
|
684
|
-
if (appConfig) {
|
|
685
|
-
for (const [key, value] of Object.entries(appConfig)) {
|
|
686
|
-
const attr = `android:${key}="${value}"`;
|
|
687
|
-
if (!content.includes(`android:${key}=`)) {
|
|
688
|
-
content = content.replace("<application", `<application ${attr}`);
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
// intentFilters 설정
|
|
694
|
-
const intentFilters = this._config.platform?.android?.intentFilters ?? [];
|
|
695
|
-
for (const filter of intentFilters) {
|
|
696
|
-
const filterKey = filter.action ?? filter.category ?? "";
|
|
697
|
-
if (filterKey && !content.includes(filterKey)) {
|
|
698
|
-
const actionLine = filter.action != null ? `<action android:name="${filter.action}"/>` : "";
|
|
699
|
-
const categoryLine =
|
|
700
|
-
filter.category != null ? `<category android:name="${filter.category}"/>` : "";
|
|
701
|
-
|
|
702
|
-
content = content.replace(
|
|
703
|
-
/(<activity[\s\S]*?android:name="\.MainActivity"[\s\S]*?>)/,
|
|
704
|
-
`$1
|
|
705
|
-
<intent-filter>
|
|
706
|
-
${actionLine}
|
|
707
|
-
${categoryLine}
|
|
708
|
-
</intent-filter>`,
|
|
709
|
-
);
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
await fsx.write(manifestPath, content);
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
/**
|
|
717
|
-
* 루트 build.gradle에 Kotlin Gradle 플러그인 classpath 추가
|
|
718
|
-
*/
|
|
719
|
-
private async _configureAndroidRootBuildGradle(androidPath: string): Promise<void> {
|
|
720
|
-
const rootBuildGradlePath = pathx.posixResolve(androidPath, "build.gradle");
|
|
721
|
-
|
|
722
|
-
if (!(await fsx.exists(rootBuildGradlePath))) {
|
|
723
|
-
Capacitor._logger.warn(`루트 build.gradle 파일을 찾을 수 없습니다: ${rootBuildGradlePath}`);
|
|
724
|
-
return;
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
let content = await fsx.read(rootBuildGradlePath);
|
|
728
|
-
|
|
729
|
-
if (!content.includes("kotlin-gradle-plugin")) {
|
|
730
|
-
content = content.replace(
|
|
731
|
-
/classpath 'com\.android\.tools\.build:gradle:[^']+'/,
|
|
732
|
-
`$&\n classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.20'`,
|
|
733
|
-
);
|
|
734
|
-
await fsx.write(rootBuildGradlePath, content);
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
/**
|
|
739
|
-
* build.gradle 수정 (서명 설정 제외)
|
|
740
|
-
*/
|
|
741
|
-
private async _configureAndroidBuildGradle(androidPath: string): Promise<void> {
|
|
742
|
-
const buildGradlePath = pathx.posixResolve(androidPath, "app/build.gradle");
|
|
743
|
-
|
|
744
|
-
if (!(await fsx.exists(buildGradlePath))) {
|
|
745
|
-
throw new Error(`build.gradle 파일을 찾을 수 없습니다: ${buildGradlePath}`);
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
let content = await fsx.read(buildGradlePath);
|
|
749
|
-
|
|
750
|
-
// versionName, versionCode 설정
|
|
751
|
-
const version = this._npmConfig.version;
|
|
752
|
-
const cleanVersion = version.replace(/-.*$/, "");
|
|
753
|
-
const versionParts = cleanVersion.split(".");
|
|
754
|
-
const versionCode =
|
|
755
|
-
parseInt(versionParts[0] ?? "0") * 10000 +
|
|
756
|
-
parseInt(versionParts[1] ?? "0") * 100 +
|
|
757
|
-
parseInt(versionParts[2] ?? "0");
|
|
758
|
-
|
|
759
|
-
content = content.replace(/versionCode \d+/, `versionCode ${versionCode}`);
|
|
760
|
-
content = content.replace(/versionName "[^"]+"/, `versionName "${version}"`);
|
|
761
|
-
|
|
762
|
-
// SDK 버전 설정
|
|
763
|
-
if (this._config.platform?.android?.sdkVersion != null) {
|
|
764
|
-
const sdkVersion = this._config.platform.android.sdkVersion;
|
|
765
|
-
content = content.replace(/minSdkVersion .+/, `minSdkVersion ${sdkVersion}`);
|
|
766
|
-
content = content.replace(/targetSdkVersion .+/, `targetSdkVersion ${sdkVersion}`);
|
|
767
|
-
} else {
|
|
768
|
-
content = content.replace(/minSdkVersion .+/, `minSdkVersion rootProject.ext.minSdkVersion`);
|
|
769
|
-
content = content.replace(
|
|
770
|
-
/targetSdkVersion .+/,
|
|
771
|
-
`targetSdkVersion rootProject.ext.targetSdkVersion`,
|
|
772
|
-
);
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
await fsx.write(buildGradlePath, content);
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
/**
|
|
779
|
-
* styles.xml의 스플래시 테마 수정
|
|
780
|
-
*
|
|
781
|
-
* 1. Theme.SplashScreen parent → Theme.AppCompat.DayNight.NoActionBar
|
|
782
|
-
* Theme.SplashScreen은 android:windowBackground에 compat_splash_screen을 설정하여
|
|
783
|
-
* android:background(@drawable/splash)와 이중 표시를 발생시킨다.
|
|
784
|
-
* installSplashScreen()을 호출하지 않으므로 Theme.SplashScreen 기능이 불필요하다.
|
|
785
|
-
*
|
|
786
|
-
* 2. android:background → android:windowBackground
|
|
787
|
-
* android:background는 View 레벨 속성으로 AppCompat 뷰 계층의 여러 View에 상속되어
|
|
788
|
-
* 동일한 splash 로고가 다중 레이어에 중복 렌더링된다.
|
|
789
|
-
* android:windowBackground는 Window의 DecorView에만 적용되어 단일 렌더링을 보장한다.
|
|
790
|
-
*/
|
|
791
|
-
private async _configureAndroidStyles(androidPath: string): Promise<void> {
|
|
792
|
-
const stylesPath = pathx.posixResolve(androidPath, "app/src/main/res/values/styles.xml");
|
|
793
|
-
|
|
794
|
-
if (!(await fsx.exists(stylesPath))) {
|
|
795
|
-
Capacitor._logger.warn(`styles.xml 파일을 찾을 수 없습니다: ${stylesPath}`);
|
|
796
|
-
return;
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
let content = await fsx.read(stylesPath);
|
|
800
|
-
let changed = false;
|
|
801
|
-
|
|
802
|
-
if (content.includes('parent="Theme.SplashScreen"')) {
|
|
803
|
-
content = content.replace(
|
|
804
|
-
'parent="Theme.SplashScreen"',
|
|
805
|
-
'parent="Theme.AppCompat.DayNight.NoActionBar"',
|
|
806
|
-
);
|
|
807
|
-
changed = true;
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
if (content.includes('"android:background">@drawable/splash')) {
|
|
811
|
-
content = content.replace(
|
|
812
|
-
'"android:background">@drawable/splash',
|
|
813
|
-
'"android:windowBackground">@drawable/splash',
|
|
814
|
-
);
|
|
815
|
-
changed = true;
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
if (changed) {
|
|
819
|
-
await fsx.write(stylesPath, content);
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
//#endregion
|
|
824
|
-
|
|
825
512
|
//#region Public — 기기 실행
|
|
826
513
|
|
|
827
514
|
/**
|
package/src/commands/check.ts
CHANGED
|
@@ -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 "
|
|
4
|
-
import { executeLint, type LintResult } from "
|
|
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";
|
package/src/commands/lint.ts
CHANGED
|
@@ -1,207 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createJiti } from "jiti";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { fsx, pathx } from "@simplysm/core-node";
|
|
5
|
-
import { env, SdError } from "@simplysm/core-common";
|
|
6
|
-
import { consola } from "consola";
|
|
7
|
-
|
|
8
|
-
//#region Types
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* ESLint 실행 옵션
|
|
12
|
-
*/
|
|
13
|
-
export interface LintOptions {
|
|
14
|
-
/** 린트 대상 경로 필터 (예: `packages/core-common`). 빈 배열이면 전체 대상 */
|
|
15
|
-
targets: string[];
|
|
16
|
-
/** 자동 수정 활성화 */
|
|
17
|
-
fix: boolean;
|
|
18
|
-
/** ESLint 규칙별 실행 시간 측정 활성화 (TIMING 환경변수 설정) */
|
|
19
|
-
timing: boolean;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* executeLint()의 반환 타입
|
|
24
|
-
*/
|
|
25
|
-
export interface LintResult {
|
|
26
|
-
/** 린트 에러가 없으면 true */
|
|
27
|
-
success: boolean;
|
|
28
|
-
/** ESLint 전체 에러 수 */
|
|
29
|
-
errorCount: number;
|
|
30
|
-
/** ESLint 전체 경고 수 */
|
|
31
|
-
warningCount: number;
|
|
32
|
-
/** 포매터 출력 문자열 (stdout에 출력할 내용) */
|
|
33
|
-
formattedOutput: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
//#endregion
|
|
37
|
-
|
|
38
|
-
//#region Utilities
|
|
39
|
-
|
|
40
|
-
/** ESLint 설정 파일 검색 순서 */
|
|
41
|
-
const ESLINT_CONFIG_FILES = [
|
|
42
|
-
"eslint.config.ts",
|
|
43
|
-
"eslint.config.mts",
|
|
44
|
-
"eslint.config.js",
|
|
45
|
-
"eslint.config.mjs",
|
|
46
|
-
] as const;
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* ESLint 설정 객체가 ignores 속성만 가지고 있는지 확인하는 타입 가드
|
|
50
|
-
*/
|
|
51
|
-
function isGlobalIgnoresConfig(item: unknown): item is { ignores: string[] } {
|
|
52
|
-
if (item == null || typeof item !== "object") return false;
|
|
53
|
-
if (!("ignores" in item)) return false;
|
|
54
|
-
if ("files" in item) return false; // files가 있으면 globalIgnores가 아님
|
|
55
|
-
const ignores = (item as { ignores: unknown }).ignores;
|
|
56
|
-
if (!Array.isArray(ignores)) return false;
|
|
57
|
-
return ignores.every((i) => typeof i === "string");
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* eslint.config.ts/js에서 globalIgnores 패턴을 추출한다.
|
|
62
|
-
* ignores만 있고 files가 없는 설정 객체가 globalIgnores이다.
|
|
63
|
-
* @internal 테스트용으로 export
|
|
64
|
-
*/
|
|
65
|
-
export async function loadIgnorePatterns(cwd: string): Promise<string[]> {
|
|
66
|
-
let configPath: string | undefined;
|
|
67
|
-
for (const f of ESLINT_CONFIG_FILES) {
|
|
68
|
-
const p = path.join(cwd, f);
|
|
69
|
-
if (await fsx.exists(p)) {
|
|
70
|
-
configPath = p;
|
|
71
|
-
break;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (configPath == null) {
|
|
76
|
-
throw new SdError(
|
|
77
|
-
`ESLint 설정 파일을 찾을 수 없습니다 (cwd: ${cwd}): ${ESLINT_CONFIG_FILES.join(", ")}`,
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const jiti = createJiti(import.meta.url);
|
|
82
|
-
const configModule = await jiti.import<{ default: Record<string, unknown>[] } | undefined>(
|
|
83
|
-
configPath,
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
let configs: unknown;
|
|
87
|
-
if (Array.isArray(configModule)) {
|
|
88
|
-
configs = configModule;
|
|
89
|
-
} else if (
|
|
90
|
-
configModule != null &&
|
|
91
|
-
typeof configModule === "object" &&
|
|
92
|
-
"default" in configModule
|
|
93
|
-
) {
|
|
94
|
-
configs = configModule.default;
|
|
95
|
-
} else {
|
|
96
|
-
throw new SdError(`ESLint 설정 파일의 형식이 올바르지 않습니다: ${configPath}`);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (!Array.isArray(configs)) {
|
|
100
|
-
throw new SdError(`ESLint 설정이 배열이 아닙니다: ${configPath}`);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return configs.filter(isGlobalIgnoresConfig).flatMap((item) => item.ignores);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
//#endregion
|
|
1
|
+
import { executeLint, type LintOptions } from "../utils/lint-core";
|
|
107
2
|
|
|
108
3
|
//#region Main
|
|
109
4
|
|
|
110
|
-
/**
|
|
111
|
-
* ESLint를 실행하고 결과를 반환한다.
|
|
112
|
-
*
|
|
113
|
-
* - `eslint.config.ts/js`에서 globalIgnores 패턴을 추출하여 glob 필터링에 적용
|
|
114
|
-
* - consola를 사용하여 진행 상황 표시
|
|
115
|
-
* - 캐시 활성화 (`.cache/eslint.cache`에 저장, 설정 변경 시 자동 무효화)
|
|
116
|
-
* - stdout 출력이나 process.exitCode 설정 없음 (호출자가 결정)
|
|
117
|
-
*
|
|
118
|
-
* @param options - 린트 실행 옵션
|
|
119
|
-
* @returns 린트 결과 (성공 여부, 에러/경고 수, 포매터 출력)
|
|
120
|
-
*/
|
|
121
|
-
export async function executeLint(options: LintOptions): Promise<LintResult> {
|
|
122
|
-
const { targets, fix, timing } = options;
|
|
123
|
-
const cwd = process.cwd();
|
|
124
|
-
const logger = consola.withTag("sd:cli:lint");
|
|
125
|
-
|
|
126
|
-
logger.debug("린트 시작", { targets, fix, timing });
|
|
127
|
-
|
|
128
|
-
// TIMING 환경변수 설정
|
|
129
|
-
if (timing) {
|
|
130
|
-
env("TIMING", "1");
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// ESLint 설정 로드
|
|
134
|
-
logger.start("ESLint 설정 로딩 중");
|
|
135
|
-
const ignorePatterns = await loadIgnorePatterns(cwd);
|
|
136
|
-
logger.debug("무시 패턴 로드 완료", { ignorePatternCount: ignorePatterns.length });
|
|
137
|
-
logger.success(`ESLint 설정 로드 완료 (${ignorePatterns.length}개 무시 패턴)`);
|
|
138
|
-
|
|
139
|
-
// 린트 대상 파일 수집
|
|
140
|
-
logger.start("린트 대상 파일 수집 중");
|
|
141
|
-
let files = await fsx.glob("**/*.{ts,js,mjs,cjs}", {
|
|
142
|
-
cwd,
|
|
143
|
-
ignore: ignorePatterns,
|
|
144
|
-
nodir: true,
|
|
145
|
-
absolute: true,
|
|
146
|
-
});
|
|
147
|
-
files = pathx.filterByTargets(files, targets, cwd);
|
|
148
|
-
logger.debug("파일 수집 완료", { fileCount: files.length });
|
|
149
|
-
logger.success(`린트 대상 파일 수집 완료 (${files.length}개 파일)`);
|
|
150
|
-
|
|
151
|
-
// 린트 실행
|
|
152
|
-
let eslint: ESLint | undefined;
|
|
153
|
-
let eslintResults: ESLint.LintResult[] | undefined;
|
|
154
|
-
if (files.length > 0) {
|
|
155
|
-
logger.start(`린트 실행 중... (${files.length}개 파일)`);
|
|
156
|
-
eslint = new ESLint({
|
|
157
|
-
cwd,
|
|
158
|
-
fix,
|
|
159
|
-
cache: true,
|
|
160
|
-
cacheLocation: path.join(cwd, ".cache", "eslint.cache"),
|
|
161
|
-
});
|
|
162
|
-
eslintResults = await eslint.lintFiles(files);
|
|
163
|
-
logger.success("린트 실행 완료");
|
|
164
|
-
|
|
165
|
-
// 자동 수정 적용
|
|
166
|
-
if (fix) {
|
|
167
|
-
logger.debug("자동 수정 적용 중...");
|
|
168
|
-
await ESLint.outputFixes(eslintResults);
|
|
169
|
-
logger.debug("자동 수정 적용 완료");
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// 파일이 없거나 린트가 실행되지 않은 경우 성공 결과 반환
|
|
174
|
-
if (files.length === 0 || eslintResults == null || eslint == null) {
|
|
175
|
-
logger.info("린트할 파일 없음");
|
|
176
|
-
return { success: true, errorCount: 0, warningCount: 0, formattedOutput: "" };
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// 결과 집계
|
|
180
|
-
let errorCount = eslintResults.sum((r) => r.errorCount);
|
|
181
|
-
let warningCount = eslintResults.sum((r) => r.warningCount);
|
|
182
|
-
|
|
183
|
-
if (errorCount > 0) {
|
|
184
|
-
logger.error("린트 에러 발생", { errorCount, warningCount });
|
|
185
|
-
} else {
|
|
186
|
-
logger.info("린트 완료", { errorCount, warningCount });
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// 포매터 출력 수집
|
|
190
|
-
let formattedOutput = "";
|
|
191
|
-
const formatter = await eslint.loadFormatter("stylish");
|
|
192
|
-
const resultText = await formatter.format(eslintResults);
|
|
193
|
-
if (resultText) {
|
|
194
|
-
formattedOutput += resultText;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return {
|
|
198
|
-
success: errorCount === 0,
|
|
199
|
-
errorCount,
|
|
200
|
-
warningCount,
|
|
201
|
-
formattedOutput,
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
|
|
205
5
|
/**
|
|
206
6
|
* ESLint를 실행한다.
|
|
207
7
|
*
|
package/src/commands/publish.ts
CHANGED
|
@@ -770,7 +770,8 @@ export async function runPublish(options: PublishOptions): Promise<void> {
|
|
|
770
770
|
|
|
771
771
|
// 실패한 패키지 확인
|
|
772
772
|
const allPkgNames = publishPackages.map((p) => p.name);
|
|
773
|
-
const
|
|
773
|
+
const publishedSet = new Set(publishedPackages);
|
|
774
|
+
const failedPkgNames = allPkgNames.filter((n) => !publishedSet.has(n));
|
|
774
775
|
|
|
775
776
|
if (failedPkgNames.length > 0) {
|
|
776
777
|
if (publishedPackages.length > 0) {
|