cuelo 0.10.2 → 0.10.3
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/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +11 -11
- package/.next/build-manifest.json +2 -2
- package/.next/prerender-manifest.json +3 -3
- package/.next/required-server-files.js +1 -1
- package/.next/required-server-files.json +1 -1
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/api/agent/[id]/account/route.js +1 -1
- package/.next/server/app/api/agent/[id]/events/route.js +1 -1
- package/.next/server/app/api/agent/[id]/route.js +1 -1
- package/.next/server/app/api/agent/new/route.js +1 -1
- package/.next/server/app/api/agent/running/route.js +1 -1
- package/.next/server/app/api/cwd/validate/route.js +1 -1
- package/.next/server/app/api/default-cwd/route.js +1 -1
- package/.next/server/app/api/display-settings/route.js +1 -1
- package/.next/server/app/api/file-index/route.js +1 -1
- package/.next/server/app/api/files/[...path]/route.js +1 -1
- package/.next/server/app/api/git/diff/route.js +2 -2
- package/.next/server/app/api/git/status/route.js +2 -2
- package/.next/server/app/api/gpt6/handles/route.js +1 -1
- package/.next/server/app/api/gpt6/reply/route.js +1 -1
- package/.next/server/app/api/gpt6/token/route.js +1 -1
- package/.next/server/app/api/gpt6/web6-session/route.js +1 -1
- package/.next/server/app/api/live/call/[callId]/route.js +1 -1
- package/.next/server/app/api/live/character-voice/route.js +1 -1
- package/.next/server/app/api/live/events/route.js +1 -1
- package/.next/server/app/api/live/offer/route.js +1 -1
- package/.next/server/app/api/live/voices/route.js +1 -1
- package/.next/server/app/api/lounge/events/route.js +1 -1
- package/.next/server/app/api/lounge/route.js +1 -1
- package/.next/server/app/api/mcp/route.js +2 -2
- package/.next/server/app/api/model-roles/route.js +1 -1
- package/.next/server/app/api/models/route.js +1 -1
- package/.next/server/app/api/plugins/route.js +1 -1
- package/.next/server/app/api/project-trust/route.js +1 -1
- package/.next/server/app/api/projects/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/auto-name/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/context/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/entries/[entryId]/thinking/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/state/route.js +1 -1
- package/.next/server/app/api/sessions/route.js +1 -1
- package/.next/server/app/api/settings/route.js +1 -1
- package/.next/server/app/api/skills/check/route.js +1 -1
- package/.next/server/app/api/skills/install/route.js +1 -1
- package/.next/server/app/api/skills/route.js +1 -1
- package/.next/server/app/api/skills/update/route.js +1 -1
- package/.next/server/app/api/theme/route.js +1 -1
- package/.next/server/app/api/tool-image/route.js +1 -1
- package/.next/server/app/api/update-maintenance/route.js +1 -1
- package/.next/server/app/api/updates/route.js +1 -1
- package/.next/server/app/api/worktrees/route.js +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +4 -4
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/office/page_client-reference-manifest.js +1 -1
- package/.next/server/app/office-preview/page_client-reference-manifest.js +1 -1
- package/.next/server/app/office-preview.html +1 -1
- package/.next/server/app/office-preview.rsc +2 -2
- package/.next/server/app/office-preview.segments/_full.segment.rsc +2 -2
- package/.next/server/app/office-preview.segments/_head.segment.rsc +1 -1
- package/.next/server/app/office-preview.segments/_index.segment.rsc +2 -2
- package/.next/server/app/office-preview.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/office-preview.segments/office-preview/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/office-preview.segments/office-preview.segment.rsc +1 -1
- package/.next/server/app/office.html +1 -1
- package/.next/server/app/office.rsc +4 -4
- package/.next/server/app/office.segments/_full.segment.rsc +4 -4
- package/.next/server/app/office.segments/_head.segment.rsc +1 -1
- package/.next/server/app/office.segments/_index.segment.rsc +2 -2
- package/.next/server/app/office.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/office.segments/office/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/office.segments/office.segment.rsc +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/recover/page_client-reference-manifest.js +1 -1
- package/.next/server/app/recover.html +1 -1
- package/.next/server/app/recover.rsc +2 -2
- package/.next/server/app/recover.segments/_full.segment.rsc +2 -2
- package/.next/server/app/recover.segments/_head.segment.rsc +1 -1
- package/.next/server/app/recover.segments/_index.segment.rsc +2 -2
- package/.next/server/app/recover.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/recover.segments/recover/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/recover.segments/recover.segment.rsc +1 -1
- package/.next/server/app-paths-manifest.json +11 -11
- package/.next/server/chunks/1008.js +3 -3
- package/.next/server/chunks/5489.js +2 -2
- package/.next/server/chunks/5582.js +2 -2
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/{9575-9196474bb9e5c752.js → 9575-1c8a5b26e8a14373.js} +2 -2
- package/.next/static/chunks/app/{layout-713017c65de14601.js → layout-3b14a00bf951db3a.js} +1 -1
- package/.next/trace +108 -108
- package/.next/trace-build +1 -1
- package/Tools/CUELO_Setup/files/native-runtime-patch.js +1 -1
- package/Tools/OMP_Global_Config/agent/agents/maker.md +14 -11
- package/Tools/OMP_Global_Config/agent/extensions/character-voice.ts +1 -1
- package/Tools/OMP_Global_Config/agent/extensions/command-guard/index.ts +12 -4
- package/Tools/OMP_Global_Config/agent/extensions/korean-reply-guard.ts +2 -2
- package/Tools/OMP_Global_Config/agent/rules/harness-policy.json +2 -2
- package/Tools/OMP_Global_Config/agent/rules/meta-harness.md +2 -2
- package/Tools/OMP_Global_Config/agent/rules/subagent.md +5 -3
- package/Tools/OMP_Global_Config/agent/sop/_common.md +12 -9
- package/Tools/OMP_Global_Config/agent/sop/maker.md +1 -1
- package/Tools/OMP_Global_Config/agent/tools/git-finalizer/index.ts +7 -28
- package/Tools/OMP_Global_Config/agent/tools/git-finalizer/wsl-git.ts +96 -0
- package/Tools/OMP_Global_Config/agent/tools/windows-build/index.ts +328 -0
- package/package.json +1 -1
- /package/.next/static/{S8tJkE71e2D0DgH4X1s0S → A_r0uYOELdy8wTQbOhBCg}/_buildManifest.js +0 -0
- /package/.next/static/{S8tJkE71e2D0DgH4X1s0S → A_r0uYOELdy8wTQbOhBCg}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import type { CustomToolFactory } from "@oh-my-pi/pi-coding-agent";
|
|
2
|
+
import { access, mkdir, readFile } from "node:fs/promises";
|
|
3
|
+
import { posix, win32 } from "node:path";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* WSL(또는 Windows)에서 Windows 전용 프로젝트(VB.NET/.NET Framework WinForms, C# 솔루션)를 MSBuild로 빌드한다.
|
|
7
|
+
* 빌드만 한다: 산출물 실행·복사·배포를 하지 않고 프로젝트 파일은 읽기만 한다.
|
|
8
|
+
* 콘솔 출력은 PowerShell 5.1/콘솔 코드페이지(CP949)에 따라 깨질 수 있어 쓰지 않고(-noconlog),
|
|
9
|
+
* MSBuild 파일 로거가 UTF-8로 쓴 로그에서 오류·경고를 읽는다.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export interface WindowsBuildParams {
|
|
13
|
+
project: string;
|
|
14
|
+
configuration?: string;
|
|
15
|
+
platform?: string;
|
|
16
|
+
target?: string;
|
|
17
|
+
properties?: Record<string, string>;
|
|
18
|
+
restore?: boolean;
|
|
19
|
+
maxErrors?: number;
|
|
20
|
+
timeoutSeconds?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ExecResult { code: number; stdout: string; stderr?: string; killed?: boolean }
|
|
24
|
+
export type Exec = (command: string, args: string[], options: { cwd: string; timeout?: number }) => Promise<ExecResult>;
|
|
25
|
+
|
|
26
|
+
export interface BuildDeps {
|
|
27
|
+
exec: Exec;
|
|
28
|
+
cwd: string;
|
|
29
|
+
/** WSL이면 경로를 wslpath로 바꾼다. Windows 네이티브면 변환 없이 그대로 쓴다. */
|
|
30
|
+
wsl: boolean;
|
|
31
|
+
exists: (path: string) => Promise<boolean>;
|
|
32
|
+
mkdirp: (path: string) => Promise<void>;
|
|
33
|
+
readText: (path: string) => Promise<string | undefined>;
|
|
34
|
+
env: (name: string) => string | undefined;
|
|
35
|
+
now: () => number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface BuildDiagnostics {
|
|
39
|
+
errorCount: number;
|
|
40
|
+
warningCount: number;
|
|
41
|
+
errors: string[];
|
|
42
|
+
warnings: string[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface BuildResult extends BuildDiagnostics {
|
|
46
|
+
ok: boolean;
|
|
47
|
+
exitCode: number;
|
|
48
|
+
killed: boolean;
|
|
49
|
+
project: string;
|
|
50
|
+
msbuildPath: string;
|
|
51
|
+
msbuildVersion: string;
|
|
52
|
+
logPath: string;
|
|
53
|
+
logPathPosix: string;
|
|
54
|
+
logMissing: boolean;
|
|
55
|
+
consoleTail?: string[];
|
|
56
|
+
durationMs: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const DEFAULT_MAX_ERRORS = 20;
|
|
60
|
+
const MAX_WARNING_LINES = 5;
|
|
61
|
+
const DEFAULT_TIMEOUT_SECONDS = 600;
|
|
62
|
+
const MAX_TIMEOUT_SECONDS = 3600;
|
|
63
|
+
const PROJECT_EXTENSION = /\.(sln|slnx|\w*proj)$/i;
|
|
64
|
+
const DRIVE_PATH = /^[A-Za-z]:[\\/]/;
|
|
65
|
+
const WSL_DRIVE_PATH = /^\/mnt\/[a-z]\//i;
|
|
66
|
+
const VSWHERE_RELATIVE = "Microsoft Visual Studio\\Installer\\vswhere.exe";
|
|
67
|
+
const DEFAULT_PROGRAM_FILES_X86 = "C:\\Program Files (x86)";
|
|
68
|
+
// 빌드만 한다: 실행·배포 계열 target과 빌드 전후 명령·시작 프로그램 속성은 거부한다.
|
|
69
|
+
const DENIED_TARGETS: Record<string, true> = { run: true, deploy: true, publish: true, install: true };
|
|
70
|
+
const DENIED_PROPERTIES: Record<string, true> = {
|
|
71
|
+
postbuildevent: true, prebuildevent: true, startprogram: true, startaction: true, deployonbuild: true, deploytarget: true,
|
|
72
|
+
};
|
|
73
|
+
const DIAGNOSTIC_LINE = /^\s*(?:(?<origin>.*?):\s+)?(?<severity>error|warning)(?:\s+(?<code>[A-Za-z]+\d+))?\s*:\s*(?<message>.*?)\s*$/i;
|
|
74
|
+
|
|
75
|
+
export interface ProjectInput { windows?: string; posix?: string }
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 프로젝트 경로를 받아 드라이브 위 Windows 경로 또는 `/mnt/<드라이브>/` POSIX 경로로 정규화한다.
|
|
79
|
+
* WSL 안(ext4)·UNC 경로는 MSBuild가 안정적으로 열지 못하므로 거부한다. 파일 존재는 여기서 보지 않는다.
|
|
80
|
+
*/
|
|
81
|
+
export function normalizeProjectInput(input: string, cwd: string, platform: NodeJS.Platform = process.platform): ProjectInput {
|
|
82
|
+
const value = input.trim().replace(/^"(.*)"$/, "$1");
|
|
83
|
+
if (!value) throw new Error("windows_build: project 경로가 비어 있다.");
|
|
84
|
+
let result: ProjectInput;
|
|
85
|
+
if (DRIVE_PATH.test(value)) {
|
|
86
|
+
result = { windows: win32.normalize(value.replace(/\//g, "\\")) };
|
|
87
|
+
} else if (/^\\\\/.test(value)) {
|
|
88
|
+
throw new Error(`windows_build: UNC 경로는 지원하지 않는다: ${value}`);
|
|
89
|
+
} else if (platform === "win32") {
|
|
90
|
+
result = { windows: win32.resolve(cwd, value) };
|
|
91
|
+
} else {
|
|
92
|
+
const resolved = posix.resolve(cwd, value);
|
|
93
|
+
if (!WSL_DRIVE_PATH.test(resolved)) {
|
|
94
|
+
throw new Error(`windows_build: /mnt/<드라이브>/ 아래 Windows 드라이브 프로젝트만 빌드한다(받은 경로: ${resolved}).`);
|
|
95
|
+
}
|
|
96
|
+
result = { posix: resolved };
|
|
97
|
+
}
|
|
98
|
+
const name = result.windows ?? result.posix!;
|
|
99
|
+
if (!PROJECT_EXTENSION.test(name)) {
|
|
100
|
+
throw new Error(`windows_build: project는 .sln/.slnx 또는 *proj 파일이어야 한다: ${name}`);
|
|
101
|
+
}
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function validateToken(label: string, value: string, pattern: RegExp): string {
|
|
106
|
+
const trimmed = value.trim();
|
|
107
|
+
if (!trimmed || !pattern.test(trimmed)) throw new Error(`windows_build: ${label} 값이 올바르지 않다: ${JSON.stringify(value)}`);
|
|
108
|
+
return trimmed;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** MSBuild 명령줄 인자를 만든다. project·logFile은 이미 Windows 경로여야 한다. */
|
|
112
|
+
export function buildMsbuildArgs(params: Omit<WindowsBuildParams, "project">, project: string, logFile: string): string[] {
|
|
113
|
+
if (logFile.includes(";")) throw new Error(`windows_build: 로그 경로에 ';'가 있어 MSBuild 파일 로거에 넘길 수 없다: ${logFile}`);
|
|
114
|
+
const args = ["-nologo", "-m", "-nodeReuse:false", "-noconlog", `-flp:logfile=${logFile};encoding=UTF-8;verbosity=minimal`];
|
|
115
|
+
if (params.restore) args.push("-restore");
|
|
116
|
+
const properties: Array<[string, string]> = [];
|
|
117
|
+
if (params.configuration !== undefined) properties.push(["Configuration", validateToken("configuration", params.configuration, /^[\w .+-]+$/)]);
|
|
118
|
+
if (params.platform !== undefined) properties.push(["Platform", validateToken("platform", params.platform, /^[\w .+-]+$/)]);
|
|
119
|
+
for (const [name, value] of Object.entries(params.properties ?? {})) {
|
|
120
|
+
if (!/^[A-Za-z_][\w.]*$/.test(name)) throw new Error(`windows_build: 속성 이름이 올바르지 않다: ${JSON.stringify(name)}`);
|
|
121
|
+
if (DENIED_PROPERTIES[name.toLowerCase()]) throw new Error(`windows_build: 빌드만 허용하므로 속성 ${name}은 쓸 수 없다.`);
|
|
122
|
+
if (/[\r\n\0]/.test(value)) throw new Error(`windows_build: 속성 ${name} 값에 줄바꿈이 있다.`);
|
|
123
|
+
properties.push([name, value]);
|
|
124
|
+
}
|
|
125
|
+
for (const [name, value] of properties) args.push(`-p:${name}=${value}`);
|
|
126
|
+
if (params.target !== undefined) {
|
|
127
|
+
const targets = params.target.split(/[;,]/).map((item) => item.trim()).filter(Boolean);
|
|
128
|
+
if (targets.length === 0) throw new Error("windows_build: target이 비어 있다.");
|
|
129
|
+
for (const item of targets) {
|
|
130
|
+
if (!/^[A-Za-z_][\w.-]*$/.test(item)) throw new Error(`windows_build: target 이름이 올바르지 않다: ${JSON.stringify(item)}`);
|
|
131
|
+
if (DENIED_TARGETS[item.toLowerCase()]) throw new Error(`windows_build: 빌드만 허용하므로 target ${item}은 쓸 수 없다.`);
|
|
132
|
+
}
|
|
133
|
+
args.push(`-t:${targets.join(";")}`);
|
|
134
|
+
}
|
|
135
|
+
args.push(project);
|
|
136
|
+
return args;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* MSBuild 로그·콘솔 텍스트에서 `origin: error CODE: message` / `warning` 줄을 모은다.
|
|
141
|
+
* 같은 줄이 요약부에 다시 나오므로 줄 전체가 같으면 한 번만 센다. 진단 접두어(error/warning)는 현지화되지 않아
|
|
142
|
+
* 한국어 MSBuild 로그에서도 같다. 요약의 "N 오류" 문구는 현지화되므로 쓰지 않는다.
|
|
143
|
+
*/
|
|
144
|
+
export function parseBuildLog(text: string, maxErrors = DEFAULT_MAX_ERRORS): BuildDiagnostics {
|
|
145
|
+
const errors = new Set<string>();
|
|
146
|
+
const warnings = new Set<string>();
|
|
147
|
+
for (const raw of text.replace(/^\uFEFF/, "").split(/\r?\n/)) {
|
|
148
|
+
const match = DIAGNOSTIC_LINE.exec(raw);
|
|
149
|
+
if (!match?.groups) continue;
|
|
150
|
+
(match.groups.severity.toLowerCase() === "error" ? errors : warnings).add(raw.trim());
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
errorCount: errors.size,
|
|
154
|
+
warningCount: warnings.size,
|
|
155
|
+
errors: [...errors].slice(0, maxErrors),
|
|
156
|
+
warnings: [...warnings].slice(0, Math.min(maxErrors, MAX_WARNING_LINES)),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** vswhere 출력에서 첫 MSBuild.exe 경로를 고른다. 없으면 undefined. */
|
|
161
|
+
export function parseVswhereOutput(stdout: string): string | undefined {
|
|
162
|
+
return stdout.split(/\r?\n/).map((line) => line.trim()).find((line) => /msbuild\.exe$/i.test(line));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function nonEmptyLines(text: string): string[] {
|
|
166
|
+
return text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function describeFailure(label: string, result: ExecResult): string {
|
|
170
|
+
return `${label} (exit ${result.code}): ${(result.stderr || result.stdout || "").trim() || "출력 없음"}`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export async function runWindowsBuild(params: WindowsBuildParams, deps: BuildDeps): Promise<BuildResult> {
|
|
174
|
+
const { exec, cwd } = deps;
|
|
175
|
+
const maxErrors = Math.max(1, Math.min(200, Math.trunc(params.maxErrors ?? DEFAULT_MAX_ERRORS)));
|
|
176
|
+
const timeoutSeconds = Math.max(10, Math.min(MAX_TIMEOUT_SECONDS, Math.trunc(params.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS)));
|
|
177
|
+
const input = normalizeProjectInput(params.project, cwd, deps.wsl ? "linux" : "win32");
|
|
178
|
+
|
|
179
|
+
const convert = async (flag: "-w" | "-u", path: string): Promise<string> => {
|
|
180
|
+
if (!deps.wsl) return path;
|
|
181
|
+
const result = await exec("wslpath", [flag, path], { cwd });
|
|
182
|
+
const converted = result.stdout.trim();
|
|
183
|
+
if (result.code !== 0 || !converted) throw new Error(describeFailure(`windows_build: wslpath ${flag} ${path} 변환 실패`, result));
|
|
184
|
+
return converted;
|
|
185
|
+
};
|
|
186
|
+
const toWindows = (path: string) => convert("-w", path);
|
|
187
|
+
const toPosix = (path: string) => convert("-u", path);
|
|
188
|
+
|
|
189
|
+
const projectWindows = input.windows ?? await toWindows(input.posix!);
|
|
190
|
+
const projectPosix = input.posix ?? await toPosix(input.windows!);
|
|
191
|
+
if (!(await deps.exists(projectPosix))) throw new Error(`windows_build: 프로젝트 파일이 없다: ${projectPosix}`);
|
|
192
|
+
const projectDir = deps.wsl ? posix.dirname(projectPosix) : win32.dirname(projectWindows);
|
|
193
|
+
|
|
194
|
+
// Windows 쪽 TEMP와 Program Files (x86). cmd를 UTF-8 코드페이지로 바꿔 한국어 사용자 이름 경로도 깨지지 않게 읽는다.
|
|
195
|
+
let tempDir: string | undefined;
|
|
196
|
+
let programFilesX86: string | undefined;
|
|
197
|
+
if (deps.wsl) {
|
|
198
|
+
const result = await exec("cmd.exe", ["/c", "chcp 65001>nul&echo %TEMP%&echo %ProgramFiles(x86)%"], { cwd: projectDir });
|
|
199
|
+
const lines = nonEmptyLines(result.stdout);
|
|
200
|
+
if (result.code === 0 && DRIVE_PATH.test(lines[0] ?? "")) tempDir = lines[0];
|
|
201
|
+
if (result.code === 0 && DRIVE_PATH.test(lines[1] ?? "")) programFilesX86 = lines[1];
|
|
202
|
+
} else {
|
|
203
|
+
tempDir = deps.env("TEMP");
|
|
204
|
+
programFilesX86 = deps.env("ProgramFiles(x86)");
|
|
205
|
+
}
|
|
206
|
+
if (!tempDir) throw new Error("windows_build: Windows TEMP 폴더를 알아내지 못했다.");
|
|
207
|
+
programFilesX86 ??= DEFAULT_PROGRAM_FILES_X86;
|
|
208
|
+
|
|
209
|
+
const vswhereWindows = win32.join(programFilesX86, VSWHERE_RELATIVE);
|
|
210
|
+
const vswherePosix = await toPosix(vswhereWindows);
|
|
211
|
+
if (!(await deps.exists(vswherePosix))) {
|
|
212
|
+
throw new Error(`windows_build: MSBuild를 찾지 못했다. vswhere.exe가 없다(${vswhereWindows}). Visual Studio 또는 Build Tools(MSBuild 구성 요소)를 설치해야 한다.`);
|
|
213
|
+
}
|
|
214
|
+
const found = await exec(
|
|
215
|
+
vswherePosix,
|
|
216
|
+
["-latest", "-products", "*", "-requires", "Microsoft.Component.MSBuild", "-find", "MSBuild\\**\\Bin\\MSBuild.exe", "-utf8"],
|
|
217
|
+
{ cwd: projectDir },
|
|
218
|
+
);
|
|
219
|
+
const msbuildWindows = found.code === 0 ? parseVswhereOutput(found.stdout) : undefined;
|
|
220
|
+
if (!msbuildWindows) {
|
|
221
|
+
throw new Error(`windows_build: MSBuild를 찾지 못했다. vswhere가 MSBuild 구성 요소가 있는 설치를 찾지 못했다${found.code === 0 ? "" : ` (exit ${found.code})`}. Visual Studio 또는 Build Tools(MSBuild 구성 요소)를 설치해야 한다.`);
|
|
222
|
+
}
|
|
223
|
+
const msbuildPosix = await toPosix(msbuildWindows);
|
|
224
|
+
if (!(await deps.exists(msbuildPosix))) throw new Error(`windows_build: vswhere가 알려준 MSBuild가 없다: ${msbuildWindows}`);
|
|
225
|
+
const versionResult = await exec(msbuildPosix, ["-version", "-nologo"], { cwd: projectDir });
|
|
226
|
+
const msbuildVersion = nonEmptyLines(versionResult.stdout).at(-1) ?? "unknown";
|
|
227
|
+
|
|
228
|
+
const logDirWindows = win32.join(tempDir, "omp-windows-build");
|
|
229
|
+
const logDirPosix = await toPosix(logDirWindows);
|
|
230
|
+
await deps.mkdirp(logDirPosix);
|
|
231
|
+
const stamp = new Date(deps.now()).toISOString().replace(/\D/g, "").slice(0, 14);
|
|
232
|
+
const logName = `build-${stamp}-${Math.random().toString(36).slice(2, 8)}.log`;
|
|
233
|
+
const logPath = win32.join(logDirWindows, logName);
|
|
234
|
+
const logPathPosix = deps.wsl ? posix.join(logDirPosix, logName) : logPath;
|
|
235
|
+
|
|
236
|
+
const args = buildMsbuildArgs(params, projectWindows, logPath);
|
|
237
|
+
const started = deps.now();
|
|
238
|
+
const run = await exec(msbuildPosix, args, { cwd: projectDir, timeout: timeoutSeconds * 1000 });
|
|
239
|
+
const durationMs = deps.now() - started;
|
|
240
|
+
|
|
241
|
+
const logText = await deps.readText(logPathPosix);
|
|
242
|
+
const consoleText = `${run.stdout}\n${run.stderr ?? ""}`;
|
|
243
|
+
// 로그가 없으면 MSBuild가 빌드 전에 멈춘 것(MSB1009 프로젝트 없음 등)이라 콘솔 출력에서 읽는다.
|
|
244
|
+
const diagnostics = parseBuildLog(logText ?? consoleText, maxErrors);
|
|
245
|
+
const failed = run.code !== 0;
|
|
246
|
+
return {
|
|
247
|
+
ok: !failed,
|
|
248
|
+
exitCode: run.code,
|
|
249
|
+
killed: Boolean(run.killed),
|
|
250
|
+
project: projectWindows,
|
|
251
|
+
msbuildPath: msbuildWindows,
|
|
252
|
+
msbuildVersion,
|
|
253
|
+
logPath,
|
|
254
|
+
logPathPosix,
|
|
255
|
+
logMissing: logText === undefined,
|
|
256
|
+
...diagnostics,
|
|
257
|
+
...(failed && diagnostics.errorCount === 0 ? { consoleTail: nonEmptyLines(consoleText).slice(-15) } : {}),
|
|
258
|
+
durationMs,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function formatBuildResult(result: BuildResult, maxErrors = DEFAULT_MAX_ERRORS): string {
|
|
263
|
+
const lines: string[] = [];
|
|
264
|
+
const seconds = (result.durationMs / 1000).toFixed(1);
|
|
265
|
+
const status = result.ok ? "빌드 성공" : result.killed ? "빌드 중단(시간 초과 또는 취소)" : "빌드 실패";
|
|
266
|
+
lines.push(`${status} — exit ${result.exitCode}, ${seconds}s`);
|
|
267
|
+
lines.push(`프로젝트: ${result.project}`);
|
|
268
|
+
lines.push(`MSBuild: ${result.msbuildPath} (${result.msbuildVersion})`);
|
|
269
|
+
lines.push(`오류 ${result.errorCount}개 · 경고 ${result.warningCount}개`);
|
|
270
|
+
if (result.errors.length > 0) {
|
|
271
|
+
lines.push("오류:", ...result.errors.map((line) => ` ${line}`));
|
|
272
|
+
if (result.errorCount > result.errors.length) lines.push(` … 외 ${result.errorCount - result.errors.length}개(전체는 로그 참고, 최대 ${maxErrors}줄 표시)`);
|
|
273
|
+
}
|
|
274
|
+
if (result.warnings.length > 0) {
|
|
275
|
+
lines.push("경고(앞 일부):", ...result.warnings.map((line) => ` ${line}`));
|
|
276
|
+
}
|
|
277
|
+
if (result.consoleTail?.length) lines.push("MSBuild 콘솔 끝부분:", ...result.consoleTail.map((line) => ` ${line}`));
|
|
278
|
+
lines.push(`로그: ${result.logPath}${result.logMissing ? " (생성되지 않음)" : ""}`);
|
|
279
|
+
if (result.logPathPosix !== result.logPath) lines.push(`로그(POSIX): ${result.logPathPosix}`);
|
|
280
|
+
return lines.join("\n");
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const defaultDeps = (cwd: string, exec: Exec): BuildDeps => ({
|
|
284
|
+
exec,
|
|
285
|
+
cwd,
|
|
286
|
+
wsl: process.platform === "linux" && Boolean(process.env.WSL_DISTRO_NAME),
|
|
287
|
+
exists: (path) => access(path).then(() => true, () => false),
|
|
288
|
+
mkdirp: async (path) => { await mkdir(path, { recursive: true }); },
|
|
289
|
+
readText: (path) => readFile(path, "utf8").then((text) => text, () => undefined),
|
|
290
|
+
env: (name) => process.env[name],
|
|
291
|
+
now: () => Date.now(),
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
const factory: CustomToolFactory = (pi) => ({
|
|
295
|
+
name: "windows_build",
|
|
296
|
+
label: "Windows Build",
|
|
297
|
+
loadMode: "essential",
|
|
298
|
+
description:
|
|
299
|
+
"Build a Windows-only project (.sln/.slnx/*proj, e.g. VB.NET or C# WinForms on a /mnt/<drive> or X:\\ path) with Windows MSBuild from WSL. "
|
|
300
|
+
+ "Locates MSBuild through vswhere.exe, converts paths with wslpath, runs `MSBuild -nologo -m` with a UTF-8 file logger, and returns exit code, MSBuild path/version, error/warning counts, the first error lines and the log path. "
|
|
301
|
+
+ "Build only: never runs the built program, never touches devices, never deploys or copies outputs; Run/Deploy/Publish/Install targets and Pre/PostBuildEvent properties are rejected. "
|
|
302
|
+
+ "`restore: true` adds -restore (NuGet network access); default is off. Build writes bin/obj inside the project folder as a normal build does.",
|
|
303
|
+
parameters: pi.zod.object({
|
|
304
|
+
project: pi.zod.string().describe("Solution or project file: /mnt/<drive>/... or X:\\... (relative paths resolve from the session cwd)"),
|
|
305
|
+
configuration: pi.zod.string().optional().describe("Configuration property, e.g. Debug or Release"),
|
|
306
|
+
platform: pi.zod.string().optional().describe("Platform property, e.g. x86, x64, Any CPU"),
|
|
307
|
+
target: pi.zod.string().optional().describe("MSBuild target(s), e.g. Rebuild or Clean;Build. Run/Deploy/Publish/Install are rejected"),
|
|
308
|
+
properties: pi.zod.record(pi.zod.string()).optional().describe("Extra -p:Name=Value properties"),
|
|
309
|
+
restore: pi.zod.boolean().optional().describe("Add -restore before building (default false)"),
|
|
310
|
+
maxErrors: pi.zod.number().optional().describe("Number of error lines to return (default 20, max 200)"),
|
|
311
|
+
timeoutSeconds: pi.zod.number().optional().describe("Build time limit in seconds (default 600, max 3600)"),
|
|
312
|
+
}),
|
|
313
|
+
|
|
314
|
+
async execute(_toolCallId, params, onUpdate, _ctx, signal) {
|
|
315
|
+
const exec: Exec = (command, args, options) => pi.exec(command, args, { ...options, signal });
|
|
316
|
+
onUpdate?.({
|
|
317
|
+
content: [{ type: "text", text: "MSBuild를 찾아 빌드하는 중입니다." }],
|
|
318
|
+
details: { phase: "build", project: params.project },
|
|
319
|
+
});
|
|
320
|
+
const result = await runWindowsBuild(params as WindowsBuildParams, defaultDeps(pi.cwd, exec));
|
|
321
|
+
return {
|
|
322
|
+
content: [{ type: "text", text: formatBuildResult(result, Math.max(1, Math.min(200, Math.trunc(params.maxErrors ?? DEFAULT_MAX_ERRORS)))) }],
|
|
323
|
+
details: result,
|
|
324
|
+
};
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
export default factory;
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|