@ohos-cpf/3rdloop 0.0.7 → 0.0.9

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 (230) hide show
  1. package/README.md +44 -25
  2. package/lib/cli.js +54 -29
  3. package/lib/config-cmd.js +1 -1
  4. package/lib/config.js +32 -0
  5. package/lib/doctor.js +266 -48
  6. package/lib/mcp.js +498 -0
  7. package/lib/opencode.js +75 -29
  8. package/lib/orch.js +32 -10
  9. package/lib/runner.js +8 -5
  10. package/lib/serve.js +162 -149
  11. package/lib/step.js +15 -4
  12. package/lib/web.js +107 -265
  13. package/lib/workflow.js +18 -8
  14. package/package.json +6 -2
  15. package/vendor/Archive/data/.index/meta.json +7 -0
  16. package/vendor/Archive/data/.index/search.db +0 -0
  17. package/vendor/Archive/data/docs/experiences/arkts-check-tool-pitfalls-baseline-comparison.md +13 -0
  18. package/vendor/Archive/data/docs/experiences/arkts-deep-path-import-recognition.md +31 -0
  19. package/vendor/Archive/data/docs/experiences/arkts-demo-coverage-entry-file-case-mismatch.md +25 -0
  20. package/vendor/Archive/data/docs/experiences/arkts-demo-coverage-import-trailing-slash.md +20 -0
  21. package/vendor/Archive/data/docs/experiences/arkts-library-source-files-may-be-ts.md +18 -0
  22. package/vendor/Archive/data/docs/experiences/arkts-typedarray-buffer-interface-cast-compile-fixes.md +36 -0
  23. package/vendor/Archive/data/docs/experiences/arkts-xts-compile-error-clustering-fix.md +24 -0
  24. package/vendor/Archive/data/docs/experiences/arkts-xts-doc-expected-value-runtime-probe.md +35 -0
  25. package/vendor/Archive/data/docs/experiences/arkts-xts-fixture-rawfile-testability-injection.md +33 -0
  26. package/vendor/Archive/data/docs/experiences/code-linter-chinese-comment-false-positive.md +16 -0
  27. package/vendor/Archive/data/docs/experiences/esobject-new-expression-variable-tracking.md +33 -0
  28. package/vendor/Archive/data/docs/experiences/filesystem-case-sensitivity-module-resolution.md +31 -0
  29. package/vendor/Archive/data/docs/experiences/gitcode-clone-redirect-warning-normal.md +13 -0
  30. package/vendor/Archive/data/docs/experiences/kn_msgvq1w585ec8319.md +77 -0
  31. package/vendor/Archive/data/docs/experiences/kn_msovnm6rd46de72c.md +98 -0
  32. package/vendor/Archive/data/docs/experiences/kn_mspt8vntfe708576.md +87 -0
  33. package/vendor/Archive/data/docs/experiences/kn_mspu1ei77adbbb48.md +133 -0
  34. package/vendor/Archive/data/docs/experiences/kn_mspubgak8d30bfce.md +128 -0
  35. package/vendor/Archive/data/docs/experiences/kn_mspug2de3cb1f7c3.md +115 -0
  36. package/vendor/Archive/data/docs/experiences/kn_skill_opt_001.md +38 -0
  37. package/vendor/Archive/data/docs/experiences/loop-artifacts-verification-evidence-triangle.md +19 -0
  38. package/vendor/Archive/data/docs/experiences/macos-bash-replace-build-ps1.md +19 -0
  39. package/vendor/Archive/data/docs/experiences/macos-bsd-diff-q-illegal-option.md +15 -0
  40. package/vendor/Archive/data/docs/experiences/migrate-cjs-compatible-api-version-map-limit.md +17 -0
  41. package/vendor/Archive/data/docs/experiences/mp3agic-arkts-library-behavior-quirks.md +21 -0
  42. package/vendor/Archive/data/docs/experiences/ohos-face-detection-scan-api-mapping.md +15 -0
  43. package/vendor/Archive/data/docs/experiences/pr-metadata-and-diff-integrity-verification.md +12 -0
  44. package/vendor/Archive/data/docs/experiences/rn-coverage-index-signature-pseudo-member-false-negative.md +32 -0
  45. package/vendor/Archive/data/docs/experiences/rn-memory-check-severity-calibration.md +21 -0
  46. package/vendor/Archive/data/docs/experiences/rn-platform-specific-api-exclusion-evidence.md +24 -0
  47. package/vendor/Archive/data/docs/experiences/rn-upstream-version-diff-pitfalls.md +17 -0
  48. package/vendor/Archive/data/docs/experiences/rn-version-gap-risk-rating-rules.md +13 -0
  49. package/vendor/Archive/data/docs/experiences/rn-view-lib-native-contract-diff-fabric-spec.md +16 -0
  50. package/vendor/Archive/data/docs/experiences/rnoh-adapter-package-version-field-semantics.md +18 -0
  51. package/vendor/Archive/data/docs/experiences/rnoh-arkts-sqlite-rows-array-format.md +18 -0
  52. package/vendor/Archive/data/docs/experiences/rnoh-dependency-residue-scan-strategy.md +18 -0
  53. package/vendor/Archive/data/docs/experiences/rnoh-example-local-lib-dep-use-tgz.md +53 -0
  54. package/vendor/Archive/data/docs/experiences/rnoh-example-prereserve-rntesterlist.md +17 -0
  55. package/vendor/Archive/data/docs/experiences/rnoh-example-template-compatiblesdkversion-upgrade.md +29 -0
  56. package/vendor/Archive/data/docs/experiences/rnoh-example-turbomodule-shell-adaptation-checklist.md +41 -0
  57. package/vendor/Archive/data/docs/experiences/rnoh-mcp-build-project-tool-risk.md +18 -0
  58. package/vendor/Archive/data/docs/experiences/rnoh-npm-devdependencies-version-precheck.md +29 -0
  59. package/vendor/Archive/data/docs/experiences/rnoh-repo-baseline-version-verification.md +13 -0
  60. package/vendor/Archive/data/docs/experiences/rnoh-repo-upstream-directory-mapping.md +18 -0
  61. package/vendor/Archive/data/docs/experiences/rnoh-stateless-library-zero-leak-verification.md +31 -0
  62. package/vendor/Archive/data/docs/experiences/rnoh-template-npmrc-credential-removal.md +15 -0
  63. package/vendor/Archive/data/docs/experiences/rnoh-turbomodule-codegen-wiring-verification.md +19 -0
  64. package/vendor/Archive/data/docs/experiences/rnoh-turbomodule-cpp-methodmap-compatibility-check.md +20 -0
  65. package/vendor/Archive/data/docs/experiences/scenario-doc-vs-source-signature-diff-four-patterns.md +24 -0
  66. package/vendor/Archive/data/docs/experiences/static-analysis-grep-cross-validation.md +29 -0
  67. package/vendor/Archive/demo.js +489 -0
  68. package/vendor/Archive/index.js +43 -0
  69. package/vendor/Archive/package.json +16 -0
  70. package/vendor/Archive/src/constants.js +83 -0
  71. package/vendor/Archive/src/data/stopwords.txt +110 -0
  72. package/vendor/Archive/src/data/synonyms.json +25 -0
  73. package/vendor/Archive/src/data/terms.txt +59 -0
  74. package/vendor/Archive/src/db/sqlite-backend.js +312 -0
  75. package/vendor/Archive/src/db/sqlite-schema.js +64 -0
  76. package/vendor/Archive/src/engine.js +501 -0
  77. package/vendor/Archive/src/index/index-manager.js +203 -0
  78. package/vendor/Archive/src/index/index-state.js +94 -0
  79. package/vendor/Archive/src/index/search-text-builder.js +106 -0
  80. package/vendor/Archive/src/index.js +22 -0
  81. package/vendor/Archive/src/parser/api-identifiers.js +121 -0
  82. package/vendor/Archive/src/parser/api-suffixes.js +48 -0
  83. package/vendor/Archive/src/parser/heading-normalize.js +40 -0
  84. package/vendor/Archive/src/parser/markdown-splitter.js +215 -0
  85. package/vendor/Archive/src/search/catalog-routing.js +87 -0
  86. package/vendor/Archive/src/search/fts-search.js +289 -0
  87. package/vendor/Archive/src/search/query-normalizer.js +159 -0
  88. package/vendor/Archive/src/search/query-rewriter.js +72 -0
  89. package/vendor/Archive/src/search/search-engine.js +72 -0
  90. package/vendor/Archive/src/search/snippet.js +64 -0
  91. package/vendor/Archive/src/store/dir-store.js +103 -0
  92. package/vendor/Archive/src/store/doc-store.js +36 -0
  93. package/vendor/Archive/src/tokenizer/lexicon.js +151 -0
  94. package/vendor/Archive/src/tokenizer/tokenizer.js +167 -0
  95. package/vendor/Archive/src/types.js +86 -0
  96. package/vendor/Archive/src/utils/hash.js +16 -0
  97. package/vendor/Archive/src/utils/lock.js +73 -0
  98. package/vendor/Archive/src/utils/path-safety.js +96 -0
  99. package/vendor/Archive/src/utils/paths.js +39 -0
  100. package/vendor/BadCase/rn/INDEX.md +127 -0
  101. package/vendor/BadCase/rn/cases/KI-001-webview/350/247/206/351/242/221/346/227/240/346/263/225/346/250/252/345/261/217.md +52 -0
  102. package/vendor/BadCase/rn/cases/KI-002-fastImage/347/203/255/346/233/264/346/226/260/345/233/276/347/211/207/345/244/261/350/264/245.md +37 -0
  103. package/vendor/BadCase/rn/cases/KI-003-wechat/347/274/251/347/225/245/345/233/276/346/234/254/345/234/260/345/233/276/347/211/207.md +62 -0
  104. package/vendor/BadCase/rn/cases/KI-004-wechat/345/233/236/350/260/203/346/227/266/345/272/217.md +47 -0
  105. package/vendor/BadCase/rn/cases/KI-005-geolocation/345/257/274/345/205/245/350/267/257/345/276/204/345/220/216/347/274/200.md +31 -0
  106. package/vendor/BadCase/rn/cases/KI-006-SmartRefreshLayout/346/231/272/350/203/275/346/214/207/351/222/210/345/264/251/346/272/203.md +55 -0
  107. package/vendor/BadCase/rn/cases/KI-007-orientation-locker/344/274/240/346/204/237/345/231/250/345/210/235/345/247/213/345/214/226.md +58 -0
  108. package/vendor/BadCase/rn/cases/KI-008-orientation-locker/344/274/240/346/204/237/345/231/250/345/274/225/347/224/250/350/256/241/346/225/260.md +47 -0
  109. package/vendor/BadCase/rn/cases/KI-009-pager-view/345/265/214/345/245/227/346/273/221/345/212/250.md +38 -0
  110. package/vendor/BadCase/rn/cases/KI-010-screens/346/211/213/345/212/277/345/206/262/347/252/201.md +48 -0
  111. package/vendor/BadCase/rn/cases/KI-011-spring-scrollview/346/211/213/345/212/277/351/207/215/345/244/215/347/273/221/345/256/232.md +49 -0
  112. package/vendor/BadCase/rn/cases/KI-012-qr-camera/346/250/252/345/261/217/351/200/202/351/205/215.md +59 -0
  113. package/vendor/BadCase/rn/cases/KI-013-webview/346/267/261/350/211/262/346/250/241/345/274/217/350/203/214/346/231/257/350/211/262.md +37 -0
  114. package/vendor/BadCase/rn/cases/KI-014-amap-geolocation/345/233/236/350/260/203/347/274/272/345/244/261.md +42 -0
  115. package/vendor/BadCase/rn/cases/KI-015-reanimated/350/277/220/350/241/214/346/227/266/346/236/220/346/236/204/345/264/251/346/272/203.md +54 -0
  116. package/vendor/BadCase/rn/cases/KI-016-fs/346/226/207/344/273/266/350/257/273/345/217/226/347/274/226/347/240/201.md +58 -0
  117. package/vendor/BadCase/rn/cases/KI-017-blob-util/345/217/202/346/225/260/347/274/226/347/240/201/351/224/231/350/257/257.md +39 -0
  118. package/vendor/BadCase/rn/cases/KI-018-modal/345/274/271/347/252/227/351/227/252/347/203/201.md +53 -0
  119. package/vendor/BadCase/rn/cases/KI-019-code-push/351/207/215/345/220/257/351/200/273/350/276/221/345/244/261/346/225/210.md +79 -0
  120. package/vendor/BadCase/rn/cases/KI-020-netinfo/346/225/217/346/204/237/344/277/241/346/201/257/346/263/204/351/234/262.md +60 -0
  121. package/vendor/BadCase/rn/cases/KI-021-video/346/234/254/345/234/260/350/247/206/351/242/221/346/222/255/346/224/276/345/274/202/345/270/270.md +79 -0
  122. package/vendor/BadCase/rn/cases/KI-022-audio-recorder-player/346/236/232/344/270/276/347/274/272/345/244/261.md +38 -0
  123. package/vendor/BadCase/rn/cases/KI-023-svg/346/270/220/345/217/230/347/273/247/346/211/277/345/244/261/346/225/210.md +62 -0
  124. package/vendor/BadCase/rn/generalization-log.md +55 -0
  125. package/vendor/BadCase/rn/pr-info/rntpc_react-native-SmartRefreshLayout-32.json +328 -0
  126. package/vendor/BadCase/rn/pr-info/rntpc_react-native-SmartRefreshLayout-32.md +382 -0
  127. package/vendor/BadCase/rn/pr-info/rntpc_react-native-amap-geolocation-27.json +1351 -0
  128. package/vendor/BadCase/rn/pr-info/rntpc_react-native-amap-geolocation-27.md +1176 -0
  129. package/vendor/BadCase/rn/pr-info/rntpc_react-native-audio-recorder-player-22.json +538 -0
  130. package/vendor/BadCase/rn/pr-info/rntpc_react-native-audio-recorder-player-22.md +1488 -0
  131. package/vendor/BadCase/rn/pr-info/rntpc_react-native-blob-util-23.json +211 -0
  132. package/vendor/BadCase/rn/pr-info/rntpc_react-native-blob-util-23.md +35 -0
  133. package/vendor/BadCase/rn/pr-info/rntpc_react-native-code-push-17.json +2048 -0
  134. package/vendor/BadCase/rn/pr-info/rntpc_react-native-code-push-17.md +3866 -0
  135. package/vendor/BadCase/rn/pr-info/rntpc_react-native-fast-image-102.json +198 -0
  136. package/vendor/BadCase/rn/pr-info/rntpc_react-native-fast-image-102.md +40 -0
  137. package/vendor/BadCase/rn/pr-info/rntpc_react-native-fs-38.json +751 -0
  138. package/vendor/BadCase/rn/pr-info/rntpc_react-native-fs-38.md +883 -0
  139. package/vendor/BadCase/rn/pr-info/rntpc_react-native-geolocation-24.json +1544 -0
  140. package/vendor/BadCase/rn/pr-info/rntpc_react-native-geolocation-24.md +2598 -0
  141. package/vendor/BadCase/rn/pr-info/rntpc_react-native-modal-20.json +246 -0
  142. package/vendor/BadCase/rn/pr-info/rntpc_react-native-modal-20.md +554 -0
  143. package/vendor/BadCase/rn/pr-info/rntpc_react-native-netinfo-31.json +211 -0
  144. package/vendor/BadCase/rn/pr-info/rntpc_react-native-netinfo-31.md +54 -0
  145. package/vendor/BadCase/rn/pr-info/rntpc_react-native-orientation-locker-45.json +1626 -0
  146. package/vendor/BadCase/rn/pr-info/rntpc_react-native-orientation-locker-45.md +2213 -0
  147. package/vendor/BadCase/rn/pr-info/rntpc_react-native-orientation-locker-51.json +208 -0
  148. package/vendor/BadCase/rn/pr-info/rntpc_react-native-orientation-locker-51.md +77 -0
  149. package/vendor/BadCase/rn/pr-info/rntpc_react-native-pager-view-32.json +377 -0
  150. package/vendor/BadCase/rn/pr-info/rntpc_react-native-pager-view-32.md +174 -0
  151. package/vendor/BadCase/rn/pr-info/rntpc_react-native-qr-decode-image-camera-37.json +232 -0
  152. package/vendor/BadCase/rn/pr-info/rntpc_react-native-qr-decode-image-camera-37.md +194 -0
  153. package/vendor/BadCase/rn/pr-info/rntpc_react-native-reanimated-82.json +198 -0
  154. package/vendor/BadCase/rn/pr-info/rntpc_react-native-reanimated-82.md +67 -0
  155. package/vendor/BadCase/rn/pr-info/rntpc_react-native-screens-87.json +198 -0
  156. package/vendor/BadCase/rn/pr-info/rntpc_react-native-screens-87.md +38 -0
  157. package/vendor/BadCase/rn/pr-info/rntpc_react-native-screens-93.json +208 -0
  158. package/vendor/BadCase/rn/pr-info/rntpc_react-native-screens-93.md +34 -0
  159. package/vendor/BadCase/rn/pr-info/rntpc_react-native-spring-scrollview-38.json +222 -0
  160. package/vendor/BadCase/rn/pr-info/rntpc_react-native-spring-scrollview-38.md +56 -0
  161. package/vendor/BadCase/rn/pr-info/rntpc_react-native-svg-117.json +463 -0
  162. package/vendor/BadCase/rn/pr-info/rntpc_react-native-svg-117.md +383 -0
  163. package/vendor/BadCase/rn/pr-info/rntpc_react-native-video-66.json +464 -0
  164. package/vendor/BadCase/rn/pr-info/rntpc_react-native-video-66.md +565 -0
  165. package/vendor/BadCase/rn/pr-info/rntpc_react-native-webview-112.json +198 -0
  166. package/vendor/BadCase/rn/pr-info/rntpc_react-native-webview-112.md +47 -0
  167. package/vendor/BadCase/rn/pr-info/rntpc_react-native-webview-121.json +232 -0
  168. package/vendor/BadCase/rn/pr-info/rntpc_react-native-webview-121.md +422 -0
  169. package/vendor/BadCase/rn/pr-info/rntpc_react-native-wechat-lib-15.json +295 -0
  170. package/vendor/BadCase/rn/pr-info/rntpc_react-native-wechat-lib-15.md +1038 -0
  171. package/vendor/BadCase/rn/rules/R-01-/345/205/250/345/261/217/347/252/227/345/217/243/347/212/266/346/200/201/347/256/241/347/220/206/345/256/214/346/225/264/346/200/247.md +20 -0
  172. package/vendor/BadCase/rn/rules/R-02-/350/265/204/346/272/220URI/345/275/242/346/200/201/345/210/206/346/224/257/350/246/206/347/233/226.md +21 -0
  173. package/vendor/BadCase/rn/rules/R-03-/351/231/204/345/261/236/346/223/215/344/275/234/345/244/261/350/264/245/351/230/273/346/226/255/344/270/273/346/265/201/347/250/213.md +20 -0
  174. package/vendor/BadCase/rn/rules/R-04-/345/274/202/346/255/245/345/233/236/350/260/203/346/263/250/345/206/214/346/227/266/345/272/217.md +20 -0
  175. package/vendor/BadCase/rn/rules/R-05-/346/250/241/345/235/227/345/257/274/345/205/245/350/267/257/345/276/204/350/247/204/350/214/203.md +19 -0
  176. package/vendor/BadCase/rn/rules/R-06-/344/274/240/346/204/237/345/231/250/347/241/254/344/273/266/346/235/203/351/231/220/346/214/211/351/234/200/350/216/267/345/217/226.md +21 -0
  177. package/vendor/BadCase/rn/rules/R-07-/346/231/272/350/203/275/346/214/207/351/222/210/347/224/237/345/221/275/345/221/250/346/234/237/347/256/241/347/220/206.md +20 -0
  178. package/vendor/BadCase/rn/rules/R-08-/346/211/213/345/212/277/344/272/213/344/273/266/346/263/250/345/206/214/344/270/216/345/206/262/347/252/201/345/244/204/347/220/206.md +22 -0
  179. package/vendor/BadCase/rn/rules/R-09-/346/250/252/347/253/226/345/261/217/345/244/232/345/275/242/346/200/201/345/270/203/345/261/200/351/200/202/351/205/215.md +20 -0
  180. package/vendor/BadCase/rn/rules/R-10-/346/267/261/350/211/262/346/250/241/345/274/217/344/270/273/351/242/230/351/242/234/350/211/262/351/200/202/351/205/215.md +19 -0
  181. package/vendor/BadCase/rn/rules/R-11-/346/216/245/345/217/243/345/233/236/350/260/203/345/256/236/347/216/260/345/256/214/346/225/264/346/200/247.md +20 -0
  182. package/vendor/BadCase/rn/rules/R-12-/345/216/237/347/224/237/350/277/220/350/241/214/346/227/266/350/265/204/346/272/220/346/236/220/346/236/204/346/227/266/345/272/217.md +20 -0
  183. package/vendor/BadCase/rn/rules/R-13-/346/226/207/344/273/266/346/225/260/346/215/256/347/274/226/347/240/201/344/270/216/346/240/274/345/274/217/345/205/274/345/256/271.md +21 -0
  184. package/vendor/BadCase/rn/rules/R-14-/345/212/250/347/224/273/345/210/235/345/247/213/345/214/226/346/227/266/345/272/217/344/270/216/347/212/266/346/200/201/345/220/214/346/255/245.md +20 -0
  185. package/vendor/BadCase/rn/rules/R-15-/347/212/266/346/200/201/346/214/201/344/271/205/345/214/226/344/270/216/351/207/215/345/220/257/351/200/273/350/276/221/344/270/200/350/207/264/346/200/247.md +20 -0
  186. package/vendor/BadCase/rn/rules/R-16-/346/225/217/346/204/237/344/277/241/346/201/257/346/227/245/345/277/227/346/263/204/351/234/262.md +19 -0
  187. package/vendor/BadCase/rn/rules/R-17-/345/252/222/344/275/223/350/265/204/346/272/220/347/224/237/345/221/275/345/221/250/346/234/237/344/270/216/351/224/231/350/257/257/345/244/204/347/220/206.md +21 -0
  188. package/vendor/BadCase/rn/rules/R-18-/350/203/275/345/212/233/346/236/232/344/270/276/351/205/215/347/275/256/345/256/214/346/225/264/346/200/247.md +19 -0
  189. package/vendor/BadCase/rn/rules/R-19-/345/233/276/345/275/242/346/270/262/346/237/223/345/261/236/346/200/247/347/273/247/346/211/277/344/270/216/351/207/215/347/275/256.md +19 -0
  190. package/vendor/MCP/README.md +490 -0
  191. package/vendor/MCP/config.json +29 -0
  192. package/vendor/MCP/package.json +23 -0
  193. package/vendor/MCP/scripts/analyze-demo-coverage.cjs +1203 -0
  194. package/vendor/MCP/scripts/analyze-xts-coverage.cjs +1245 -0
  195. package/vendor/MCP/scripts/deveco_docs.js +247 -0
  196. package/vendor/MCP/scripts/extract-interfaces.cjs +1487 -0
  197. package/vendor/MCP/scripts/flutter-analyze-demo-coverage.cjs +666 -0
  198. package/vendor/MCP/scripts/flutter-analyze-xts-coverage.cjs +684 -0
  199. package/vendor/MCP/scripts/flutter-extract-interfaces.cjs +1251 -0
  200. package/vendor/MCP/scripts/flutter-render-spec-doc.cjs +351 -0
  201. package/vendor/MCP/scripts/render-spec-doc.cjs +347 -0
  202. package/vendor/MCP/scripts/rn-analyze-demo-coverage.cjs +645 -0
  203. package/vendor/MCP/scripts/rn-analyze-test-coverage.cjs +614 -0
  204. package/vendor/MCP/scripts/rn-extract-interfaces.cjs +1462 -0
  205. package/vendor/MCP/scripts/rn-render-spec-doc.cjs +327 -0
  206. package/vendor/MCP/scripts/rn-scan-exports.cjs +1336 -0
  207. package/vendor/MCP/src/client/MCPClientPool.js +220 -0
  208. package/vendor/MCP/src/config/ConfigLoader.js +189 -0
  209. package/vendor/MCP/src/executor/ScriptExecutor.js +110 -0
  210. package/vendor/MCP/src/gateway/Gateway.js +451 -0
  211. package/vendor/MCP/src/index.js +87 -0
  212. package/vendor/MCP/src/library/KBToolProvider.js +229 -0
  213. package/vendor/MCP/src/library/logger.js +60 -0
  214. package/vendor/MCP/src/registry/ScriptRegistry.js +238 -0
  215. package/vendor/MCP/src/registry/ToolRegistry.js +111 -0
  216. package/vendor/MCP/src/types.js +115 -0
  217. package/vendor/Server/CLI/cli.js +3 -3
  218. package/vendor/Server/Routes/server.js +3 -3
  219. package/vendor/Server/Skills/arkts-library-test-coverage-check/references/MCP_TOOL_USAGE.md +4 -2
  220. package/vendor/Server/Skills/arkts-library-xts-coverage/SKILL.md +3 -3
  221. package/vendor/Server/Skills/knowledge-import/SKILL.md +1 -1
  222. package/vendor/Server/Skills/knowledge-import/references/MCP_TOOL_USAGE.md +12 -4
  223. package/vendor/Server/Skills/ohos-lib-pr-push/SKILL.md +5 -4
  224. package/vendor/Server/Skills/rn-library-issue-generalizer/SKILL.md +2 -2
  225. package/vendor/Server/Skills/rn-library-known-issue-check/SKILL.md +2 -2
  226. package/vendor/Server/Skills/rnoh-lib-demo-coverage/SKILL.md +1 -2
  227. package/vendor/Server/Skills/rnoh-lib-test-align/references/port-test-demo-guide.md +1 -1
  228. package/vendor/Server/Skills/rnoh-lib-xts-coverage/SKILL.md +1 -2
  229. package/vendor/Server/package.json +5 -1
  230. package/vendor/VERSION +3 -3
