@ohos-cpf/3rdloop 0.0.4 → 0.0.5
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/README.md +119 -128
- package/lib/cli.js +25 -0
- package/lib/serve.js +268 -0
- package/lib/update.js +46 -6
- package/lib/web-ext.js +454 -0
- package/lib/web.js +664 -0
- package/package.json +2 -1
- package/vendor/Server/Skills/arkts-code-use/SKILL.md +270 -0
- package/vendor/Server/Skills/arkts-code-use/assets/TEMPLATES.md +367 -0
- package/vendor/Server/Skills/arkts-code-use/references/API_VERIFICATION.md +144 -0
- package/vendor/Server/Skills/arkts-code-use/references/ARKTS_RULES.md +240 -0
- package/vendor/Server/Skills/arkts-code-use/references/CODE_PATTERNS.md +431 -0
- package/vendor/Server/Skills/arkts-code-use/references/SYNTAX_CHECK_GUIDE.md +164 -0
- package/vendor/Server/Skills/arkts-code-use/scripts/verify-arkts.cjs +428 -0
- package/vendor/Server/Skills/gitcode-repo-fork/SKILL.md +310 -0
- package/vendor/Server/Skills/gitcode-repo-fork/assets/FORK_REPORT_TEMPLATE.md +113 -0
- package/vendor/Server/Skills/gitcode-repo-fork/references/FORK_DECISION_GUIDE.md +124 -0
- package/vendor/Server/Skills/gitcode-repo-fork/references/GITCODE_FORK_API.md +95 -0
- package/vendor/Server/Skills/gitcode-repo-fork/scripts/gitcode-fork.cjs +285 -0
- package/vendor/VERSION +3 -3
- package/web/css/arktslibrarycheck.css +322 -0
- package/web/css/codecheck.css +464 -0
- package/web/css/flutterlibrarycheck.css +322 -0
- package/web/css/knowledge.css +332 -0
- package/web/css/loop.css +578 -0
- package/web/css/md-reader.css +240 -0
- package/web/css/rnlibrarycheck.css +322 -0
- package/web/css/theme.css +702 -0
- package/web/index.html +713 -0
- package/web/js/arktslibrarycheck.js +1413 -0
- package/web/js/codecheck.js +1039 -0
- package/web/js/flutterlibrarycheck.js +1364 -0
- package/web/js/health.js +69 -0
- package/web/js/knowledge.js +358 -0
- package/web/js/loop.js +1102 -0
- package/web/js/md-reader.js +435 -0
- package/web/js/navigation.js +238 -0
- package/web/js/rnlibrarycheck.js +1378 -0
- package/web/js/stats.js +110 -0
- package/web/js/theme.js +46 -0
- package/web/js/utils.js +228 -0
- package/web/knowledge.html +146 -0
- package/web/loop.html +219 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @tool verify-arkts
|
|
4
|
+
* @description 验证 ArkTS(.ets) 代码无语法错误。工程模式:对目标鸿蒙工程执行 devecocli build 并解析错误;独立模式:自动脚手架临时鸿蒙工程(devecocli create)、将待验证文件接入构建图(side-effect import)后编译验证,错误按 规则(文件:行:列) 映射回源文件输出。跨平台,需本机已安装 devecocli 与 DevEco Studio SDK。
|
|
5
|
+
* @param files: string[] - 独立模式:待验证的 .ets 文件绝对路径列表(与 project 二选一)
|
|
6
|
+
* @param project: string - 工程模式:目标鸿蒙工程路径(与 files 二选一)
|
|
7
|
+
* @param workDir: string - 独立模式:临时工程所在目录(默认系统临时目录;指定后工程保留可复用)
|
|
8
|
+
* @param keep: boolean - 独立模式:验证后保留临时工程(默认删除自动创建的临时目录)
|
|
9
|
+
* @param apiLevel: number - 脚手架 API Level(可选,传入 devecocli create)
|
|
10
|
+
* @param timeout: number - 构建超时毫秒数(默认 600000)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const fs = require('node:fs');
|
|
16
|
+
const os = require('node:os');
|
|
17
|
+
const path = require('node:path');
|
|
18
|
+
const { spawnSync } = require('node:child_process');
|
|
19
|
+
|
|
20
|
+
const PROJECT_NAME = 'ArkTSVerify';
|
|
21
|
+
const APP_NAME = 'ArkTSVerify';
|
|
22
|
+
const DEFAULT_BUILD_TIMEOUT_MS = 600000;
|
|
23
|
+
const CREATE_TIMEOUT_MS = 300000;
|
|
24
|
+
|
|
25
|
+
// ── 参数解析(兼容 MCP Gateway JSON 模式与 CLI 模式) ──────────
|
|
26
|
+
|
|
27
|
+
function parseArgs(argv) {
|
|
28
|
+
if (argv.length === 1 && typeof argv[0] === 'string' && argv[0].startsWith('{')) {
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(argv[0]);
|
|
31
|
+
} catch (e) {
|
|
32
|
+
usageAndExit('Invalid JSON argument');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const args = { files: [] };
|
|
36
|
+
for (let i = 0; i < argv.length; i++) {
|
|
37
|
+
const a = argv[i];
|
|
38
|
+
if (a === '--project' && i + 1 < argv.length) {
|
|
39
|
+
args.project = argv[++i];
|
|
40
|
+
} else if (a === '--files') {
|
|
41
|
+
while (i + 1 < argv.length && !argv[i + 1].startsWith('--')) {
|
|
42
|
+
args.files.push(argv[++i]);
|
|
43
|
+
}
|
|
44
|
+
} else if (a === '--work-dir' && i + 1 < argv.length) {
|
|
45
|
+
args.workDir = argv[++i];
|
|
46
|
+
} else if (a === '--keep') {
|
|
47
|
+
args.keep = true;
|
|
48
|
+
} else if (a === '--api-level' && i + 1 < argv.length) {
|
|
49
|
+
args.apiLevel = Number(argv[++i]);
|
|
50
|
+
} else if (a === '--timeout' && i + 1 < argv.length) {
|
|
51
|
+
args.timeout = Number(argv[++i]);
|
|
52
|
+
} else {
|
|
53
|
+
usageAndExit(`Unknown or incomplete argument: ${a}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return args;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function usageAndExit(message) {
|
|
60
|
+
if (message) {
|
|
61
|
+
console.error(`Error: ${message}`);
|
|
62
|
+
}
|
|
63
|
+
console.log('Usage:');
|
|
64
|
+
console.log(' node verify-arkts.cjs --project <工程目录>');
|
|
65
|
+
console.log(' node verify-arkts.cjs --files <a.ets> [b.ets ...] [--work-dir <目录>] [--keep] [--api-level <n>] [--timeout <ms>]');
|
|
66
|
+
console.log('MCP mode:');
|
|
67
|
+
console.log(` node verify-arkts.cjs '{"files":["/abs/a.ets"],"workDir":"/tmp/x","keep":false}'`);
|
|
68
|
+
process.exit(2);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── 通用工具 ─────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
const ANSI_RE = /\x1b\[[0-9;]*[A-Za-z]/g;
|
|
74
|
+
|
|
75
|
+
function stripAnsi(text) {
|
|
76
|
+
return text.replace(ANSI_RE, '');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function toPosix(p) {
|
|
80
|
+
return p.split(path.sep).join('/');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** 跨平台执行 CLI 命令(Windows 下走 shell 解析 .cmd) */
|
|
84
|
+
function runCli(cmd, args, options) {
|
|
85
|
+
const isWin = process.platform === 'win32';
|
|
86
|
+
const finalArgs = isWin ? args.map((a) => (/\s/.test(a) ? `"${a}"` : a)) : args;
|
|
87
|
+
return spawnSync(cmd, finalArgs, {
|
|
88
|
+
cwd: options.cwd,
|
|
89
|
+
encoding: 'utf-8',
|
|
90
|
+
timeout: options.timeout,
|
|
91
|
+
shell: isWin,
|
|
92
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function checkDevecocli() {
|
|
97
|
+
const res = runCli('devecocli', ['-V'], { cwd: process.cwd(), timeout: 30000 });
|
|
98
|
+
if (res.error || res.status !== 0) {
|
|
99
|
+
console.error('Error: devecocli not available. Install deveco-cli and ensure DevEco Studio + SDK are installed.');
|
|
100
|
+
process.exit(2);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** 计算一组绝对路径的最长公共父目录 */
|
|
105
|
+
function commonAncestor(files) {
|
|
106
|
+
if (files.length === 1) {
|
|
107
|
+
return path.dirname(files[0]);
|
|
108
|
+
}
|
|
109
|
+
let parts = toPosix(path.resolve(files[0])).split('/');
|
|
110
|
+
for (let i = 1; i < files.length; i++) {
|
|
111
|
+
const cur = toPosix(path.resolve(files[i])).split('/');
|
|
112
|
+
let j = 0;
|
|
113
|
+
while (j < parts.length && j < cur.length && parts[j] === cur[j]) {
|
|
114
|
+
j += 1;
|
|
115
|
+
}
|
|
116
|
+
parts = parts.slice(0, j);
|
|
117
|
+
}
|
|
118
|
+
return parts.join('/') || '/';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function copyRecursive(src, dest) {
|
|
122
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
123
|
+
fs.copyFileSync(src, dest);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function rmRecursive(dir) {
|
|
127
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ── 构建输出解析 ──────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 解析 devecocli build 输出。识别三类错误格式:
|
|
134
|
+
* 1. ArkTS 规则错误:Error Message: <msg> (arkts-规则名) At File: <路径>:<行>:<列>
|
|
135
|
+
* 2. 模块解析错误:Error Message: Cannot find module '...' ... At File: <路径>:<行>:<列>
|
|
136
|
+
* 3. Rollup/通用错误:ERROR: <错误码> <名称> Error + Error Message: <msg>. At file: <路径>(无行列)
|
|
137
|
+
* 返回 { successful: boolean, failed: boolean, errors: [{rule, file, line, col, message}], rawTail: string }
|
|
138
|
+
*/
|
|
139
|
+
function parseBuildOutput(rawOutput) {
|
|
140
|
+
const output = stripAnsi(rawOutput || '');
|
|
141
|
+
const errors = [];
|
|
142
|
+
const seen = new Set();
|
|
143
|
+
|
|
144
|
+
// 按 "ERROR: <错误码>" 头切分错误块
|
|
145
|
+
const blocks = output.split(/(?=ERROR: \d+)/);
|
|
146
|
+
for (const block of blocks) {
|
|
147
|
+
const headMatch = block.match(/^ERROR:\s*(\d+)\s+(.+?)\s*Error\s*$/m);
|
|
148
|
+
const msgMatch = block.match(/Error Message:\s*([^\r\n]+)/);
|
|
149
|
+
if (!msgMatch) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const full = msgMatch[1].trim();
|
|
153
|
+
if (seen.has(full)) {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
seen.add(full);
|
|
157
|
+
|
|
158
|
+
const code = headMatch ? `${headMatch[1]} ${headMatch[2]}` : 'build-error';
|
|
159
|
+
|
|
160
|
+
// 格式1:带规则名(arkts-*)与行列
|
|
161
|
+
let m = full.match(/^(.*)\s*\(([\w-]+)\)\s*At File:\s*(.+):(\d+):(\d+)$/);
|
|
162
|
+
if (m) {
|
|
163
|
+
errors.push({
|
|
164
|
+
message: m[1].trim(),
|
|
165
|
+
rule: m[2],
|
|
166
|
+
file: m[3].trim(),
|
|
167
|
+
line: Number(m[4]),
|
|
168
|
+
col: Number(m[5]),
|
|
169
|
+
});
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
// 格式2:无规则名,但带 At File + 行列(Cannot find module 等)
|
|
173
|
+
m = full.match(/^(.+?)\.?\s*At File:\s*(.+):(\d+):(\d+)$/);
|
|
174
|
+
if (m) {
|
|
175
|
+
errors.push({
|
|
176
|
+
message: m[1].trim(),
|
|
177
|
+
rule: code,
|
|
178
|
+
file: m[2].trim(),
|
|
179
|
+
line: Number(m[3]),
|
|
180
|
+
col: Number(m[4]),
|
|
181
|
+
});
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
// 格式3:Rollup 等(At file: 小写,无行列)
|
|
185
|
+
m = full.match(/At file:\s*(.+?)\s*\.?$/);
|
|
186
|
+
errors.push({
|
|
187
|
+
message: full.replace(/\s*At file:.*$/, '').replace(/\.$/, ''),
|
|
188
|
+
rule: code,
|
|
189
|
+
file: m ? m[1].trim() : '',
|
|
190
|
+
line: 0,
|
|
191
|
+
col: 0,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const successful = /BUILD SUCCESSFUL/.test(output) || /Build completed successfully/.test(output);
|
|
196
|
+
const failed = /BUILD FAILED/.test(output) || /COMPILE RESULT:FAIL/.test(output);
|
|
197
|
+
const tailLines = output.split(/\r?\n/).filter((l) => l.trim().length > 0);
|
|
198
|
+
const rawTail = tailLines.slice(-40).join('\n');
|
|
199
|
+
return { successful, failed, errors, rawTail };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ── 工程模式 ─────────────────────────────────────────────
|
|
203
|
+
|
|
204
|
+
function verifyProject(projectDir, timeoutMs) {
|
|
205
|
+
const profile = path.join(projectDir, 'build-profile.json5');
|
|
206
|
+
if (!fs.existsSync(profile)) {
|
|
207
|
+
console.error(`Error: not a valid HarmonyOS project (build-profile.json5 not found): ${projectDir}`);
|
|
208
|
+
process.exit(2);
|
|
209
|
+
}
|
|
210
|
+
console.log('=== ArkTS 编译验证(工程模式) ===');
|
|
211
|
+
console.log(`工程: ${projectDir}`);
|
|
212
|
+
console.log('');
|
|
213
|
+
const res = runCli('devecocli', ['build'], { cwd: projectDir, timeout: timeoutMs });
|
|
214
|
+
if (res.error && res.error.code === 'ETIMEDOUT') {
|
|
215
|
+
console.error(`Error: build timed out after ${timeoutMs}ms`);
|
|
216
|
+
process.exit(2);
|
|
217
|
+
}
|
|
218
|
+
const parsed = parseBuildOutput(`${res.stdout || ''}\n${res.stderr || ''}`);
|
|
219
|
+
reportResult(parsed, null);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ── 独立模式 ─────────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
function scaffoldProject(projectPath, apiLevel) {
|
|
225
|
+
const createArgs = ['create', '--app-name', APP_NAME, '--project-path', projectPath];
|
|
226
|
+
if (Number.isFinite(apiLevel) && apiLevel > 0) {
|
|
227
|
+
createArgs.push('--api-level', String(apiLevel));
|
|
228
|
+
}
|
|
229
|
+
const res = runCli('devecocli', createArgs, { cwd: process.cwd(), timeout: CREATE_TIMEOUT_MS });
|
|
230
|
+
const ok = fs.existsSync(path.join(projectPath, 'build-profile.json5'));
|
|
231
|
+
if (!ok) {
|
|
232
|
+
console.error('Error: devecocli create failed to scaffold verification project.');
|
|
233
|
+
const out = stripAnsi(`${res.stdout || ''}\n${res.stderr || ''}`);
|
|
234
|
+
if (out.trim().length > 0) {
|
|
235
|
+
console.error('---- create output ----');
|
|
236
|
+
console.error(out.split(/\r?\n/).slice(-20).join('\n'));
|
|
237
|
+
}
|
|
238
|
+
process.exit(2);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function verifyStandalone(files, options) {
|
|
243
|
+
const timeoutMs = options.timeout || DEFAULT_BUILD_TIMEOUT_MS;
|
|
244
|
+
|
|
245
|
+
// 1. 校验输入文件
|
|
246
|
+
const absFiles = [];
|
|
247
|
+
for (const f of files) {
|
|
248
|
+
const abs = path.resolve(f);
|
|
249
|
+
if (!fs.existsSync(abs) || !fs.statSync(abs).isFile()) {
|
|
250
|
+
console.error(`Error: file not found: ${f}`);
|
|
251
|
+
process.exit(2);
|
|
252
|
+
}
|
|
253
|
+
if (!abs.toLowerCase().endsWith('.ets')) {
|
|
254
|
+
console.error(`Error: not an .ets file: ${f}`);
|
|
255
|
+
process.exit(2);
|
|
256
|
+
}
|
|
257
|
+
absFiles.push(abs);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// 2. 准备工作目录与临时工程
|
|
261
|
+
const userProvidedWorkDir = Boolean(options.workDir);
|
|
262
|
+
let workDir = userProvidedWorkDir ? path.resolve(options.workDir) : '';
|
|
263
|
+
if (!userProvidedWorkDir) {
|
|
264
|
+
workDir = fs.mkdtempSync(path.join(os.tmpdir(), 'arkts-verify-'));
|
|
265
|
+
} else {
|
|
266
|
+
fs.mkdirSync(workDir, { recursive: true });
|
|
267
|
+
}
|
|
268
|
+
const projectPath = path.join(workDir, PROJECT_NAME);
|
|
269
|
+
|
|
270
|
+
let scaffolded = false;
|
|
271
|
+
if (!fs.existsSync(path.join(projectPath, 'build-profile.json5'))) {
|
|
272
|
+
if (fs.existsSync(projectPath)) {
|
|
273
|
+
rmRecursive(projectPath); // 清理不完整工程
|
|
274
|
+
}
|
|
275
|
+
console.log('>>> 脚手架临时验证工程(首次约 10-60s)...');
|
|
276
|
+
scaffoldProject(projectPath, options.apiLevel);
|
|
277
|
+
scaffolded = true;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const verifyDir = path.join(projectPath, 'entry', 'src', 'main', 'ets', 'verify');
|
|
281
|
+
rmRecursive(verifyDir);
|
|
282
|
+
fs.mkdirSync(verifyDir, { recursive: true });
|
|
283
|
+
|
|
284
|
+
// 3. 复制文件(保留相对目录结构,支持文件间相对导入)
|
|
285
|
+
const ancestor = commonAncestor(absFiles);
|
|
286
|
+
const copiedMap = []; // { dest: 绝对路径, src: 原文件, rel: posix 相对路径 }
|
|
287
|
+
for (const src of absFiles) {
|
|
288
|
+
const rel = toPosix(path.relative(ancestor, src));
|
|
289
|
+
const dest = path.join(verifyDir, ...rel.split('/'));
|
|
290
|
+
copyRecursive(src, dest);
|
|
291
|
+
copiedMap.push({ dest, src, rel });
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// 4. 生成入口页,side-effect import 接入构建图
|
|
295
|
+
const importLines = copiedMap
|
|
296
|
+
.map((c) => `import '../verify/${c.rel.slice(0, -'.ets'.length)}';`)
|
|
297
|
+
.join('\n');
|
|
298
|
+
const indexContent = [
|
|
299
|
+
'// Auto-generated by verify-arkts.cjs — syntax verification entry. Do not edit.',
|
|
300
|
+
importLines,
|
|
301
|
+
'',
|
|
302
|
+
'@Entry',
|
|
303
|
+
'@Component',
|
|
304
|
+
'struct Index {',
|
|
305
|
+
' build() {',
|
|
306
|
+
' Column() {',
|
|
307
|
+
" Text('verify')",
|
|
308
|
+
' }',
|
|
309
|
+
' }',
|
|
310
|
+
'}',
|
|
311
|
+
'',
|
|
312
|
+
].join('\n');
|
|
313
|
+
const pagesDir = path.join(projectPath, 'entry', 'src', 'main', 'ets', 'pages');
|
|
314
|
+
fs.mkdirSync(pagesDir, { recursive: true });
|
|
315
|
+
fs.writeFileSync(path.join(pagesDir, 'Index.ets'), indexContent, 'utf-8');
|
|
316
|
+
|
|
317
|
+
console.log('=== ArkTS 编译验证(独立模式) ===');
|
|
318
|
+
console.log(`文件: ${absFiles.length} 个`);
|
|
319
|
+
console.log(`临时工程: ${projectPath}${scaffolded ? '' : '(复用)'}`);
|
|
320
|
+
console.log('');
|
|
321
|
+
|
|
322
|
+
// 5. 构建
|
|
323
|
+
const res = runCli('devecocli', ['build'], { cwd: projectPath, timeout: timeoutMs });
|
|
324
|
+
if (res.error && res.error.code === 'ETIMEDOUT') {
|
|
325
|
+
cleanup(workDir, userProvidedWorkDir, options.keep);
|
|
326
|
+
console.error(`Error: build timed out after ${timeoutMs}ms`);
|
|
327
|
+
process.exit(2);
|
|
328
|
+
}
|
|
329
|
+
const parsed = parseBuildOutput(`${res.stdout || ''}\n${res.stderr || ''}`);
|
|
330
|
+
|
|
331
|
+
// 6. 错误文件路径与消息中的临时工程路径映射回源文件
|
|
332
|
+
// 兼容:绝对路径、工程相对路径、编译器拼接的异常路径(temp工程路径+源文件路径)
|
|
333
|
+
for (const err of parsed.errors) {
|
|
334
|
+
const errPath = toPosix(err.file);
|
|
335
|
+
let mapped = false;
|
|
336
|
+
for (const c of copiedMap) {
|
|
337
|
+
const marker = `entry/src/main/ets/verify/${c.rel}`;
|
|
338
|
+
const srcPosix = toPosix(c.src);
|
|
339
|
+
if (errPath === marker || errPath.endsWith(`/${marker}`)
|
|
340
|
+
|| errPath === srcPosix || errPath.endsWith(`/${srcPosix}`)) {
|
|
341
|
+
err.file = c.src;
|
|
342
|
+
mapped = true;
|
|
343
|
+
}
|
|
344
|
+
// 消息内嵌的临时路径替换为源文件路径
|
|
345
|
+
err.message = err.message.split(marker).join(c.src);
|
|
346
|
+
err.message = err.message.split(toPosix(c.dest)).join(c.src);
|
|
347
|
+
}
|
|
348
|
+
if (!mapped && err.file.length > 0) {
|
|
349
|
+
// 非被验证文件内部的错误(如生成入口页/依赖),原样展示
|
|
350
|
+
err.file = err.file.replace(projectPath + path.sep, '');
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
cleanup(workDir, userProvidedWorkDir, options.keep);
|
|
355
|
+
reportResult(parsed, { userProvidedWorkDir, workDir, keep: options.keep });
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function cleanup(workDir, userProvidedWorkDir, keep) {
|
|
359
|
+
if (userProvidedWorkDir) {
|
|
360
|
+
return; // 用户指定目录:保留工程供复用
|
|
361
|
+
}
|
|
362
|
+
if (!keep) {
|
|
363
|
+
rmRecursive(workDir);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// ── 结果输出 ─────────────────────────────────────────────
|
|
368
|
+
|
|
369
|
+
function reportResult(parsed, standaloneInfo) {
|
|
370
|
+
if (parsed.successful && parsed.errors.length === 0) {
|
|
371
|
+
console.log('✅ 验证通过:BUILD SUCCESSFUL,无 ArkTS 编译错误');
|
|
372
|
+
if (standaloneInfo && !standaloneInfo.userProvidedWorkDir && standaloneInfo.keep) {
|
|
373
|
+
console.log(`临时工程已保留: ${standaloneInfo.workDir}`);
|
|
374
|
+
}
|
|
375
|
+
process.exit(0);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const errorCount = parsed.errors.length;
|
|
379
|
+
console.log(`❌ 验证失败:${errorCount > 0 ? `${errorCount} 个编译错误` : '构建失败'}`);
|
|
380
|
+
if (errorCount > 0) {
|
|
381
|
+
console.log('');
|
|
382
|
+
const grouped = new Map();
|
|
383
|
+
for (const e of parsed.errors) {
|
|
384
|
+
const loc = e.line > 0 ? `${e.file}:${e.line}:${e.col}` : (e.file.length > 0 ? e.file : '(位置未知)');
|
|
385
|
+
const key = `[${e.rule}] ${loc}`;
|
|
386
|
+
if (!grouped.has(key)) {
|
|
387
|
+
grouped.set(key, []);
|
|
388
|
+
}
|
|
389
|
+
grouped.get(key).push(e);
|
|
390
|
+
}
|
|
391
|
+
for (const [key, errs] of grouped) {
|
|
392
|
+
for (const e of errs) {
|
|
393
|
+
console.log(` ${key}`);
|
|
394
|
+
console.log(` ${e.message}`);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
} else if (parsed.rawTail.trim().length > 0) {
|
|
398
|
+
console.log('');
|
|
399
|
+
console.log('---- 构建输出(末尾) ----');
|
|
400
|
+
console.log(parsed.rawTail);
|
|
401
|
+
}
|
|
402
|
+
process.exit(1);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// ── 主流程 ───────────────────────────────────────────────
|
|
406
|
+
|
|
407
|
+
function main() {
|
|
408
|
+
const args = parseArgs(process.argv.slice(2));
|
|
409
|
+
const hasFiles = Array.isArray(args.files) && args.files.length > 0;
|
|
410
|
+
const hasProject = typeof args.project === 'string' && args.project.trim().length > 0;
|
|
411
|
+
|
|
412
|
+
if (hasProject && hasFiles) {
|
|
413
|
+
usageAndExit('--project and --files are mutually exclusive');
|
|
414
|
+
}
|
|
415
|
+
if (!hasProject && !hasFiles) {
|
|
416
|
+
usageAndExit('provide --project <dir> or --files <a.ets> [...]');
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
checkDevecocli();
|
|
420
|
+
|
|
421
|
+
if (hasProject) {
|
|
422
|
+
verifyProject(path.resolve(args.project), args.timeout || DEFAULT_BUILD_TIMEOUT_MS);
|
|
423
|
+
} else {
|
|
424
|
+
verifyStandalone(args.files, args);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
main();
|