@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.
- package/dist/angular/vite-angular-plugin.d.ts +2 -0
- package/dist/angular/vite-angular-plugin.d.ts.map +1 -1
- package/dist/angular/vite-angular-plugin.js +57 -28
- package/dist/angular/vite-angular-plugin.js.map +1 -1
- 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 -50
- package/dist/capacitor/capacitor.d.ts.map +1 -1
- package/dist/capacitor/capacitor.js +16 -281
- 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/device.d.ts.map +1 -1
- package/dist/commands/device.js +3 -2
- package/dist/commands/device.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.d.ts.map +1 -1
- package/dist/electron/electron.js +20 -8
- package/dist/electron/electron.js.map +1 -1
- package/dist/orchestrators/DevWatchOrchestrator.d.ts +1 -0
- package/dist/orchestrators/DevWatchOrchestrator.d.ts.map +1 -1
- package/dist/orchestrators/DevWatchOrchestrator.js +16 -0
- 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/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.d.ts +1 -1
- package/dist/utils/vite-config.d.ts.map +1 -1
- package/dist/utils/vite-config.js +76 -26
- package/dist/utils/vite-config.js.map +1 -1
- package/dist/utils/vite-scope-watch-plugin.d.ts.map +1 -1
- package/dist/utils/vite-scope-watch-plugin.js +7 -1
- package/dist/utils/vite-scope-watch-plugin.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 +11 -161
- package/dist/workers/server-build.worker.js.map +1 -1
- package/dist/workers/server-runtime.worker.d.ts.map +1 -1
- package/dist/workers/server-runtime.worker.js +15 -0
- package/dist/workers/server-runtime.worker.js.map +1 -1
- package/package.json +9 -7
- package/src/angular/vite-angular-plugin.ts +88 -34
- package/src/angular/vite-postcss-inline-plugin.ts +5 -1
- package/src/capacitor/capacitor-android.ts +368 -0
- package/src/capacitor/capacitor.ts +18 -363
- package/src/commands/check.ts +2 -2
- package/src/commands/device.ts +3 -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 +15 -8
- package/src/orchestrators/DevWatchOrchestrator.ts +18 -0
- 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/server-production-files.ts +186 -0
- package/src/utils/vite-config.ts +83 -27
- package/src/utils/vite-scope-watch-plugin.ts +6 -1
- package/src/workers/lint.worker.ts +1 -1
- package/src/workers/server-build.worker.ts +10 -185
- package/src/workers/server-runtime.worker.ts +15 -0
- package/tests/angular/linker-disk-cache.spec.ts +31 -25
- package/tests/angular/vite-angular-plugin-hmr-fallback.spec.ts +15 -15
- package/tests/angular/vite-angular-plugin-hmr.spec.ts +9 -9
- package/tests/angular/vite-angular-plugin-legacy-watch.spec.ts +108 -0
- package/tests/angular/vite-angular-plugin-lint.spec.ts +4 -4
- package/tests/angular/vite-angular-plugin-scss-hmr.spec.ts +10 -15
- package/tests/angular/vite-angular-plugin.spec.ts +80 -15
- 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 +30 -25
- package/tests/commands/check.spec.ts +2 -2
- package/tests/commands/device.spec.ts +12 -7
- 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/electron/electron.spec.ts +27 -2
- 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/utils/vite-config.spec.ts +255 -133
- package/tests/utils/vite-scope-watch-plugin.spec.ts +22 -0
- package/tests/workers/server-build-context-dispose.verify.md +8 -0
- package/tests/workers/server-runtime-worker.spec.ts +48 -4
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) {
|
|
@@ -1,294 +1,9 @@
|
|
|
1
|
-
import ts from "typescript";
|
|
2
|
-
import { err as errNs } from "@simplysm/core-common";
|
|
3
|
-
import { pathx } from "@simplysm/core-node";
|
|
4
|
-
import { consola } from "consola";
|
|
5
|
-
import { loadSdConfig } from "../utils/sd-config";
|
|
6
|
-
import { deserializeDiagnostic } from "../utils/typecheck-serialization";
|
|
7
|
-
import { createBuildEngine } from "../engines/index";
|
|
8
|
-
import { typecheckNonPackageFiles } from "../utils/typecheck-non-package";
|
|
9
|
-
import { runWithConcurrency, getMaxConcurrency } from "../utils/concurrency";
|
|
10
|
-
import { discoverWorkspacePackages, mergeTestsPackagesIntoConfig } from "../utils/package-utils";
|
|
11
|
-
import type { EngineResult } from "../engines/types";
|
|
12
|
-
import type { TypecheckEnv } from "../utils/tsconfig";
|
|
13
|
-
import { toTypecheckEnvs } from "../utils/tsconfig";
|
|
14
|
-
|
|
15
|
-
//#region Types
|
|
16
|
-
|
|
17
1
|
/**
|
|
18
|
-
*
|
|
2
|
+
* TypecheckOrchestrator에 위임하는 CLI 래퍼.
|
|
3
|
+
* 타입과 executeTypecheck를 re-export하여 기존 호출자와의 호환성을 유지한다.
|
|
19
4
|
*/
|
|
20
|
-
export
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
/** true이면 엔진에 lint: true를 전달하고 lint 결과를 수집한다 */
|
|
26
|
-
lint?: boolean;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* TypeScript 타입체크 실행 결과
|
|
31
|
-
*/
|
|
32
|
-
export interface TypecheckResult {
|
|
33
|
-
success: boolean;
|
|
34
|
-
errorCount: number;
|
|
35
|
-
warningCount: number;
|
|
36
|
-
formattedOutput: string;
|
|
37
|
-
/** lint 결과 (TypecheckOptions.lint가 true일 때 존재) */
|
|
38
|
-
lint?: {
|
|
39
|
-
success: boolean;
|
|
40
|
-
errorCount: number;
|
|
41
|
-
warningCount: number;
|
|
42
|
-
formattedOutput: string;
|
|
43
|
-
};
|
|
44
|
-
/** 건너뛴 scripts 패키지 경로 (별도 lint용) */
|
|
45
|
-
scriptsPackagePaths?: string[];
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
//#endregion
|
|
49
|
-
|
|
50
|
-
//#region Utilities
|
|
51
|
-
|
|
52
|
-
const TARGET_PATH_PATTERN = /^(?:packages|tests)\/([^/]+)/;
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* 대상 경로에서 패키지명을 추출한다.
|
|
56
|
-
* "packages/core-common" → "core-common"
|
|
57
|
-
* "tests/orm" → "orm"
|
|
58
|
-
*/
|
|
59
|
-
function extractTargetPackageNames(targets: string[]): Set<string> {
|
|
60
|
-
const names = new Set<string>();
|
|
61
|
-
for (const target of targets) {
|
|
62
|
-
const match = target.match(TARGET_PATH_PATTERN);
|
|
63
|
-
if (match) names.add(match[1]);
|
|
64
|
-
}
|
|
65
|
-
return names;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
//#endregion
|
|
69
|
-
|
|
70
|
-
//#region Main
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* BuildEngine을 사용하여 TypeScript 타입체크를 실행한다.
|
|
74
|
-
*
|
|
75
|
-
* sd.config.ts의 각 패키지에 대해:
|
|
76
|
-
* - 라이브러리/서버 패키지 → BuildEngine.run({js:false, dts:false})
|
|
77
|
-
* - client 패키지 → browser target으로 변환하여 포함
|
|
78
|
-
* - scripts 패키지 → 타입체크 제외 (별도 lint만 수행)
|
|
79
|
-
* 비패키지 파일 → typecheckNonPackageFiles 유틸리티
|
|
80
|
-
*
|
|
81
|
-
* @param options - 타입체크 실행 옵션
|
|
82
|
-
* @returns 타입체크 결과 (성공 여부, 에러/경고 수, 포매팅된 출력 문자열)
|
|
83
|
-
*/
|
|
84
|
-
export async function executeTypecheck(options: TypecheckOptions): Promise<TypecheckResult> {
|
|
85
|
-
const { targets } = options;
|
|
86
|
-
const cwd = process.cwd();
|
|
87
|
-
const logger = consola.withTag("sd:cli:typecheck");
|
|
88
|
-
|
|
89
|
-
const phaseLabel = options.lint === true ? "타입체크/린트" : "타입체크";
|
|
90
|
-
|
|
91
|
-
logger.debug(`${phaseLabel} 시작`, { targets, lint: options.lint });
|
|
92
|
-
|
|
93
|
-
const formatHost: ts.FormatDiagnosticsHost = {
|
|
94
|
-
getCanonicalFileName: (f) => f,
|
|
95
|
-
getCurrentDirectory: () => cwd,
|
|
96
|
-
getNewLine: () => ts.sys.newLine,
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
// sd.config.ts 로드
|
|
100
|
-
const sdConfig = await loadSdConfig({ cwd, dev: false, opt: options.options });
|
|
101
|
-
logger.debug("sd.config.ts 로드 완료");
|
|
102
|
-
|
|
103
|
-
// 워크스페이스 패키지 탐색 및 tests/를 설정에 병합
|
|
104
|
-
const workspacePackages = discoverWorkspacePackages(cwd);
|
|
105
|
-
const { merged, pathMap } = mergeTestsPackagesIntoConfig(sdConfig.packages, workspacePackages);
|
|
106
|
-
|
|
107
|
-
// 경로 기반 대상에서 패키지명 결정
|
|
108
|
-
const targetNames = extractTargetPackageNames(targets);
|
|
109
|
-
|
|
110
|
-
// scripts 패키지 경로 수집 (별도 lint용)
|
|
111
|
-
const scriptsPackagePaths: string[] = [];
|
|
112
|
-
|
|
113
|
-
// 타���체크할 패키지 수집 (scripts 제외), env별로 확장
|
|
114
|
-
const typecheckTasks: Array<{ name: string; dir: string; config: any; env: TypecheckEnv }> = [];
|
|
115
|
-
for (const [name, config] of Object.entries(merged)) {
|
|
116
|
-
if (config == null) continue;
|
|
117
|
-
if (config.target === "scripts") {
|
|
118
|
-
if (targets.length === 0 || targetNames.has(name)) {
|
|
119
|
-
const relPath = pathMap.get(name) ?? `packages/${name}`;
|
|
120
|
-
scriptsPackagePaths.push(relPath);
|
|
121
|
-
}
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
if (targets.length > 0 && !targetNames.has(name)) continue;
|
|
125
|
-
|
|
126
|
-
const relPath = pathMap.get(name) ?? `packages/${name}`;
|
|
127
|
-
// 클라이언트 패키지의 경우 browser 타겟을 사용하여 createBuildEngine이 ViteEngine 대신 NgtscEngine으로 라우팅되도록 함
|
|
128
|
-
const typecheckConfig = config.target === "client" ? { target: "browser" as const } : config;
|
|
129
|
-
const envs = toTypecheckEnvs(config.target);
|
|
130
|
-
for (const env of envs) {
|
|
131
|
-
typecheckTasks.push({
|
|
132
|
-
name,
|
|
133
|
-
dir: pathx.posixResolve(cwd, relPath),
|
|
134
|
-
config: typecheckConfig,
|
|
135
|
-
env,
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// 비패키지 타입체크: 대상이 지정되지 않은 경우에만 (= 전체 검사)
|
|
141
|
-
const includeNonPackage = targets.length === 0;
|
|
142
|
-
|
|
143
|
-
if (typecheckTasks.length === 0 && !includeNonPackage) {
|
|
144
|
-
logger.info(`${phaseLabel} 대상 없음`);
|
|
145
|
-
return {
|
|
146
|
-
success: true,
|
|
147
|
-
errorCount: 0,
|
|
148
|
-
warningCount: 0,
|
|
149
|
-
formattedOutput: `✔ ${phaseLabel} 대상 없음.\n`,
|
|
150
|
-
scriptsPackagePaths: scriptsPackagePaths.length > 0 ? scriptsPackagePaths : undefined,
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// 동시성 제한이 있는 BuildEngine 작업 생성
|
|
155
|
-
const allDiagnostics: ts.Diagnostic[] = [];
|
|
156
|
-
let totalErrorCount = 0;
|
|
157
|
-
let totalWarningCount = 0;
|
|
158
|
-
const fileCache = new Map<string, string>();
|
|
159
|
-
|
|
160
|
-
// Lint 결과 집계
|
|
161
|
-
let lintErrorCount = 0;
|
|
162
|
-
let lintWarningCount = 0;
|
|
163
|
-
let lintSuccess = true;
|
|
164
|
-
const lintOutputs: string[] = [];
|
|
165
|
-
|
|
166
|
-
if (typecheckTasks.length > 0) {
|
|
167
|
-
const tasks = typecheckTasks.map((task) => async (): Promise<EngineResult> => {
|
|
168
|
-
const label = `${task.name}:${task.env}`;
|
|
169
|
-
const engine = createBuildEngine(
|
|
170
|
-
{ name: task.name, dir: task.dir, config: task.config },
|
|
171
|
-
{ cwd },
|
|
172
|
-
);
|
|
173
|
-
try {
|
|
174
|
-
logger.debug(`[${label}] 타입체크 시작됨`);
|
|
175
|
-
const result = await engine.run({
|
|
176
|
-
js: false,
|
|
177
|
-
dts: false,
|
|
178
|
-
env: task.env,
|
|
179
|
-
includeTests: true,
|
|
180
|
-
...(options.lint === true ? { lint: true } : {}),
|
|
181
|
-
});
|
|
182
|
-
logger.debug(`[${label}] 타입체크 ${result.build.success ? "완료" : "실패"}`);
|
|
183
|
-
return result;
|
|
184
|
-
} catch (err) {
|
|
185
|
-
const message = errNs.message(err);
|
|
186
|
-
const stack = err instanceof Error ? err.stack : undefined;
|
|
187
|
-
logger.error(`[${label}] 엔진 작업 실패: ${message}`);
|
|
188
|
-
if (stack != null) {
|
|
189
|
-
logger.debug(`[${label}] 스택 트레이스:\n${stack}`);
|
|
190
|
-
}
|
|
191
|
-
return {
|
|
192
|
-
build: {
|
|
193
|
-
success: false,
|
|
194
|
-
errors: [`[${label}] ${message}`],
|
|
195
|
-
warnings: [],
|
|
196
|
-
diagnostics: [],
|
|
197
|
-
},
|
|
198
|
-
};
|
|
199
|
-
} finally {
|
|
200
|
-
await engine.stop();
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
const concurrency = getMaxConcurrency();
|
|
205
|
-
logger.start(`${phaseLabel} 실행 중... (${tasks.length}개 작업, 동시성: ${concurrency})`);
|
|
206
|
-
const results = await runWithConcurrency(tasks, concurrency);
|
|
207
|
-
logger.success(`${phaseLabel} 실행 완료`);
|
|
208
|
-
|
|
209
|
-
// 엔진 결과 집계 (모든 task는 catch로 인해 항상 fulfilled)
|
|
210
|
-
for (const settled of results) {
|
|
211
|
-
if (settled.status !== "fulfilled") continue;
|
|
212
|
-
const engineResult = settled.value;
|
|
213
|
-
const buildDiags = engineResult.build.diagnostics.map((d) => deserializeDiagnostic(d, fileCache));
|
|
214
|
-
allDiagnostics.push(...buildDiags);
|
|
215
|
-
// 역직렬화된 진단 정보에서 에러/경고 수 집계
|
|
216
|
-
// 숫자 카테고리 값 사용 (ts.DiagnosticCategory: Error=1, Warning=0)
|
|
217
|
-
totalErrorCount += buildDiags.filter((d) => d.category === 1).length;
|
|
218
|
-
totalWarningCount += buildDiags.filter((d) => d.category === 0).length;
|
|
219
|
-
if (!engineResult.build.success && buildDiags.length === 0) {
|
|
220
|
-
for (const errMsg of engineResult.build.errors) {
|
|
221
|
-
allDiagnostics.push({
|
|
222
|
-
category: 1,
|
|
223
|
-
code: 0,
|
|
224
|
-
messageText: errMsg,
|
|
225
|
-
file: undefined,
|
|
226
|
-
start: undefined,
|
|
227
|
-
length: undefined,
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
totalErrorCount += engineResult.build.errors.length || 1;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Lint 결과 수집
|
|
234
|
-
if (engineResult.lint != null) {
|
|
235
|
-
lintErrorCount += engineResult.lint.errorCount;
|
|
236
|
-
lintWarningCount += engineResult.lint.warningCount;
|
|
237
|
-
if (!engineResult.lint.success) lintSuccess = false;
|
|
238
|
-
if (engineResult.lint.formattedOutput !== "") {
|
|
239
|
-
lintOutputs.push(engineResult.lint.formattedOutput);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// 비패키지 타입체크
|
|
246
|
-
if (includeNonPackage) {
|
|
247
|
-
logger.debug("비패키지 타입체크 실행 중");
|
|
248
|
-
const nonPkgResult = typecheckNonPackageFiles(cwd);
|
|
249
|
-
totalErrorCount += nonPkgResult.errorCount;
|
|
250
|
-
totalWarningCount += nonPkgResult.warningCount;
|
|
251
|
-
const nonPkgDiags = nonPkgResult.diagnostics.map((d) => deserializeDiagnostic(d, fileCache));
|
|
252
|
-
allDiagnostics.push(...nonPkgDiags);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// 요약 로그
|
|
256
|
-
const resultMeta: Record<string, number> = { errorCount: totalErrorCount, warningCount: totalWarningCount };
|
|
257
|
-
if (options.lint === true) {
|
|
258
|
-
resultMeta["lintErrorCount"] = lintErrorCount;
|
|
259
|
-
resultMeta["lintWarningCount"] = lintWarningCount;
|
|
260
|
-
}
|
|
261
|
-
if (totalErrorCount > 0) {
|
|
262
|
-
logger.error(`${phaseLabel} 에러 발생`, resultMeta);
|
|
263
|
-
} else {
|
|
264
|
-
logger.info(`${phaseLabel} 완료`, resultMeta);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// 진단 출력 포매팅
|
|
268
|
-
let formattedOutput = "";
|
|
269
|
-
if (allDiagnostics.length > 0) {
|
|
270
|
-
const uniqueDiagnostics = ts.sortAndDeduplicateDiagnostics(allDiagnostics);
|
|
271
|
-
formattedOutput = ts.formatDiagnosticsWithColorAndContext(uniqueDiagnostics, formatHost);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// lint가 요청된 경우 lint 결과 생성
|
|
275
|
-
const lintResult = options.lint === true
|
|
276
|
-
? {
|
|
277
|
-
success: lintSuccess,
|
|
278
|
-
errorCount: lintErrorCount,
|
|
279
|
-
warningCount: lintWarningCount,
|
|
280
|
-
formattedOutput: lintOutputs.join("\n"),
|
|
281
|
-
}
|
|
282
|
-
: undefined;
|
|
283
|
-
|
|
284
|
-
return {
|
|
285
|
-
success: totalErrorCount === 0,
|
|
286
|
-
errorCount: totalErrorCount,
|
|
287
|
-
warningCount: totalWarningCount,
|
|
288
|
-
formattedOutput,
|
|
289
|
-
lint: lintResult,
|
|
290
|
-
scriptsPackagePaths: scriptsPackagePaths.length > 0 ? scriptsPackagePaths : undefined,
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
//#endregion
|
|
5
|
+
export {
|
|
6
|
+
executeTypecheck,
|
|
7
|
+
type TypecheckOptions,
|
|
8
|
+
type TypecheckResult,
|
|
9
|
+
} from "../orchestrators/TypecheckOrchestrator";
|
package/src/electron/electron.ts
CHANGED
|
@@ -125,6 +125,9 @@ export class Electron {
|
|
|
125
125
|
};
|
|
126
126
|
|
|
127
127
|
const envBanner = createEnvBanner({ ELECTRON_DEV_URL: url, ...this._config.env });
|
|
128
|
+
const bannerJs =
|
|
129
|
+
"import { createRequire } from 'module'; const require = createRequire(import.meta.url);" +
|
|
130
|
+
envBanner;
|
|
128
131
|
|
|
129
132
|
Electron._logger.debug("esbuild context 생성 시작");
|
|
130
133
|
const ctx = await esbuild.context({
|
|
@@ -132,10 +135,10 @@ export class Electron {
|
|
|
132
135
|
outfile: pathx.posixResolve(this._srcPath, "electron-main.js"),
|
|
133
136
|
platform: "node",
|
|
134
137
|
target: "node20",
|
|
135
|
-
format: "
|
|
138
|
+
format: "esm",
|
|
136
139
|
bundle: true,
|
|
137
140
|
external: ["electron", ...builtinModules, ...reinstallDeps, ...this._exclude],
|
|
138
|
-
banner: { js:
|
|
141
|
+
banner: { js: bannerJs },
|
|
139
142
|
plugins: [
|
|
140
143
|
{
|
|
141
144
|
name: "electron-restart",
|
|
@@ -264,6 +267,7 @@ export class Electron {
|
|
|
264
267
|
name: this._npmConfig.name.replace(/^@/, "").replace(/\//, "-"),
|
|
265
268
|
version: this._npmConfig.version,
|
|
266
269
|
description: this._npmConfig.description,
|
|
270
|
+
type: "module",
|
|
267
271
|
main: "electron-main.js",
|
|
268
272
|
dependencies,
|
|
269
273
|
devDependencies,
|
|
@@ -292,6 +296,9 @@ export class Electron {
|
|
|
292
296
|
const reinstallDeps = this._config.reinstallDependencies ?? [];
|
|
293
297
|
|
|
294
298
|
const envBanner = createEnvBanner(this._config.env);
|
|
299
|
+
const bannerJs =
|
|
300
|
+
"import { createRequire } from 'module'; const require = createRequire(import.meta.url);" +
|
|
301
|
+
envBanner;
|
|
295
302
|
|
|
296
303
|
Electron._logger.debug(`esbuild 번들링: ${entryPoint}`);
|
|
297
304
|
await esbuild.build({
|
|
@@ -299,10 +306,10 @@ export class Electron {
|
|
|
299
306
|
outfile: pathx.posixResolve(this._srcPath, "electron-main.js"),
|
|
300
307
|
platform: "node",
|
|
301
308
|
target: "node20",
|
|
302
|
-
format: "
|
|
309
|
+
format: "esm",
|
|
303
310
|
bundle: true,
|
|
304
311
|
external: ["electron", ...builtinModules, ...reinstallDeps, ...this._exclude],
|
|
305
|
-
banner: { js:
|
|
312
|
+
banner: { js: bannerJs },
|
|
306
313
|
});
|
|
307
314
|
}
|
|
308
315
|
|
|
@@ -341,12 +348,12 @@ export class Electron {
|
|
|
341
348
|
try {
|
|
342
349
|
fs.writeFileSync(testTarget, "test");
|
|
343
350
|
fs.symlinkSync(testTarget, testLink, "file");
|
|
344
|
-
|
|
345
|
-
fs.unlinkSync(testLink);
|
|
346
|
-
fs.unlinkSync(testTarget);
|
|
347
|
-
return isSymlink;
|
|
351
|
+
return fs.lstatSync(testLink).isSymbolicLink();
|
|
348
352
|
} catch {
|
|
349
353
|
return false;
|
|
354
|
+
} finally {
|
|
355
|
+
try { fs.unlinkSync(testLink); } catch { /* 파일 없으면 무시 */ }
|
|
356
|
+
try { fs.unlinkSync(testTarget); } catch { /* 파일 없으면 무시 */ }
|
|
350
357
|
}
|
|
351
358
|
}
|
|
352
359
|
|
|
@@ -74,6 +74,7 @@ export class DevWatchOrchestrator {
|
|
|
74
74
|
|
|
75
75
|
// 워처
|
|
76
76
|
private _copySrcWatchers: FsWatcher[] = [];
|
|
77
|
+
private _distDeleteWatchers: FsWatcher[] = [];
|
|
77
78
|
private readonly _watchHookWatchers: FsWatcher[] = [];
|
|
78
79
|
private readonly _watchHookChildren = new Map<string, ChildProcess>();
|
|
79
80
|
private _replaceDepWatcher: WatchReplaceDepResult | undefined;
|
|
@@ -252,6 +253,7 @@ export class DevWatchOrchestrator {
|
|
|
252
253
|
|
|
253
254
|
// 워처 종료 (watch 모드)
|
|
254
255
|
shutdownTasks.push(...this._copySrcWatchers.map((w) => w.close()));
|
|
256
|
+
shutdownTasks.push(...this._distDeleteWatchers.map((w) => w.close()));
|
|
255
257
|
shutdownTasks.push(...this._watchHookWatchers.map((w) => w.close()));
|
|
256
258
|
|
|
257
259
|
// hook 자식 프로세스 종료
|
|
@@ -267,6 +269,7 @@ export class DevWatchOrchestrator {
|
|
|
267
269
|
|
|
268
270
|
await Promise.all(shutdownTasks);
|
|
269
271
|
this._copySrcWatchers = [];
|
|
272
|
+
this._distDeleteWatchers = [];
|
|
270
273
|
this._watchHookWatchers.length = 0;
|
|
271
274
|
this._replaceDepWatcher?.dispose();
|
|
272
275
|
|
|
@@ -277,6 +280,21 @@ export class DevWatchOrchestrator {
|
|
|
277
280
|
|
|
278
281
|
private async _startWatchMode(): Promise<void> {
|
|
279
282
|
this._logger.debug("watch 모드 시작");
|
|
283
|
+
|
|
284
|
+
// 라이브러리 패키지 dist 삭제 감지 워처
|
|
285
|
+
for (const pkg of this._libraryPackages) {
|
|
286
|
+
const distDir = pathx.posixResolve(pkg.dir, "dist");
|
|
287
|
+
const watcher = await FsWatcher.watch([distDir]);
|
|
288
|
+
watcher.onChange({ delay: 100 }, (changes) => {
|
|
289
|
+
for (const c of changes) {
|
|
290
|
+
if (c.event === "unlink" || c.event === "unlinkDir") {
|
|
291
|
+
this._logger.error(`[dist-delete:${pkg.name}] ${c.event}: ${c.path}\n${new Error().stack}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
this._distDeleteWatchers.push(watcher);
|
|
296
|
+
}
|
|
297
|
+
|
|
280
298
|
// Start copySrc watchers for library packages
|
|
281
299
|
for (const pkg of this._libraryPackages) {
|
|
282
300
|
if (pkg.config.copySrc != null && pkg.config.copySrc.length > 0) {
|