@@ -0,0 +1,1462 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @tool rn_extract_interfaces
4
+ * @description 从 React Native 三方库的鸿蒙化仓库提取接口规格。自动拉取上游 npm 源码(index.js/index.d.ts),从本地鸿蒙工程提取 TurboModule Spec(Native*.ts),解析 JS 层导出(CommonJS/ES Module/Side-effect)和类型定义,输出 JSON 接口规格文件。用于分析 RN 三方库的完整 API 表面,是覆盖率分析和文档生成的基础。
5
+ * @param libroot: string - 库根目录路径(RN 三方库的鸿蒙化仓库目录,包含 package.json)
6
+ * @param output: string - 输出 JSON 文件路径(可选,不指定则输出 JSON 到 stdout)
7
+ * @param version: string - 上游 npm 版本号(可选,如 "2.2.0",不指定则自动从 dependencies/README 推断)
8
+ * @hidden
9
+ * extract-rn-interfaces.cjs — RN 接口提取与覆盖率分析核心引擎
10
+ *
11
+ * 本文件是整个工具集的唯一核心模块,包含:
12
+ * Part 0: 共享工具 (文件IO, 库发现, 解析工具, 字符串工具, CLI, 日志, TS加载)
13
+ * Part 1: 上游 npm 源码拉取 (版本确定 + npm pack + 解压)
14
+ * Part 2: 接口提取 (导出模式检测, 方法签名解析, TurboModule Spec, JS层导出, index.d.ts)
15
+ * Part 3: 接口规格生成 (generateRNSpec + loadSpec)
16
+ * Part 4: 覆盖率分析引擎 (正则匹配 + AST匹配 + 结果聚合 + Markdown渲染 + runCoverage)
17
+ *
18
+ * JS 层源码(index.js, index.d.ts)强制从上游 npm 拉取。
19
+ * TurboModule Spec(src/Native*.ts)从本地鸿蒙工程提取。
20
+ *
21
+ * 被依赖: rn-render-spec-doc.cjs, rn-analyze-demo-coverage.cjs, rn-analyze-test-coverage.cjs
22
+ *
23
+ * 用法 (CLI):
24
+ * node rn-extract-interfaces.cjs --repoDir <库根目录> --output <spec.json> [--version <上游版本>]
25
+ *
26
+ * 用法 (模块):
27
+ * const { generateRNSpec, loadSpec, runCoverage } = require('./rn-extract-interfaces.cjs');
28
+ */
29
+
30
+ 'use strict';
31
+
32
+ const fs = require('fs');
33
+ const path = require('path');
34
+ const { execSync } = require('child_process');
35
+
36
+ // ════════════════════════════════════════════════════════════
37
+ // Part 0: 共享工具
38
+ // ════════════════════════════════════════════════════════════
39
+
40
+ const COLORS = { RED: '\x1b[0;31m', GREEN: '\x1b[0;32m', YELLOW: '\x1b[1;33m', BLUE: '\x1b[0;34m', NC: '\x1b[0m' };
41
+ function info(msg) { console.log(`${COLORS.BLUE}[INFO]${COLORS.NC} ${msg}`); }
42
+ function ok(msg) { console.log(`${COLORS.GREEN}[OK]${COLORS.NC} ${msg}`); }
43
+ function warn(msg) { console.log(`${COLORS.YELLOW}[WARN]${COLORS.NC} ${msg}`); }
44
+ function error(msg) { console.error(`${COLORS.RED}[ERROR]${COLORS.NC} ${msg}`); process.exit(1); }
45
+
46
+ function readText(file) {
47
+ try {
48
+ let c = fs.readFileSync(file, 'utf-8');
49
+ if (c.charCodeAt(0) === 0xFEFF) c = c.slice(1);
50
+ return c;
51
+ } catch (e) {
52
+ return '';
53
+ }
54
+ }
55
+
56
+ function rel(filePath, repoDir) {
57
+ let r = path.relative(repoDir, filePath).split(path.sep).join('/');
58
+ if (!r.startsWith('.')) r = './' + r;
59
+ return r;
60
+ }
61
+
62
+ function walkDir(dir, opts, srcRoot) {
63
+ const exts = opts.exts || ['.ts', '.tsx'];
64
+ const excludeDirs = opts.excludeDirs || ['node_modules', 'lib', 'dist', '.git'];
65
+ srcRoot = srcRoot || dir;
66
+ const results = [];
67
+ if (!fs.existsSync(dir)) return results;
68
+ const relDir = path.relative(srcRoot, dir);
69
+ const topDir = relDir.split(path.sep)[0] || '';
70
+ if (relDir !== '' && excludeDirs.includes(topDir)) return results;
71
+ let entries;
72
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return results; }
73
+ for (const entry of entries) {
74
+ const fullPath = path.join(dir, entry.name);
75
+ if (entry.isDirectory()) results.push(...walkDir(fullPath, opts, srcRoot));
76
+ else if (entry.isFile() && exts.some(ext => entry.name.endsWith(ext))) results.push(fullPath);
77
+ }
78
+ return results;
79
+ }
80
+
81
+ function readPackageJson(dir) {
82
+ const p = path.join(dir, 'package.json');
83
+ if (!fs.existsSync(p)) return null;
84
+ try { return JSON.parse(fs.readFileSync(p, 'utf-8')); } catch { return null; }
85
+ }
86
+
87
+ function getLibraryAlias(pkg) {
88
+ if (!pkg) return null;
89
+ if (pkg.harmony && pkg.harmony.alias) return pkg.harmony.alias;
90
+ if (pkg.name) return pkg.name.replace(/^@[^/]+\//, '');
91
+ return null;
92
+ }
93
+
94
+ function detectEntryFile(dir, explicitEntry) {
95
+ if (explicitEntry) {
96
+ const p = path.resolve(dir, explicitEntry);
97
+ if (fs.existsSync(p)) return p;
98
+ warn(`指定的入口文件不存在: ${explicitEntry}`);
99
+ }
100
+ const pkg = readPackageJson(dir);
101
+ const fields = ['source', 'react-native', 'main', 'module'];
102
+ if (pkg) {
103
+ for (const field of fields) {
104
+ const val = pkg[field];
105
+ if (val && typeof val === 'string') {
106
+ const variants = [val.replace(/^lib\//, 'src/'), val.replace(/^lib\/commonjs\//, 'src/'), val.replace(/^lib\/module\//, 'src/'), val.replace(/^lib\//, 'src/').replace(/\.js$/, '.ts'), val.replace(/^lib\//, 'src/').replace(/\.js$/, '.tsx'), val.replace(/^lib\/module\//, 'src/').replace(/\.js$/, '.ts'), val.replace(/^lib\/module\//, 'src/').replace(/\.js$/, '.tsx'), val, val.replace(/\.js$/, '.ts'), val.replace(/\.jsx$/, '.tsx')];
107
+ for (const v of variants) {
108
+ const p = path.resolve(dir, v);
109
+ if (fs.existsSync(p) && (p.endsWith('.ts') || p.endsWith('.tsx') || p.endsWith('.js') || p.endsWith('.jsx'))) return p;
110
+ }
111
+ const p = path.resolve(dir, val);
112
+ if (fs.existsSync(p) && fs.statSync(p).isFile()) return p;
113
+ // Try with extensions appended (e.g., main: "dist/CheckBox" -> "dist/CheckBox.js")
114
+ for (const ext of ['.ts', '.tsx', '.js', '.jsx']) {
115
+ const pe = path.resolve(dir, val + ext);
116
+ if (fs.existsSync(pe) && fs.statSync(pe).isFile()) return pe;
117
+ }
118
+ // If it's a directory, try index files
119
+ if (fs.existsSync(p) && fs.statSync(p).isDirectory()) {
120
+ for (const idx of ['index.ts', 'index.tsx', 'index.js', 'index.jsx', 'index.d.ts']) {
121
+ const idxPath = path.join(p, idx);
122
+ if (fs.existsSync(idxPath) && fs.statSync(idxPath).isFile()) return idxPath;
123
+ }
124
+ }
125
+ }
126
+ }
127
+ }
128
+ for (const conv of ['src/index.ts', 'src/index.tsx', 'index.ts', 'index.tsx', 'index.js', 'index.jsx']) {
129
+ const p = path.resolve(dir, conv);
130
+ if (fs.existsSync(p) && fs.statSync(p).isFile()) return p;
131
+ }
132
+ return null;
133
+ }
134
+
135
+ function findTurboModuleSpecFiles(dir) {
136
+ const results = [];
137
+ const pkg = readPackageJson(dir);
138
+ const srcDirs = [];
139
+ if (pkg && pkg.codegenConfig && pkg.codegenConfig.jsSrcsDir) srcDirs.push(path.resolve(dir, pkg.codegenConfig.jsSrcsDir));
140
+ srcDirs.push(path.resolve(dir, 'src'));
141
+ srcDirs.push(dir);
142
+ for (const srcDir of srcDirs) {
143
+ if (!fs.existsSync(srcDir)) continue;
144
+ scanDirForNativeSpec(srcDir, results);
145
+ }
146
+ const allTsFiles = [];
147
+ for (const srcDir of srcDirs) {
148
+ if (!fs.existsSync(srcDir)) continue;
149
+ collectTsFiles(srcDir, allTsFiles);
150
+ }
151
+ for (const tsFile of allTsFiles) {
152
+ if (results.includes(tsFile)) continue;
153
+ try {
154
+ const content = readText(tsFile);
155
+ if (/interface\s+Spec\s+extends\s+TurboModule/.test(content) ||
156
+ /codegenNativeComponent/.test(content) ||
157
+ /codegenNativeCommands/.test(content)) {
158
+ results.push(tsFile);
159
+ }
160
+ } catch {}
161
+ }
162
+ return [...new Set(results)];
163
+ }
164
+
165
+ function scanDirForNativeSpec(dir, results) {
166
+ let entries;
167
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
168
+ for (const entry of entries) {
169
+ if (['node_modules', 'lib', 'dist', '.git', '.upstream-cache'].includes(entry.name)) continue;
170
+ const fullPath = path.join(dir, entry.name);
171
+ if (entry.isDirectory()) scanDirForNativeSpec(fullPath, results);
172
+ else if (entry.isFile() && /^Native[A-Z].*\.(ts|tsx)$/.test(entry.name)) results.push(fullPath);
173
+ }
174
+ }
175
+
176
+ function collectTsFiles(dir, results) {
177
+ let entries;
178
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
179
+ for (const entry of entries) {
180
+ if (['node_modules', 'lib', 'dist', '.git', '.upstream-cache', 'harmony', 'android', 'ios'].includes(entry.name)) continue;
181
+ const fullPath = path.join(dir, entry.name);
182
+ if (entry.isDirectory()) collectTsFiles(fullPath, results);
183
+ else if (entry.isFile() && /\.(ts|tsx)$/.test(entry.name)) results.push(fullPath);
184
+ }
185
+ }
186
+
187
+ function resolveRelativePath(fromFile, relativePath) {
188
+ if (!relativePath) return null;
189
+ const base = path.resolve(path.dirname(fromFile), relativePath);
190
+ for (const c of [base + '.ts', base + '.tsx', base + '.d.ts', base + '.js', base + '.jsx', path.join(base, 'index.d.ts'), path.join(base, 'index.ts'), path.join(base, 'index.tsx'), path.join(base, 'index.js'), path.join(base, 'index.jsx')]) {
191
+ if (fs.existsSync(c)) return c;
192
+ }
193
+ return null;
194
+ }
195
+
196
+ function resolveRelativePathDts(fromFile, relativePath) {
197
+ if (!relativePath) return null;
198
+ const base = path.resolve(path.dirname(fromFile), relativePath);
199
+ const candidates = [
200
+ base + '.d.ts', base + '.ts',
201
+ path.join(base, 'index.d.ts'), path.join(base, 'index.ts'),
202
+ ];
203
+ for (const c of candidates) {
204
+ if (fs.existsSync(c)) return c;
205
+ }
206
+ return resolveRelativePath(fromFile, relativePath);
207
+ }
208
+
209
+ function stripComments(content) { return content.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, ''); }
210
+
211
+ function extractBalancedBraces(text, startIdx) {
212
+ let depth = 1, i = startIdx;
213
+ while (i < text.length && depth > 0) {
214
+ if (text[i] === '{') depth++;
215
+ else if (text[i] === '}') depth--;
216
+ if (depth === 0) return text.substring(startIdx, i);
217
+ i++;
218
+ }
219
+ return null;
220
+ }
221
+
222
+ function findSignatureEnd(text, start) {
223
+ let depth = 0, i = start;
224
+ while (i < text.length) {
225
+ const ch = text[i];
226
+ if (ch === '{') {
227
+ if (depth === 0) {
228
+ // Check if this is a method body or an object type
229
+ let k = i - 1;
230
+ while (k >= 0 && /[ \t\r\n]/.test(text[k])) k--;
231
+ const prevCh = k >= 0 ? text[k] : '';
232
+ if (!'|=,&<'.includes(prevCh)) return i; // Method body start
233
+ }
234
+ depth++;
235
+ }
236
+ else if (ch === '(' || ch === '[') depth++;
237
+ else if (ch === '}' || ch === ')' || ch === ']') { if (depth === 0) return i; depth--; }
238
+ else if (ch === ';' && depth === 0) return i;
239
+ else if (ch === '\n' && depth === 0) {
240
+ // ASI: stop at newline if next line starts a new declaration
241
+ // and current line doesn't end with a continuation character
242
+ let j = i + 1;
243
+ while (j < text.length && /[ \t\r]/.test(text[j])) j++;
244
+ let k = i - 1;
245
+ while (k >= 0 && /[ \t\r]/.test(text[k])) k--;
246
+ const prevCh = k >= 0 ? text[k] : '';
247
+ if (/[a-zA-Z_$]/.test(text[j]) && !'|&({[,<.'.includes(prevCh)) {
248
+ return i;
249
+ }
250
+ }
251
+ i++;
252
+ }
253
+ return text.length;
254
+ }
255
+
256
+ function normalizeSignature(sig) { return sig.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/.*$/gm, '').replace(/\s+/g, ' ').replace(/\s*;\s*$/, '').trim(); }
257
+
258
+ function extractExports(content) {
259
+ let defaultExportDesc = null;
260
+ const named = [], types = [], reExports = [];
261
+ const cleaned = stripComments(content);
262
+ const defaultMatch = cleaned.match(/export\s+default\s+(?:function\s+(\w+)|class\s+(\w+)|([^\s;{]+))/);
263
+ if (defaultMatch) defaultExportDesc = defaultMatch[1] || defaultMatch[2] || defaultMatch[3] || 'anonymous';
264
+ const exportBraceRegex = /export\s+(type\s+)?\{([^}]+)\}\s*(?:from\s*['"]([^'"]+)['"])?/g;
265
+ let m;
266
+ while ((m = exportBraceRegex.exec(cleaned)) !== null) {
267
+ const isType = !!m[1], namesRaw = m[2], fromModule = m[3] || null;
268
+ for (let name of namesRaw.split(',').map(s => s.trim()).filter(Boolean)) {
269
+ let isTypeExport = isType;
270
+ if (name.startsWith('type ')) { isTypeExport = true; name = name.substring(5).trim(); }
271
+ const asMatch = name.match(/^(\w+)\s+as\s+(\w+)/);
272
+ const exportName = asMatch ? asMatch[2] : name;
273
+ const originalName = asMatch ? asMatch[1] : name;
274
+ if (fromModule) reExports.push({ name: exportName, original: originalName, from: fromModule, isType: isTypeExport });
275
+ else if (isTypeExport) { if (!types.includes(exportName)) types.push(exportName); }
276
+ else { if (!named.includes(exportName)) named.push(exportName); }
277
+ }
278
+ }
279
+ const starRegex = /export\s+\*\s*(?:as\s+(\w+)\s+)?from\s*['"]([^'"]+)['"]/g;
280
+ while ((m = starRegex.exec(cleaned)) !== null) reExports.push({ name: m[1] || '*', original: '*', from: m[2], isType: false, isWildcard: true });
281
+ const patterns = [
282
+ { regex: /export\s+(?:declare\s+)?(?:async\s+)?function\s+(\w+)/g, kind: 'named' },
283
+ { regex: /export\s+(?:declare\s+)?(?:abstract\s+)?class\s+(\w+)/g, kind: 'named' },
284
+ { regex: /export\s+(?:declare\s+)?(?:const|let|var)\s+(\w+)/g, kind: 'named' },
285
+ { regex: /export\s+(?:declare\s+)?interface\s+(\w+)/g, kind: 'types' },
286
+ { regex: /export\s+(?:declare\s+)?type\s+(\w+)\s*[=<]/g, kind: 'types' },
287
+ { regex: /export\s+(?:declare\s+)?(?:const\s+)?enum\s+(\w+)/g, kind: 'types' },
288
+ ];
289
+ for (const { regex, kind } of patterns) {
290
+ while ((m = regex.exec(cleaned)) !== null) {
291
+ if (kind === 'named') { if (!named.includes(m[1])) named.push(m[1]); }
292
+ else { if (!types.includes(m[1])) types.push(m[1]); }
293
+ }
294
+ }
295
+ // export = Name (TypeScript CommonJS export pattern)
296
+ const exportEqMatch = cleaned.match(/export\s*=\s*(\w+)\s*;?/);
297
+ if (exportEqMatch) {
298
+ const nsName = exportEqMatch[1];
299
+ const nsMatch = cleaned.match(new RegExp('(?:declare\\s+)?namespace\\s+' + nsName + '\\s*\\{'));
300
+ if (nsMatch) {
301
+ const nsBody = extractBalancedBraces(cleaned, nsMatch.index + nsMatch[0].length);
302
+ if (nsBody) {
303
+ const funcRegex = /function\s+(\w+)\s*\(/g;
304
+ let fm;
305
+ while ((fm = funcRegex.exec(nsBody)) !== null) {
306
+ if (!named.includes(fm[1])) named.push(fm[1]);
307
+ }
308
+ }
309
+ }
310
+ }
311
+ return { defaultExport: defaultExportDesc, named, types, reExports };
312
+ }
313
+
314
+ function extractCJSExportNames(content) {
315
+ const named = [], reExports = [];
316
+ let defaultExport = null;
317
+
318
+ const isReExportName = (n) => reExports.some(r => r.name === n);
319
+ const addNamed = (n) => { if (n !== '__esModule' && !named.includes(n) && !isReExportName(n)) named.push(n); };
320
+ const addDefault = () => { if (!defaultExport) defaultExport = 'default'; };
321
+ const addReExport = (n, from, isWild) => {
322
+ if (n === '__esModule') return;
323
+ if (n === 'default') { addDefault(); return; }
324
+ if (named.includes(n)) return;
325
+ if (isReExportName(n)) return;
326
+ reExports.push({ name: n, original: n, from: from, isType: false, isWildcard: !!isWild });
327
+ };
328
+
329
+ // 1. Build require map: var _xxx = require("./module")
330
+ const requireMap = {};
331
+ const requireRegex = /(?:var|let|const)\s+(\w+)\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
332
+ let m;
333
+ while ((m = requireRegex.exec(content)) !== null) requireMap[m[1]] = m[2];
334
+
335
+ // 2. Object.defineProperty(exports, "NAME", { ... }) — detect re-exports via "return _var."
336
+ const definePropRegex = /Object\.defineProperty\s*\(\s*exports\s*,\s*['"]([^'"]+)['"]\s*,/g;
337
+ while ((m = definePropRegex.exec(content)) !== null) {
338
+ const name = m[1];
339
+ if (name === '__esModule') continue;
340
+ const after = content.substring(m.index, m.index + 300);
341
+ const returnMatch = after.match(/return\s+(\w+)\s*\./);
342
+ if (returnMatch && requireMap[returnMatch[1]]) {
343
+ addReExport(name, requireMap[returnMatch[1]]);
344
+ } else if (name === 'default') { addDefault(); }
345
+ else { addNamed(name); }
346
+ }
347
+
348
+ // 3. Direct assignments: exports.NAME = ... (skip if already categorized in step 2)
349
+ const directRegex = /(?:^|[\n;])exports\.(\w+)\s*=/gm;
350
+ while ((m = directRegex.exec(content)) !== null) {
351
+ const name = m[1];
352
+ if (name === '__esModule') continue;
353
+ if (name === 'default') { addDefault(); }
354
+ else { addNamed(name); }
355
+ }
356
+
357
+ // 3b. module.exports.NAME = ... (another CommonJS pattern, e.g. idle-timer)
358
+ const moduleExportRegex = /module\.exports\.(\w+)\s*=/gm;
359
+ while ((m = moduleExportRegex.exec(content)) !== null) {
360
+ addNamed(m[1]);
361
+ }
362
+
363
+ // 4. __exportStar(require("./module"), exports) → wildcard re-export
364
+ const exportStarRegex = /__exportStar\s*\(\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
365
+ while ((m = exportStarRegex.exec(content)) !== null) {
366
+ reExports.push({ name: '*', original: '*', from: m[1], isType: false, isWildcard: true });
367
+ }
368
+
369
+ return { defaultExport, named, types: [], reExports };
370
+ }
371
+
372
+ function extractTurboModuleSpec(content) {
373
+ const cleaned = stripComments(content);
374
+ let moduleName = null;
375
+ const nameMatch = cleaned.match(/TurboModuleRegistry\.get(?:Enforcing)?(?:<[^>]+>)?\s*\(\s*['"]([^'"]+)['"]\s*\)/);
376
+ if (nameMatch) moduleName = nameMatch[1];
377
+ if (!moduleName) { const c = cleaned.match(/export\s+const\s+NAME\s*=\s*['"]([^'"]+)['"]/); if (c) moduleName = c[1]; }
378
+ const methods = [];
379
+ const interfaceMatch = cleaned.match(/export\s+interface\s+Spec\s+extends\s+TurboModule\s*\{/);
380
+ if (interfaceMatch) {
381
+ const body = extractBalancedBraces(cleaned, interfaceMatch.index + interfaceMatch[0].length);
382
+ if (body) methods.push(...extractMethodsFromInterfaceBody(body));
383
+ }
384
+ return { moduleName, methods };
385
+ }
386
+
387
+ function extractMethodsFromInterfaceBody(body) {
388
+ const methods = [];
389
+ let i = 0;
390
+ while (i < body.length) {
391
+ while (i < body.length && /\s/.test(body[i])) i++;
392
+ const modMatch = body.substring(i).match(/^(?:readonly|private|public|protected|static|async|override|abstract|get|set)\s+/);
393
+ if (modMatch) { i += modMatch[0].length; continue; }
394
+ const idMatch = body.substring(i).match(/^([a-zA-Z_$][\w$]*)/);
395
+ if (!idMatch) { i++; continue; }
396
+ const name = idMatch[1], nameStart = i;
397
+ i += idMatch[0].length;
398
+ while (i < body.length && /\s/.test(body[i])) i++;
399
+ if (i >= body.length) break;
400
+ if (body[i] === '?' || body[i] === ':') {
401
+ if (body[i] === '?') { i++; while (i < body.length && /\s/.test(body[i])) i++; }
402
+ if (body[i] === ':') {
403
+ const sigEnd = findSignatureEnd(body, nameStart);
404
+ const fullSig = body.substring(nameStart, sigEnd).trim();
405
+ methods.push({ name, signature: normalizeSignature(fullSig) });
406
+ if (body[sigEnd] === '{') {
407
+ let bd = 1, j = sigEnd + 1;
408
+ while (j < body.length && bd > 0) { if (body[j] === '{') bd++; else if (body[j] === '}') bd--; if (bd === 0) break; j++; }
409
+ i = j + 1;
410
+ } else { i = sigEnd + 1; }
411
+ } else { const sigEnd = findSignatureEnd(body, i); i = sigEnd + 1; }
412
+ continue;
413
+ }
414
+ if (body[i] === '(') {
415
+ const sigEnd = findSignatureEnd(body, nameStart);
416
+ methods.push({ name, signature: normalizeSignature(body.substring(nameStart, sigEnd).trim()) });
417
+ if (body[sigEnd] === '{') {
418
+ let bd = 1, j = sigEnd + 1;
419
+ while (j < body.length && bd > 0) { if (body[j] === '{') bd++; else if (body[j] === '}') bd--; if (bd === 0) break; j++; }
420
+ i = j + 1;
421
+ } else { i = sigEnd + 1; }
422
+ continue;
423
+ }
424
+ // Assignment: `name = value` (e.g. static propTypes = {...}, static defaultProps = {...}).
425
+ // Skip the whole value — including balanced object/array/call literals — so its
426
+ // inner members are not mis-parsed as class members.
427
+ if (body[i] === '=') {
428
+ i++;
429
+ while (i < body.length && /\s/.test(body[i])) i++;
430
+ if (i < body.length && '{[('.includes(body[i])) {
431
+ const open = body[i], close = open === '{' ? '}' : (open === '[' ? ']' : ')');
432
+ let bd = 1, j = i + 1;
433
+ while (j < body.length && bd > 0) { if (body[j] === open) bd++; else if (body[j] === close) bd--; if (bd === 0) break; j++; }
434
+ i = (j < body.length && bd === 0) ? j + 1 : j;
435
+ }
436
+ while (i < body.length && body[i] !== ';' && body[i] !== '\n') i++;
437
+ if (body[i] === ';') i++;
438
+ continue;
439
+ }
440
+ while (i < body.length && body[i] !== ';' && body[i] !== '\n') i++;
441
+ i++;
442
+ }
443
+ return methods;
444
+ }
445
+
446
+ function extractInterfaceMethods(content, interfaceName) {
447
+ const cleaned = stripComments(content);
448
+ let regex = new RegExp(`(?:export\\s+)?interface\\s+${interfaceName}\\s*(?:extends\\s+[^{]+)?\\s*\\{`);
449
+ let match = regex.exec(cleaned);
450
+ if (!match) { regex = new RegExp(`(?:export\\s+)?type\\s+${interfaceName}\\s*=\\s*(?:[^{]*?)\\{`); match = regex.exec(cleaned); }
451
+ if (!match) return [];
452
+ const body = extractBalancedBraces(cleaned, match.index + match[0].length);
453
+ return body ? extractMethodsFromInterfaceBody(body) : [];
454
+ }
455
+
456
+ function extractClassMethods(content, className) {
457
+ const cleaned = stripComments(content);
458
+ const regex = new RegExp(`(?:export\\s+)?(?:default\\s+)?(?:declare\\s+)?(?:abstract\\s+)?class\\s+${className}\\s*(?:extends\\s+[^{]+)?\\s*\\{`);
459
+ const match = regex.exec(cleaned);
460
+ if (!match) return [];
461
+ const body = extractBalancedBraces(cleaned, match.index + match[0].length);
462
+ return body ? extractMethodsFromInterfaceBody(body) : [];
463
+ }
464
+
465
+ // Parse the body of an object/type-literal `{ name(): ret; name?: type; ... }`
466
+ // (comma- or semicolon-separated) into member descriptors. Handles both
467
+ // method signatures (name(params): ret) and fields (name?: type).
468
+ function parseTypeLiteralMembers(body) {
469
+ const members = [];
470
+ const parts = [];
471
+ let depth = 0, current = '';
472
+ for (const ch of body) {
473
+ if (ch === '{' || ch === '(' || ch === '[') depth++;
474
+ else if (ch === '}' || ch === ')' || ch === ']') depth--;
475
+ if ((ch === ',' || ch === ';') && depth === 0) { if (current.trim()) parts.push(current.trim()); current = ''; }
476
+ else current += ch;
477
+ }
478
+ if (current.trim()) parts.push(current.trim());
479
+ for (const part of parts) {
480
+ if (!part) continue;
481
+ if (/^\w+\??\s*\(/.test(part)) {
482
+ const nm = part.match(/^(\w+)/);
483
+ if (nm) { members.push({ name: nm[1], signature: part }); continue; }
484
+ }
485
+ const fm = part.match(/^(\w+)\s*\??\s*:\s*([\s\S]+)$/);
486
+ if (fm) { members.push({ name: fm[1], signature: part }); continue; }
487
+ }
488
+ return members;
489
+ }
490
+
491
+ function extractClassAndInterfaceDefs(content) {
492
+ const cleaned = stripComments(content);
493
+ const defs = [];
494
+
495
+ // Classes: use simple regex to find class name, then manually skip generic params to find body
496
+ const classRegex = /(?:export\s+)?(?:default\s+)?(?:declare\s+)?(?:abstract\s+)?class\s+(\w+)\b/g;
497
+ let m;
498
+ while ((m = classRegex.exec(cleaned)) !== null) {
499
+ const className = m[1];
500
+ let bracePos = m.index + m[0].length;
501
+ let angleDepth = 0;
502
+ while (bracePos < cleaned.length) {
503
+ const ch = cleaned[bracePos];
504
+ if (ch === '<') angleDepth++;
505
+ else if (ch === '>') angleDepth--;
506
+ else if (ch === '{' && angleDepth === 0) break;
507
+ bracePos++;
508
+ }
509
+ if (bracePos >= cleaned.length) continue;
510
+ const body = extractBalancedBraces(cleaned, bracePos + 1);
511
+ if (!body) continue;
512
+ const members = extractMethodsFromInterfaceBody(body);
513
+ defs.push({ typeKind: 'class', name: className, members });
514
+ }
515
+
516
+ // Interfaces
517
+ const ifaceRegex = /(?:export\s+)?interface\s+(\w+)\s*(?:extends\s+[^{]+)?\s*\{/g;
518
+ while ((m = ifaceRegex.exec(cleaned)) !== null) {
519
+ const ifaceName = m[1];
520
+ const body = extractBalancedBraces(cleaned, m.index + m[0].length);
521
+ if (!body) continue;
522
+ const members = extractMethodsFromInterfaceBody(body);
523
+ defs.push({ typeKind: 'interface', name: ifaceName, members });
524
+ }
525
+
526
+ // Type aliases — capture inline object-literal members when present
527
+ // (e.g. `export type XProps = ViewProps & { field?: type; ... }`)
528
+ const typeRegex = /(?:export\s+)?type\s+(\w+)\s*=/g;
529
+ while ((m = typeRegex.exec(cleaned)) !== null) {
530
+ const typeName = m[1];
531
+ let members = [];
532
+ let p = m.index + m[0].length;
533
+ // scan for the first inline object literal, stopping at statement end / ASI
534
+ while (p < cleaned.length) {
535
+ const ch = cleaned[p];
536
+ if (ch === '{') break;
537
+ if (ch === ';') { p = -1; break; }
538
+ if (ch === '\n') {
539
+ let k = p + 1;
540
+ while (k < cleaned.length && /[ \t\r]/.test(cleaned[k])) k++;
541
+ if (/^(export|type|interface|class|declare|const|function|let|var|\})\b/.test(cleaned.substring(k))) { p = -1; break; }
542
+ }
543
+ p++;
544
+ }
545
+ if (p >= 0 && p < cleaned.length && cleaned[p] === '{') {
546
+ const body = extractBalancedBraces(cleaned, p + 1);
547
+ if (body) members = parseTypeLiteralMembers(body);
548
+ }
549
+ defs.push({ typeKind: 'type-alias', name: typeName, members });
550
+ }
551
+
552
+ // `declare const X: { ... }` / `export declare const X: { ... }` — object-literal
553
+ // API surface (e.g. `export declare const Clipboard: { getString(): ...; ... }`)
554
+ const declConstRegex = /(?:export\s+)?declare\s+const\s+(\w+)\s*:\s*\{/g;
555
+ while ((m = declConstRegex.exec(cleaned)) !== null) {
556
+ const constName = m[1];
557
+ const body = extractBalancedBraces(cleaned, m.index + m[0].length);
558
+ if (!body) continue;
559
+ const members = parseTypeLiteralMembers(body);
560
+ if (members.length > 0) defs.push({ typeKind: 'const', name: constName, members });
561
+ }
562
+
563
+ return defs;
564
+ }
565
+
566
+ function defsToInterfaces(defs, repoDir, sourceFile, typeLabel, layerLabel, content) {
567
+ const type = typeLabel || 'type-decl';
568
+ const layer = layerLabel || 'js';
569
+ const interfaces = [];
570
+ const sourceRel = rel(sourceFile, repoDir);
571
+ for (const def of defs) {
572
+ for (const member of def.members) {
573
+ // A real method starts with `name(`; otherwise a `:` annotation (incl.
574
+ // function-type fields like `name: (args) => ret`) is a field.
575
+ const isField = !/^\w+\??\s*\(/.test(member.signature) && member.signature.includes(':');
576
+ const isDeprecated = content ? isDeprecatedMember(content, member.name) : false;
577
+ if (isField) {
578
+ const typeMatch = member.signature.match(/^\w+\??\s*:\s*(.+)$/);
579
+ const fieldType = typeMatch ? typeMatch[1].trim() : '-';
580
+ interfaces.push({
581
+ className: def.name, memberName: member.name, fullName: `${def.name}.${member.name}`,
582
+ kind: 'field', isStatic: false, isReadonly: /\breadonly\b/.test(member.signature),
583
+ isOptional: member.signature.includes('?'), isEvent: /(?:onAnimation|onLayout)/.test(member.name),
584
+ isDeprecated, type, layer, signature: member.signature, params: [], returnType: fieldType,
585
+ description: '-', source: sourceRel, startLine: 0,
586
+ });
587
+ } else {
588
+ const parsed = parseMethodSignature(member.signature);
589
+ interfaces.push({
590
+ className: def.name, memberName: member.name, fullName: `${def.name}.${member.name}`,
591
+ kind: 'method', isStatic: /\bstatic\b/.test(member.signature), isReadonly: false,
592
+ isOptional: false, isEvent: /(?:onAnimation|onLayout|Listener)/.test(member.name),
593
+ isDeprecated, type, layer, signature: member.signature, params: parsed.params, returnType: parsed.returnType,
594
+ description: '-', source: sourceRel, startLine: 0,
595
+ });
596
+ }
597
+ }
598
+ }
599
+ return interfaces;
600
+ }
601
+
602
+ function escapeRegex(str) { return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
603
+ function escapeMd(text) { if (!text || text === '-') return '-'; return text.replace(/\|/g, '\\|').replace(/\n/g, ' '); }
604
+ function formatParams(params) {
605
+ if (!params || params.length === 0) return '';
606
+ return params.map(p => `${p.name}${p.optional ? '?' : ''}: ${escapeMd(p.type)}`).join(', ');
607
+ }
608
+ function coverageBadge(rate) { const r = parseFloat(rate); if (r >= 80) return '高'; if (r >= 50) return '中'; if (r >= 20) return '低'; return '极低'; }
609
+
610
+ function parseArgs(argv) {
611
+ // MCP Gateway 模式:第一个参数是 JSON 字符串
612
+ if (argv.length > 0 && typeof argv[0] === 'string' && argv[0].startsWith('{')) {
613
+ try { return JSON.parse(argv[0]); } catch (e) { /* 降级到 CLI 模式 */ }
614
+ }
615
+ const parsed = {};
616
+ for (let i = 0; i < argv.length; i++) {
617
+ if (argv[i].startsWith('--')) {
618
+ const key = argv[i].substring(2);
619
+ if (i + 1 < argv.length && !argv[i + 1].startsWith('--')) { parsed[key] = argv[i + 1]; i++; }
620
+ else parsed[key] = true;
621
+ }
622
+ }
623
+ return parsed;
624
+ }
625
+
626
+ function loadTypeScript() {
627
+ // 最优先:libraryTestScan.js 通过环境变量注入的精确路径
628
+ if (process.env.TYPESCRIPT_PATH) {
629
+ try { const ts = require(process.env.TYPESCRIPT_PATH); if (ts && typeof ts.createProgram === 'function') return ts; } catch {}
630
+ }
631
+ for (const p of [path.join(__dirname, '..', 'node_modules', 'typescript'), path.join(__dirname, 'node_modules', 'typescript')]) {
632
+ try { const ts = require(p); if (ts && typeof ts.createProgram === 'function') return ts; } catch {}
633
+ }
634
+ try { const ts = require('typescript'); if (ts && typeof ts.createProgram === 'function') return ts; } catch {}
635
+ return null;
636
+ }
637
+
638
+ const ts = loadTypeScript();
639
+
640
+ // ════════════════════════════════════════════════════════════
641
+ // Part 1: 上游 npm 源码拉取
642
+ // ════════════════════════════════════════════════════════════
643
+
644
+ function extractFromReadme(repoDir) {
645
+ const readmePath = path.join(repoDir, 'README.md');
646
+ if (!fs.existsSync(readmePath)) return { pkgName: null, version: null };
647
+ const content = fs.readFileSync(readmePath, 'utf-8');
648
+ const match = content.match(/\[([a-z][a-z0-9-]*@(\d+\.\d+\.\d+[^)\]]*))\]/i);
649
+ if (match) { const full = match[1], atIdx = full.indexOf('@'); return { pkgName: full.substring(0, atIdx), version: full.substring(atIdx + 1) }; }
650
+ return { pkgName: null, version: null };
651
+ }
652
+
653
+ function resolveUpstream(repoDir, explicitVersion) {
654
+ const pkg = readPackageJson(repoDir);
655
+ const alias = getLibraryAlias(pkg);
656
+ let pkgName = alias || (pkg && pkg.name && pkg.name.replace(/^@[^/]+\//, '')) || path.basename(repoDir);
657
+ let version = null, source = '';
658
+ if (explicitVersion) { version = explicitVersion; source = '用户指定 (--version)'; }
659
+ if (!version && pkg && pkg.dependencies) {
660
+ if (pkg.dependencies[alias]) { version = pkg.dependencies[alias]; source = `dependencies[${alias}]`; }
661
+ if (!version) {
662
+ for (const [dep, ver] of Object.entries(pkg.dependencies)) {
663
+ if (dep.startsWith('react-native-') && !dep.startsWith('@react-native-ohos/') && !dep.includes('harmony-cli')) { version = ver; pkgName = dep; source = `dependencies[${dep}]`; break; }
664
+ }
665
+ }
666
+ }
667
+ if (!version) { const r = extractFromReadme(repoDir); if (r.version) { version = r.version; if (r.pkgName) pkgName = r.pkgName; source = `README 声明: ${pkgName}@${version}`; } }
668
+ if (!version && pkg && pkg.version) { const inferred = pkg.version.replace(/-(beta|rc)\..*$/i, ''); if (inferred && inferred !== pkg.version) { version = inferred; source = `从鸿蒙版本 ${pkg.version} 推断`; } }
669
+ if (!version) { version = 'latest'; source = 'npm latest (降级)'; }
670
+ return { pkgName, version, source };
671
+ }
672
+
673
+ function npmPackAndExtract(pkgName, version, cacheDir) {
674
+ const versionLabel = version === 'latest' ? 'latest' : version;
675
+ const targetDir = path.join(cacheDir, `${pkgName}@${versionLabel}`);
676
+ if (fs.existsSync(path.join(targetDir, 'package', 'package.json'))) { info(`上游源码缓存命中: ${targetDir}`); return path.join(targetDir, 'package'); }
677
+ fs.mkdirSync(targetDir, { recursive: true });
678
+ const spec = version === 'latest' ? pkgName : `${pkgName}@${version}`;
679
+ info(`npm pack ${spec} ...`);
680
+ try { execSync(`npm pack ${spec} --pack-destination "${targetDir}"`, { cwd: targetDir, stdio: 'pipe', timeout: 60000 }); }
681
+ catch (e) { if (version !== 'latest') { warn(`版本 ${version} 不存在,降级到 latest`); return npmPackAndExtract(pkgName, 'latest', cacheDir); } error(`npm pack 失败: ${e.message}`); }
682
+ const tgzFiles = fs.readdirSync(targetDir).filter(f => f.endsWith('.tgz'));
683
+ if (tgzFiles.length === 0) error(`npm pack 未生成 tgz 文件: ${targetDir}`);
684
+ info(`解压: ${tgzFiles[0]}`);
685
+ try { execSync(`tar xzf "${path.join(targetDir, tgzFiles[0])}" -C "${targetDir}"`, { stdio: 'pipe' }); } catch (e) { error(`解压失败: ${e.message}`); }
686
+ fs.unlinkSync(path.join(targetDir, tgzFiles[0]));
687
+ const sourceDir = path.join(targetDir, 'package');
688
+ if (!fs.existsSync(sourceDir)) error(`解压后未找到 package/ 目录: ${targetDir}`);
689
+ ok(`上游源码已拉取: ${pkgName}@${versionLabel}`);
690
+ return sourceDir;
691
+ }
692
+
693
+ function fetchUpstream(repoDir, opts = {}) {
694
+ const { version: explicitVersion, cacheDir } = opts;
695
+ const { pkgName, version, source: versionSource } = resolveUpstream(repoDir, explicitVersion);
696
+ info(`上游包: ${pkgName}, 版本: ${version} (${versionSource})`);
697
+ const finalCacheDir = cacheDir || path.join(repoDir, '.upstream-cache');
698
+ const sourceDir = npmPackAndExtract(pkgName, version, finalCacheDir);
699
+ return { sourceDir, isUpstream: true, pkgName, version };
700
+ }
701
+
702
+ // ════════════════════════════════════════════════════════════
703
+ // Part 2: 接口提取
704
+ // ════════════════════════════════════════════════════════════
705
+
706
+ function detectExportStyle(content) {
707
+ const cleaned = stripComments(content);
708
+ if (/module\.exports\s*=/.test(cleaned)) return 'commonjs';
709
+ if (/Object\.defineProperty\(exports,\s*['"]__esModule['"]/.test(cleaned)) return 'commonjs';
710
+ if (/exports\.default\s*=/.test(cleaned)) return 'commonjs';
711
+ if (/export\s+(default|{|function|class|const|interface|type|\*)/.test(cleaned)) return 'es-module';
712
+ if (/global\.\w+/.test(cleaned)) return 'side-effect';
713
+ return 'unknown';
714
+ }
715
+
716
+ function parseMethodSignature(sig) {
717
+ const params = [];
718
+ let returnType = 'void';
719
+ let match = sig.match(/^(\w+)\s*\(([^)]*)\)\s*:\s*(.+)$/);
720
+ if (!match) match = sig.match(/^(\w+)\s*:\s*\(([^)]*)\)\s*=>\s*(.+)$/);
721
+ if (!match) match = sig.match(/^(\w+)\s*\(([^)]*)\)$/);
722
+ if (match) {
723
+ if (match[2] && match[2].trim()) params.push(...parseParamList(match[2]));
724
+ if (match[3]) returnType = match[3].trim();
725
+ }
726
+ return { params, returnType };
727
+ }
728
+
729
+ function parseParamList(paramStr) {
730
+ const params = [];
731
+ if (!paramStr || !paramStr.trim()) return params;
732
+ const parts = [];
733
+ let depth = 0, current = '';
734
+ for (const ch of paramStr) {
735
+ if (ch === '(' || ch === '{' || ch === '[') depth++;
736
+ if (ch === ')' || ch === '}' || ch === ']') depth--;
737
+ if (ch === ',' && depth === 0) { parts.push(current.trim()); current = ''; } else current += ch;
738
+ }
739
+ if (current.trim()) parts.push(current.trim());
740
+ for (const part of parts) {
741
+ const m = part.match(/^(\w+)\s*(\?)?\s*:\s*(.+)$/);
742
+ if (m) params.push({ name: m[1], type: m[3].trim(), optional: !!m[2], description: '-' });
743
+ else params.push({ name: part, type: 'any', optional: false, description: '-' });
744
+ }
745
+ return params;
746
+ }
747
+
748
+ function extractPropTypesFields(content, componentName) {
749
+ const cleaned = stripComments(content);
750
+ const fields = [];
751
+ const patterns = [
752
+ /static\s+propTypes\s*=\s*\{/g,
753
+ new RegExp(`\\b${escapeRegex(componentName)}\\.propTypes\\s*=\\s*\\{`, 'g'),
754
+ ];
755
+ for (const pat of patterns) {
756
+ let m;
757
+ while ((m = pat.exec(cleaned)) !== null) {
758
+ const body = extractBalancedBraces(cleaned, m.index + m[0].length);
759
+ if (!body) continue;
760
+ for (const p of parseParamList(body)) {
761
+ fields.push({ name: p.name, optional: p.optional, type: p.type });
762
+ }
763
+ }
764
+ }
765
+ return fields;
766
+ }
767
+
768
+ // Follow HOC / indirect component imports for a named const export such as
769
+ // `export const Comp = connectToState(Inner)` or `export const Comp = Inner`,
770
+ // where `Inner` is imported from another file. Reads the source file of the
771
+ // imported identifier and extracts the underlying component class's methods +
772
+ // propTypes, attributing them to the exported name.
773
+ function followHocImports(content, repoDir, fromFile, exportedName) {
774
+ const cleaned = stripComments(content);
775
+ const interfaces = [];
776
+ const importMap = {};
777
+ let im;
778
+ const defaultImportRe = /import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g;
779
+ const namedImportRe = /import\s+\{\s*([^}]+?)\s*\}\s+from\s+['"]([^'"]+)['"]/g;
780
+ while ((im = defaultImportRe.exec(cleaned)) !== null) importMap[im[1]] = { from: im[2], original: null };
781
+ while ((im = namedImportRe.exec(cleaned)) !== null) {
782
+ for (let part of im[1].split(',')) {
783
+ part = part.trim(); if (!part) continue;
784
+ const asM = part.match(/^(\w+)\s+as\s+(\w+)/);
785
+ if (asM) importMap[asM[2]] = { from: im[2], original: asM[1] };
786
+ else importMap[part] = { from: im[2], original: part };
787
+ }
788
+ }
789
+ const constRe = new RegExp(`export\\s+(?:const|let|var)\\s+${escapeRegex(exportedName)}\\s*=\\s*([^;\\n]+)`);
790
+ const cm = constRe.exec(cleaned);
791
+ if (!cm) return interfaces;
792
+ const expr = cm[1];
793
+ const referenced = Object.keys(importMap).filter(local => new RegExp(`\\b${escapeRegex(local)}\\b`).test(expr));
794
+ for (const local of referenced) {
795
+ const { from: modPath, original: origName } = importMap[local];
796
+ const targetFile = resolveRelativePath(fromFile, modPath);
797
+ if (!targetFile) continue;
798
+ let tc;
799
+ try { tc = readText(targetFile); } catch { continue; }
800
+ const tCleaned = stripComments(tc);
801
+ let className = null;
802
+ const defClass = tCleaned.match(/export\s+default\s+(?:abstract\s+)?class\s+(\w+)/);
803
+ if (defClass) className = defClass[1];
804
+ if (!className && origName) {
805
+ if (new RegExp(`(?:export\\s+)?(?:default\\s+)?(?:abstract\\s+)?class\\s+${escapeRegex(origName)}\\b`).test(tCleaned)) className = origName;
806
+ }
807
+ if (!className) continue;
808
+ const targetRel = rel(targetFile, repoDir);
809
+ for (const method of extractClassMethods(tc, className)) {
810
+ const parsed = parseMethodSignature(method.signature);
811
+ interfaces.push({
812
+ className: exportedName, memberName: method.name, fullName: `${exportedName}.${method.name}`, kind: 'method',
813
+ isStatic: /\bstatic\b/.test(method.signature), isReadonly: false, isOptional: false, isEvent: false,
814
+ type: 'es-export', layer: 'js', signature: method.signature,
815
+ params: parsed.params, returnType: parsed.returnType, description: extractMethodJSDoc(tc, method.name),
816
+ source: targetRel, startLine: 0,
817
+ });
818
+ }
819
+ for (const p of extractPropTypesFields(tc, className)) {
820
+ interfaces.push({
821
+ className: exportedName, memberName: p.name, fullName: `${exportedName}.${p.name}`, kind: 'field',
822
+ isStatic: true, isReadonly: false, isOptional: p.optional, isEvent: /^on[A-Z]/.test(p.name),
823
+ type: 'es-export', layer: 'js', signature: `${p.name}: ${p.type}`,
824
+ params: [], returnType: p.type, description: '-', source: targetRel, startLine: 0,
825
+ });
826
+ }
827
+ }
828
+ return interfaces;
829
+ }
830
+
831
+ function extractMethodJSDoc(content, methodName) {
832
+ const patterns = [
833
+ new RegExp(`(?:^|\\n)\\s*(?://|/\\*)\\s*([^\\n*/]*)\\s*(?:\\*/)?\\s*(?:^|\\n)\\s*(?:readonly\\s+|static\\s+)*${methodName}\\s*[\\(:]`, 'm'),
834
+ new RegExp(`\\/\\*\\*\\s*([\\s\\S]*?)\\*\\/\\s*(?:readonly\\s+|static\\s+)*${methodName}\\s*[\\(:]`),
835
+ ];
836
+ for (const pattern of patterns) {
837
+ const m = content.match(pattern);
838
+ if (m && m[1]) { const desc = m[1].replace(/^\s*\*\s?/gm, '').trim(); if (desc && !desc.startsWith('@')) return desc.split('\n')[0].trim(); }
839
+ }
840
+ const lm = content.match(new RegExp(`\\/\\/\\s*(.+)\\s*\\n\\s*${methodName}\\s*[\\(:]`));
841
+ if (lm && lm[1]) return lm[1].trim();
842
+ return '-';
843
+ }
844
+
845
+ function isDeprecatedMember(content, memberName) {
846
+ const lines = content.split('\n');
847
+ for (let i = 0; i < lines.length; i++) {
848
+ if (lines[i].includes('@deprecated')) {
849
+ for (let j = i - 1; j >= 0; j--) {
850
+ if (lines[j].includes('/**')) {
851
+ let endJ = i;
852
+ while (endJ < lines.length && !lines[endJ].includes('*/')) endJ++;
853
+ for (let k = endJ + 1; k < Math.min(endJ + 4, lines.length); k++) {
854
+ const line = lines[k].trim();
855
+ if (!line || line.startsWith('*') || line.startsWith('//')) continue;
856
+ const declPattern = new RegExp(`(?:export\\s+)?(?:async\\s+)?(?:function|const|let|var|class|interface|type|readonly|static|get|set)\\s+${memberName}\\b`);
857
+ const fieldPattern = new RegExp(`^${memberName}\\s*\\??\\s*:`);
858
+ if (declPattern.test(line) || fieldPattern.test(line)) return true;
859
+ break;
860
+ }
861
+ break;
862
+ }
863
+ }
864
+ }
865
+ }
866
+ return false;
867
+ }
868
+
869
+ function findMethodLineNumber(content, methodName) {
870
+ const lines = content.split('\n');
871
+ for (let i = 0; i < lines.length; i++) {
872
+ if (new RegExp(`\\b${methodName}\\s*[\\(:]`).test(lines[i]) && !lines[i].trim().startsWith('//')) return i + 1;
873
+ }
874
+ return 0;
875
+ }
876
+
877
+ function extractTurboModuleSpecs(repoDir) {
878
+ const specFiles = findTurboModuleSpecFiles(repoDir);
879
+ const files = [], interfaces = [];
880
+ for (const specFile of specFiles) {
881
+ const content = readText(specFile);
882
+ const basic = extractTurboModuleSpec(content);
883
+ if (basic.methods.length === 0 && !basic.moduleName) continue;
884
+ files.push(rel(specFile, repoDir));
885
+ for (const method of basic.methods) {
886
+ const parsed = parseMethodSignature(method.signature);
887
+ interfaces.push({
888
+ className: basic.moduleName, memberName: method.name, fullName: `${basic.moduleName}.${method.name}`,
889
+ kind: 'method', isStatic: false, isReadonly: false, isOptional: false,
890
+ isEvent: /(?:add|remove)\w*Listener/.test(method.name),
891
+ type: 'turbo-module', layer: 'turbo', signature: method.signature,
892
+ params: parsed.params, returnType: parsed.returnType,
893
+ description: extractMethodJSDoc(content, method.name), source: rel(specFile, repoDir), startLine: findMethodLineNumber(content, method.name),
894
+ });
895
+ }
896
+ }
897
+ return { files, interfaces };
898
+ }
899
+
900
+ function extractFabricComponentSpecs(repoDir) {
901
+ const specFiles = findTurboModuleSpecFiles(repoDir);
902
+ const files = [], interfaces = [];
903
+
904
+ for (const specFile of specFiles) {
905
+ const content = readText(specFile);
906
+ const cleaned = stripComments(content);
907
+ const sourceRel = rel(specFile, repoDir);
908
+
909
+ const isFabric = /codegenNativeComponent/.test(cleaned) || /codegenNativeCommands/.test(cleaned);
910
+ const isTurboModule = /interface\s+Spec\s+extends\s+TurboModule/.test(cleaned);
911
+ if (!isFabric && !isTurboModule) continue;
912
+
913
+ files.push(sourceRel);
914
+
915
+ const defs = extractClassAndInterfaceDefs(content);
916
+ const allFabricInterfaces = defsToInterfaces(defs, repoDir, specFile, 'fabric-spec', 'fabric', content);
917
+
918
+ for (const iface of allFabricInterfaces) {
919
+ // Capture component props interfaces regardless of exact name
920
+ // (covers NativeProps, VLCNativeProps, *Props, etc.)
921
+ if (/Props$/.test(iface.className)) {
922
+ if (iface.memberName === 'isFirstRefresh') continue;
923
+ interfaces.push(iface);
924
+ }
925
+ }
926
+ }
927
+
928
+ return { files, interfaces };
929
+ }
930
+
931
+ function extractMethodsFromCjsObject(body, content, sourceFile, repoDir) {
932
+ const interfaces = [];
933
+ const funcRegex = /(?:^|\n)\s*(?:async\s+)?(\w+)\s*\(([^)]*)\)\s*\{/g;
934
+ const seen = new Set();
935
+ let m;
936
+ while ((m = funcRegex.exec(body)) !== null) {
937
+ const name = m[1];
938
+ if (seen.has(name) || ['if', 'for', 'while', 'switch', 'catch', 'function', 'return', 'else', 'try'].includes(name)) continue;
939
+ seen.add(name);
940
+ const paramStr = m[2];
941
+ interfaces.push({
942
+ className: null, memberName: name, fullName: name, kind: 'function',
943
+ isStatic: false, isReadonly: false, isOptional: false,
944
+ isEvent: /(?:add|remove)\w*Listener/.test(name),
945
+ type: 'commonjs-export', layer: 'js', signature: `${name}(${paramStr})`,
946
+ params: parseParamList(paramStr), returnType: 'void',
947
+ description: extractMethodJSDoc(content, name), source: rel(sourceFile, repoDir), startLine: findMethodLineNumber(content, name),
948
+ });
949
+ }
950
+ const namedFuncRegex = /(\w+)\s*:\s*(?:async\s+)?function\s*\(([^)]*)\)/g;
951
+ while ((m = namedFuncRegex.exec(body)) !== null) {
952
+ const name = m[1];
953
+ if (seen.has(name)) continue;
954
+ seen.add(name);
955
+ const paramStr = m[2];
956
+ interfaces.push({
957
+ className: null, memberName: name, fullName: name, kind: 'function',
958
+ isStatic: false, isReadonly: false, isOptional: false,
959
+ isEvent: /(?:add|remove)\w*Listener/.test(name),
960
+ type: 'commonjs-export', layer: 'js', signature: `${name}(${paramStr})`,
961
+ params: parseParamList(paramStr), returnType: 'void',
962
+ description: extractMethodJSDoc(content, name), source: rel(sourceFile, repoDir), startLine: findMethodLineNumber(content, name),
963
+ });
964
+ }
965
+ return interfaces;
966
+ }
967
+
968
+ function extractCommonJSExports(content, repoDir, entryFile) {
969
+ const cleaned = stripComments(content);
970
+ const interfaces = [];
971
+ const match = cleaned.match(/module\.exports\s*=\s*\{/);
972
+ if (match) {
973
+ const body = extractBalancedBraces(cleaned, match.index + match[0].length);
974
+ if (body) interfaces.push(...extractMethodsFromCjsObject(body, content, entryFile, repoDir));
975
+ return interfaces;
976
+ }
977
+ // Follow `module.exports = <identifier>` (re-export of an imported binding)
978
+ const reMatch = cleaned.match(/module\.exports\s*=\s*([A-Za-z_$][\w$]*)\s*;?\s*$/m);
979
+ if (reMatch) {
980
+ const identifier = reMatch[1];
981
+ const importMatch = cleaned.match(new RegExp(`import\\s+${escapeRegex(identifier)}\\s+from\\s+['"]([^'"]+)['"]`));
982
+ const requireMatch = !importMatch ? cleaned.match(new RegExp(`(?:const|let|var)\\s+${escapeRegex(identifier)}\\s*=\\s*require\\(\\s*['"]([^'"]+)['"]\\s*\\)`)) : null;
983
+ const fromPath = importMatch ? importMatch[1] : (requireMatch ? requireMatch[1] : null);
984
+ if (fromPath) {
985
+ const targetFile = resolveRelativePath(entryFile, fromPath);
986
+ if (targetFile && fs.existsSync(targetFile) && fs.statSync(targetFile).isFile()) {
987
+ const srcContent = readText(targetFile);
988
+ const srcCleaned = stripComments(srcContent);
989
+ const objMatch = srcCleaned.match(new RegExp(`(?:let|const|var)\\s+${escapeRegex(identifier)}\\s*=\\s*\\{`));
990
+ if (objMatch) {
991
+ const objBody = extractBalancedBraces(srcCleaned, objMatch.index + objMatch[0].length);
992
+ if (objBody) interfaces.push(...extractMethodsFromCjsObject(objBody, srcContent, targetFile, repoDir));
993
+ }
994
+ }
995
+ }
996
+ }
997
+ return interfaces;
998
+ }
999
+
1000
+ function processNamedExportsFromContent(content, repoDir, sourceFile, namedExports) {
1001
+ const interfaces = [];
1002
+ const cleaned = stripComments(content);
1003
+ const sourceRel = rel(sourceFile, repoDir);
1004
+ for (const name of namedExports) {
1005
+ const isDeprecated = isDeprecatedMember(content, name);
1006
+ const funcMatch = cleaned.match(new RegExp(`export\\s+(?:async\\s+)?function\\s+${name}\\s*(?:<[^>]*>)?\\s*\\(([\\s\\S]*?)\\)\\s*(?::\\s*([^{{]+?))?\\s*[{=]`));
1007
+ if (funcMatch) {
1008
+ const paramStr = (funcMatch[1] || '').replace(/\n/g, ' ').trim();
1009
+ const retType = funcMatch[2] ? funcMatch[2].trim() : '-';
1010
+ interfaces.push({
1011
+ className: null, memberName: name, fullName: name, kind: 'function',
1012
+ isStatic: false, isReadonly: false, isOptional: false, isEvent: false, isDeprecated,
1013
+ type: 'es-export', layer: 'js', signature: `${name}(${paramStr})${retType !== '-' ? ': ' + retType : ''}`,
1014
+ params: parseParamList(paramStr), returnType: retType,
1015
+ description: extractMethodJSDoc(content, name), source: sourceRel, startLine: findMethodLineNumber(content, name),
1016
+ });
1017
+ continue;
1018
+ }
1019
+ const classMatch = cleaned.match(new RegExp(`export\\s+(?:default\\s+)?(?:abstract\\s+)?class\\s+${name}\\b`));
1020
+ if (classMatch) {
1021
+ const classMethods = extractClassMethods(content, name);
1022
+ for (const method of classMethods) {
1023
+ const parsed = parseMethodSignature(method.signature);
1024
+ const methodDeprecated = isDeprecatedMember(content, method.name);
1025
+ interfaces.push({
1026
+ className: name, memberName: method.name, fullName: `${name}.${method.name}`, kind: 'method',
1027
+ isStatic: /\bstatic\b/.test(method.signature), isReadonly: false, isOptional: false, isEvent: false,
1028
+ isDeprecated: methodDeprecated, type: 'es-export', layer: 'js', signature: method.signature,
1029
+ params: parsed.params, returnType: parsed.returnType, description: '-',
1030
+ source: sourceRel, startLine: 0,
1031
+ });
1032
+ }
1033
+ for (const p of extractPropTypesFields(content, name)) {
1034
+ interfaces.push({
1035
+ className: name, memberName: p.name, fullName: `${name}.${p.name}`, kind: 'field',
1036
+ isStatic: true, isReadonly: false, isOptional: p.optional, isEvent: /^on[A-Z]/.test(p.name),
1037
+ isDeprecated, type: 'es-export', layer: 'js', signature: `${p.name}: ${p.type}`,
1038
+ params: [], returnType: p.type, description: '-', source: sourceRel, startLine: 0,
1039
+ });
1040
+ }
1041
+ continue;
1042
+ }
1043
+ const constMatch = cleaned.match(new RegExp(`export\\s+(?:const|let|var)\\s+${name}\\s*(?::\\s*([^=]+?))?\\s*=\\s*([^;\\n]+)`));
1044
+ if (constMatch) {
1045
+ const varType = constMatch[1] ? constMatch[1].trim() : '-';
1046
+ const varValue = constMatch[2].trim();
1047
+ interfaces.push({
1048
+ className: null, memberName: name, fullName: name, kind: 'variable',
1049
+ isStatic: false, isReadonly: false, isOptional: false, isEvent: false, isDeprecated,
1050
+ type: 'es-export', layer: 'js', signature: `${name}${varType !== '-' ? ': ' + varType : ''} = ${varValue}`,
1051
+ params: [], returnType: varType, description: '-', source: sourceRel, startLine: 0,
1052
+ });
1053
+ // Follow HOC / indirect imports (e.g. `export const X = connectToState(Inner)`)
1054
+ // to capture the underlying component's methods + propTypes.
1055
+ interfaces.push(...followHocImports(content, repoDir, sourceFile, name));
1056
+ }
1057
+ }
1058
+ return interfaces;
1059
+ }
1060
+
1061
+ function extractESModuleExports(content, repoDir, entryFile) {
1062
+ const exports = extractExports(content);
1063
+ const interfaces = [];
1064
+ const cleaned = stripComments(content);
1065
+ interfaces.push(...processNamedExportsFromContent(content, repoDir, entryFile, exports.named));
1066
+ for (const name of exports.types) {
1067
+ const ifaceMethods = extractInterfaceMethods(content, name);
1068
+ for (const method of ifaceMethods) {
1069
+ const parsed = parseMethodSignature(method.signature);
1070
+ interfaces.push({
1071
+ className: name, memberName: method.name, fullName: `${name}.${method.name}`, kind: 'method',
1072
+ isStatic: false, isReadonly: false, isOptional: false, isEvent: false,
1073
+ type: 'type-decl', layer: 'js', signature: method.signature,
1074
+ params: parsed.params, returnType: parsed.returnType, description: '-',
1075
+ source: rel(entryFile, repoDir), startLine: 0,
1076
+ });
1077
+ }
1078
+ }
1079
+
1080
+ // ── re-export 链追踪 ──────────────────────────────────
1081
+
1082
+ // 1. Wildcard re-export tracking (export * from './types')
1083
+ for (const reExport of exports.reExports) {
1084
+ if (!reExport.isWildcard) continue;
1085
+ const targetFile = resolveRelativePath(entryFile, reExport.from);
1086
+ if (!targetFile) continue;
1087
+ const targetContent = readText(targetFile);
1088
+ const defs = extractClassAndInterfaceDefs(targetContent);
1089
+ interfaces.push(...defsToInterfaces(defs, repoDir, targetFile, undefined, undefined, targetContent));
1090
+ const targetExports = extractExports(targetContent);
1091
+ interfaces.push(...processNamedExportsFromContent(targetContent, repoDir, targetFile, targetExports.named));
1092
+
1093
+ // Recursively process re-exports in the target file
1094
+ for (const subReExport of targetExports.reExports) {
1095
+ if (!subReExport.isWildcard) continue;
1096
+ const subTarget = resolveRelativePath(targetFile, subReExport.from);
1097
+ if (!subTarget) continue;
1098
+ const subContent = readText(subTarget);
1099
+ const subDefs = extractClassAndInterfaceDefs(subContent);
1100
+ interfaces.push(...defsToInterfaces(subDefs, repoDir, subTarget, undefined, undefined, subContent));
1101
+ const subExports = extractExports(subContent);
1102
+ interfaces.push(...processNamedExportsFromContent(subContent, repoDir, subTarget, subExports.named));
1103
+ }
1104
+ }
1105
+
1106
+ // 2. Named re-export tracking (export { Foo } from './bar')
1107
+ for (const reExport of exports.reExports) {
1108
+ if (reExport.isWildcard) continue;
1109
+ const targetFile = resolveRelativePath(entryFile, reExport.from);
1110
+ if (!targetFile) continue;
1111
+ const targetContent = readText(targetFile);
1112
+ const defs = extractClassAndInterfaceDefs(targetContent);
1113
+ const filteredDefs = defs.filter(d => d.name === reExport.original || d.name === reExport.name);
1114
+ interfaces.push(...defsToInterfaces(filteredDefs, repoDir, targetFile, undefined, undefined, targetContent));
1115
+ if (!reExport.isType) {
1116
+ interfaces.push(...processNamedExportsFromContent(targetContent, repoDir, targetFile, [reExport.original, reExport.name]));
1117
+ }
1118
+ }
1119
+
1120
+ // 3. Default export import tracking (import { LottieView } from './LottieView'; export default LottieView)
1121
+ if (exports.defaultExport && !exports.named.includes(exports.defaultExport)) {
1122
+ const importRegex = new RegExp(`import\\s+(?:type\\s+)?\\{[^}]*\\b${escapeRegex(exports.defaultExport)}\\b[^}]*\\}\\s+from\\s+['"]([^'"]+)['"]`);
1123
+ const importMatch = cleaned.match(importRegex);
1124
+ if (importMatch) {
1125
+ const targetFile = resolveRelativePath(entryFile, importMatch[1]);
1126
+ if (targetFile) {
1127
+ const targetContent = readText(targetFile);
1128
+ const defs = extractClassAndInterfaceDefs(targetContent);
1129
+ const filteredDefs = defs.filter(d => d.name === exports.defaultExport);
1130
+ interfaces.push(...defsToInterfaces(filteredDefs, repoDir, targetFile, undefined, undefined, targetContent));
1131
+ }
1132
+ } else {
1133
+ // Inline `export default class X` / `export default function X` defined in this file
1134
+ const dn = exports.defaultExport;
1135
+ const inlineClassRe = new RegExp(`export\\s+default\\s+(?:abstract\\s+)?class\\s+${escapeRegex(dn)}\\b`);
1136
+ const inlineFuncRe = new RegExp(`export\\s+default\\s+(?:async\\s+)?function\\s+${escapeRegex(dn)}\\b`);
1137
+ if (inlineClassRe.test(cleaned)) {
1138
+ const members = extractClassMethods(content, dn);
1139
+ const sourceRel = rel(entryFile, repoDir);
1140
+ for (const method of members) {
1141
+ const parsed = parseMethodSignature(method.signature);
1142
+ interfaces.push({
1143
+ className: dn, memberName: method.name, fullName: `${dn}.${method.name}`, kind: 'method',
1144
+ isStatic: /\bstatic\b/.test(method.signature), isReadonly: false, isOptional: false, isEvent: false,
1145
+ type: 'es-export', layer: 'js', signature: method.signature,
1146
+ params: parsed.params, returnType: parsed.returnType, description: extractMethodJSDoc(content, method.name),
1147
+ source: sourceRel, startLine: findMethodLineNumber(content, method.name),
1148
+ });
1149
+ }
1150
+ for (const p of extractPropTypesFields(content, dn)) {
1151
+ interfaces.push({
1152
+ className: dn, memberName: p.name, fullName: `${dn}.${p.name}`, kind: 'field',
1153
+ isStatic: true, isReadonly: false, isOptional: p.optional, isEvent: /^on[A-Z]/.test(p.name),
1154
+ type: 'es-export', layer: 'js', signature: `${p.name}: ${p.type}`,
1155
+ params: [], returnType: p.type, description: '-', source: sourceRel, startLine: 0,
1156
+ });
1157
+ }
1158
+ } else if (inlineFuncRe.test(cleaned)) {
1159
+ const sourceRel = rel(entryFile, repoDir);
1160
+ const sigMatch = cleaned.match(new RegExp(`export\\s+default\\s+(?:async\\s+)?function\\s+${escapeRegex(dn)}\\s*\\(([^)]*)\\)`));
1161
+ const paramStr = sigMatch ? sigMatch[1] : '';
1162
+ interfaces.push({
1163
+ className: null, memberName: dn, fullName: dn, kind: 'function',
1164
+ isStatic: false, isReadonly: false, isOptional: false, isEvent: false,
1165
+ type: 'es-export', layer: 'js', signature: `${dn}(${paramStr})`,
1166
+ params: parseParamList(paramStr), returnType: '-',
1167
+ description: extractMethodJSDoc(content, dn), source: sourceRel, startLine: findMethodLineNumber(content, dn),
1168
+ });
1169
+ } else {
1170
+ // `export default new ClassName()` — find the local class and extract methods
1171
+ const newClassMatch = cleaned.match(/export\s+default\s+new\s+(\w+)\s*\(/);
1172
+ if (newClassMatch) {
1173
+ const className = newClassMatch[1];
1174
+ const classDefRe = new RegExp(`(?:^|\\n)\\s*class\\s+${escapeRegex(className)}\\b`);
1175
+ if (classDefRe.test(cleaned)) {
1176
+ const members = extractClassMethods(content, className);
1177
+ const sourceRel = rel(entryFile, repoDir);
1178
+ for (const method of members) {
1179
+ const parsed = parseMethodSignature(method.signature);
1180
+ interfaces.push({
1181
+ className, memberName: method.name, fullName: `${className}.${method.name}`, kind: 'method',
1182
+ isStatic: /\bstatic\b/.test(method.signature), isReadonly: false, isOptional: false, isEvent: false,
1183
+ type: 'es-export', layer: 'js', signature: method.signature,
1184
+ params: parsed.params, returnType: parsed.returnType, description: extractMethodJSDoc(content, method.name),
1185
+ source: sourceRel, startLine: findMethodLineNumber(content, method.name),
1186
+ });
1187
+ }
1188
+ }
1189
+ }
1190
+ }
1191
+ }
1192
+ }
1193
+
1194
+ return interfaces;
1195
+ }
1196
+
1197
+ function extractSideEffectExports(content, repoDir, entryFile) {
1198
+ const cleaned = stripComments(content);
1199
+ const interfaces = [];
1200
+ const globalAssignRegex = /global\.(\w+(?:\.\w+)*)\s*=\s*(\w+|function\s*\([^)]*\))/g;
1201
+ const seen = new Set();
1202
+ let m;
1203
+ while ((m = globalAssignRegex.exec(cleaned)) !== null) {
1204
+ const fullPath = m[1], target = m[2];
1205
+ if (seen.has(fullPath)) continue;
1206
+ seen.add(fullPath);
1207
+ const parts = fullPath.split('.');
1208
+ const memberName = parts[parts.length - 1];
1209
+ const className = parts.length > 1 ? parts.slice(0, -1).join('.') : null;
1210
+ const isFunction = target.startsWith('function') || /^[a-zA-Z_$]/.test(target);
1211
+ interfaces.push({
1212
+ className, memberName, fullName: fullPath,
1213
+ kind: isFunction ? 'function' : 'variable',
1214
+ isStatic: false, isReadonly: false, isOptional: false, isEvent: false,
1215
+ type: 'side-effect', layer: 'js', signature: `global.${fullPath} = ${target}`,
1216
+ params: [], returnType: isFunction ? 'void' : '-',
1217
+ description: `Side-effect: patches global.${fullPath}`,
1218
+ source: rel(entryFile, repoDir), startLine: findMethodLineNumber(content, memberName),
1219
+ });
1220
+ }
1221
+ return interfaces;
1222
+ }
1223
+
1224
+ function extractTypeDefinitions(repoDir) {
1225
+ const pkg = readPackageJson(repoDir);
1226
+ const candidates = [
1227
+ path.join(repoDir, 'index.d.ts'),
1228
+ path.join(repoDir, 'lib', 'typescript', 'index.d.ts'),
1229
+ path.join(repoDir, 'types', 'index.d.ts'),
1230
+ path.join(repoDir, 'dist', 'index.d.ts'),
1231
+ ];
1232
+ if (pkg && pkg.types) candidates.unshift(path.resolve(repoDir, pkg.types));
1233
+ if (pkg && pkg.typings) candidates.unshift(path.resolve(repoDir, pkg.typings));
1234
+ // Derive d.ts location from the main/source/module entry (handles dirs like
1235
+ // `main: "lib/js"` -> `lib/js/index.d.ts`, and `main: "lib/index.js"` -> `lib/index.d.ts`).
1236
+ for (const field of ['source', 'react-native', 'main', 'module']) {
1237
+ if (pkg && pkg[field] && typeof pkg[field] === 'string') {
1238
+ const entry = path.resolve(repoDir, pkg[field]);
1239
+ candidates.unshift(entry.replace(/\.jsx?$/, '.d.ts'));
1240
+ candidates.unshift(path.join(entry, 'index.d.ts'));
1241
+ }
1242
+ }
1243
+
1244
+ let dtsPath = null;
1245
+ for (const c of candidates) {
1246
+ if (fs.existsSync(c) && fs.statSync(c).isFile()) { dtsPath = c; break; }
1247
+ }
1248
+ if (!dtsPath) return { found: false, interfaces: [], source: null, allSources: [] };
1249
+
1250
+ const allSources = [];
1251
+ const interfaces = [];
1252
+ const visited = new Set();
1253
+
1254
+ function processDts(filePath) {
1255
+ if (visited.has(filePath)) return;
1256
+ visited.add(filePath);
1257
+ // Skip directories (can happen when a re-export resolves to a folder).
1258
+ try { if (fs.statSync(filePath).isDirectory()) return; } catch (e) { return; }
1259
+ allSources.push(rel(filePath, repoDir));
1260
+
1261
+ const content = readText(filePath);
1262
+ const defs = extractClassAndInterfaceDefs(content);
1263
+ interfaces.push(...defsToInterfaces(defs, repoDir, filePath, undefined, undefined, content));
1264
+
1265
+ // Track re-exports
1266
+ const exports = extractExports(content);
1267
+ for (const reExport of exports.reExports) {
1268
+ const targetFile = resolveRelativePathDts(filePath, reExport.from);
1269
+ if (targetFile) processDts(targetFile);
1270
+ }
1271
+
1272
+ // Track default export import source
1273
+ if (exports.defaultExport) {
1274
+ const importRegex = new RegExp(`import\\s+(?:type\\s+)?\\{[^}]*\\b${escapeRegex(exports.defaultExport)}\\b[^}]*\\}\\s+from\\s+['"]([^'"]+)['"]`);
1275
+ const cleaned = stripComments(content);
1276
+ const importMatch = cleaned.match(importRegex);
1277
+ if (importMatch) {
1278
+ const targetFile = resolveRelativePathDts(filePath, importMatch[1]);
1279
+ if (targetFile) processDts(targetFile);
1280
+ }
1281
+ }
1282
+
1283
+ // Track named export import sources (including type exports) — follow ALL matches
1284
+ const cleanedDts = stripComments(content);
1285
+ for (const namedExport of [...exports.named, ...exports.types]) {
1286
+ const importRegex = new RegExp(`import\\s+(?:type\\s+)?\\{[^}]*\\b${escapeRegex(namedExport)}\\b[^}]*\\}\\s+from\\s+['"]([^'"]+)['"]`, 'g');
1287
+ let importMatch;
1288
+ while ((importMatch = importRegex.exec(cleanedDts)) !== null) {
1289
+ const targetFile = resolveRelativePathDts(filePath, importMatch[1]);
1290
+ if (targetFile) processDts(targetFile);
1291
+ }
1292
+ }
1293
+ }
1294
+
1295
+ processDts(dtsPath);
1296
+
1297
+ return { found: true, interfaces, source: rel(dtsPath, repoDir), allSources };
1298
+ }
1299
+
1300
+ // ════════════════════════════════════════════════════════════
1301
+ // Part 3: 接口规格生成
1302
+ // ════════════════════════════════════════════════════════════
1303
+
1304
+ function generateRNSpec(repoDir, opts = {}) {
1305
+ const absDir = path.resolve(repoDir);
1306
+ if (!fs.existsSync(absDir)) error(`目录不存在: ${repoDir}`);
1307
+ const pkg = readPackageJson(absDir);
1308
+ const libraryName = (pkg && pkg.name) || path.basename(absDir);
1309
+ const libraryAlias = getLibraryAlias(pkg) || libraryName.replace(/^@[^/]+\//, '');
1310
+
1311
+ info('拉取上游 npm 源码...');
1312
+ const upstreamInfo = fetchUpstream(absDir, { version: opts.upstreamVersion });
1313
+ const upstreamDir = upstreamInfo.sourceDir;
1314
+ function upstreamRel(filePath) { return `upstream:${upstreamInfo.pkgName}@${upstreamInfo.version}/${path.relative(upstreamDir, filePath).split(path.sep).join('/')}`; }
1315
+
1316
+ const sources = [], interfaces = [];
1317
+
1318
+ // JS 层导出 — 从上游源码提取
1319
+ const entryFile = detectEntryFile(upstreamDir);
1320
+ const entryContent = entryFile ? readText(entryFile) : '';
1321
+ const exportStyle = entryFile ? detectExportStyle(entryContent) : 'unknown';
1322
+ if (entryFile) sources.push(upstreamRel(entryFile));
1323
+
1324
+ info(`JS 层导出 (模式: ${exportStyle}, 来源: 上游)...`);
1325
+ let jsInterfaces = [];
1326
+ if (exportStyle === 'commonjs') {
1327
+ jsInterfaces = extractCommonJSExports(entryContent, upstreamDir, entryFile);
1328
+ for (const i of jsInterfaces) i.source = upstreamRel(entryFile);
1329
+ }
1330
+ else if (exportStyle === 'es-module') {
1331
+ jsInterfaces = extractESModuleExports(entryContent, upstreamDir, entryFile);
1332
+ const entryRel = rel(entryFile, upstreamDir);
1333
+ for (const i of jsInterfaces) {
1334
+ if (i.source && !i.source.startsWith('upstream:') && i.source !== entryRel) {
1335
+ const fullSrc = `upstream:${upstreamInfo.pkgName}@${upstreamInfo.version}/${i.source.replace(/^\.\//, '')}`;
1336
+ i.source = fullSrc;
1337
+ if (!sources.includes(fullSrc)) sources.push(fullSrc);
1338
+ } else if (!i.source || i.source === entryRel) {
1339
+ i.source = upstreamRel(entryFile);
1340
+ }
1341
+ }
1342
+ }
1343
+ else if (exportStyle === 'side-effect') {
1344
+ jsInterfaces = extractSideEffectExports(entryContent, upstreamDir, entryFile);
1345
+ for (const i of jsInterfaces) i.source = upstreamRel(entryFile);
1346
+ }
1347
+ interfaces.push(...jsInterfaces);
1348
+ ok(`JS 层导出: ${jsInterfaces.length} 个接口`);
1349
+
1350
+ // 类型定义层 (index.d.ts) — 从上游源码提取
1351
+ info('提取类型定义 (index.d.ts, 来源: 上游)...');
1352
+ const typeDefs = extractTypeDefinitions(upstreamDir);
1353
+ if (typeDefs.found) {
1354
+ for (const src of typeDefs.allSources || [typeDefs.source]) {
1355
+ const fullSrc = `upstream:${upstreamInfo.pkgName}@${upstreamInfo.version}/${src.replace(/^\.\//, '')}`;
1356
+ if (!sources.includes(fullSrc)) sources.push(fullSrc);
1357
+ }
1358
+ interfaces.push(...typeDefs.interfaces);
1359
+ for (const i of typeDefs.interfaces) {
1360
+ const fullSrc = `upstream:${upstreamInfo.pkgName}@${upstreamInfo.version}/${(i.source || typeDefs.source).replace(/^\.\//, '')}`;
1361
+ i.source = fullSrc;
1362
+ }
1363
+ ok(`类型定义: ${typeDefs.interfaces.length} 个接口`);
1364
+ } else { info('上游源码未找到 index.d.ts,跳过类型定义提取'); }
1365
+
1366
+ // Fabric Component / TurboModule Spec — 从鸿蒙化仓库提取
1367
+ info('提取 Fabric Component / TurboModule Spec (来源: 鸿蒙化仓库)...');
1368
+ const fabricSpecs = extractFabricComponentSpecs(absDir);
1369
+ if (fabricSpecs.files.length > 0) {
1370
+ for (const f of fabricSpecs.files) {
1371
+ const fullSrc = `ohos:${f}`;
1372
+ if (!sources.includes(fullSrc)) sources.push(fullSrc);
1373
+ }
1374
+ for (const i of fabricSpecs.interfaces) { i.source = `ohos:${i.source}`; }
1375
+ interfaces.push(...fabricSpecs.interfaces);
1376
+ ok(`Fabric Component Spec: ${fabricSpecs.interfaces.length} 个接口`);
1377
+ } else { info('未找到 Fabric Component Spec 文件'); }
1378
+
1379
+ // TurboModule Spec 方法 — 从鸿蒙化仓库提取 (interface Spec extends TurboModule)
1380
+ info('提取 TurboModule Spec 方法 (来源: 鸿蒙化仓库)...');
1381
+ const turboSpecs = extractTurboModuleSpecs(absDir);
1382
+ if (turboSpecs.files.length > 0) {
1383
+ for (const f of turboSpecs.files) {
1384
+ const fullSrc = `ohos:${f}`;
1385
+ if (!sources.includes(fullSrc)) sources.push(fullSrc);
1386
+ }
1387
+ for (const i of turboSpecs.interfaces) { i.source = `ohos:${i.source}`; }
1388
+ interfaces.push(...turboSpecs.interfaces);
1389
+ ok(`TurboModule Spec: ${turboSpecs.interfaces.length} 个接口`);
1390
+ } else { info('未找到 TurboModule Spec 方法'); }
1391
+
1392
+ const seen = new Set();
1393
+ const deduped = interfaces.filter(i => {
1394
+ if (i.className === 'NativeSafeAreaProviderProps') return false;
1395
+ if (seen.has(i.fullName)) return false;
1396
+ seen.add(i.fullName);
1397
+ return true;
1398
+ });
1399
+
1400
+ const methods = deduped.filter(i => i.kind === 'method' || i.kind === 'function');
1401
+ const fields = deduped.filter(i => i.kind === 'field');
1402
+ const classSet = new Set(deduped.filter(i => i.className).map(i => i.className));
1403
+
1404
+ ok(`接口提取完成: ${deduped.length} 个接口, ${classSet.size} 个类/模块`);
1405
+ return {
1406
+ libraryName, libraryAlias,
1407
+ entryFile: entryFile ? upstreamRel(entryFile) : null,
1408
+ exportStyle,
1409
+ upstream: { pkgName: upstreamInfo.pkgName, version: upstreamInfo.version, isUpstream: true },
1410
+ extractedAt: new Date().toISOString(),
1411
+ summary: { S: classSet.size, N: deduped.length, M: 0, methods: methods.length, properties: 0, fields: fields.length },
1412
+ sources, interfaces: deduped,
1413
+ };
1414
+ }
1415
+
1416
+ function loadSpec(args) {
1417
+ const specPath = args.spec;
1418
+ const libRoot = args.libroot || args.repoDir || (specPath ? path.dirname(specPath) : '');
1419
+ let spec, specBasename;
1420
+ if (specPath) {
1421
+ spec = JSON.parse(fs.readFileSync(specPath, 'utf-8'));
1422
+ specBasename = spec.libraryName ? spec.libraryName.replace(/^@[^/]+\//, '') : path.basename(specPath, '-interface-spec.json');
1423
+ info(`读取接口规格: ${specPath} (${spec.interfaces ? spec.interfaces.length : 0} 个接口)`);
1424
+ } else if (libRoot) {
1425
+ info(`从源码目录提取接口: ${libRoot}`);
1426
+ spec = generateRNSpec(libRoot, { upstreamVersion: args.version });
1427
+ specBasename = spec.libraryName ? spec.libraryName.replace(/^@[^/]+\//, '') : path.basename(libRoot);
1428
+ ok(`提取完成: ${spec.interfaces ? spec.interfaces.length : 0} 个接口, 库名: ${spec.libraryName}`);
1429
+ } else return null;
1430
+ return { spec, libRoot, specBasename };
1431
+ }
1432
+
1433
+ // ════════════════════════════════════════════════════════════
1434
+ // 导出
1435
+ // ════════════════════════════════════════════════════════════
1436
+
1437
+ module.exports = {
1438
+ generateRNSpec, loadSpec,
1439
+ fetchUpstream, resolveUpstream,
1440
+ // 供 rn-scan-exports.cjs 等外部模块使用
1441
+ readText, rel, walkDir, readPackageJson, getLibraryAlias,
1442
+ detectEntryFile, findTurboModuleSpecFiles, resolveRelativePath, resolveRelativePathDts,
1443
+ stripComments, extractBalancedBraces, findSignatureEnd, normalizeSignature,
1444
+ extractMethodsFromInterfaceBody, extractExports, extractCJSExportNames, extractTurboModuleSpec,
1445
+ extractInterfaceMethods, extractClassMethods,
1446
+ extractClassAndInterfaceDefs, defsToInterfaces,
1447
+ extractFabricComponentSpecs,
1448
+ escapeRegex, escapeMd, formatParams, coverageBadge,
1449
+ parseArgs, loadTypeScript, info, ok, warn, error,
1450
+ };
1451
+
1452
+ // ── CLI 入口 ──────────────────────────────────────────────
1453
+
1454
+ if (require.main === module) {
1455
+ const args = parseArgs(process.argv.slice(2));
1456
+ const repoDir = args.repoDir || args.libroot || args._;
1457
+ if (!repoDir) { console.error('用法: node rn-extract-interfaces.cjs --repoDir <库根目录> [--output <spec.json>] [--version <上游版本>]'); process.exit(1); }
1458
+ const spec = generateRNSpec(repoDir, { upstreamVersion: args.version });
1459
+ const json = JSON.stringify(spec, null, 2);
1460
+ if (args.output) { fs.mkdirSync(path.dirname(args.output), { recursive: true }); fs.writeFileSync(args.output, json, 'utf-8'); ok(`接口规格已写入: ${args.output}`); }
1461
+ else console.log(json);
1462
+ }