@ohos-cpf/3rdloop 0.0.11 → 0.0.13

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/lib/cli.js +2 -2
  2. package/package.json +1 -1
  3. package/vendor/Server/Routes/controllers/LoopEngineController.js +36 -3
  4. package/vendor/Server/Routes/controllers/OrchestratorController.js +193 -9
  5. package/vendor/Server/Skills/flutter-build-test/SKILL.md +252 -0
  6. package/vendor/Server/Skills/flutter-build-test/assets/BUILDENV_TEMPLATE.md +42 -0
  7. package/vendor/Server/Skills/flutter-build-test/assets/BUILD_REPORT_TEMPLATE.md +64 -0
  8. package/vendor/Server/Skills/flutter-build-test/assets/README_SECTION_TEMPLATE.md +78 -0
  9. package/vendor/Server/Skills/flutter-build-test/references/BUILD_TROUBLESHOOTING.md +128 -0
  10. package/vendor/Server/Skills/flutter-build-test/references/DOC_UPDATE_GUIDE.md +119 -0
  11. package/vendor/Server/Skills/flutter-build-test/references/FLVM_GUIDE.md +89 -0
  12. package/vendor/Server/Skills/flutter-build-test/scripts/build-matrix.cjs +374 -0
  13. package/vendor/Server/Skills/flutter-build-test/scripts/locate-example.cjs +189 -0
  14. package/vendor/Server/Skills/flutter-build-test/scripts/update-buildenv.cjs +171 -0
  15. package/vendor/Server/Skills/flutter-code-use/SKILL.md +316 -0
  16. package/vendor/Server/Skills/flutter-code-use/references/event-channel.md +440 -0
  17. package/vendor/Server/Skills/flutter-code-use/references/federated.md +295 -0
  18. package/vendor/Server/Skills/flutter-code-use/references/ffi-binding-translate.md +130 -0
  19. package/vendor/Server/Skills/flutter-code-use/references/ffi-compile-from-source.md +169 -0
  20. package/vendor/Server/Skills/flutter-code-use/references/ffi-fetch-at-build.md +161 -0
  21. package/vendor/Server/Skills/flutter-code-use/references/ffi-prebuilt-bundle.md +175 -0
  22. package/vendor/Server/Skills/flutter-code-use/references/ffi-rhttp-guide.md +235 -0
  23. package/vendor/Server/Skills/flutter-code-use/references/ffi-rust-cross-compile.md +514 -0
  24. package/vendor/Server/Skills/flutter-code-use/references/ffi.md +220 -0
  25. package/vendor/Server/Skills/flutter-code-use/references/method-channel.md +643 -0
  26. package/vendor/Server/Skills/flutter-code-use/references/monorepo.md +188 -0
  27. package/vendor/Server/Skills/flutter-code-use/references/ohos-api-pitfalls.md +717 -0
  28. package/vendor/Server/Skills/flutter-code-use/references/platform-view.md +448 -0
  29. package/vendor/Server/Skills/flutter-code-use/references/pure-dart.md +180 -0
  30. package/vendor/Server/Skills/flutter-code-use/references/texture.md +459 -0
  31. package/vendor/Server/Skills/flutter-demo-code-generator/SKILL.md +270 -0
  32. package/vendor/Server/Skills/flutter-demo-code-generator/assets/PAGE_TEMPLATES.md +544 -0
  33. package/vendor/Server/Skills/flutter-demo-code-generator/references/CODE_STANDARDS.md +328 -0
  34. package/vendor/Server/Skills/flutter-demo-code-generator/references/DEMO_DOC_PARSING.md +126 -0
  35. package/vendor/Server/Skills/flutter-demo-code-generator/references/EXAMPLES.md +629 -0
  36. package/vendor/Server/Skills/flutter-demo-code-generator/scripts/validate-flutter-demo.cjs +268 -0
  37. package/vendor/Server/Skills/flutter-demo-doc-generator/SKILL.md +227 -0
  38. package/vendor/Server/Skills/flutter-demo-doc-generator/assets/DEMO_DOC_TEMPLATE.md +78 -0
  39. package/vendor/Server/Skills/flutter-demo-doc-generator/references/COVERAGE_REPORT_PARSING.md +174 -0
  40. package/vendor/Server/Skills/flutter-demo-doc-generator/references/EXAMPLES.md +162 -0
  41. package/vendor/Server/Skills/flutter-demo-doc-generator/references/MCP_TOOL_GUIDE.md +123 -0
  42. package/vendor/Server/Skills/flutter-demo-doc-generator/references/OUTPUT_FORMAT.md +190 -0
  43. package/vendor/Server/Skills/flutter-demo-doc-generator/references/QUALITY_CHECKLIST.md +83 -0
  44. package/vendor/Server/Skills/flutter-demo-doc-generator/scripts/validate-skill.cjs +259 -0
  45. package/vendor/Server/Skills/flutter-library-demo-coverage/SKILL.md +175 -0
  46. package/vendor/VERSION +3 -3
