@aipanel/core 1.2.5 → 1.2.7

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.
@@ -0,0 +1,53 @@
1
+ /** 是否为可诊断的源码文件(供宿主钩子过滤 edit/write 目标) */
2
+ export declare function isJsFile(filePath: string): boolean;
3
+ /** LSP 风格诊断项(供宿主写入 metadata.diagnostics 等结构化输出) */
4
+ export interface DiagnosticItem {
5
+ /** 所属文件路径(相对/绝对,按来源解析);跨文件诊断(全量模式)时必有 */
6
+ file?: string;
7
+ severity: number;
8
+ range: {
9
+ start: {
10
+ line: number;
11
+ character: number;
12
+ };
13
+ end: {
14
+ line: number;
15
+ character: number;
16
+ };
17
+ };
18
+ message: string;
19
+ source: string;
20
+ }
21
+ export interface TscResult {
22
+ rawOutput: string;
23
+ exitCode: number;
24
+ diagnostics?: DiagnosticItem[];
25
+ }
26
+ export interface EslintOutput {
27
+ text?: string;
28
+ diagnostics?: DiagnosticItem[];
29
+ }
30
+ export interface DiagnosticsResult {
31
+ eslintOutput: EslintOutput;
32
+ tscOutput: TscResult;
33
+ }
34
+ /**
35
+ * ESLint 检查,接受文件路径或 glob 模式。
36
+ * 结果按 error / warning 分级格式化;warnings 数量超限时截断并注明。
37
+ */
38
+ export declare function lintFiles(pattern: string, cwd: string, warnLimit?: number): Promise<EslintOutput>;
39
+ /** 从文件路径向上查找最近的 tsconfig.json 所在目录 */
40
+ export declare function findTsconfigDir(filePath: string): string | null;
41
+ /** 在工作区子目录中查找 tsconfig.json(不包括根目录;调用方已处理根目录场景) */
42
+ export declare function findAllTsconfigDirs(workspace: string): string[];
43
+ /** 运行 vue-tsc --build --noEmit,返回原始输出 */
44
+ export declare function runVueTsc(filePath: string | undefined, cwd: string): Promise<TscResult>;
45
+ /** 并行运行 ESLint + vue-tsc 检查(单文件或 glob) */
46
+ export declare function runAllChecks(pattern: string, cwd: string): Promise<DiagnosticsResult>;
47
+ /**
48
+ * 全量项目诊断:优先从根 tsconfig 运行一次 vue-tsc --build,
49
+ * 根无 tsconfig 时回退到逐个子目录 build;ESLint 以 "." 全量扫描。
50
+ */
51
+ export declare function runProjectDiagnostics(workspace: string): Promise<DiagnosticsResult>;
52
+ /** 组装统一的分区诊断文本(ESLint / vue-tsc,空结果显示占位文案) */
53
+ export declare function formatDiagnosticsSections(title: string, eslintOutput: EslintOutput, tscOutput: TscResult): string;
@@ -0,0 +1,266 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { exec } from "node:child_process";
4
+ import { createRequire } from "node:module";
5
+ import { SEVERITY_ERROR, SEVERITY_WARN } from "../constants.mjs";
6
+ import { createLogger } from "../node-logger.mjs";
7
+ const log = createLogger("Diagnostics");
8
+ const JS_EXTENSIONS = /* @__PURE__ */ new Set([
9
+ ".js",
10
+ ".jsx",
11
+ ".ts",
12
+ ".tsx",
13
+ ".mjs",
14
+ ".cjs",
15
+ ".mts",
16
+ ".cts",
17
+ ".vue"
18
+ ]);
19
+ function isJsFile(filePath) {
20
+ return JS_EXTENSIONS.has(path.extname(filePath));
21
+ }
22
+ let ESLintClass;
23
+ function loadESLint(workspace) {
24
+ if (ESLintClass) return;
25
+ log.debug("Loading eslint", { workspace });
26
+ try {
27
+ const req = createRequire(path.join(workspace, "package.json"));
28
+ const eslintModule = req("eslint");
29
+ ESLintClass ?? (ESLintClass = eslintModule.ESLint ?? eslintModule.FlatESLint);
30
+ log.debug("eslint loaded", { hasClass: !!ESLintClass });
31
+ } catch (e) {
32
+ log.warn("eslint not found", { error: e.message });
33
+ }
34
+ }
35
+ async function lintFiles(pattern, cwd, warnLimit = 5) {
36
+ loadESLint(cwd);
37
+ if (!ESLintClass) return {};
38
+ try {
39
+ const eslint = new ESLintClass({ cwd });
40
+ const results = await eslint.lintFiles(pattern);
41
+ const messages = results.flatMap(
42
+ (r) => (r.messages ?? []).map((m) => ({ ...m, filePath: r.filePath }))
43
+ );
44
+ log.debug("ESLint lint", {
45
+ pattern,
46
+ fileCount: results.length,
47
+ messageCount: messages.length
48
+ });
49
+ if (messages.length === 0) return {};
50
+ const ESLINT_ERROR = 2;
51
+ const ESLINT_WARN = 1;
52
+ const lines = [];
53
+ const errors = messages.filter((m) => m.severity === ESLINT_ERROR);
54
+ const warnings = messages.filter((m) => m.severity === ESLINT_WARN);
55
+ if (errors.length > 0) {
56
+ lines.push(
57
+ ...errors.map(
58
+ (m) => `ERROR [${m.filePath}:${m.line}:${m.column}] ${m.message} (${m.ruleId})`
59
+ )
60
+ );
61
+ }
62
+ if (warnings.length > 0) {
63
+ lines.push(
64
+ ...warnings.slice(0, warnLimit).map((m) => `WARN [${m.filePath}:${m.line}:${m.column}] ${m.message} (${m.ruleId})`)
65
+ );
66
+ if (warnings.length > warnLimit)
67
+ lines.push(`... and ${warnings.length - warnLimit} more warnings`);
68
+ }
69
+ const diagnostics = messages.map((m) => ({
70
+ severity: m.severity === ESLINT_ERROR ? SEVERITY_ERROR : m.severity === ESLINT_WARN ? SEVERITY_WARN : m.severity,
71
+ file: m.filePath,
72
+ range: {
73
+ start: { line: (m.line || 1) - 1, character: (m.column || 1) - 1 },
74
+ end: {
75
+ line: (m.endLine || m.line || 1) - 1,
76
+ character: (m.endColumn || m.column || 1) - 1
77
+ }
78
+ },
79
+ message: `[ESLint] ${m.message} (${m.ruleId})`,
80
+ source: "eslint"
81
+ }));
82
+ return { text: lines.length > 0 ? lines.join("\n") : void 0, diagnostics };
83
+ } catch (err) {
84
+ log.warn("ESLint failed", { pattern, error: err.message });
85
+ return {};
86
+ }
87
+ }
88
+ let _vueTscBin;
89
+ function resolveVueTscBin() {
90
+ if (_vueTscBin !== void 0) return _vueTscBin;
91
+ try {
92
+ const req = createRequire(import.meta.url);
93
+ _vueTscBin = req.resolve("vue-tsc/bin/vue-tsc.js");
94
+ } catch {
95
+ _vueTscBin = null;
96
+ }
97
+ return _vueTscBin;
98
+ }
99
+ function findTsconfigDir(filePath) {
100
+ const resolved = path.resolve(filePath);
101
+ let dir = path.dirname(resolved);
102
+ log.debug("findTsconfigDir start", { filePath: resolved });
103
+ while (true) {
104
+ const tsconfigPath = path.join(dir, "tsconfig.json");
105
+ if (fs.existsSync(tsconfigPath)) {
106
+ log.debug("findTsconfigDir found", { dir, tsconfigPath });
107
+ return dir;
108
+ }
109
+ const parent = path.dirname(dir);
110
+ if (parent === dir) {
111
+ log.warn("findTsconfigDir not found", { filePath: resolved });
112
+ return null;
113
+ }
114
+ dir = parent;
115
+ }
116
+ }
117
+ function findAllTsconfigDirs(workspace) {
118
+ const dirs = [];
119
+ function walk(dir) {
120
+ let entries;
121
+ try {
122
+ entries = fs.readdirSync(dir, { withFileTypes: true });
123
+ } catch {
124
+ return;
125
+ }
126
+ for (const entry of entries) {
127
+ if (!entry.isDirectory()) continue;
128
+ if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
129
+ const full = path.join(dir, entry.name);
130
+ if (fs.existsSync(path.join(full, "tsconfig.json"))) {
131
+ dirs.push(full);
132
+ }
133
+ walk(full);
134
+ }
135
+ }
136
+ walk(workspace);
137
+ log.debug("findAllTsconfigDirs result", {
138
+ workspace,
139
+ count: dirs.length,
140
+ dirs: dirs.map((d) => path.relative(workspace, d))
141
+ });
142
+ return dirs;
143
+ }
144
+ function parseTscDiags(rawOutput, filePath, projectDir) {
145
+ const errorLinePat = /^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+TS(\d+):\s+(.+)$/;
146
+ const diags = [];
147
+ const resolved = filePath ? path.resolve(filePath) : void 0;
148
+ const lines = rawOutput.split("\n");
149
+ for (const line of lines) {
150
+ const match = errorLinePat.exec(line);
151
+ if (match) {
152
+ const [, file, lineNum, col, severity, code, message] = match;
153
+ const resolvedFile = projectDir ? path.resolve(projectDir, file) : path.resolve(file);
154
+ if (resolved) {
155
+ if (resolvedFile !== resolved) continue;
156
+ }
157
+ diags.push({
158
+ severity: severity === "error" ? SEVERITY_ERROR : SEVERITY_WARN,
159
+ file: resolvedFile,
160
+ range: {
161
+ start: { line: Number(lineNum) - 1, character: Number(col) - 1 },
162
+ end: { line: Number(lineNum) - 1, character: Number(col) - 1 }
163
+ },
164
+ message: `[TS${code}] ${message}`,
165
+ source: "vue-tsc"
166
+ });
167
+ }
168
+ }
169
+ return diags;
170
+ }
171
+ async function runVueTsc(filePath, cwd) {
172
+ const dir = cwd;
173
+ const projectDir = filePath ? findTsconfigDir(filePath) ?? dir : dir;
174
+ log.debug("runVueTsc", {
175
+ filePath: filePath || "(all)",
176
+ cwd: dir,
177
+ projectDir,
178
+ processCwd: process.cwd()
179
+ });
180
+ const bin = resolveVueTscBin();
181
+ if (!bin) {
182
+ log.warn("vue-tsc bin not found", { projectDir });
183
+ return { rawOutput: "", exitCode: 0 };
184
+ }
185
+ const timeout = filePath ? 6e4 : 12e4;
186
+ const maxBuffer = filePath ? 10 * 1024 * 1024 : 50 * 1024 * 1024;
187
+ return new Promise((resolve) => {
188
+ exec(
189
+ `node "${bin}" --build --noEmit --pretty false`,
190
+ { cwd: projectDir, timeout, maxBuffer },
191
+ (error, stdout, stderr) => {
192
+ let rawOutput = stdout + stderr;
193
+ const killed = error?.killed;
194
+ const exitCode = typeof error?.code === "number" ? error.code : killed ? 1 : 0;
195
+ if (killed && !rawOutput) {
196
+ rawOutput = "vue-tsc \u68C0\u67E5\u8D85\u65F6\uFF0C\u8BF7\u5C1D\u8BD5\u7F29\u5C0F\u68C0\u67E5\u8303\u56F4\u6216\u4F18\u5316\u9879\u76EE\u914D\u7F6E\u3002";
197
+ }
198
+ const diagnostics = parseTscDiags(rawOutput, filePath, projectDir);
199
+ if (filePath) {
200
+ const resolved = path.resolve(filePath);
201
+ const errorLinePat = /^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+TS\d+:/;
202
+ const lines = rawOutput.split("\n");
203
+ const filtered = [];
204
+ let keep = false;
205
+ for (const line of lines) {
206
+ const m = errorLinePat.exec(line);
207
+ if (m) {
208
+ keep = path.resolve(projectDir, m[1]) === resolved;
209
+ } else if (!/^\s/.test(line)) {
210
+ keep = false;
211
+ }
212
+ if (keep) filtered.push(line);
213
+ }
214
+ rawOutput = filtered.join("\n");
215
+ }
216
+ log.debug("vue-tsc finished", {
217
+ filePath: filePath || "(all)",
218
+ exitCode,
219
+ outputLength: rawOutput.length
220
+ });
221
+ resolve({ rawOutput, exitCode, diagnostics });
222
+ }
223
+ );
224
+ });
225
+ }
226
+ async function runAllChecks(pattern, cwd) {
227
+ log.debug("runAllChecks", { pattern, cwd });
228
+ const [eslintOutput, tscOutput] = await Promise.all([
229
+ lintFiles(pattern, cwd),
230
+ runVueTsc(pattern, cwd)
231
+ ]);
232
+ return { eslintOutput, tscOutput };
233
+ }
234
+ async function runProjectDiagnostics(workspace) {
235
+ const tscDirs = fs.existsSync(path.join(workspace, "tsconfig.json")) ? [workspace] : findAllTsconfigDirs(workspace);
236
+ log.debug("Tsc dirs to check", { count: tscDirs.length, dirs: tscDirs });
237
+ const [eslintOutput, ...tscOutputs] = await Promise.all([
238
+ lintFiles(".", workspace, 10),
239
+ ...tscDirs.map((dir) => runVueTsc(void 0, dir))
240
+ ]);
241
+ const mergedTsc = {
242
+ rawOutput: tscOutputs.flatMap((o) => o.rawOutput).filter(Boolean).join("\n"),
243
+ exitCode: tscOutputs.reduce((max, o) => Math.max(max, o.exitCode), 0),
244
+ diagnostics: tscOutputs.flatMap((o) => o.diagnostics ?? [])
245
+ };
246
+ return { eslintOutput, tscOutput: mergedTsc };
247
+ }
248
+ function formatDiagnosticsSections(title, eslintOutput, tscOutput) {
249
+ const parts = [];
250
+ parts.push("## ESLint\n\n" + (eslintOutput.text || "\u6CA1\u6709\u53D1\u73B0\u95EE\u9898"));
251
+ const tscLines = tscOutput.rawOutput.trim();
252
+ parts.push("## vue-tsc\n\n" + (tscLines || "\u6CA1\u6709\u53D1\u73B0\u7C7B\u578B\u9519\u8BEF"));
253
+ return `${title}
254
+
255
+ ` + parts.join("\n\n");
256
+ }
257
+ export {
258
+ findAllTsconfigDirs,
259
+ findTsconfigDir,
260
+ formatDiagnosticsSections,
261
+ isJsFile,
262
+ lintFiles,
263
+ runAllChecks,
264
+ runProjectDiagnostics,
265
+ runVueTsc
266
+ };
package/es/node.d.ts CHANGED
@@ -4,3 +4,4 @@ export * from "./node-logger";
4
4
  export * from "./process-logger";
