@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
package/lib/cli.js CHANGED
@@ -150,7 +150,7 @@ export async function runCli(argv) {
150
150
  return cmdKnowledgeImpl({ rest, user3libHome, dataDir, jsonMode });
151
151
 
152
152
  case 'serve':
153
- return cmdServe({ rest, jsonMode, dataDir, user3libHome });
153
+ return cmdServe({ flags, jsonMode, dataDir, user3libHome });
154
154
 
155
155
  case 'update':
156
156
  return cmdUpdate({ flags, rest, jsonMode, out });
@@ -265,7 +265,7 @@ async function cmdUpdate({ flags, rest, jsonMode, out }) {
265
265
 
266
266
  // ─── serve(同时启动 Server HTTP 后端 + Web 前端)─────────────────
267
267
 
268
- async function cmdServe({ flags, rest, jsonMode, dataDir, user3libHome }) {
268
+ async function cmdServe({ flags, jsonMode, dataDir, user3libHome }) {
269
269
  const { cmdServe: impl } = await import('./serve.js');
270
270
  return impl({ rest: flags.rest, jsonMode, dataDir, user3libHome });
271
271
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ohos-cpf/3rdloop",
3
- "version": "0.0.11",
3
+ "version": "0.0.13",
4
4
  "description": "3rdLibraryLoop 三方库自动化检视 CLI:嵌入式复用核心引擎,任务生命周期管理(submit/run/wait/progress/abort/list/result/doctor)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -120,6 +120,33 @@ class LoopEngineController {
120
120
  return this.storage;
121
121
  }
122
122
 
123
+ /**
124
+ * 判断磁盘上的任务是否为"单次执行任务"。
125
+ *
126
+ * 单次执行任务由 FlexRunnerController.startSingle 在启动时写入精简版
127
+ * loop.json(isSingleExecution=true,见其「写精简版 loop.json」逻辑);
128
+ * 大循环任务的 loop.json(LoopEngine 写入)无此标记。
129
+ * 用于 progress 的"单次执行"分支判定,避免把"收尾轮只有 1 个步骤"的
130
+ * 大循环任务误判为单次执行。
131
+ *
132
+ * 无 loop.json / 读取失败时保守返回 true(维持单次执行判定旧行为,
133
+ * 不影响真正的单次执行任务)。
134
+ *
135
+ * @param {string} taskId
136
+ * @returns {Promise<boolean>}
137
+ * @private
138
+ */
139
+ async _isSingleExecutionOnDisk(taskId) {
140
+ try {
141
+ const storage = this._getStorage();
142
+ const loop = await storage.json('task').read(`${taskId}/loop`);
143
+ if (!loop) return true;
144
+ return loop.isSingleExecution === true;
145
+ } catch {
146
+ return true;
147
+ }
148
+ }
149
+
123
150
  // ─── HTTP Handlers ─────────────────────────────────────────────────
124
151
 
125
152
  /**
@@ -309,9 +336,15 @@ class LoopEngineController {
309
336
 
310
337
  if (orch) {
311
338
  const orchProgress = orch.getProgress();
312
- // 判断是否为单次执行(步骤数为 1loopCount === 0
313
- const isSingle = orchProgress &&
314
- Array.isArray(orchProgress.steps) && orchProgress.steps.length === 1;
339
+ // 判断是否为单次执行:步骤数为 1loopCount === 0,且磁盘 loop.json
340
+ // 不存在或标记 isSingleExecution=true(单次执行任务由 startSingle 写入该标记)。
341
+ // 不能仅凭"步骤数为 1"判定——大循环任务重规划后的收尾轮常只有 1 个步骤,
342
+ // 服务重启 restoreFromDisk 后内存中恰是这种快照,会被误判为单次执行,
343
+ // 导致 progress 返回合成的单轮视图,前端丢失完整多轮历史。
344
+ const isSingle = orchProgress
345
+ && Array.isArray(orchProgress.steps) && orchProgress.steps.length === 1
346
+ && (orchProgress.loopCount ?? 0) === 0
347
+ && (await this._isSingleExecutionOnDisk(taskId));
315
348
 
316
349
  if (isSingle) {
317
350
  // 构造兼容 LoopEngine.getProgress 的响应格式
@@ -767,6 +767,155 @@ class OrchestratorController {
767
767
  }
768
768
  }
769
769
 
770
+ /**
771
+ * 跨机迁移路径自愈(幂等,best-effort)。
772
+ *
773
+ * 背景:任务目录可能从其他机器整体拷贝过来(共享/排查场景),其中
774
+ * orchestration.json / orchestration_loop{N}.json / loop.json 持久化的
775
+ * taskDir / steps[].resultPath / skillDirectory 是异机绝对路径(如
776
+ * C:\Users\...),在本机不可用:isLoopTask 判断恒 false(任务被错误
777
+ * 注册为 orchestrator,大循环历史页看不到)、详情页报告链接 404、
778
+ * 步骤重执行提示的工作目录错误。
779
+ *
780
+ * 本机规范位置是 db/task/{taskId}/(storage 适配器的读写基准),按
781
+ * 「同相对路径」原则将旧前缀重映射为本机路径:
782
+ * - data.taskDir / steps[].taskDir → db/task/{taskId}
783
+ * - steps[].resultPath → db/task/{taskId}/<相对路径>
784
+ * - data.skillDirectory(异机无效时) → 服务端默认 SKILL 根目录
785
+ *
786
+ * 同时回写自愈后的快照(JsonAdapter 原子写),使直接读盘的消费方
787
+ * (getStepInfoByLoop / LoopEngine.restoreLoop 等)一并修复。
788
+ *
789
+ * @param {string} taskId
790
+ * @param {object} state - orchestration.json 反序列化对象(原地修改)
791
+ * @returns {Promise<object>} 自愈后的 state
792
+ * @private
793
+ */
794
+ async _healCrossMachinePaths(taskId, state) {
795
+ if (!this.storage || !state) return state;
796
+ const localTaskDir = path.join(this.storage.manager.rootDir, 'task', taskId);
797
+
798
+ const oldDir = state.taskDir;
799
+ // 同机恢复(含自定义 taskDir)、无 taskDir、或本机规范目录不存在 → 无需自愈
800
+ if (!oldDir || path.resolve(oldDir) === path.resolve(localTaskDir)) return state;
801
+ let oldExists = false;
802
+ try { oldExists = fs.existsSync(oldDir); } catch { /* 非法路径视为不存在 */ }
803
+ if (oldExists || !fs.existsSync(localTaskDir)) return state;
804
+
805
+ this._remapStateToLocal(state, localTaskDir);
806
+
807
+ // 回写主快照;running 状态保持原样(与 restoreState 的"保留崩溃现场快照"语义一致)
808
+ if (state.status !== 'running') {
809
+ try {
810
+ await this.storage.json('task').write(`${taskId}/orchestration`, state);
811
+ console.log(
812
+ `[OrchestratorController] 已自愈异机路径并回写: taskId=${taskId}, ${oldDir} → ${localTaskDir}`
813
+ );
814
+ } catch (err) {
815
+ console.warn(`[OrchestratorController] 自愈回写 orchestration.json 失败: taskId=${taskId}, ${err.message}`);
816
+ }
817
+ }
818
+
819
+ // 历史轮次快照与 loop.json 同步自愈(直接读盘的消费方)
820
+ await this._healTaskSnapshotFiles(taskId, localTaskDir);
821
+ return state;
822
+ }
823
+
824
+ /**
825
+ * 自愈任务目录下的历史轮次快照(orchestration_loop{N}.json)与 loop.json。
826
+ * 这些文件由消费方直接从磁盘读取(getStepInfoByLoop / LoopEngine.restoreLoop),
827
+ * 仅修内存实例无法覆盖。running 状态的快照保持原样(崩溃现场语义)。
828
+ *
829
+ * @param {string} taskId
830
+ * @param {string} localTaskDir - 本机任务目录(db/task/{taskId})
831
+ * @returns {Promise<void>}
832
+ * @private
833
+ */
834
+ async _healTaskSnapshotFiles(taskId, localTaskDir) {
835
+ try {
836
+ const names = fs.readdirSync(localTaskDir);
837
+ const targets = names.filter(n => /^orchestration_loop\d+\.json$/.test(n) || n === 'loop.json');
838
+ for (const name of targets) {
839
+ const key = `${taskId}/${name.replace(/\.json$/, '')}`;
840
+ let data;
841
+ try {
842
+ data = await this.storage.json('task').read(key);
843
+ } catch { continue; }
844
+ if (!data || data.status === 'running') continue;
845
+ if (!this._remapStateToLocal(data, localTaskDir)) continue;
846
+ try {
847
+ await this.storage.json('task').write(key, data);
848
+ console.log(`[OrchestratorController] 已自愈异机路径并回写: ${name}, taskId=${taskId}`);
849
+ } catch (err) {
850
+ console.warn(`[OrchestratorController] 自愈回写 ${name} 失败: taskId=${taskId}, ${err.message}`);
851
+ }
852
+ }
853
+ } catch (err) {
854
+ console.warn(`[OrchestratorController] 扫描轮次快照失败: taskId=${taskId}, ${err.message}`);
855
+ }
856
+ }
857
+
858
+ /**
859
+ * 将单个快照对象内的异机路径重映射为本机路径(原地修改)。
860
+ * 依据快照自身的 taskDir 前缀匹配(兼容正反斜杠分隔符)。
861
+ *
862
+ * @param {object} data - orchestration / loop 快照对象
863
+ * @param {string} localTaskDir - 本机任务目录(db/task/{taskId})
864
+ * @returns {boolean} 是否发生了修改
865
+ * @private
866
+ */
867
+ _remapStateToLocal(data, localTaskDir) {
868
+ if (!data || typeof data !== 'object') return false;
869
+
870
+ const oldDir = data.taskDir;
871
+ if (typeof oldDir !== 'string' || !oldDir) return false;
872
+ const oldPrefix = oldDir.replace(/\\/g, '/').replace(/\/+$/, '');
873
+ const localPrefix = localTaskDir.replace(/\/+$/, '');
874
+ if (!oldPrefix || oldPrefix === localPrefix) return false;
875
+
876
+ const remap = (p) => {
877
+ if (typeof p !== 'string' || !p) return p;
878
+ const np = p.replace(/\\/g, '/');
879
+ if (np === oldPrefix) return localTaskDir;
880
+ if (np.startsWith(oldPrefix + '/')) {
881
+ return path.join(localTaskDir, np.slice(oldPrefix.length + 1));
882
+ }
883
+ return p;
884
+ };
885
+
886
+ // skillDirectory:异机路径本机无效时,回退到服务端默认 SKILL 根目录
887
+ const healSkillDir = (sd) => {
888
+ if (typeof sd !== 'string' || !sd || !this.skillDirectory) return sd;
889
+ try {
890
+ return fs.existsSync(sd) ? sd : this.skillDirectory;
891
+ } catch {
892
+ return this.skillDirectory;
893
+ }
894
+ };
895
+
896
+ let changed = false;
897
+ if (data.taskDir !== localTaskDir) { data.taskDir = localTaskDir; changed = true; }
898
+ if (data.skillDirectory) {
899
+ const next = healSkillDir(data.skillDirectory);
900
+ if (next !== data.skillDirectory) { data.skillDirectory = next; changed = true; }
901
+ }
902
+ if (Array.isArray(data.steps)) {
903
+ for (const s of data.steps) {
904
+ if (!s || typeof s !== 'object') continue;
905
+ if (s.taskDir !== localTaskDir) { s.taskDir = localTaskDir; changed = true; }
906
+ if (s.resultPath) {
907
+ const next = remap(s.resultPath);
908
+ if (next !== s.resultPath) { s.resultPath = next; changed = true; }
909
+ }
910
+ if (s.skillDirectory) {
911
+ const next = healSkillDir(s.skillDirectory);
912
+ if (next !== s.skillDirectory) { s.skillDirectory = next; changed = true; }
913
+ }
914
+ }
915
+ }
916
+ return changed;
917
+ }
918
+
770
919
  /**
771
920
  * 判断 running 状态的持久化任务是否有仍存活的"外部属主进程"。
772
921
  *
@@ -861,6 +1010,9 @@ class OrchestratorController {
861
1010
  continue;
862
1011
  }
863
1012
 
1013
+ // ── 跨机路径自愈:异机拷贝的任务目录内是异机绝对路径,重映射为本机路径 ──
1014
+ await this._healCrossMachinePaths(taskId, state);
1015
+
864
1016
  const wasRunning = state.status === 'running';
865
1017
  const orch = this._createOrchestrator();
866
1018
  await orch.restoreState(state);
@@ -934,22 +1086,54 @@ class OrchestratorController {
934
1086
  // finally 未执行,task_type 可能停留在被编排 load() 覆盖后的
935
1087
  // 'orchestrator' —— 修正为 'loop',否则大循环历史页
936
1088
  // (taskType='loop' 过滤)看不到该任务。
937
- const isLoopTask = state.taskDir
938
- && fs.existsSync(path.join(state.taskDir, 'loop.json'));
1089
+ // 注意不能只看 state.taskDir:跨机拷贝的任务目录里持久化的是
1090
+ // 异机绝对路径(本机 existsSync 恒 false),需同时检查本机
1091
+ // 规范路径 db/task/{taskId}/loop.json。
1092
+ const loopFileLocal = this.storage
1093
+ ? path.join(this.storage.manager.rootDir, 'task', taskId, 'loop.json')
1094
+ : null;
1095
+ const isLoopTask = Boolean(
1096
+ (state.taskDir && fs.existsSync(path.join(state.taskDir, 'loop.json')))
1097
+ || (loopFileLocal && fs.existsSync(loopFileLocal))
1098
+ );
1099
+
1100
+ // 大循环任务的对外状态/标题以 loop.json 为准(编排只是单轮载体):
1101
+ // 终态(passed/failed/...)、标题(用户原始任务描述)、创建时间
1102
+ // 均取 loop 快照,与其他大循环任务的注册口径一致。
1103
+ let loopState = null;
1104
+ if (isLoopTask) {
1105
+ try {
1106
+ loopState = await this.storage.json('task').read(`${taskId}/loop`);
1107
+ } catch { /* ignore */ }
1108
+ }
1109
+ const finalStatus = (isLoopTask && loopState?.status) ? loopState.status : status;
1110
+ const title = (isLoopTask && loopState?.taskDescription)
1111
+ ? String(loopState.taskDescription).slice(0, 200)
1112
+ : String(firstStep.taskDescription || taskId).slice(0, 200);
1113
+ const description = (isLoopTask && loopState?.taskDescription)
1114
+ ? loopState.taskDescription
1115
+ : (firstStep.taskDescription || '');
1116
+ const createdAt = (isLoopTask && loopState?.startTime)
1117
+ ? loopState.startTime
1118
+ : (state.startTime || new Date().toISOString());
1119
+ if (isLoopTask && loopState) {
1120
+ metadata.currentLoop = loopState.currentLoop ?? 0;
1121
+ metadata.maxLoops = loopState.maxLoops ?? 3;
1122
+ }
939
1123
 
940
1124
  if (!existing) {
941
- // 老任务未入库(TaskRegistryStore 上线前创建):补录摘要,保留原始创建时间
1125
+ // 老任务未入库(TaskRegistryStore 上线前创建 / 跨机拷贝):补录摘要,保留原始创建时间
942
1126
  await registry.upsert({
943
1127
  taskId,
944
1128
  taskType: isLoopTask ? 'loop' : 'orchestrator',
945
- status,
946
- title: (firstStep.taskDescription || taskId).slice(0, 200),
947
- description: firstStep.taskDescription || '',
948
- createdAt: state.startTime || new Date().toISOString(),
1129
+ status: finalStatus,
1130
+ title,
1131
+ description,
1132
+ createdAt,
949
1133
  metadata
950
1134
  });
951
1135
  } else {
952
- const patchBody = { status, metadata };
1136
+ const patchBody = { status: finalStatus, metadata };
953
1137
  if (isLoopTask && existing.task_type !== 'loop') {
954
1138
  patchBody.task_type = 'loop';
955
1139
  }
@@ -957,7 +1141,7 @@ class OrchestratorController {
957
1141
  }
958
1142
  console.log(
959
1143
  `[OrchestratorController] 注册表已修正: taskId=${taskId}, ` +
960
- `${existing ? existing.status : '(未入库)'} → ${status}`
1144
+ `${existing ? existing.status : '(未入库)'} → ${finalStatus}`
961
1145
  );
962
1146
  } catch (err) {
963
1147
  console.warn(`[OrchestratorController] 恢复后回写注册表失败: taskId=${taskId}, ${err.message}`);
@@ -0,0 +1,252 @@
1
+ ---
2
+ name: flutter-build-test
3
+ description: 对 Flutter 三方库(OHOS 鸿蒙化仓库)执行多版本编译验证:定位 example 工程并确认含 ohos 工程代码,使用 flvm 在 3.7/3.22/3.27/3.32/3.35/3.41 六个 Flutter 版本间切换并逐版本编译 hap,判定该三方库可在哪些版本使用;随后按编译结果更新 README.OpenHarmony(_CN).md(版本支持、TAG 对应表、约束与限制)与 buildEnv.sh(FLUTTER_SDK_VERSION)。当需要验证 Flutter 三方库在哪些鸿蒙 Flutter 版本可用、或将编译验证结果回写仓库文档与构建环境脚本时使用。
4
+ license: Apache-2.0
5
+ compatibility: 需要 Node.js、flvm(Flutter 多版本管理器 https://gitcode.com/lalhanOrz/flvm)、OHOS Flutter SDK、DevEco Studio 工具链(java/ohpm/hvigor/hdc)、git,需可访问 pub.flutter-io.cn 镜像
6
+ metadata:
7
+ author: LoopEngine
8
+ version: "1.0.0"
9
+ category: flutter-testing
10
+ ---
11
+
12
+ # Flutter 三方库多版本编译验证
13
+
14
+ 本 Skill 对 Flutter 三方库鸿蒙化仓库执行**多版本编译验证闭环**:定位 example 工程(含 ohos 工程代码校验)→ flvm 切换 6 个 Flutter 版本(3.7/3.22/3.27/3.32/3.35/3.41)逐版本编译 hap → 判定可用版本范围 → 按结果更新 README.OpenHarmony(_CN).md 与 buildEnv.sh → 输出编译验证报告。
15
+
16
+ > ⚠️ **核心原则**:
17
+ > 1. 本 Skill 的定位是**验证**而非适配——除环境类问题(镜像、DevEco 路径、工具链)外,**不做代码级修复**;版本不兼容导致的编译失败如实记录
18
+ > 2. **环境问题导致的失败不能记为"该版本不支持"**——必须先排除环境因素再下结论(区分标准见 [编译排错手册](references/BUILD_TROUBLESHOOTING.md))
19
+ > 3. 六个版本**必须串行**测试(flvm use 切换的是全局 Flutter),每版本独立记录结果
20
+ > 4. 文档与 buildEnv.sh 的修改必须与编译结果**逐字对应**,禁止写入未经实测的版本结论
21
+
22
+ ---
23
+
24
+ ## 任务参数
25
+
26
+ | 参数 | 类型 | 必填 | 说明 |
27
+ |------|------|------|------|
28
+ | `libRoot` | string | ✅ | Flutter 三方库仓库根目录(含 `pubspec.yaml`,通常含 `example/`、`ohos/`、`README.OpenHarmony_CN.md`、`buildEnv.sh`) |
29
+ | `versions` | string | ❌ | 待验证版本列表,逗号分隔;缺省 `3.7,3.22,3.27,3.32,3.35,3.41` |
30
+ | `buildMode` | string | ❌ | 编译模式 `debug`/`release`;缺省 `debug` |
31
+ | `codesign` | boolean | ❌ | 是否产出签名 hap;缺省 false(`--no-codesign`,无头验证编译链无需 DevEco 签名配置) |
32
+ | `outputDir` | string | ❌ | 结果输出目录;缺省 `{libRoot}/flutter-build-test/` |
33
+ | `updateDocs` | boolean | ❌ | 是否执行 Phase 3/4 文档回写;缺省 true |
34
+
35
+ ---
36
+
37
+ ## 输入依赖
38
+
39
+ - **前置SKILL**: 无(可作为独立任务调用;仓库已克隆到本地即可)
40
+ - **输入文件**:
41
+ - 库工程:`{libRoot}/pubspec.yaml`、`{libRoot}/example/`(含 `ohos/` 子目录的鸿蒙工程)
42
+ - 环境脚本(若存在):`{libRoot}/buildEnv.sh`
43
+ - 文档(若存在):`{libRoot}/README.OpenHarmony_CN.md`、`{libRoot}/README.OpenHarmony.md`
44
+
45
+ ---
46
+
47
+ ## 工作流程概览
48
+
49
+ ```
50
+ Phase 1: 环境检查与 example 工程定位(flvm doctor + locate-example.cjs)
51
+
52
+ Phase 2: 多版本编译矩阵(build-matrix.cjs:flvm 切换 + flutter build hap × 6 版本)
53
+
54
+ Phase 3: 更新 README.OpenHarmony(_CN).md(TAG 对应表 + 约束与限制)
55
+
56
+ Phase 4: 更新 buildEnv.sh(FLUTTER_SDK_VERSION / BUILD_PKG_DIR)
57
+
58
+ Phase 5: 输出编译验证报告
59
+ ```
60
+
61
+ ---
62
+
63
+ ## Phase 1:环境检查与 example 工程定位
64
+
65
+ ### 1.1 环境体检(不通过禁止进入 Phase 2)
66
+
67
+ ```bash
68
+ flvm doctor
69
+ ```
70
+
71
+ 确认以下各项(✓ 为通过,! 为警告可继续,✗ 必须修复):
72
+
73
+ | 检查项 | 不通过时的处理 |
74
+ |--------|---------------|
75
+ | flvm 已安装 | `npm install -g flvm`(或从 https://gitcode.com/lalhanOrz/flvm 克隆安装) |
76
+ | flvm 已 init | `flvm init --deveco "/Applications/DevEco-Studio.app/Contents"`(Windows 为 DevEco 安装目录;详见 [FLVM 使用指南](references/FLVM_GUIDE.md)) |
77
+ | Flutter 镜像(PUB_HOSTED_URL) | 按 flvm doctor 提示设置 `https://pub.flutter-io.cn` |
78
+ | OHOS 工具链(java/TOOL_HOME/ohpm/hvigor/node/hdc) | 安装/修复 DevEco Studio 后重跑 `flvm init --deveco <path>` |
79
+
80
+ 将 `flvm doctor` 完整输出保存到 `{outputDir}/env-doctor.txt`(环境信息将写入最终报告)。
81
+
82
+ ### 1.2 定位 example 工程并校验 ohos 工程代码
83
+
84
+ ```bash
85
+ node {skillDir}/scripts/locate-example.cjs --source {libRoot}
86
+ ```
87
+
88
+ 脚本行为:
89
+ - 优先读取 `{libRoot}/buildEnv.sh` 的 `BUILD_PKG_DIR`(部分库的 example 不在根下,如 `YFree_Flutter/Lite/example`)
90
+ - 扫描根目录及两层深度内含 `pubspec.yaml` 的 example 类目录(`example/`、`Example/`、`example_app/` 等)
91
+ - 对每个候选校验:存在 `pubspec.yaml` **且** `ohos/` 子目录含鸿蒙工程特征(`build-profile.json5` / `oh-package.json5` / `AppScope/`)
92
+ - 输出 JSON:`exampleDir`(绝对路径)、`ohosDir`、`buildPkgDir`(buildEnv.sh 实际值)、`hasOhos`
93
+
94
+ **结果判定**:
95
+
96
+ | 情况 | 处理 |
97
+ |------|------|
98
+ | 找到含 ohos 的 example | 记录 `exampleDir`,进入 Phase 2 |
99
+ | 有 example 但无 `ohos/` 目录 | 可尝试 `flutter create --platforms ohos .`(在 example 目录下,需当前 Flutter 为 OHOS fork)生成后重跑定位;仍失败 → 终止并报告"缺少鸿蒙工程代码" |
100
+ | 无任何 example 目录 | 终止并报告"未找到 example 工程" |
101
+
102
+ > 🔴 **禁止**以根目录 `ohos/`(插件平台代码)替代 example 工程作为编译目标——那是库的鸿蒙实现代码(相当于 `android/`),不是可编译的应用工程。
103
+
104
+ ---
105
+
106
+ ## Phase 2:多版本编译矩阵
107
+
108
+ ### 2.1 执行编译矩阵脚本
109
+
110
+ ```bash
111
+ node {skillDir}/scripts/build-matrix.cjs --source {libRoot} --mode {buildMode} --output {outputDir}
112
+ ```
113
+
114
+ 常用参数:
115
+
116
+ | 参数 | 说明 |
117
+ |------|------|
118
+ | `--versions "3.7,3.22"` | 只测指定版本(会话超时时可分批执行) |
119
+ | `--resume` | 断点续跑:跳过 results JSON 中已有最终结论的版本 |
120
+ | `--example <dir>` | 显式指定 example 目录(覆盖定位结果/buildEnv.sh) |
121
+ | `--build-timeout <sec>` | 单版本编译超时;缺省 1800 |
122
+ | `--install-timeout <sec>` | flvm 版本安装超时;缺省 3600 |
123
+
124
+ > ⚠️ **外层命令超时必须放大**:6 版本全量串行可能耗时 1 小时以上(含 flvm 版本安装与引擎缓存预热)。建议外层 timeout 设为 3600000ms 以上,或用 `--versions` 分批 + `--resume` 续跑。
125
+
126
+ 脚本每个版本的执行序列:
127
+
128
+ 1. `flvm use <分支名>` 切换版本(分支映射见下表,未安装自动安装;失败时回退模糊匹配 `flvm use <主.次>`)
129
+ 2. `flutter --version` 校验实际版本与预期一致(主.次版本号比对)
130
+ 3. 在 example 目录:`flutter clean`(忽略失败)→ 备份并删除 `pubspec.lock`(保证每版本独立求解依赖)→ `flutter pub get`
131
+ 4. `flutter build hap --{mode} --no-codesign` 编译(默认不签名——无头验证只需编译链通过;`--codesign` 需工程已配置 DevEco 调试签名,否则 flutter 工具直接报错)
132
+ 5. 校验 hap 产物存在(`example/ohos/entry/build/**/outputs/**/*.hap`)
133
+ 6. 按错误特征分类失败原因,结果**增量写入** `{outputDir}/build-matrix-results.json`,日志存 `{outputDir}/logs/{版本}.log`
134
+
135
+ **版本-分支映射表**(脚本内置,与 flvm 远程分支对应):
136
+
137
+ | 主版本 | 首选分支 | 回退分支 |
138
+ |--------|----------|----------|
139
+ | 3.7 | `br_3.7.12-ohos-1.1.3` | `br_3.7.12-ohos-dev` |
140
+ | 3.22 | `oh-3.22.3-release` | `oh-3.22.3-dev` |
141
+ | 3.27 | `oh-3.27.4-dev` | `oh-3.27.0-release` |
142
+ | 3.32 | `oh-3.32.0-release` | `oh-3.32.4-dev` |
143
+ | 3.35 | `oh-3.35.7-dev` | `oh-3.35.7-release` |
144
+ | 3.41 | `oh-3.41.9-release` | `oh-3.41.9-dev` |
145
+
146
+ > 映射表过期时(flvm 远程分支更新),以 `flvm list -r` 实际输出为准修正脚本 `PREFERRED_BRANCHES` 表。
147
+
148
+ ### 2.2 失败诊断与环境因素排除
149
+
150
+ 对每个失败版本,按 [编译排错手册](references/BUILD_TROUBLESHOOTING.md) 分类处理:
151
+
152
+ | 失败类别 | 是否重试 | 是否计入"版本不支持" |
153
+ |----------|----------|---------------------|
154
+ | 环境类(镜像/网络/工具链/hvigor 缓存/Java) | 修复环境后重试 | 否(修复后通过则记为支持) |
155
+ | SDK 约束不兼容(Dart SDK 版本过低/过高) | 否 | 是 |
156
+ | 依赖冲突(version solving failed) | 否 | 是 |
157
+ | 鸿蒙工程编译错误(ArkTS/NAPI/hvigor 业务报错) | 否 | 是 |
158
+ | hvigor 任务异常退出(exit 255 等) | 否(人工判定) | 结合日志定性(工具链 vs 版本不兼容) |
159
+ | 签名错误(仅 `--codesign` 模式) | 换默认无签名模式重试 | 否 |
160
+
161
+ > 🔴 **判定纪律**:同一环境问题(如 ohpm 源不可达)在多个版本上重复失败时,只诊断一次、修复一次、全部重试;禁止把环境故障逐版本重复记为"不支持"。
162
+
163
+ ### 2.3 版本支持结论
164
+
165
+ 全部版本测完后,从 `{outputDir}/build-matrix-results.json` 提取:
166
+
167
+ - **支持版本列表**:`status == "pass"` 的版本(含 `pass` 所用的 flvm 分支名,供 Phase 3/4 使用)
168
+ - **最高支持版本**:支持列表中版本号最大者(决定 buildEnv.sh 的 `FLUTTER_SDK_VERSION`)
169
+ - **逐版本失败原因**:写入报告
170
+
171
+ ---
172
+
173
+ ## Phase 3:更新 README.OpenHarmony(_CN).md
174
+
175
+ > 详细修改规范与完整模板参见 [文档修改规范](references/DOC_UPDATE_GUIDE.md) 与 [README 段落模板](assets/README_SECTION_TEMPLATE.md)。参考实例:https://gitcode.com/CPF-Flutter/fluttertpc_file_picker/blob/master/README.OpenHarmony_CN.md
176
+
177
+ 按 Phase 2 结论修改文档,**三处必改**:
178
+
179
+ ### 3.1 TAG 对应表(补充支持使用的版本)
180
+
181
+ 在"下载安装"章节的 TAG 表中,按版本支持结论逐行更新:
182
+
183
+ ```markdown
184
+ | Flutter 框架版本 | TAG 名称 | 分支名 |
185
+ | --- | --- | --- |
186
+ | 3.7 | ------ | ------ |
187
+ | 3.22 | {TAG} | {分支} |
188
+ | 3.27 | {TAG} | {分支} |
189
+ ```
190
+
191
+ - 支持的版本:`{TAG}` = 当前仓库 ohos TAG(`git describe --tags --abbrev=0` 或 `git tag --list '*ohos*'`,无 TAG 时从 CHANGELOG.OpenHarmony.md / pubspec.yaml version 推断并标注);`{分支}` = `git branch --show-current`
192
+ - 不支持的版本:填 `------`
193
+ - 表格行覆盖全部 6 个框架版本(3.7/3.22/3.27/3.32/3.35/3.41),不遗漏
194
+
195
+ ### 3.2 约束与限制(兼容性)
196
+
197
+ 在"约束与限制 → 兼容性"中,将"已测试通过"清单替换为实测结果:
198
+
199
+ ```markdown
200
+ ### 兼容性
201
+
202
+ 在以下版本中已测试通过:
203
+
204
+ - Flutter: {flutter版本+ohos版本}, DevEco Studio: {版本}, SDK: {API版本}({SDK版本}), ROM: {ROM版本};
205
+ ```
206
+
207
+ - 每个支持版本一行,环境信息取自 `{outputDir}/env-doctor.txt` 与本机 DevEco Studio(`devecocli --version`、`DEVECO_SDK_HOME` 下 sdk 版本目录);ROM 版本在有真机时经 `hdc shell param get const.product.software.version` 获取,无设备则省略 ROM 字段并在报告注明
208
+ - 保留该章节其余内容(权限要求等),**只增改版本相关部分,禁止删除既有内容**
209
+
210
+ ### 3.3 英文版同步
211
+
212
+ `README.OpenHarmony.md`(英文版)存在时,镜像同步 3.1/3.2 两处修改(英文措辞,表格结构一致)。README 均不存在时,按 [README 段落模板](assets/README_SECTION_TEMPLATE.md) 新建。
213
+
214
+ ---
215
+
216
+ ## Phase 4:更新 buildEnv.sh
217
+
218
+ ```bash
219
+ node {skillDir}/scripts/update-buildenv.cjs --source {libRoot} --results {outputDir}/build-matrix-results.json
220
+ ```
221
+
222
+ 脚本行为(参考实例:https://gitcode.com/CPF-Flutter/fluttertpc_flutter_udid/blob/br_v4.1.1_ohos_dev/buildEnv.sh):
223
+
224
+ 1. 读取 `{libRoot}/buildEnv.sh`(不存在时按 [buildEnv 模板](assets/BUILDENV_TEMPLATE.md) 新建,含 Apache 头)
225
+ 2. `FLUTTER_SDK_VERSION` ← 最高支持版本实测通过的 flvm 分支名(取自 results JSON 的 `branch` 字段,如 `oh-3.35.7-dev`)——**按分支实际支持的版本回写**
226
+ 3. `BUILD_PKG_DIR` ← Phase 1 定位的实际 example 相对路径(原值正确则不动)
227
+ 4. 保留 License 头部与既有结构,只改上述两个变量行
228
+ 5. 输出变更对照到 `{outputDir}/buildenv-update-report.json`
229
+
230
+ > 用 `--branch` 可显式覆盖(仅当调用方明确指定时使用)。所有支持版本均失败时不修改 `FLUTTER_SDK_VERSION`,在报告中标注。
231
+
232
+ ---
233
+
234
+ ## Phase 5:输出编译验证报告
235
+
236
+ `write_file` `{outputDir}/flutter-build-test-report.md`,模板参见 [编译验证报告模板](assets/BUILD_REPORT_TEMPLATE.md),内容必须包含:
237
+
238
+ 1. **环境信息**:flvm 版本、Flutter 已装版本、DevEco Studio / SDK / ROM(引用 env-doctor.txt)
239
+ 2. **编译矩阵结果表**:版本 | flvm 分支 | pub get | build hap | 结论 | 失败原因摘要
240
+ 3. **版本支持结论**:支持版本列表 + 最高支持版本
241
+ 4. **文档更新记录**:README.OpenHarmony(_CN).md 修改点(TAG 对应表前后对照)、buildEnv.sh 变更对照(引用 buildenv-update-report.json)
242
+ 5. **仓库状态说明**:测试期间的临时改动——pubspec.lock 已由脚本恢复原文件;`build/`、`.dart_tool/` 等未跟踪产物遗留于 example 目录(建议 `flutter clean` 或按需保留);flutter 工具会自动修改 `example/ohos/build-profile.json5`、`oh-package.json5` 注册插件模块(属构建正常副作用,`build-matrix-results.json` 的 `repoChangesDuringBuild` 字段已记录,报告中如实列出,调用方要求干净仓库时可 `git checkout` 恢复)
243
+ 6. **遗留问题**:环境类失败、待人工复核项
244
+
245
+ ---
246
+
247
+ ## 参考资料
248
+
249
+ - 共享脚本:`scripts/locate-example.cjs` / `scripts/build-matrix.cjs` / `scripts/update-buildenv.cjs`
250
+ - flvm 工具:https://gitcode.com/lalhanOrz/flvm(`flvm --help` / [FLVM 使用指南](references/FLVM_GUIDE.md))
251
+ - 文档范例:https://gitcode.com/CPF-Flutter/fluttertpc_file_picker/blob/master/README.OpenHarmony_CN.md
252
+ - buildEnv 范例:https://gitcode.com/CPF-Flutter/fluttertpc_flutter_udid/blob/br_v4.1.1_ohos_dev/buildEnv.sh
@@ -0,0 +1,42 @@
1
+ # buildEnv.sh 模板
2
+
3
+ > `buildEnv.sh` 不存在时按此模板新建(与 CPF-Flutter 社区仓库保持一致,参考 fluttertpc_flutter_udid)。`{占位符}` 由 update-buildenv.cjs 填充。本文件是**目标仓库的交付物模板**(Bash 脚本),非本 SKILL 的可执行脚本。
4
+
5
+ ---
6
+
7
+ ## 模板内容
8
+
9
+ ```bash
10
+ #!/bin/bash
11
+ # Copyright (C) 2026 Huawei Device Co., Ltd.
12
+ # Licensed under the Apache License, Version 2.0 (the "License");
13
+ # you may not use this file except in compliance with the License.
14
+ # You may obtain a copy of the License at
15
+ #
16
+ # http://www.apache.org/licenses/LICENSE-2.0
17
+ #
18
+ # Unless required by applicable law or agreed to in writing, software
19
+ # distributed under the License is distributed on an "AS IS" BASIS,
20
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
+ # See the License for the specific language governing permissions and
22
+ # limitations under the License.
23
+ #/
24
+
25
+ BUILD_PKG_DIR={example相对路径,通常为 example}
26
+ FLUTTER_SDK_VERSION={最高支持版本实测通过的 flvm 分支名,如 oh-3.35.7-dev}
27
+ ```
28
+
29
+ ## 填充规则
30
+
31
+ | 变量 | 取值 |
32
+ |------|------|
33
+ | `BUILD_PKG_DIR` | Phase 1 定位的 example 相对路径(`example` / `YFree_Flutter/Lite/example` 等) |
34
+ | `FLUTTER_SDK_VERSION` | build-matrix-results.json 中最高 pass 版本的 `branch` 字段(如 `oh-3.35.7-dev`、`oh-3.27.4-dev`) |
35
+
36
+ ## 使用方式
37
+
38
+ ```bash
39
+ node {skillDir}/scripts/update-buildenv.cjs --source {libRoot} --results {outputDir}/build-matrix-results.json
40
+ ```
41
+
42
+ 脚本内置本模板,自动完成新建/更新并赋予可执行权限(0755)。