@ohos-cpf/3rdloop 0.0.3 → 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.
Files changed (46) hide show
  1. package/README.md +124 -87
  2. package/lib/cli.js +25 -0
  3. package/lib/config-cmd.js +1 -1
  4. package/lib/serve.js +268 -0
  5. package/lib/update.js +46 -6
  6. package/lib/web-ext.js +454 -0
  7. package/lib/web.js +664 -0
  8. package/package.json +2 -1
  9. package/vendor/Server/Agent/SkillSelector/README.md +11 -11
  10. package/vendor/Server/Agent/SkillSelector/llm/llmClient.js +3 -3
  11. package/vendor/Server/Skills/arkts-code-use/SKILL.md +270 -0
  12. package/vendor/Server/Skills/arkts-code-use/assets/TEMPLATES.md +367 -0
  13. package/vendor/Server/Skills/arkts-code-use/references/API_VERIFICATION.md +144 -0
  14. package/vendor/Server/Skills/arkts-code-use/references/ARKTS_RULES.md +240 -0
  15. package/vendor/Server/Skills/arkts-code-use/references/CODE_PATTERNS.md +431 -0
  16. package/vendor/Server/Skills/arkts-code-use/references/SYNTAX_CHECK_GUIDE.md +164 -0
  17. package/vendor/Server/Skills/arkts-code-use/scripts/verify-arkts.cjs +428 -0
  18. package/vendor/Server/Skills/gitcode-repo-fork/SKILL.md +310 -0
  19. package/vendor/Server/Skills/gitcode-repo-fork/assets/FORK_REPORT_TEMPLATE.md +113 -0
  20. package/vendor/Server/Skills/gitcode-repo-fork/references/FORK_DECISION_GUIDE.md +124 -0
  21. package/vendor/Server/Skills/gitcode-repo-fork/references/GITCODE_FORK_API.md +95 -0
  22. package/vendor/Server/Skills/gitcode-repo-fork/scripts/gitcode-fork.cjs +285 -0
  23. package/vendor/VERSION +3 -3
  24. package/web/css/arktslibrarycheck.css +322 -0
  25. package/web/css/codecheck.css +464 -0
  26. package/web/css/flutterlibrarycheck.css +322 -0
  27. package/web/css/knowledge.css +332 -0
  28. package/web/css/loop.css +578 -0
  29. package/web/css/md-reader.css +240 -0
  30. package/web/css/rnlibrarycheck.css +322 -0
  31. package/web/css/theme.css +702 -0
  32. package/web/index.html +713 -0
  33. package/web/js/arktslibrarycheck.js +1413 -0
  34. package/web/js/codecheck.js +1039 -0
  35. package/web/js/flutterlibrarycheck.js +1364 -0
  36. package/web/js/health.js +69 -0
  37. package/web/js/knowledge.js +358 -0
  38. package/web/js/loop.js +1102 -0
  39. package/web/js/md-reader.js +435 -0
  40. package/web/js/navigation.js +238 -0
  41. package/web/js/rnlibrarycheck.js +1378 -0
  42. package/web/js/stats.js +110 -0
  43. package/web/js/theme.js +46 -0
  44. package/web/js/utils.js +228 -0
  45. package/web/knowledge.html +146 -0
  46. package/web/loop.html +219 -0