5
5
  export * from "./file-log-watcher";
6
6
  export * from "./node-utils";
7
+ export * from "./node/diagnostics";
package/es/node.mjs CHANGED
@@ -4,3 +4,4 @@ export * from "./node-logger.mjs";
4
4
  export * from "./process-logger.mjs";
5
5
  export * from "./file-log-watcher.mjs";
6
6
  export * from "./node-utils.mjs";
7
+ export * from "./node/diagnostics.mjs";
package/es/options.d.ts CHANGED
@@ -48,8 +48,6 @@ export interface PluginOptions<P extends Record<string, unknown> = Record<string
48
48
  settings?: unknown;
49
49
  /** @deprecated 使用 providerOptions.enableLsp */
50
50
  enableLsp?: boolean;
51
- /** @deprecated 使用 providerOptions.enableBlockOnError */
52
- enableBlockOnError?: boolean;
53
51
  /** @deprecated 使用 providerOptions.enablePrettier */
54
52
  enablePrettier?: boolean;
55
53
  }
@@ -0,0 +1,307 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+ var diagnostics_exports = {};
29
+ __export(diagnostics_exports, {
30
+ findAllTsconfigDirs: () => findAllTsconfigDirs,
31
+ findTsconfigDir: () => findTsconfigDir,
32
+ formatDiagnosticsSections: () => formatDiagnosticsSections,
33
+ isJsFile: () => isJsFile,
34
+ lintFiles: () => lintFiles,
35
+ runAllChecks: () => runAllChecks,
36
+ runProjectDiagnostics: () => runProjectDiagnostics,
37
+ runVueTsc: () => runVueTsc
38
+ });
39
+ module.exports = __toCommonJS(diagnostics_exports);
40
+ var import_node_fs = __toESM(require("node:fs"));
41
+ var import_node_path = __toESM(require("node:path"));
42
+ var import_node_child_process = require("node:child_process");
43
+ var import_node_module = require("node:module");
44
+ var import_constants = require("../constants.cjs");
45
+ var import_node_logger = require("../node-logger.cjs");
46
+ const import_meta = {};
47
+ const log = (0, import_node_logger.createLogger)("Diagnostics");
48
+ const JS_EXTENSIONS = /* @__PURE__ */ new Set([
49
+ ".js",
50
+ ".jsx",
51
+ ".ts",
52
+ ".tsx",
53
+ ".mjs",
54
+ ".cjs",
55
+ ".mts",
56
+ ".cts",
57
+ ".vue"
58
+ ]);
59
+ function isJsFile(filePath) {
60
+ return JS_EXTENSIONS.has(import_node_path.default.extname(filePath));
61
+ }
62
+ let ESLintClass;
63
+ function loadESLint(workspace) {
64
+ if (ESLintClass) return;
65
+ log.debug("Loading eslint", { workspace });
66
+ try {
67
+ const req = (0, import_node_module.createRequire)(import_node_path.default.join(workspace, "package.json"));
68
+ const eslintModule = req("eslint");
69
+ ESLintClass ?? (ESLintClass = eslintModule.ESLint ?? eslintModule.FlatESLint);
70
+ log.debug("eslint loaded", { hasClass: !!ESLintClass });
71
+ } catch (e) {
72
+ log.warn("eslint not found", { error: e.message });
73
+ }
74
+ }
75
+ async function lintFiles(pattern, cwd, warnLimit = 5) {
76
+ loadESLint(cwd);
77
+ if (!ESLintClass) return {};
78
+ try {
79
+ const eslint = new ESLintClass({ cwd });
80
+ const results = await eslint.lintFiles(pattern);
81
+ const messages = results.flatMap(
82
+ (r) => (r.messages ?? []).map((m) => ({ ...m, filePath: r.filePath }))
83
+ );
84
+ log.debug("ESLint lint", {
85
+ pattern,
86
+ fileCount: results.length,
87
+ messageCount: messages.length
88
+ });
89
+ if (messages.length === 0) return {};
90
+ const ESLINT_ERROR = 2;
91
+ const ESLINT_WARN = 1;
92
+ const lines = [];
93
+ const errors = messages.filter((m) => m.severity === ESLINT_ERROR);
94
+ const warnings = messages.filter((m) => m.severity === ESLINT_WARN);
95
+ if (errors.length > 0) {
96
+ lines.push(
97
+ ...errors.map(
98
+ (m) => `ERROR [${m.filePath}:${m.line}:${m.column}] ${m.message} (${m.ruleId})`
99
+ )
100
+ );
101
+ }
102
+ if (warnings.length > 0) {
103
+ lines.push(
104
+ ...warnings.slice(0, warnLimit).map((m) => `WARN [${m.filePath}:${m.line}:${m.column}] ${m.message} (${m.ruleId})`)
105
+ );
106
+ if (warnings.length > warnLimit)
107
+ lines.push(`... and ${warnings.length - warnLimit} more warnings`);
108
+ }
109
+ const diagnostics = messages.map((m) => ({
110
+ severity: m.severity === ESLINT_ERROR ? import_constants.SEVERITY_ERROR : m.severity === ESLINT_WARN ? import_constants.SEVERITY_WARN : m.severity,
111
+ file: m.filePath,
112
+ range: {
113
+ start: { line: (m.line || 1) - 1, character: (m.column || 1) - 1 },
114
+ end: {
115
+ line: (m.endLine || m.line || 1) - 1,
116
+ character: (m.endColumn || m.column || 1) - 1
117
+ }
118
+ },
119
+ message: `[ESLint] ${m.message} (${m.ruleId})`,
120
+ source: "eslint"
121
+ }));
122
+ return { text: lines.length > 0 ? lines.join("\n") : void 0, diagnostics };
123
+ } catch (err) {
124
+ log.warn("ESLint failed", { pattern, error: err.message });
125
+ return {};
126
+ }
127
+ }
128
+ let _vueTscBin;
129
+ function resolveVueTscBin() {
130
+ if (_vueTscBin !== void 0) return _vueTscBin;
131
+ try {
132
+ const req = (0, import_node_module.createRequire)(import_meta.url);
133
+ _vueTscBin = req.resolve("vue-tsc/bin/vue-tsc.js");
134
+ } catch {
135
+ _vueTscBin = null;
136
+ }
137
+ return _vueTscBin;
138
+ }
139
+ function findTsconfigDir(filePath) {
140
+ const resolved = import_node_path.default.resolve(filePath);
141
+ let dir = import_node_path.default.dirname(resolved);
142
+ log.debug("findTsconfigDir start", { filePath: resolved });
143
+ while (true) {
144
+ const tsconfigPath = import_node_path.default.join(dir, "tsconfig.json");
145
+ if (import_node_fs.default.existsSync(tsconfigPath)) {
146
+ log.debug("findTsconfigDir found", { dir, tsconfigPath });
147
+ return dir;
148
+ }
149
+ const parent = import_node_path.default.dirname(dir);
150
+ if (parent === dir) {
151
+ log.warn("findTsconfigDir not found", { filePath: resolved });
152
+ return null;
153
+ }
154
+ dir = parent;
155
+ }
156
+ }
157
+ function findAllTsconfigDirs(workspace) {
158
+ const dirs = [];
159
+ function walk(dir) {
160
+ let entries;
161
+ try {
162
+ entries = import_node_fs.default.readdirSync(dir, { withFileTypes: true });
163
+ } catch {
164
+ return;
165
+ }
166
+ for (const entry of entries) {
167
+ if (!entry.isDirectory()) continue;
168
+ if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
169
+ const full = import_node_path.default.join(dir, entry.name);
170
+ if (import_node_fs.default.existsSync(import_node_path.default.join(full, "tsconfig.json"))) {
171
+ dirs.push(full);
172
+ }
173
+ walk(full);
174
+ }
175
+ }
176
+ walk(workspace);
177
+ log.debug("findAllTsconfigDirs result", {
178
+ workspace,
179
+ count: dirs.length,
180
+ dirs: dirs.map((d) => import_node_path.default.relative(workspace, d))
181
+ });
182
+ return dirs;
183
+ }
184
+ function parseTscDiags(rawOutput, filePath, projectDir) {
185
+ const errorLinePat = /^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+TS(\d+):\s+(.+)$/;
186
+ const diags = [];
187
+ const resolved = filePath ? import_node_path.default.resolve(filePath) : void 0;
188
+ const lines = rawOutput.split("\n");
189
+ for (const line of lines) {
190
+ const match = errorLinePat.exec(line);
191
+ if (match) {
192
+ const [, file, lineNum, col, severity, code, message] = match;
193
+ const resolvedFile = projectDir ? import_node_path.default.resolve(projectDir, file) : import_node_path.default.resolve(file);
194
+ if (resolved) {
195
+ if (resolvedFile !== resolved) continue;
196
+ }
197
+ diags.push({
198
+ severity: severity === "error" ? import_constants.SEVERITY_ERROR : import_constants.SEVERITY_WARN,
199
+ file: resolvedFile,
200
+ range: {
201
+ start: { line: Number(lineNum) - 1, character: Number(col) - 1 },
202
+ end: { line: Number(lineNum) - 1, character: Number(col) - 1 }
203
+ },
204
+ message: `[TS${code}] ${message}`,
205
+ source: "vue-tsc"
206
+ });
207
+ }
208
+ }
209
+ return diags;
210
+ }
211
+ async function runVueTsc(filePath, cwd) {
212
+ const dir = cwd;
213
+ const projectDir = filePath ? findTsconfigDir(filePath) ?? dir : dir;
214
+ log.debug("runVueTsc", {
215
+ filePath: filePath || "(all)",
216
+ cwd: dir,
217
+ projectDir,
218
+ processCwd: process.cwd()
219
+ });
220
+ const bin = resolveVueTscBin();
221
+ if (!bin) {
222
+ log.warn("vue-tsc bin not found", { projectDir });
223
+ return { rawOutput: "", exitCode: 0 };
224
+ }
225
+ const timeout = filePath ? 6e4 : 12e4;
226
+ const maxBuffer = filePath ? 10 * 1024 * 1024 : 50 * 1024 * 1024;
227
+ return new Promise((resolve) => {
228
+ (0, import_node_child_process.exec)(
229
+ `node "${bin}" --build --noEmit --pretty false`,
230
+ { cwd: projectDir, timeout, maxBuffer },
231
+ (error, stdout, stderr) => {
232
+ let rawOutput = stdout + stderr;
233
+ const killed = error?.killed;
234
+ const exitCode = typeof error?.code === "number" ? error.code : killed ? 1 : 0;
235
+ if (killed && !rawOutput) {
236
+ rawOutput = "vue-tsc \u68C0\u67E5\u8D85\u65F6\uFF0C\u8BF7\u5C1D\u8BD5\u7F29\u5C0F\u68C0\u67E5\u8303\u56F4\u6216\u4F18\u5316\u9879\u76EE\u914D\u7F6E\u3002";
237
+ }
238
+ const diagnostics = parseTscDiags(rawOutput, filePath, projectDir);
239
+ if (filePath) {
240
+ const resolved = import_node_path.default.resolve(filePath);
241
+ const errorLinePat = /^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+TS\d+:/;
242
+ const lines = rawOutput.split("\n");
243
+ const filtered = [];
244
+ let keep = false;
245
+ for (const line of lines) {
246
+ const m = errorLinePat.exec(line);
247
+ if (m) {
248
+ keep = import_node_path.default.resolve(projectDir, m[1]) === resolved;
249
+ } else if (!/^\s/.test(line)) {
250
+ keep = false;
251
+ }
252
+ if (keep) filtered.push(line);
253
+ }
254
+ rawOutput = filtered.join("\n");
255
+ }
256
+ log.debug("vue-tsc finished", {
257
+ filePath: filePath || "(all)",
258
+ exitCode,
259
+ outputLength: rawOutput.length
260
+ });
261
+ resolve({ rawOutput, exitCode, diagnostics });
262
+ }
263
+ );
264
+ });
265
+ }
266
+ async function runAllChecks(pattern, cwd) {
267
+ log.debug("runAllChecks", { pattern, cwd });
268
+ const [eslintOutput, tscOutput] = await Promise.all([
269
+ lintFiles(pattern, cwd),
270
+ runVueTsc(pattern, cwd)
271
+ ]);
272
+ return { eslintOutput, tscOutput };
273
+ }
274
+ async function runProjectDiagnostics(workspace) {
275
+ const tscDirs = import_node_fs.default.existsSync(import_node_path.default.join(workspace, "tsconfig.json")) ? [workspace] : findAllTsconfigDirs(workspace);
276
+ log.debug("Tsc dirs to check", { count: tscDirs.length, dirs: tscDirs });
277
+ const [eslintOutput, ...tscOutputs] = await Promise.all([
278
+ lintFiles(".", workspace, 10),
279
+ ...tscDirs.map((dir) => runVueTsc(void 0, dir))
280
+ ]);
281
+ const mergedTsc = {
282
+ rawOutput: tscOutputs.flatMap((o) => o.rawOutput).filter(Boolean).join("\n"),
283
+ exitCode: tscOutputs.reduce((max, o) => Math.max(max, o.exitCode), 0),
284
+ diagnostics: tscOutputs.flatMap((o) => o.diagnostics ?? [])
285
+ };
286
+ return { eslintOutput, tscOutput: mergedTsc };
287
+ }
288
+ function formatDiagnosticsSections(title, eslintOutput, tscOutput) {
289
+ const parts = [];
290
+ parts.push("## ESLint\n\n" + (eslintOutput.text || "\u6CA1\u6709\u53D1\u73B0\u95EE\u9898"));
291
+ const tscLines = tscOutput.rawOutput.trim();
292
+ parts.push("## vue-tsc\n\n" + (tscLines || "\u6CA1\u6709\u53D1\u73B0\u7C7B\u578B\u9519\u8BEF"));
293
+ return `${title}
294
+
295
+ ` + parts.join("\n\n");
296
+ }
297
+ // Annotate the CommonJS export names for ESM import in node:
298
+ 0 && (module.exports = {
299
+ findAllTsconfigDirs,
300
+ findTsconfigDir,
301
+ formatDiagnosticsSections,
302
+ isJsFile,
303
+ lintFiles,
304
+ runAllChecks,
305
+ runProjectDiagnostics,
306
+ runVueTsc
307
+ });
@@ -0,0 +1,53 @@
1
+ /** 是否为可诊断的源码文件(供宿主钩子过滤 edit/write 目标) */
2
+ export declare function isJsFile(filePath: string): boolean;
3
+ /** LSP 风格诊断项(供宿主写入 metadata.diagnostics 等结构化输出) */
4
+ export interface DiagnosticItem {
5
+ /** 所属文件路径(相对/绝对,按来源解析);跨文件诊断(全量模式)时必有 */
6
+ file?: string;
7
+ severity: number;
8
+ range: {
9
+ start: {
10
+ line: number;
11
+ character: number;
12
+ };
13
+ end: {
14
+ line: number;
15
+ character: number;
16
+ };
17
+ };
18
+ message: string;
19
+ source: string;
20
+ }
21
+ export interface TscResult {
22
+ rawOutput: string;
23
+ exitCode: number;
24
+ diagnostics?: DiagnosticItem[];
25
+ }
26
+ export interface EslintOutput {
27
+ text?: string;
28
+ diagnostics?: DiagnosticItem[];
29
+ }
30
+ export interface DiagnosticsResult {
31
+ eslintOutput: EslintOutput;
32
+ tscOutput: TscResult;
33
+ }
34
+ /**
35
+ * ESLint 检查,接受文件路径或 glob 模式。
36
+ * 结果按 error / warning 分级格式化;warnings 数量超限时截断并注明。
37
+ */
38
+ export declare function lintFiles(pattern: string, cwd: string, warnLimit?: number): Promise<EslintOutput>;
39
+ /** 从文件路径向上查找最近的 tsconfig.json 所在目录 */
40
+ export declare function findTsconfigDir(filePath: string): string | null;
41
+ /** 在工作区子目录中查找 tsconfig.json(不包括根目录;调用方已处理根目录场景) */
42
+ export declare function findAllTsconfigDirs(workspace: string): string[];
43
+ /** 运行 vue-tsc --build --noEmit,返回原始输出 */
44
+ export declare function runVueTsc(filePath: string | undefined, cwd: string): Promise<TscResult>;
45
+ /** 并行运行 ESLint + vue-tsc 检查(单文件或 glob) */
46
+ export declare function runAllChecks(pattern: string, cwd: string): Promise<DiagnosticsResult>;
47
+ /**
48
+ * 全量项目诊断:优先从根 tsconfig 运行一次 vue-tsc --build,
49
+ * 根无 tsconfig 时回退到逐个子目录 build;ESLint 以 "." 全量扫描。
50
+ */
51
+ export declare function runProjectDiagnostics(workspace: string): Promise<DiagnosticsResult>;
52
+ /** 组装统一的分区诊断文本(ESLint / vue-tsc,空结果显示占位文案) */
53
+ export declare function formatDiagnosticsSections(title: string, eslintOutput: EslintOutput, tscOutput: TscResult): string;
package/lib/node.cjs CHANGED
@@ -20,6 +20,7 @@ __reExport(node_exports, require("./node-logger.cjs"), module.exports);
20
20
  __reExport(node_exports, require("./process-logger.cjs"), module.exports);