@@ -0,0 +1,374 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * build-matrix.cjs — flvm 多版本编译矩阵:切换 Flutter 版本并逐版本编译验证 ohos hap
4
+ *
5
+ * 每版本执行序列:
6
+ * 1. flvm use <分支> (未安装自动安装,失败回退模糊匹配 flvm use <主.次>)
7
+ * 2. flutter --version 校验主.次版本一致
8
+ * 3. example 目录: flutter clean → 备份并删除 pubspec.lock → flutter pub get
9
+ * 4. flutter build hap --{mode}
10
+ * 5. 校验 hap 产物存在;按错误特征分类失败原因
11
+ * 6. 结果增量写入 {output}/build-matrix-results.json,日志存 {output}/logs/{版本}.log
12
+ *
13
+ * 用法:
14
+ * node build-matrix.cjs --source <libRoot> [--example <dir>] [--versions "3.7,3.22"] \
15
+ * [--mode debug|release] [--output <dir>] [--resume] [--codesign] \
16
+ * [--build-timeout <sec>] [--install-timeout <sec>] [--no-fresh-resolve]
17
+ *
18
+ * 默认 --no-codesign(无头验证编译链;签名构建需工程已配置 DevEco 调试签名)
19
+ */
20
+
21
+ 'use strict';
22
+
23
+ const fs = require('node:fs');
24
+ const path = require('node:path');
25
+ const { spawnSync } = require('node:child_process');
26
+
27
+ // ── 版本-分支映射(与 flvm 远程分支对应;过期时以 `flvm list -r` 为准修正) ──
28
+
29
+ const PREFERRED_BRANCHES = {
30
+ '3.7': ['br_3.7.12-ohos-1.1.3', 'br_3.7.12-ohos-dev'],
31
+ '3.22': ['oh-3.22.3-release', 'oh-3.22.3-dev'],
32
+ '3.27': ['oh-3.27.4-dev', 'oh-3.27.0-release'],
33
+ '3.32': ['oh-3.32.0-release', 'oh-3.32.4-dev'],
34
+ '3.35': ['oh-3.35.7-dev', 'oh-3.35.7-release'],
35
+ '3.41': ['oh-3.41.9-release', 'oh-3.41.9-dev'],
36
+ };
37
+
38
+ const DEFAULT_VERSIONS = ['3.7', '3.22', '3.27', '3.32', '3.35', '3.41'];
39
+
40
+ // ── 失败分类规则 ─────────────────────────────────────────
41
+
42
+ const ERROR_PATTERNS = [
43
+ { type: 'sdk-constraint', label: 'Dart SDK 约束不兼容', re: /requires SDK version|current Dart SDK version|Dart SDK version.*(?:does not|not compatible)/i },
44
+ { type: 'dependency-conflict', label: '依赖版本冲突', re: /version solving failed|Because .+ depends on|incompatible dependency|dependency conflict/i },
45
+ { type: 'pubspec-invalid', label: 'pubspec.yaml 配置错误', re: /Error on line \d+ .*(?:pubspec\.yaml|yaml)/i },
46
+ // hvigor-crash 须先于 ohos-arkts-error:ProcessException/exit 255 崩溃特征更具体,
47
+ // 避免被泛化的 hvigor.*error 模式抢先匹配
48
+ { type: 'hvigor-crash', label: 'hvigor 任务异常退出(exit 255 等)', re: /ProcessException.*hvigorw|exit code:?\s*255|hvigorw[,\s]+(?:failed|OS error)/i },
49
+ { type: 'ohos-arkts-error', label: '鸿蒙工程编译错误(ArkTS/NAPI/hvigor)', re: /ArkTS|\.ets\(\d+,\d+\)|hvigor.*(?:ERROR|FAIL)|ERROR:.*\.ets|napi_/i },
50
+ { type: 'signing-error', label: '签名错误', re: /signature|signing|\.p12|\.cer\b|profile.*invalid/i },
51
+ { type: 'network', label: '网络/镜像故障', re: /Failed to fetch|network .*unreachable|timed? ?out|ECONNREFUSED|ECONNRESET|socket hang up|Connection refused|Failed host lookup/i },
52
+ { type: 'toolchain-env', label: '工具链环境问题', re: /hvigorw.*(?:not found|No such file)|ohpm.*(?:not found|No such file)|(?:JAVA_HOME|TOOL_HOME|DEVECO_SDK_HOME).*(?:not|undefined|missing)/i },
53
+ { type: 'build-failed', label: '构建失败(通用)', re: /Build process failed|BUILD FAILED|Compilation failed|Compilation error/i },
54
+ ];
55
+
56
+ function classifyError(logText) {
57
+ const lines = logText.split(/\r?\n/);
58
+ const tail = lines.slice(-200).join('\n');
59
+ for (const p of ERROR_PATTERNS) {
60
+ if (p.re.test(tail)) return p;
61
+ }
62
+ return { type: 'unknown', label: '未分类失败' };
63
+ }
64
+
65
+ // ── 参数解析 ─────────────────────────────────────────────
66
+
67
+ function parseArgs(argv) {
68
+ if (argv.length > 0 && typeof argv[0] === 'string' && argv[0].startsWith('{')) {
69
+ try { return JSON.parse(argv[0]); } catch (e) { return {}; }
70
+ }
71
+ const args = {};
72
+ for (let i = 0; i < argv.length; i++) {
73
+ switch (argv[i]) {
74
+ case '--source': args.source = argv[++i]; break;
75
+ case '--example': args.example = argv[++i]; break;
76
+ case '--versions': args.versions = argv[++i]; break;
77
+ case '--mode': args.mode = argv[++i]; break;
78
+ case '--output': args.output = argv[++i]; break;
79
+ case '--resume': args.resume = true; break;
80
+ case '--codesign': args.codesign = true; break;
81
+ case '--no-fresh-resolve': args.freshResolve = false; break;
82
+ case '--build-timeout': args.buildTimeout = parseInt(argv[++i], 10); break;
83
+ case '--install-timeout': args.installTimeout = parseInt(argv[++i], 10); break;
84
+ }
85
+ }
86
+ return args;
87
+ }
88
+
89
+ // ── 子进程封装 ───────────────────────────────────────────
90
+
91
+ function run(cmd, cmdArgs, opts) {
92
+ const r = spawnSync(cmd, cmdArgs, {
93
+ cwd: opts.cwd || process.cwd(),
94
+ encoding: 'utf-8',
95
+ timeout: (opts.timeoutSec || 600) * 1000,
96
+ maxBuffer: 64 * 1024 * 1024,
97
+ env: process.env,
98
+ });
99
+ return {
100
+ code: r.status,
101
+ error: r.error ? String(r.error.message) : null,
102
+ stdout: r.stdout || '',
103
+ stderr: r.stderr || '',
104
+ get output() { return `${this.stdout}\n${this.stderr}`; },
105
+ };
106
+ }
107
+
108
+ // ── hap 产物查找 ─────────────────────────────────────────
109
+
110
+ function findHap(exampleDir) {
111
+ const ohosDir = path.join(exampleDir, 'ohos');
112
+ if (!fs.existsSync(ohosDir)) return null;
113
+ const haps = [];
114
+ const walk = (dir, depth) => {
115
+ if (depth > 6) return;
116
+ let entries;
117
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (e) { return; }
118
+ for (const ent of entries) {
119
+ const full = path.join(dir, ent.name);
120
+ if (ent.isDirectory()) walk(full, depth + 1);
121
+ else if (ent.name.endsWith('-signed.hap') || ent.name.endsWith('.hap')) haps.push(full);
122
+ }
123
+ };
124
+ walk(ohosDir, 0);
125
+ const signed = haps.find((h) => h.endsWith('-signed.hap'));
126
+ return signed || haps[0] || null;
127
+ }
128
+
129
+ // ── git 状态快照(构建副作用记录) ────────────────────────
130
+
131
+ function gitStatusSnapshot(dir) {
132
+ const r = run('git', ['status', '--short'], { cwd: dir, timeoutSec: 60 });
133
+ if (r.code !== 0) return null;
134
+ return r.stdout.split(/\r?\n/).filter(Boolean);
135
+ }
136
+
137
+ // ── 主流程 ───────────────────────────────────────────────
138
+
139
+ function main() {
140
+ const args = parseArgs(process.argv.slice(2));
141
+ const libRoot = args.source ? path.resolve(args.source) : null;
142
+ if (!libRoot || !fs.existsSync(path.join(libRoot, 'pubspec.yaml'))) {
143
+ console.error('[build-matrix] 错误: --source 必须为含 pubspec.yaml 的库根目录');
144
+ process.exit(1);
145
+ }
146
+ const mode = args.mode === 'release' ? 'release' : 'debug';
147
+ const versions = (args.versions ? args.versions.split(',').map((v) => v.trim()) : DEFAULT_VERSIONS).filter(Boolean);
148
+ const outputDir = path.resolve(args.output || path.join(libRoot, 'flutter-build-test'));
149
+ const logsDir = path.join(outputDir, 'logs');
150
+ const backupDir = path.join(outputDir, 'backups');
151
+ fs.mkdirSync(logsDir, { recursive: true });
152
+ fs.mkdirSync(backupDir, { recursive: true });
153
+
154
+ // example 目录:--example > buildEnv.sh BUILD_PKG_DIR > example/
155
+ let exampleDir;
156
+ if (args.example) {
157
+ exampleDir = path.resolve(args.example);
158
+ } else {
159
+ const envFile = path.join(libRoot, 'buildEnv.sh');
160
+ let pkgDir = null;
161
+ if (fs.existsSync(envFile)) {
162
+ const m = fs.readFileSync(envFile, 'utf-8').match(/^\s*BUILD_PKG_DIR\s*=\s*(.+)$/m);
163
+ if (m) pkgDir = m[1].trim().replace(/^["']|["']$/g, '');
164
+ }
165
+ exampleDir = pkgDir ? path.resolve(libRoot, pkgDir) : path.join(libRoot, 'example');
166
+ }
167
+ if (!fs.existsSync(path.join(exampleDir, 'pubspec.yaml'))) {
168
+ console.error(`[build-matrix] 错误: example 目录无效(无 pubspec.yaml): ${exampleDir}`);
169
+ console.error('[build-matrix] 请先用 locate-example.cjs 定位并用 --example 指定');
170
+ process.exit(1);
171
+ }
172
+ if (!fs.existsSync(path.join(exampleDir, 'ohos'))) {
173
+ console.error(`[build-matrix] 错误: example 目录缺少 ohos 工程代码: ${exampleDir}`);
174
+ process.exit(1);
175
+ }
176
+
177
+ // flvm 可用性检查
178
+ const flvmVer = run('flvm', ['--version'], { timeoutSec: 30 });
179
+ if (flvmVer.code !== 0) {
180
+ console.error('[build-matrix] 错误: flvm 不可用,请先安装并执行 flvm init(见 references/FLVM_GUIDE.md)');
181
+ process.exit(1);
182
+ }
183
+
184
+ // 环境体检快照
185
+ const doctor = run('flvm', ['doctor'], { timeoutSec: 120 });
186
+ fs.writeFileSync(path.join(outputDir, 'env-doctor.txt'), doctor.output, 'utf-8');
187
+
188
+ // 结果文件(增量)
189
+ const resultsFile = path.join(outputDir, 'build-matrix-results.json');
190
+ let results = { libRoot, exampleDir, mode, codesign: !!args.codesign, startedAt: new Date().toISOString(), versions: {} };
191
+ if (args.resume && fs.existsSync(resultsFile)) {
192
+ try {
193
+ results = JSON.parse(fs.readFileSync(resultsFile, 'utf-8'));
194
+ results.exampleDir = exampleDir;
195
+ results.mode = mode;
196
+ } catch (e) { /* 损坏则重建 */ }
197
+ }
198
+
199
+ // pubspec.lock 原件备份(fresh-resolve 模式)
200
+ const lockFile = path.join(exampleDir, 'pubspec.lock');
201
+ const origBackup = path.join(backupDir, 'pubspec.lock.original');
202
+ const freshResolve = args.freshResolve !== false;
203
+ if (freshResolve && fs.existsSync(lockFile) && !fs.existsSync(origBackup)) {
204
+ fs.copyFileSync(lockFile, origBackup);
205
+ }
206
+
207
+ // git 状态前后快照(flutter 工具会自动修改 example/ohos 的 build-profile/oh-package 注册插件模块,如实记录)
208
+ const gitBefore = gitStatusSnapshot(libRoot);
209
+ const gitAfter = () => {
210
+ const after = gitStatusSnapshot(libRoot) || [];
211
+ const beforeSet = new Set(gitBefore || []);
212
+ return after.filter((l) => !beforeSet.has(l));
213
+ };
214
+
215
+ console.log(`[build-matrix] 库根目录: ${libRoot}`);
216
+ console.log(`[build-matrix] example: ${exampleDir}`);
217
+ console.log(`[build-matrix] 版本列表: ${versions.join(', ')} 模式: ${mode} 续跑: ${args.resume ? '是' : '否'}`);
218
+ console.log('');
219
+
220
+ const saveResults = () => fs.writeFileSync(resultsFile, JSON.stringify(results, null, 2), 'utf-8');
221
+ const buildTimeout = args.buildTimeout || 1800;
222
+ const installTimeout = args.installTimeout || 3600;
223
+
224
+ for (const ver of versions) {
225
+ const key = ver;
226
+ const prev = results.versions[key];
227
+ if (args.resume && prev && (prev.status === 'pass' || prev.status === 'fail')) {
228
+ console.log(`⏭️ [${ver}] 已有结论 (${prev.status}),跳过(--resume)`);
229
+ continue;
230
+ }
231
+
232
+ console.log(`\n========== Flutter ${ver} ==========`);
233
+ const entry = { version: ver, branch: null, flutterVersion: null, pubGet: 'skip', build: 'skip', status: 'running', hapArtifact: null, failure: null, startedAt: new Date().toISOString() };
234
+ results.versions[key] = entry;
235
+ saveResults();
236
+ const logParts = [];
237
+
238
+ // 1. flvm 切换(首选分支 → 回退分支 → 模糊匹配)
239
+ let switched = false;
240
+ const branches = PREFERRED_BRANCHES[ver] || [ver];
241
+ for (const branch of branches) {
242
+ console.log(`[flvm] use ${branch} ...`);
243
+ const r = run('flvm', ['use', branch], { timeoutSec: installTimeout });
244
+ logParts.push(`$ flvm use ${branch}\n${r.output}`);
245
+ if (r.code === 0) { entry.branch = branch; switched = true; break; }
246
+ console.log(`[flvm] use ${branch} 失败 (exit ${r.code}),尝试下一候选`);
247
+ }
248
+ if (!switched) {
249
+ console.log(`[flvm] 首选分支均失败,回退模糊匹配: flvm use ${ver}`);
250
+ const r = run('flvm', ['use', ver], { timeoutSec: installTimeout });
251
+ logParts.push(`$ flvm use ${ver}\n${r.output}`);
252
+ if (r.code !== 0) {
253
+ entry.status = 'env-error';
254
+ entry.failure = { type: 'flvm-install', label: `flvm 无法安装/切换 ${ver} 版本` };
255
+ entry.build = 'skip';
256
+ entry.pubGet = 'skip';
257
+ saveResults();
258
+ fs.writeFileSync(path.join(logsDir, `${ver}.log`), logParts.join('\n\n'), 'utf-8');
259
+ console.log(`❌ [${ver}] flvm 切换失败,记为 env-error`);
260
+ continue;
261
+ }
262
+ entry.branch = ver;
263
+ }
264
+
265
+ // 2. flutter --version 校验
266
+ const fv = run('flutter', ['--version'], { timeoutSec: 300 });
267
+ logParts.push(`$ flutter --version\n${fv.output}`);
268
+ const vm = (fv.stdout || '').match(/Flutter (\d+)\.(\d+)\.(\d+)/);
269
+ entry.flutterVersion = vm ? `${vm[1]}.${vm[2]}.${vm[3]}` : null;
270
+ if (fv.code !== 0 || !entry.flutterVersion || `${vm[1]}.${vm[2]}` !== ver) {
271
+ entry.status = 'env-error';
272
+ entry.failure = { type: 'flvm-version-mismatch', label: `切换后 Flutter 版本异常(期望 ${ver}.x,实际 ${entry.flutterVersion || '未知'})` };
273
+ saveResults();
274
+ fs.writeFileSync(path.join(logsDir, `${ver}.log`), logParts.join('\n\n'), 'utf-8');
275
+ console.log(`❌ [${ver}] 版本校验失败: 实际 ${entry.flutterVersion}`);
276
+ continue;
277
+ }
278
+ console.log(`[flutter] ${entry.flutterVersion} (${entry.branch}) ✓`);
279
+
280
+ // 3. flutter clean + fresh pubspec.lock + pub get
281
+ const clean = run('flutter', ['clean'], { cwd: exampleDir, timeoutSec: 300 });
282
+ logParts.push(`$ flutter clean\n${clean.output}`);
283
+ if (freshResolve && fs.existsSync(lockFile)) fs.rmSync(lockFile, { force: true });
284
+
285
+ const pubGet = run('flutter', ['pub', 'get'], { cwd: exampleDir, timeoutSec: 900 });
286
+ logParts.push(`$ flutter pub get\n${pubGet.output}`);
287
+ entry.pubGet = pubGet.code === 0 ? 'pass' : 'fail';
288
+ if (pubGet.code !== 0) {
289
+ const cls = classifyError(pubGet.output);
290
+ entry.status = 'fail';
291
+ entry.build = 'skip';
292
+ entry.failure = { type: cls.type, label: cls.label, stage: 'pub-get' };
293
+ saveResults();
294
+ fs.writeFileSync(path.join(logsDir, `${ver}.log`), logParts.join('\n\n'), 'utf-8');
295
+ console.log(`❌ [${ver}] pub get 失败: ${cls.label}`);
296
+ continue;
297
+ }
298
+ console.log(`[pub get] ✓`);
299
+
300
+ // 4. flutter build hap(版本差异处理:3.7/3.27 等 fork 不支持 --no-codesign 参数,
301
+ // 需探测后决定是否附加;不支持的版本构建后由 flutter 工具强制签名检查,
302
+ // 编译链已通过且产出 unsigned hap 时按通过计——见 references/BUILD_TROUBLESHOOTING.md §3.6)
303
+ const help = run('flutter', ['build', 'hap', '--help'], { timeoutSec: 120 });
304
+ logParts.push(`$ flutter build hap --help (探测 codesign 参数支持)\n${(help.stdout || '').slice(0, 2000)}`);
305
+ const supportsCodesignFlag = /--\[no-\]codesign/.test(help.stdout || '');
306
+ const hapArgs = ['build', 'hap', `--${mode}`];
307
+ if (!args.codesign && supportsCodesignFlag) hapArgs.push('--no-codesign');
308
+ const build = run('flutter', hapArgs, { cwd: exampleDir, timeoutSec: buildTimeout });
309
+ logParts.push(`$ flutter ${hapArgs.join(' ')}\n${build.output}`);
310
+ entry.build = build.code === 0 ? 'pass' : 'fail';
311
+
312
+ // 5. 产物校验 + 结论
313
+ const hap = findHap(exampleDir);
314
+ entry.hapArtifact = hap;
315
+ const signedRejected = /配置调试签名|Automatically generate signature/.test(build.output);
316
+ if (build.code === 0 && hap) {
317
+ entry.status = 'pass';
318
+ console.log(`✅ [${ver}] 编译通过 (${path.relative(exampleDir, hap)})`);
319
+ } else if (build.code !== 0 && !supportsCodesignFlag && signedRejected && hap) {
320
+ // 不支持 --no-codesign 的版本:flutter 工具签名检查拒绝,但 hvigor 编译链已完成且产出 unsigned hap
321
+ entry.status = 'pass';
322
+ entry.build = 'pass';
323
+ entry.note = '该 Flutter 版本不支持 --no-codesign,flutter 工具签名检查拒绝退出;hvigor 编译链已完成并产出 unsigned hap,按编译验证通过计';
324
+ console.log(`✅ [${ver}] 编译通过(unsigned hap,签名检查跳过) (${path.relative(exampleDir, hap)})`);
325
+ } else if (build.code === 0 && !hap) {
326
+ // 命令成功但产物缺失:可疑,记为 fail(warning 类)
327
+ entry.status = 'fail';
328
+ entry.failure = { type: 'artifact-missing', label: '构建命令成功但未找到 hap 产物', stage: 'build' };
329
+ console.log(`⚠️ [${ver}] 构建成功但未找到 hap 产物,记为 fail`);
330
+ } else {
331
+ const cls = classifyError(build.output);
332
+ entry.status = 'fail';
333
+ entry.failure = { type: cls.type, label: cls.label, stage: 'build' };
334
+ console.log(`❌ [${ver}] 编译失败: ${cls.label}`);
335
+ }
336
+ entry.finishedAt = new Date().toISOString();
337
+ saveResults();
338
+ fs.writeFileSync(path.join(logsDir, `${ver}.log`), logParts.join('\n\n'), 'utf-8');
339
+ }
340
+
341
+ // 恢复原 pubspec.lock
342
+ if (freshResolve && fs.existsSync(origBackup)) {
343
+ fs.copyFileSync(origBackup, lockFile);
344
+ console.log('\n[build-matrix] 已恢复原 pubspec.lock');
345
+ }
346
+
347
+ // 汇总
348
+ results.finishedAt = new Date().toISOString();
349
+ const repoChanges = gitAfter();
350
+ results.repoChangesDuringBuild = repoChanges;
351
+ if (repoChanges.length > 0) {
352
+ console.log('\n[build-matrix] ⚠️ 构建期间仓库出现的变更(flutter 工具自动注册插件模块等,属正常副作用,报告中需列出):');
353
+ for (const l of repoChanges.slice(0, 20)) console.log(` ${l}`);
354
+ if (repoChanges.length > 20) console.log(` ... 共 ${repoChanges.length} 条`);
355
+ }
356
+ saveResults();
357
+ const passVersions = Object.values(results.versions).filter((v) => v.status === 'pass');
358
+ console.log('\n========== 汇总 ==========');
359
+ console.log('版本 | 分支 | pub get | build | 结论 | 失败原因');
360
+ for (const ver of versions) {
361
+ const v = results.versions[ver];
362
+ if (!v) continue;
363
+ console.log(`${ver} | ${v.branch || '-'} | ${v.pubGet} | ${v.build} | ${v.status} | ${v.failure ? v.failure.label : '-'}`);
364
+ }
365
+ console.log('');
366
+ console.log(`支持版本: ${passVersions.length ? passVersions.map((v) => v.version).sort((a, b) => parseFloat(a) - parseFloat(b)).join(', ') : '无'}`);
367
+ if (passVersions.length === 0) {
368
+ console.log('[build-matrix] ⚠️ 无任何版本通过——请先排查环境类失败(env-error 条目),修复后 --resume 重跑');
369
+ process.exit(2);
370
+ }
371
+ process.exit(0);
372
+ }
373
+
374
+ main();
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * locate-example.cjs — 定位 Flutter 三方库的 example 工程并校验 ohos 工程代码
4
+ *
5
+ * 行为:
6
+ * 1. 优先读取 {libRoot}/buildEnv.sh 的 BUILD_PKG_DIR(部分库 example 不在根下)
7
+ * 2. 扫描根目录及两层深度内含 pubspec.yaml 的 example 类目录
8
+ * 3. 校验候选目录存在 ohos/ 子目录且含鸿蒙工程特征(build-profile.json5 / oh-package.json5 / AppScope)
9
+ * 4. 输出 JSON 结论(stdout 最后一行以 JSON: 开头)
10
+ *
11
+ * 用法:
12
+ * node locate-example.cjs --source /path/to/libRoot
13
+ * node locate-example.cjs '{"source":"/path/to/libRoot"}'
14
+ */
15
+
16
+ 'use strict';
17
+
18
+ const fs = require('node:fs');
19
+ const path = require('node:path');
20
+
21
+ // ── 参数解析(兼容 MCP Gateway JSON 模式与 CLI 模式) ──────
22
+
23
+ function parseArgs(argv) {
24
+ if (argv.length > 0 && typeof argv[0] === 'string' && argv[0].startsWith('{')) {
25
+ try { return JSON.parse(argv[0]); } catch (e) { return {}; }
26
+ }
27
+ const args = {};
28
+ for (let i = 0; i < argv.length; i++) {
29
+ if (argv[i] === '--source') args.source = argv[++i];
30
+ else if (argv[i] === '--example') args.example = argv[++i];
31
+ }
32
+ return args;
33
+ }
34
+
35
+ // ── 工具函数 ─────────────────────────────────────────────
36
+
37
+ function exists(p) {
38
+ try { return fs.existsSync(p); } catch (e) { return false; }
39
+ }
40
+
41
+ function isDir(p) {
42
+ try { return fs.existsSync(p) && fs.statSync(p).isDirectory(); } catch (e) { return false; }
43
+ }
44
+
45
+ /** 从 buildEnv.sh 解析 BUILD_PKG_DIR(无引号值) */
46
+ function readBuildEnv(source) {
47
+ const file = path.join(source, 'buildEnv.sh');
48
+ if (!exists(file)) return null;
49
+ const result = { file, pkgDir: null, flutterSdkVersion: null };
50
+ const text = fs.readFileSync(file, 'utf-8');
51
+ const m1 = text.match(/^\s*BUILD_PKG_DIR\s*=\s*(.+)$/m);
52
+ if (m1) result.pkgDir = m1[1].trim().replace(/^["']|["']$/g, '');
53
+ const m2 = text.match(/^\s*FLUTTER_SDK_VERSION\s*=\s*(.+)$/m);
54
+ if (m2) result.flutterSdkVersion = m2[1].trim().replace(/^["']|["']$/g, '');
55
+ return result;
56
+ }
57
+
58
+ /** 校验目录是否为鸿蒙工程(ohos/ 下含工程特征文件) */
59
+ function checkOhosProject(dir) {
60
+ const features = ['build-profile.json5', 'oh-package.json5', 'hvigorfile.ts'];
61
+ const hasFeature = features.some((f) => exists(path.join(dir, f)));
62
+ const hasAppScope = isDir(path.join(dir, 'AppScope'));
63
+ return hasFeature || hasAppScope;
64
+ }
65
+
66
+ /** 候选 example 目录名匹配(大小写不敏感) */
67
+ function looksLikeExample(name) {
68
+ return /^(example|examples|example_app|exampleapp|demo)$/i.test(name) || /^example[-_]/i.test(name);
69
+ }
70
+
71
+ /** 扫描候选 example 目录(两层深度,跳过 node_modules/build 等) */
72
+ function scanCandidates(source) {
73
+ const candidates = [];
74
+ const SKIP = new Set(['node_modules', 'build', '.dart_tool', '.git', '.idea', 'ohos', 'android', 'ios', 'linux', 'macos', 'windows', 'web', 'test', 'lib', 'src']);
75
+ const scan = (dir, depth) => {
76
+ let entries;
77
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (e) { return; }
78
+ for (const ent of entries) {
79
+ if (!ent.isDirectory()) continue;
80
+ if (SKIP.has(ent.name)) continue;
81
+ const full = path.join(dir, ent.name);
82
+ if (exists(path.join(full, 'pubspec.yaml'))) {
83
+ candidates.push(full);
84
+ }
85
+ if (depth < 2) scan(full, depth + 1);
86
+ }
87
+ };
88
+ scan(source, 0);
89
+ // example 命名的优先
90
+ candidates.sort((a, b) => {
91
+ const ea = looksLikeExample(path.basename(a)) ? 0 : 1;
92
+ const eb = looksLikeExample(path.basename(b)) ? 0 : 1;
93
+ if (ea !== eb) return ea - eb;
94
+ return a.split(path.sep).length - b.split(path.sep).length;
95
+ });
96
+ return candidates;
97
+ }
98
+
99
+ // ── 主流程 ───────────────────────────────────────────────
100
+
101
+ function main() {
102
+ const args = parseArgs(process.argv.slice(2));
103
+ const source = args.source;
104
+ if (!source || !isDir(source)) {
105
+ console.error('[locate-example] 错误: --source 必须为存在的目录');
106
+ process.exit(1);
107
+ }
108
+ let libRoot = path.resolve(source);
109
+
110
+ // monorepo 兜底:根目录无 pubspec.yaml 但 buildEnv.sh 的 BUILD_PKG_DIR 有效时,
111
+ // 以 BUILD_PKG_DIR 向上回溯到仓库根范围内最高的含 pubspec.yaml 目录作为有效包根
112
+ // (如 BUILD_PKG_DIR=YFree_Flutter/Lite/example → 包根 YFree_Flutter/Lite,而非 example 自身)
113
+ let packageRootOverride = null;
114
+ if (!exists(path.join(libRoot, 'pubspec.yaml'))) {
115
+ const env = readBuildEnv(libRoot);
116
+ if (env && env.pkgDir) {
117
+ let probe = path.resolve(libRoot, env.pkgDir);
118
+ const repoTop = path.resolve(libRoot);
119
+ while (probe.length >= repoTop.length && probe.startsWith(repoTop)) {
120
+ if (exists(path.join(probe, 'pubspec.yaml'))) packageRootOverride = probe;
121
+ const parent = path.dirname(probe);
122
+ if (parent === probe) break;
123
+ probe = parent;
124
+ }
125
+ }
126
+ if (!packageRootOverride) {
127
+ console.error(`[locate-example] 错误: ${libRoot} 下未找到 pubspec.yaml,不是 Flutter 库根目录`);
128
+ console.error('[locate-example] 提示: monorepo 请将 --source 指向含 pubspec.yaml 的包根目录');
129
+ process.exit(1);
130
+ }
131
+ console.log(`[locate-example] 提示: 根目录无 pubspec.yaml,依据 buildEnv.sh BUILD_PKG_DIR 推导有效包根: ${packageRootOverride}`);
132
+ libRoot = packageRootOverride;
133
+ }
134
+
135
+ const buildEnv = readBuildEnv(libRoot);
136
+ const repoBuildEnv = packageRootOverride ? readBuildEnv(path.resolve(source)) : buildEnv;
137
+ const summary = { repoRoot: path.resolve(source), libRoot, buildEnv: repoBuildEnv ? { file: repoBuildEnv.file, pkgDir: repoBuildEnv.pkgDir, flutterSdkVersion: repoBuildEnv.flutterSdkVersion } : null, exampleDir: null, ohosDir: null, buildPkgDir: repoBuildEnv ? repoBuildEnv.pkgDir : null, hasOhos: false, candidates: [], selected: null };
138
+
139
+ // 候选构造:显式 --example > buildEnv.BUILD_PKG_DIR > 扫描
140
+ // (buildEnv 的 pkgDir 相对 buildEnv.sh 所在目录解析)
141
+ const envBaseDir = repoBuildEnv ? path.dirname(repoBuildEnv.file) : null;
142
+ const ordered = [];
143
+ if (args.example) ordered.push({ dir: path.resolve(libRoot, args.example), origin: '--example 参数' });
144
+ if (repoBuildEnv && repoBuildEnv.pkgDir) ordered.push({ dir: path.resolve(envBaseDir, repoBuildEnv.pkgDir), origin: 'buildEnv.sh BUILD_PKG_DIR' });
145
+ for (const c of scanCandidates(libRoot)) ordered.push({ dir: c, origin: '目录扫描' });
146
+ if (exists(path.join(libRoot, 'example', 'pubspec.yaml')) && !ordered.some((o) => o.dir === path.join(libRoot, 'example'))) {
147
+ ordered.push({ dir: path.join(libRoot, 'example'), origin: '默认 example 目录' });
148
+ }
149
+
150
+ for (const cand of ordered) {
151
+ if (!exists(path.join(cand.dir, 'pubspec.yaml'))) {
152
+ summary.candidates.push({ dir: cand.dir, origin: cand.origin, hasPubspec: false, hasOhos: false, verdict: 'reject-no-pubspec' });
153
+ continue;
154
+ }
155
+ const ohosDir = path.join(cand.dir, 'ohos');
156
+ const hasOhos = isDir(ohosDir) && checkOhosProject(ohosDir);
157
+ summary.candidates.push({ dir: cand.dir, origin: cand.origin, hasPubspec: true, hasOhos, ohosValid: hasOhos, verdict: hasOhos ? 'accept' : 'reject-no-ohos' });
158
+ if (hasOhos && !summary.exampleDir) {
159
+ summary.exampleDir = cand.dir;
160
+ summary.ohosDir = ohosDir;
161
+ summary.hasOhos = true;
162
+ summary.selected = cand;
163
+ }
164
+ }
165
+
166
+ console.log('[locate-example] 候选评估:');
167
+ for (const c of summary.candidates) {
168
+ console.log(` ${c.verdict === 'accept' ? '✅' : '❌'} ${path.relative(libRoot, c.dir) || '.'} (来源: ${c.origin}) — ${c.verdict}`);
169
+ }
170
+
171
+ if (summary.exampleDir) {
172
+ console.log(`[locate-example] 选定 example 工程: ${summary.exampleDir}`);
173
+ console.log(`[locate-example] ohos 工程目录: ${summary.ohosDir}`);
174
+ if (repoBuildEnv && repoBuildEnv.pkgDir) {
175
+ const envResolved = path.resolve(envBaseDir, repoBuildEnv.pkgDir);
176
+ if (envResolved !== summary.exampleDir) {
177
+ console.log(`[locate-example] ⚠️ buildEnv.sh BUILD_PKG_DIR (${repoBuildEnv.pkgDir}) 与实际选定目录不一致,建议在 Phase 4 修正`);
178
+ }
179
+ }
180
+ } else {
181
+ console.error('[locate-example] 未找到含 ohos 工程代码的 example 目录');
182
+ console.error('[locate-example] 可尝试: 在 example 目录执行 flutter create --platforms ohos . 生成鸿蒙工程后重试');
183
+ }
184
+
185
+ console.log('JSON: ' + JSON.stringify(summary));
186
+ process.exit(summary.exampleDir ? 0 : 1);
187
+ }
188
+
189
+ main();