@@ -0,0 +1,164 @@
1
+ # 语法验证指南(deveco-cli)
2
+
3
+ > 生成的 ArkTS 代码必须通过编译验证(`BUILD SUCCESSFUL`)才可交付。本文档说明验证机制、工具用法与错误修复。
4
+
5
+ ---
6
+
7
+ ## 一、核心机制:构建图可达性
8
+
9
+ hvigor 编译器只编译**构建图可达**的 .ets 文件:
10
+
11
+ ```
12
+ entry/src/main/ets/pages/Index.ets(页面,注册于 main_pages.json)
13
+ └── import → 组件/工具类
14
+ └── import → 更深层模块
15
+ ```
16
+
17
+ - 被页面/入口 import 链引用的文件 → 参与编译,语法/约束错误全部暴露
18
+ - **未被引用的孤立 .ets 文件 → 完全不编译,错误不会暴露**
19
+
20
+ 因此验证前必须确保新代码已接入构建图。两种方式:
21
+
22
+ | 方式 | 操作 | 适用 |
23
+ |------|------|------|
24
+ | 工程内接线 | 新页面注册 `main_pages.json`;新类被页面/`index.ets` import | 有目标工程 |
25
+ | 自动接线 | `verify-arkts.cjs` 脚本在临时工程入口页生成 side-effect import(`import '../verify/Xxx';`) | 独立代码验证 |
26
+
27
+ > side-effect import(无绑定导入)足以让目标文件参与编译并暴露全部错误,无需知道文件内导出符号名。
28
+
29
+ ---
30
+
31
+ ## 二、工程模式验证
32
+
33
+ ```bash
34
+ # 在目标工程根目录执行
35
+ devecocli build
36
+
37
+ # 指定模块/产物
38
+ devecocli build --modules entry
39
+ devecocli build --product default --build-mode debug
40
+
41
+ # 清理后构建(怀疑缓存问题时)
42
+ devecocli build clean && devecocli build
43
+ ```
44
+
45
+ **前置检查**:新代码已按 Phase 3.5 接线(页面注册 / import 可达),否则构建通过不代表语法正确。
46
+
47
+ **结果判定**:只认 `BUILD SUCCESSFUL`;`ERROR` 必须清零,`WARNING` 记录但不阻断。
48
+
49
+ ---
50
+
51
+ ## 三、独立模式验证(verify-arkts.cjs)
52
+
53
+ 无目标工程时,使用本SKILL自带脚本自动完成"脚手架 → 接线 → 编译 → 回显错误":
54
+
55
+ ```bash
56
+ node {本SKILL目录}/scripts/verify-arkts.cjs --files <文件1.ets> [文件2.ets ...] [选项]
57
+ ```
58
+
59
+ | 选项 | 说明 |
60
+ |------|------|
61
+ | `--files <f.ets>...` | 待验证的 .ets 文件(至少 1 个;多文件间相对导入按原目录结构保留) |
62
+ | `--work-dir <目录>` | 临时工程所在目录(默认系统临时目录,验证后删除;指定后工程保留可复用加速下次验证) |
63
+ | `--keep` | 保留临时工程(默认自动删除) |
64
+ | `--api-level <n>` | 指定脚手架 API Level(默认用 devecocli 默认值) |
65
+ | `--timeout <ms>` | 构建超时(默认 600000) |
66
+
67
+ **MCP Gateway 调用**(JSON 参数):
68
+
69
+ ```json
70
+ { "files": ["/abs/path/Foo.ets", "/abs/path/Bar.ets"], "workDir": "/tmp/arkts-verify", "keep": false }
71
+ ```
72
+
73
+ **退出码**:`0` 验证通过;`1` 存在编译错误(输出结构化错误列表);`2` 环境/参数错误。
74
+
75
+ **输出示例(失败)**:
76
+
77
+ ```
78
+ === ArkTS 编译验证失败(3 个错误) ===
79
+ [arkts-no-any-unknown] /abs/path/Foo.ets:2:17
80
+ Use explicit types instead of "any", "unknown"
81
+ ...
82
+ ```
83
+
84
+ **注意**:
85
+ - 引用了工程专属资源(`$r('app.string.xxx')`、rawfile、自定义 so 库)的代码无法在临时工程验证,须用工程模式
86
+ - 首次运行需脚手架(约 10-60s);指定 `--work-dir` 复用可加速
87
+ - 需要本机已安装 DevEco Studio + SDK + devecocli 可用
88
+
89
+ ---
90
+
91
+ ## 四、错误输出解析
92
+
93
+ `devecocli build` 的 ArkTS 编译错误格式(用于定位):
94
+
95
+ ```
96
+ N ERROR: 10605008 ArkTS Compiler Error
97
+ Error Message: <人读信息> (arkts-规则名) At File: <绝对路径>:<行>:<列>
98
+ ```
99
+
100
+ 解析要点:
101
+ - 规则名在**圆括号**内,如 `(arkts-no-any-unknown)`
102
+ - 位置为 `文件:行:列`,列指向违规 token 起点
103
+ - 同一文件多个错误一次全部修复再重跑,避免逐个循环
104
+
105
+ ---
106
+
107
+ ## 五、错误修复速查表
108
+
109
+ | 错误关键词 / 规则 | 修复方案 |
110
+ |-----------------|---------|
111
+ | `arkts-no-any-unknown` | `any`/`unknown` → 具体类型或泛型 `T`;`as any` → `as 具体类型` |
112
+ | `arkts-no-esobj` | `ESObject`/`Object` → `interface` 或具体类型 |
113
+ | `arkts-no-untyped-obj-literals` | `let o = {...}` → 先声明 `interface`/`class`,再 `const o: T = {...}` |
114
+ | `arkts-no-obj-literals-as-types` | 匿名对象类型参数 → 命名 `interface` |
115
+ | `arkts-limited-throw` | `throw e` → `throw e instanceof Error ? e : new Error(String(e))` |
116
+ | `arkts-no-dynamic-property` | `obj['key']` → `obj.key` |
117
+ | `arkts-no-structural-typing` | 鸭子类型赋值 → 显式 `implements` / `extends` |
118
+ | `arkts-no-unsafe-cast` | `as unknown as T` → 修正类型定义,单次断言 |
119
+ | `arkts-no-delete` | `delete obj.x` → `obj.x = undefined` 或重构结构 |
120
+ | `arkts-no-for-in` | `for...in` → `Object.keys()` + `for...of` |
121
+ | `arkts-no-prototype-assignment` | 原型扩展 → 类继承/组合 |
122
+ | `arkts-no-arguments` | `arguments` → `...args: T[]` |
123
+ | `Cannot find module 'xxx'` | import 路径拼写/大小写;相对层级;`@ohos.*` 模块名以文档为准 |
124
+ | `xxx has been deprecated` | 查官方文档替代 API,禁止保留 |
125
+ | `Page 'xxx' does not exist` | `main_pages.json` 与实际页面文件对齐 |
126
+ | `Permission denied` / 权限类 | `module.json5` `requestPermissions` 声明(对照 Phase 2 查证表) |
127
+ | `undefined property` / 资源类 | `$r` 资源需存在于 `resources/base/element`;rawfile 走 resourceManager |
128
+ | 装饰器报错 | `@Component` struct 不能继承;`@Concurrent` 函数不能捕获外部变量 |
129
+ | ArkUI 状态更新不生效 | build() 内禁止修改状态变量;修改放事件回调 |
130
+
131
+ ---
132
+
133
+ ## 六、辅助检查(可选补充)
134
+
135
+ ### 6.1 Code Linter(代码风格/安全规则)
136
+
137
+ ```bash
138
+ devecocli check lint <文件或目录> [--fix] [--format json]
139
+ ```
140
+
141
+ - 检查 TS/ArkTS 风格问题(`@performance/recommended`、`@typescript-eslint/recommended`、`@security/*`)
142
+ - 依赖工程上下文(`build-profile.json5`),且部分环境需工程完成同步后才识别文件(`Files checked: 0` 说明未识别到目标)
143
+ - **lint 通过 ≠ 语法正确**,仅作为风格/安全补充;最终以 `devecocli build` 为准
144
+
145
+ ### 6.2 版本兼容检查
146
+
147
+ ```bash
148
+ devecocli check compat versions # 列出可用 SDK 版本
149
+ devecocli check compat --source-version "<V1>" --target-version "<V2>"
150
+ devecocli check compat --source-version "<V1>" --target-version "<V2>" <文件...>
151
+ ```
152
+
153
+ 检查代码使用的 API 在目标 SDK 版本是否存在/已变更,适合交付前核对 API Level 兼容性。
154
+
155
+ > zsh 下版本号含括号,务必加引号;先用 `compat versions` 复制真实版本串。
156
+
157
+ ---
158
+
159
+ ## 七、验证纪律
160
+
161
+ 1. **一次性修复**:收集全部 ERROR 后统一修复,再重新构建
162
+ 2. **修复循环上限 5 轮**:超过后停止,向用户报告错误清单与已尝试方案
163
+ 3. **禁止降低验证标准**:不允许通过注释掉代码、放宽 lint 配置等方式"绕过"错误
164
+ 4. **速查表未覆盖的错误**:用 `script_deveco_docs` 查证涉事 API 的正确用法,或查阅 [ArkTS 规范与约束](ARKTS_RULES.md) 对应条目
@@ -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();