21
21
  __reExport(node_exports, require("./file-log-watcher.cjs"), module.exports);
22
22
  __reExport(node_exports, require("./node-utils.cjs"), module.exports);
23
+ __reExport(node_exports, require("./node/diagnostics.cjs"), module.exports);
23
24
  // Annotate the CommonJS export names for ESM import in node:
24
25
  0 && (module.exports = {
25
26
  ...require("./constants.cjs"),
@@ -27,5 +28,6 @@ __reExport(node_exports, require("./node-utils.cjs"), module.exports);
27
28
  ...require("./node-logger.cjs"),
28
29
  ...require("./process-logger.cjs"),
29
30
  ...require("./file-log-watcher.cjs"),
30
- ...require("./node-utils.cjs")
31
+ ...require("./node-utils.cjs"),
32
+ ...require("./node/diagnostics.cjs")
31
33
  });
package/lib/node.d.ts CHANGED
@@ -4,3 +4,4 @@ export * from "./node-logger";
4
4
  export * from "./process-logger";
5
5
  export * from "./file-log-watcher";
6
6
  export * from "./node-utils";
7
+ export * from "./node/diagnostics";
package/lib/options.d.ts CHANGED
@@ -48,8 +48,6 @@ export interface PluginOptions<P extends Record<string, unknown> = Record<string
48
48
  settings?: unknown;
49
49
  /** @deprecated 使用 providerOptions.enableLsp */
50
50
  enableLsp?: boolean;
51
- /** @deprecated 使用 providerOptions.enableBlockOnError */
52
- enableBlockOnError?: boolean;
53
51
  /** @deprecated 使用 providerOptions.enablePrettier */
54
52
  enablePrettier?: boolean;
55
53
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aipanel/core",
3
- "version": "1.2.5",
3
+ "version": "1.2.7",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "main": "lib/index.cjs",
@@ -26,6 +26,9 @@
26
26
  "access": "public",
27
27
  "registry": "https://registry.npmjs.org/"
28
28
  },
29
+ "dependencies": {
30
+ "vue-tsc": "^3.3.9"
31
+ },
29
32
  "scripts": {
30
33
  "build": "pagoda-cli build",
31
34
  "test": "vitest run",