dsh-crwu-workbench 0.0.10

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 (479) hide show
  1. package/CHANGELOG.md +1516 -0
  2. package/LICENSE +26 -0
  3. package/README.en.md +204 -0
  4. package/README.md +521 -0
  5. package/SECURITY.md +39 -0
  6. package/bin/darwin-arm64/crwu +0 -0
  7. package/bin/darwin-arm64/dws +0 -0
  8. package/bin/darwin-arm64/ossutil +0 -0
  9. package/bin/manifest.json +82 -0
  10. package/bin/win32-x64/crwu.exe +0 -0
  11. package/bin/win32-x64/dws.exe +4 -0
  12. package/bin/win32-x64/ossutil.exe +0 -0
  13. package/common/skills/.synced.json +8 -0
  14. package/common/skills/crwu-dws/SKILL.md +208 -0
  15. package/common/skills/crwu-dws/references/00-/347/233/256/345/275/225/345/277/253/347/205/247schema.md +113 -0
  16. package/common/skills/crwu-dws/references/01-/345/256/241/346/240/270/344/270/213/350/275/275/344/270/216manifest/350/247/204/350/214/203.md +115 -0
  17. package/common/skills/crwu-dws/references/02-/347/274/223/345/255/230/344/270/216/345/205/234/345/272/225/346/237/245/346/211/276/350/247/204/350/214/203.md +119 -0
  18. package/common/skills/crwu-dws/scripts/test_dws_source_contract.py +493 -0
  19. package/common/skills/crwu-h3yun-login/SKILL.md +130 -0
  20. package/common/skills/crwu-h3yun-query/SKILL.md +192 -0
  21. package/config/crwu-workbench.yml +26 -0
  22. package/cordis.patch.yml +46 -0
  23. package/lib/client.js +10346 -0
  24. package/lib/index.js +11584 -0
  25. package/package.json +119 -0
  26. package/scripts/prepare.mjs +59 -0
  27. package/skills/README.md +26 -0
  28. package/skills/crwu/crwu-audit/SKILL.md +152 -0
  29. package/skills/crwu/crwu-audit/references/00-input-and-route-profile.md +232 -0
  30. package/skills/crwu/crwu-audit/references/01-report-id-resolution.md +13 -0
  31. package/skills/crwu/crwu-audit/references/02-scope-classification.md +25 -0
  32. package/skills/crwu/crwu-audit/references/03-asset-classification.md +27 -0
  33. package/skills/crwu/crwu-audit/references/04-business-classification.md +63 -0
  34. package/skills/crwu/crwu-audit/references/05-method-classification.md +66 -0
  35. package/skills/crwu/crwu-audit/references/06-overlay-classification.md +42 -0
  36. package/skills/crwu/crwu-audit/references/07-skill-registry.md +57 -0
  37. package/skills/crwu/crwu-audit/references/08-union-dispatch-rules.md +142 -0
  38. package/skills/crwu/crwu-audit/references/09-review-risk-classification.md +85 -0
  39. package/skills/crwu/crwu-audit/references/10-capability-gap-proposal.md +68 -0
  40. package/skills/crwu/crwu-audit/references/11-html-delivery-spec.md +881 -0
  41. package/skills/crwu/crwu-audit/references/12-leaf-common-contract.md +202 -0
  42. package/skills/crwu/crwu-audit/references/13-dingtalk-result-publish.md +88 -0
  43. package/skills/crwu/crwu-audit/references/14-orchestration-workflow.md +52 -0
  44. package/skills/crwu/crwu-audit/references/99-maintenance.md +62 -0
  45. package/skills/crwu/crwu-audit/scripts/README.md +157 -0
  46. package/skills/crwu/crwu-audit/scripts/audit_delivery.py +2736 -0
  47. package/skills/crwu/crwu-audit/scripts/audit_result.schema.json +1659 -0
  48. package/skills/crwu/crwu-audit/scripts/examples/audit-result.sample.json +739 -0
  49. package/skills/crwu/crwu-audit/scripts/fetch_review_records.py +85 -0
  50. package/skills/crwu/crwu-audit/scripts/media_extract.py +513 -0
  51. package/skills/crwu/crwu-audit/scripts/prepare_materials.py +1252 -0
  52. package/skills/crwu/crwu-audit/scripts/test_audit_delivery.py +1678 -0
  53. package/skills/crwu/crwu-audit/scripts/test_audit_multiaxis_router.py +1339 -0
  54. package/skills/crwu/crwu-audit/scripts/test_fetch_review_records.py +77 -0
  55. package/skills/crwu/crwu-audit/scripts/test_media_extract.py +332 -0
  56. package/skills/crwu/crwu-audit/scripts/test_prepare_materials.py +1081 -0
  57. package/skills/crwu/crwu-audit/template/audit-report.html +435 -0
  58. package/skills/crwu/crwu-audit-asset-asset-group-goodwill/SKILL.md +38 -0
  59. package/skills/crwu/crwu-audit-asset-asset-group-goodwill/references/00-applicability.md +28 -0
  60. package/skills/crwu/crwu-audit-asset-asset-group-goodwill/references/01-kb-assembly.md +31 -0
  61. package/skills/crwu/crwu-audit-asset-asset-group-goodwill/references/02-review-focus.md +21 -0
  62. package/skills/crwu/crwu-audit-asset-asset-group-goodwill/references/03-common-contract.md +202 -0
  63. package/skills/crwu/crwu-audit-asset-debt/SKILL.md +38 -0
  64. package/skills/crwu/crwu-audit-asset-debt/references/00-applicability.md +40 -0
  65. package/skills/crwu/crwu-audit-asset-debt/references/01-kb-assembly.md +41 -0
  66. package/skills/crwu/crwu-audit-asset-debt/references/02-review-focus.md +54 -0
  67. package/skills/crwu/crwu-audit-asset-debt/references/03-common-contract.md +202 -0
  68. package/skills/crwu/crwu-audit-asset-enterprise-value/SKILL.md +38 -0
  69. package/skills/crwu/crwu-audit-asset-enterprise-value/references/00-applicability.md +31 -0
  70. package/skills/crwu/crwu-audit-asset-enterprise-value/references/01-kb-assembly.md +35 -0
  71. package/skills/crwu/crwu-audit-asset-enterprise-value/references/02-review-focus.md +33 -0
  72. package/skills/crwu/crwu-audit-asset-enterprise-value/references/03-common-contract.md +202 -0
  73. package/skills/crwu/crwu-audit-asset-equipment/SKILL.md +38 -0
  74. package/skills/crwu/crwu-audit-asset-equipment/references/00-applicability.md +40 -0
  75. package/skills/crwu/crwu-audit-asset-equipment/references/01-kb-assembly.md +39 -0
  76. package/skills/crwu/crwu-audit-asset-equipment/references/02-review-focus.md +52 -0
  77. package/skills/crwu/crwu-audit-asset-equipment/references/03-common-contract.md +202 -0
  78. package/skills/crwu/crwu-audit-asset-intangible/SKILL.md +38 -0
  79. package/skills/crwu/crwu-audit-asset-intangible/references/00-applicability.md +36 -0
  80. package/skills/crwu/crwu-audit-asset-intangible/references/01-kb-assembly.md +40 -0
  81. package/skills/crwu/crwu-audit-asset-intangible/references/02-review-focus.md +68 -0
  82. package/skills/crwu/crwu-audit-asset-intangible/references/03-common-contract.md +202 -0
  83. package/skills/crwu/crwu-audit-asset-inventory/SKILL.md +38 -0
  84. package/skills/crwu/crwu-audit-asset-inventory/references/00-applicability.md +28 -0
  85. package/skills/crwu/crwu-audit-asset-inventory/references/01-kb-assembly.md +31 -0
  86. package/skills/crwu/crwu-audit-asset-inventory/references/02-review-focus.md +21 -0
  87. package/skills/crwu/crwu-audit-asset-inventory/references/03-common-contract.md +202 -0
  88. package/skills/crwu/crwu-audit-asset-mining-right/SKILL.md +38 -0
  89. package/skills/crwu/crwu-audit-asset-mining-right/references/00-applicability.md +31 -0
  90. package/skills/crwu/crwu-audit-asset-mining-right/references/01-kb-assembly.md +35 -0
  91. package/skills/crwu/crwu-audit-asset-mining-right/references/02-review-focus.md +33 -0
  92. package/skills/crwu/crwu-audit-asset-mining-right/references/03-common-contract.md +202 -0
  93. package/skills/crwu/crwu-audit-asset-other/SKILL.md +38 -0
  94. package/skills/crwu/crwu-audit-asset-other/references/00-applicability.md +28 -0
  95. package/skills/crwu/crwu-audit-asset-other/references/01-kb-assembly.md +31 -0
  96. package/skills/crwu/crwu-audit-asset-other/references/02-review-focus.md +21 -0
  97. package/skills/crwu/crwu-audit-asset-other/references/03-common-contract.md +202 -0
  98. package/skills/crwu/crwu-audit-asset-portfolio/SKILL.md +38 -0
  99. package/skills/crwu/crwu-audit-asset-portfolio/references/00-applicability.md +28 -0
  100. package/skills/crwu/crwu-audit-asset-portfolio/references/01-kb-assembly.md +31 -0
  101. package/skills/crwu/crwu-audit-asset-portfolio/references/02-review-focus.md +21 -0
  102. package/skills/crwu/crwu-audit-asset-portfolio/references/03-common-contract.md +202 -0
  103. package/skills/crwu/crwu-audit-asset-realestate/SKILL.md +38 -0
  104. package/skills/crwu/crwu-audit-asset-realestate/references/00-applicability.md +43 -0
  105. package/skills/crwu/crwu-audit-asset-realestate/references/01-kb-assembly.md +75 -0
  106. package/skills/crwu/crwu-audit-asset-realestate/references/02-review-focus.md +93 -0
  107. package/skills/crwu/crwu-audit-asset-realestate/references/03-common-contract.md +202 -0
  108. package/skills/crwu/crwu-audit-asset-scrap-materials/SKILL.md +38 -0
  109. package/skills/crwu/crwu-audit-asset-scrap-materials/references/00-applicability.md +28 -0
  110. package/skills/crwu/crwu-audit-asset-scrap-materials/references/01-kb-assembly.md +31 -0
  111. package/skills/crwu/crwu-audit-asset-scrap-materials/references/02-review-focus.md +21 -0
  112. package/skills/crwu/crwu-audit-asset-scrap-materials/references/03-common-contract.md +202 -0
  113. package/skills/crwu/crwu-audit-asset-transport-equipment/SKILL.md +38 -0
  114. package/skills/crwu/crwu-audit-asset-transport-equipment/references/00-applicability.md +31 -0
  115. package/skills/crwu/crwu-audit-asset-transport-equipment/references/01-kb-assembly.md +35 -0
  116. package/skills/crwu/crwu-audit-asset-transport-equipment/references/02-review-focus.md +33 -0
  117. package/skills/crwu/crwu-audit-asset-transport-equipment/references/03-common-contract.md +202 -0
  118. package/skills/crwu/crwu-audit-biz-asset-operation/SKILL.md +38 -0
  119. package/skills/crwu/crwu-audit-biz-asset-operation/references/00-applicability.md +30 -0
  120. package/skills/crwu/crwu-audit-biz-asset-operation/references/01-kb-assembly.md +32 -0
  121. package/skills/crwu/crwu-audit-biz-asset-operation/references/02-review-focus.md +42 -0
  122. package/skills/crwu/crwu-audit-biz-asset-operation/references/03-common-contract.md +202 -0
  123. package/skills/crwu/crwu-audit-biz-consulting-review/SKILL.md +38 -0
  124. package/skills/crwu/crwu-audit-biz-consulting-review/references/00-applicability.md +30 -0
  125. package/skills/crwu/crwu-audit-biz-consulting-review/references/01-kb-assembly.md +32 -0
  126. package/skills/crwu/crwu-audit-biz-consulting-review/references/02-review-focus.md +40 -0
  127. package/skills/crwu/crwu-audit-biz-consulting-review/references/03-common-contract.md +202 -0
  128. package/skills/crwu/crwu-audit-biz-financial-reporting/SKILL.md +38 -0
  129. package/skills/crwu/crwu-audit-biz-financial-reporting/references/00-applicability.md +41 -0
  130. package/skills/crwu/crwu-audit-biz-financial-reporting/references/01-kb-assembly.md +40 -0
  131. package/skills/crwu/crwu-audit-biz-financial-reporting/references/02-review-focus.md +64 -0
  132. package/skills/crwu/crwu-audit-biz-financial-reporting/references/03-common-contract.md +202 -0
  133. package/skills/crwu/crwu-audit-biz-financing-debt/SKILL.md +38 -0
  134. package/skills/crwu/crwu-audit-biz-financing-debt/references/00-applicability.md +31 -0
  135. package/skills/crwu/crwu-audit-biz-financing-debt/references/01-kb-assembly.md +34 -0
  136. package/skills/crwu/crwu-audit-biz-financing-debt/references/02-review-focus.md +47 -0
  137. package/skills/crwu/crwu-audit-biz-financing-debt/references/03-common-contract.md +202 -0
  138. package/skills/crwu/crwu-audit-biz-investment-capital/SKILL.md +38 -0
  139. package/skills/crwu/crwu-audit-biz-investment-capital/references/00-applicability.md +31 -0
  140. package/skills/crwu/crwu-audit-biz-investment-capital/references/01-kb-assembly.md +34 -0
  141. package/skills/crwu/crwu-audit-biz-investment-capital/references/02-review-focus.md +47 -0
  142. package/skills/crwu/crwu-audit-biz-investment-capital/references/03-common-contract.md +202 -0
  143. package/skills/crwu/crwu-audit-biz-judicial-liquidation-compensation/SKILL.md +38 -0
  144. package/skills/crwu/crwu-audit-biz-judicial-liquidation-compensation/references/00-applicability.md +30 -0
  145. package/skills/crwu/crwu-audit-biz-judicial-liquidation-compensation/references/01-kb-assembly.md +32 -0
  146. package/skills/crwu/crwu-audit-biz-judicial-liquidation-compensation/references/02-review-focus.md +40 -0
  147. package/skills/crwu/crwu-audit-biz-judicial-liquidation-compensation/references/03-common-contract.md +202 -0
  148. package/skills/crwu/crwu-audit-biz-tax-history/SKILL.md +38 -0
  149. package/skills/crwu/crwu-audit-biz-tax-history/references/00-applicability.md +29 -0
  150. package/skills/crwu/crwu-audit-biz-tax-history/references/01-kb-assembly.md +30 -0
  151. package/skills/crwu/crwu-audit-biz-tax-history/references/02-review-focus.md +33 -0
  152. package/skills/crwu/crwu-audit-biz-tax-history/references/03-common-contract.md +202 -0
  153. package/skills/crwu/crwu-audit-biz-transaction-disposal/SKILL.md +38 -0
  154. package/skills/crwu/crwu-audit-biz-transaction-disposal/references/00-applicability.md +31 -0
  155. package/skills/crwu/crwu-audit-biz-transaction-disposal/references/01-kb-assembly.md +34 -0
  156. package/skills/crwu/crwu-audit-biz-transaction-disposal/references/02-review-focus.md +47 -0
  157. package/skills/crwu/crwu-audit-biz-transaction-disposal/references/03-common-contract.md +202 -0
  158. package/skills/crwu/crwu-audit-datacheck/SKILL.md +59 -0
  159. package/skills/crwu/crwu-audit-datacheck/references/00-KB/350/243/205/351/205/215/350/241/250.md +25 -0
  160. package/skills/crwu/crwu-audit-datacheck/references/01-runtime-and-check-catalog.md +139 -0
  161. package/skills/crwu/crwu-audit-datacheck/scripts/recalc_check.py +521 -0
  162. package/skills/crwu/crwu-audit-datacheck/scripts/test_recalc_check.py +419 -0
  163. package/skills/crwu/crwu-audit-datacheck/scripts/test_skill_self_containment.py +44 -0
  164. package/skills/crwu/crwu-audit-external-data/SKILL.md +73 -0
  165. package/skills/crwu/crwu-audit-external-data/references/00-KB/350/243/205/351/205/215/350/241/250.md +38 -0
  166. package/skills/crwu/crwu-audit-external-data/references/01-connector-access.md +116 -0
  167. package/skills/crwu/crwu-audit-external-data/references/02-verification-workflow.md +78 -0
  168. package/skills/crwu/crwu-audit-external-data/scripts/connector_probe.py +374 -0
  169. package/skills/crwu/crwu-audit-external-data/scripts/test_connector_probe.py +264 -0
  170. package/skills/crwu/crwu-audit-output-filter/SKILL.md +34 -0
  171. package/skills/crwu/crwu-audit-output-filter/references/00-applicability.md +7 -0
  172. package/skills/crwu/crwu-audit-output-filter/references/01-kb-assembly.md +11 -0
  173. package/skills/crwu/crwu-audit-output-filter/references/02-review-focus.md +25 -0
  174. package/skills/crwu/crwu-audit-public-general-standards/SKILL.md +46 -0
  175. package/skills/crwu/crwu-audit-public-general-standards/references/00-applicability.md +41 -0
  176. package/skills/crwu/crwu-audit-public-general-standards/references/01-kb-assembly.md +66 -0
  177. package/skills/crwu/crwu-audit-public-general-standards/references/02-review-focus.md +111 -0
  178. package/skills/crwu/crwu-audit-public-general-standards/references/03-execution-contract.md +89 -0
  179. package/skills/crwu/crwu-dev-audit-optimize/SKILL.md +98 -0
  180. package/skills/crwu/crwu-dev-audit-optimize/references/00-/344/274/230/345/214/226/350/247/204/350/214/203/344/270/216/346/226/207/344/273/266/350/220/275/347/202/271.md +132 -0
  181. package/skills/crwu/crwu-dev-audit-optimize/references/01-/345/217/215/351/246/210/345/256/232/344/275/215/344/270/216/347/224/273/345/203/217/346/265/201/347/250/213.md +108 -0
  182. package/skills/crwu/crwu-dev-audit-optimize/references/02-/346/226/271/346/241/210/346/250/241/346/235/277/344/270/216/347/241/256/350/256/244/351/227/250/347/246/201.md +92 -0
  183. package/skills/crwu/crwu-dev-audit-optimize/references/03-AI/344/272/272/345/267/245/345/267/256/350/267/235/345/210/206/346/236/220/346/265/201/347/250/213.md +95 -0
  184. package/skills/crwu/crwu-dev-audit-optimize/scripts/examples/gap-analysis.sample.json +116 -0
  185. package/skills/crwu/crwu-dev-audit-optimize/scripts/gap_analysis.schema.json +33 -0
  186. package/skills/crwu/crwu-dev-audit-optimize/scripts/gap_analysis_delivery.py +475 -0
  187. package/skills/crwu/crwu-dev-audit-optimize/scripts/test_gap_analysis_delivery.py +855 -0
  188. package/skills/crwu/crwu-dev-audit-optimize/template/NOTICE.echarts.txt +7 -0
  189. package/skills/crwu/crwu-dev-audit-optimize/template/echarts.min.js +45 -0
  190. package/skills/crwu/crwu-dev-audit-optimize/template/gap-analysis-report.html +71 -0
  191. package/skills/crwu/crwu-dev-audit-skill-maintainer/SKILL.md +74 -0
  192. package/skills/crwu/crwu-dev-audit-skill-maintainer/references/00-responsibility-and-modes.md +62 -0
  193. package/skills/crwu/crwu-dev-audit-skill-maintainer/references/01-kb-source-discovery.md +89 -0
  194. package/skills/crwu/crwu-dev-audit-skill-maintainer/references/02-child-skill-contract.md +99 -0
  195. package/skills/crwu/crwu-dev-audit-skill-maintainer/references/03-review-item-execution-contract.md +51 -0
  196. package/skills/crwu/crwu-dev-audit-skill-maintainer/references/04-registry-and-mapping-update.md +73 -0
  197. package/skills/crwu/crwu-dev-audit-skill-maintainer/references/05-validation-and-delivery.md +127 -0
  198. package/skills/crwu/crwu-dev-audit-skill-maintainer/references/06-live-routing-reconciliation.md +136 -0
  199. package/skills/crwu/crwu-dev-audit-skill-maintainer/references/07-kb-skill-map.md +411 -0
  200. package/skills/crwu/crwu-dev-audit-skill-maintainer/scripts/README.md +200 -0
  201. package/skills/crwu/crwu-dev-audit-skill-maintainer/scripts/check_audit_skill_mappings.py +2477 -0
  202. package/skills/crwu/crwu-dev-audit-skill-maintainer/scripts/kb_tool.py +577 -0
  203. package/skills/crwu/crwu-dev-audit-skill-maintainer/scripts/test_audit_skill_maintainer.py +5097 -0
  204. package/skills/dws/LICENSE +178 -0
  205. package/skills/dws/NOTICE +17 -0
  206. package/skills/dws/dingtalk-aisearch/SKILL.md +83 -0
  207. package/skills/dws/dingtalk-aisearch/references/aisearch.md +249 -0
  208. package/skills/dws/dingtalk-aisearch/references/intent-guide.md +15 -0
  209. package/skills/dws/dingtalk-aisearch/references/lite-recipes.md +31 -0
  210. package/skills/dws/dingtalk-aitable/SKILL.md +139 -0
  211. package/skills/dws/dingtalk-aitable/references/06-data-analytics.md +11 -0
  212. package/skills/dws/dingtalk-aitable/references/aitable/aitable-advperm.md +251 -0
  213. package/skills/dws/dingtalk-aitable/references/aitable/aitable-attachment.md +49 -0
  214. package/skills/dws/dingtalk-aitable/references/aitable/aitable-best-practices.md +48 -0
  215. package/skills/dws/dingtalk-aitable/references/aitable/aitable-cell-value.md +346 -0
  216. package/skills/dws/dingtalk-aitable/references/aitable/aitable-dashboard-chart.md +46 -0
  217. package/skills/dws/dingtalk-aitable/references/aitable/aitable-data-analysis-sop.md +131 -0
  218. package/skills/dws/dingtalk-aitable/references/aitable/aitable-datasource.md +272 -0
  219. package/skills/dws/dingtalk-aitable/references/aitable/aitable-error-recovery.md +128 -0
  220. package/skills/dws/dingtalk-aitable/references/aitable/aitable-export-import.md +113 -0
  221. package/skills/dws/dingtalk-aitable/references/aitable/aitable-field-properties.md +238 -0
  222. package/skills/dws/dingtalk-aitable/references/aitable/aitable-field.md +186 -0
  223. package/skills/dws/dingtalk-aitable/references/aitable/aitable-filter-sort.md +135 -0
  224. package/skills/dws/dingtalk-aitable/references/aitable/aitable-form.md +130 -0
  225. package/skills/dws/dingtalk-aitable/references/aitable/aitable-formula-guide.md +224 -0
  226. package/skills/dws/dingtalk-aitable/references/aitable/aitable-primary-doc.md +58 -0
  227. package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-create.md +52 -0
  228. package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-delete.md +20 -0
  229. package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-history.md +97 -0
  230. package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-name-key.md +47 -0
  231. package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-query.md +120 -0
  232. package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-share.md +54 -0
  233. package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-stats.md +59 -0
  234. package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-update.md +66 -0
  235. package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-upsert.md +89 -0
  236. package/skills/dws/dingtalk-aitable/references/aitable/aitable-view-config.md +140 -0
  237. package/skills/dws/dingtalk-aitable/references/aitable/aitable-view-extras.md +195 -0
  238. package/skills/dws/dingtalk-aitable/references/aitable/aitable-view-types.md +95 -0
  239. package/skills/dws/dingtalk-aitable/references/aitable/aitable-workflow.md +384 -0
  240. package/skills/dws/dingtalk-aitable/references/aitable-record-ops.md +80 -0
  241. package/skills/dws/dingtalk-aitable/references/aitable-section.md +30 -0
  242. package/skills/dws/dingtalk-aitable/references/aitable.md +59 -0
  243. package/skills/dws/dingtalk-aitable/references/field-rules.md +17 -0
  244. package/skills/dws/dingtalk-aitable/references/intent-guide.md +17 -0
  245. package/skills/dws/dingtalk-aitable/references/recipes/conventions.md +44 -0
  246. package/skills/dws/dingtalk-aitable/references/url-patterns.md +144 -0
  247. package/skills/dws/dingtalk-aitable/scripts/aitable_export_via_task.py +210 -0
  248. package/skills/dws/dingtalk-aitable/scripts/aitable_import_via_task.py +173 -0
  249. package/skills/dws/dingtalk-aitable/scripts/bulk_add_fields.py +273 -0
  250. package/skills/dws/dingtalk-aitable/scripts/import_records.py +333 -0
  251. package/skills/dws/dingtalk-aitable/scripts/upload_attachment.py +190 -0
  252. package/skills/dws/dingtalk-calendar/SKILL.md +137 -0
  253. package/skills/dws/dingtalk-calendar/references/03-meeting.md +56 -0
  254. package/skills/dws/dingtalk-calendar/references/calendar.md +760 -0
  255. package/skills/dws/dingtalk-calendar/references/intent-guide.md +9 -0
  256. package/skills/dws/dingtalk-calendar/references/lite-recipes.md +25 -0
  257. package/skills/dws/dingtalk-calendar/scripts/calendar_free_slot_finder.py +199 -0
  258. package/skills/dws/dingtalk-calendar/scripts/calendar_schedule_meeting.py +233 -0
  259. package/skills/dws/dingtalk-calendar/scripts/calendar_today_agenda.py +139 -0
  260. package/skills/dws/dingtalk-chat/SKILL.md +127 -0
  261. package/skills/dws/dingtalk-chat/references/01-messaging.md +103 -0
  262. package/skills/dws/dingtalk-chat/references/card/callback.md +10 -0
  263. package/skills/dws/dingtalk-chat/references/card/create.md +27 -0
  264. package/skills/dws/dingtalk-chat/references/card/schema.md +14 -0
  265. package/skills/dws/dingtalk-chat/references/card/update.md +13 -0
  266. package/skills/dws/dingtalk-chat/references/chat/chat-bot.md +187 -0
  267. package/skills/dws/dingtalk-chat/references/chat/chat-conversation.md +165 -0
  268. package/skills/dws/dingtalk-chat/references/chat/group-admin.md +144 -0
  269. package/skills/dws/dingtalk-chat/references/chat/group-discovery.md +121 -0
  270. package/skills/dws/dingtalk-chat/references/chat/message-actions.md +132 -0
  271. package/skills/dws/dingtalk-chat/references/chat/message-media.md +76 -0
  272. package/skills/dws/dingtalk-chat/references/chat/message-query.md +135 -0
  273. package/skills/dws/dingtalk-chat/references/chat/thread.md +46 -0
  274. package/skills/dws/dingtalk-chat/references/chat-emoji-list.md +216 -0
  275. package/skills/dws/dingtalk-chat/references/chat.md +140 -0
  276. package/skills/dws/dingtalk-chat/references/contracts.md +70 -0
  277. package/skills/dws/dingtalk-chat/references/intent-guide.md +56 -0
  278. package/skills/dws/dingtalk-contact/SKILL.md +122 -0
  279. package/skills/dws/dingtalk-contact/references/08-directory.md +51 -0
  280. package/skills/dws/dingtalk-contact/references/contact.md +595 -0
  281. package/skills/dws/dingtalk-contact/references/intent-guide.md +14 -0
  282. package/skills/dws/dingtalk-contact/references/lite-recipes.md +30 -0
  283. package/skills/dws/dingtalk-contact/references/recipes/conventions.md +44 -0
  284. package/skills/dws/dingtalk-contact/scripts/contact_dept_members.py +130 -0
  285. package/skills/dws/dingtalk-doc/SKILL.md +112 -0
  286. package/skills/dws/dingtalk-doc/references/04-document.md +57 -0
  287. package/skills/dws/dingtalk-doc/references/contracts.md +40 -0
  288. package/skills/dws/dingtalk-doc/references/doc/doc-block.md +39 -0
  289. package/skills/dws/dingtalk-doc/references/doc/doc-comment.md +29 -0
  290. package/skills/dws/dingtalk-doc/references/doc/doc-create.md +54 -0
  291. package/skills/dws/dingtalk-doc/references/doc/doc-export.md +30 -0
  292. package/skills/dws/dingtalk-doc/references/doc/doc-import.md +28 -0
  293. package/skills/dws/dingtalk-doc/references/doc/doc-info.md +12 -0
  294. package/skills/dws/dingtalk-doc/references/doc/doc-media.md +46 -0
  295. package/skills/dws/dingtalk-doc/references/doc/doc-read.md +56 -0
  296. package/skills/dws/dingtalk-doc/references/doc/doc-update.md +87 -0
  297. package/skills/dws/dingtalk-doc/references/doc/format/doc-jsonml-cookbook.md +469 -0
  298. package/skills/dws/dingtalk-doc/references/doc/format/doc-jsonml-schema.json +526 -0
  299. package/skills/dws/dingtalk-doc/references/doc/format/doc-jsonml-schema.md +711 -0
  300. package/skills/dws/dingtalk-doc/references/doc/style/doc-create-workflow.md +414 -0
  301. package/skills/dws/dingtalk-doc/references/doc/style/doc-style-guideline.md +404 -0
  302. package/skills/dws/dingtalk-doc/references/doc/style/doc-update-workflow.md +316 -0
  303. package/skills/dws/dingtalk-doc/references/doc.md +82 -0
  304. package/skills/dws/dingtalk-doc/references/intent-guide.md +20 -0
  305. package/skills/dws/dingtalk-drive/SKILL.md +128 -0
  306. package/skills/dws/dingtalk-drive/references/drive.md +126 -0
  307. package/skills/dws/dingtalk-drive/references/folder-sync.md +40 -0
  308. package/skills/dws/dingtalk-drive/references/intent-guide.md +22 -0
  309. package/skills/dws/dingtalk-drive/references/lite-recipes.md +39 -0
  310. package/skills/dws/dingtalk-event/SKILL.md +106 -0
  311. package/skills/dws/dingtalk-event/references/event-im-keys.md +54 -0
  312. package/skills/dws/dingtalk-event/references/event-im-lifecycle.md +35 -0
  313. package/skills/dws/dingtalk-event/references/event-im-operations.md +52 -0
  314. package/skills/dws/dingtalk-event/references/event-im-output.md +39 -0
  315. package/skills/dws/dingtalk-event/references/event-im.md +58 -0
  316. package/skills/dws/dingtalk-event/references/event-oa.md +155 -0
  317. package/skills/dws/dingtalk-event/references/event-todo.md +50 -0
  318. package/skills/dws/dingtalk-event/references/event-voip.md +69 -0
  319. package/skills/dws/dingtalk-mail/SKILL.md +113 -0
  320. package/skills/dws/dingtalk-mail/references/09-mail.md +45 -0
  321. package/skills/dws/dingtalk-mail/references/intent-guide.md +8 -0
  322. package/skills/dws/dingtalk-mail/references/lite-recipes.md +45 -0
  323. package/skills/dws/dingtalk-mail/references/mail.md +1889 -0
  324. package/skills/dws/dingtalk-mail/references/recipes/conventions.md +44 -0
  325. package/skills/dws/dingtalk-mail/scripts/mail_send_with_cc.py +122 -0
  326. package/skills/dws/dingtalk-mail/scripts/mail_unread_summary.py +114 -0
  327. package/skills/dws/dingtalk-minutes/SKILL.md +116 -0
  328. package/skills/dws/dingtalk-minutes/references/07-minutes.md +149 -0
  329. package/skills/dws/dingtalk-minutes/references/10-minutes-speaker-match.md +32 -0
  330. package/skills/dws/dingtalk-minutes/references/11-minutes-speaker-correct.md +40 -0
  331. package/skills/dws/dingtalk-minutes/references/intent-guide.md +79 -0
  332. package/skills/dws/dingtalk-minutes/references/lite-recipes.md +25 -0
  333. package/skills/dws/dingtalk-minutes/references/minutes.md +210 -0
  334. package/skills/dws/dingtalk-minutes/references/recipes/conventions.md +33 -0
  335. package/skills/dws/dingtalk-minutes/scripts/minutes_recent_summary.py +196 -0
  336. package/skills/dws/dingtalk-minutes/scripts/test_minutes_scripts.py +222 -0
  337. package/skills/dws/dingtalk-misc/SKILL.md +55 -0
  338. package/skills/dws/dingtalk-misc/references/05-reporting.md +29 -0
  339. package/skills/dws/dingtalk-misc/references/agoal.md +302 -0
  340. package/skills/dws/dingtalk-misc/references/attendance-intent-guide.md +7 -0
  341. package/skills/dws/dingtalk-misc/references/attendance-report.md +109 -0
  342. package/skills/dws/dingtalk-misc/references/attendance-schedule.md +115 -0
  343. package/skills/dws/dingtalk-misc/references/attendance-vacation.md +129 -0
  344. package/skills/dws/dingtalk-misc/references/attendance.md +149 -0
  345. package/skills/dws/dingtalk-misc/references/dev/app.md +42 -0
  346. package/skills/dws/dingtalk-misc/references/dev/connect.md +108 -0
  347. package/skills/dws/dingtalk-misc/references/dev/credentials.md +26 -0
  348. package/skills/dws/dingtalk-misc/references/dev/dev-index.md +13 -0
  349. package/skills/dws/dingtalk-misc/references/dev/event.md +47 -0
  350. package/skills/dws/dingtalk-misc/references/dev/mcp.md +106 -0
  351. package/skills/dws/dingtalk-misc/references/dev/member.md +19 -0
  352. package/skills/dws/dingtalk-misc/references/dev/permission.md +56 -0
  353. package/skills/dws/dingtalk-misc/references/dev/recipes.md +58 -0
  354. package/skills/dws/dingtalk-misc/references/dev/robot.md +74 -0
  355. package/skills/dws/dingtalk-misc/references/dev/security.md +21 -0
  356. package/skills/dws/dingtalk-misc/references/dev/version.md +99 -0
  357. package/skills/dws/dingtalk-misc/references/dev/webapp.md +22 -0
  358. package/skills/dws/dingtalk-misc/references/devapp.md +148 -0
  359. package/skills/dws/dingtalk-misc/references/devdoc-intent-guide.md +7 -0
  360. package/skills/dws/dingtalk-misc/references/devdoc.md +42 -0
  361. package/skills/dws/dingtalk-misc/references/ding.md +184 -0
  362. package/skills/dws/dingtalk-misc/references/hrbrain.md +67 -0
  363. package/skills/dws/dingtalk-misc/references/live.md +23 -0
  364. package/skills/dws/dingtalk-misc/references/markdown.md +167 -0
  365. package/skills/dws/dingtalk-misc/references/oa/oa-form-components.md +345 -0
  366. package/skills/dws/dingtalk-misc/references/oa/oa-process-nodes.md +374 -0
  367. package/skills/dws/dingtalk-misc/references/oa.md +1014 -0
  368. package/skills/dws/dingtalk-misc/references/openapi-explorer.md +47 -0
  369. package/skills/dws/dingtalk-misc/references/pat.md +52 -0
  370. package/skills/dws/dingtalk-misc/references/profile.md +41 -0
  371. package/skills/dws/dingtalk-misc/references/recruit.md +89 -0
  372. package/skills/dws/dingtalk-misc/references/report-conventions.md +44 -0
  373. package/skills/dws/dingtalk-misc/references/report-intent-guide.md +11 -0
  374. package/skills/dws/dingtalk-misc/references/report-lite-recipes.md +25 -0
  375. package/skills/dws/dingtalk-misc/references/report.md +109 -0
  376. package/skills/dws/dingtalk-misc/references/sheet/sheet-batch-operations.md +104 -0
  377. package/skills/dws/dingtalk-misc/references/sheet/sheet-chart.md +69 -0
  378. package/skills/dws/dingtalk-misc/references/sheet/sheet-comment.md +166 -0
  379. package/skills/dws/dingtalk-misc/references/sheet/sheet-conditional-format.md +59 -0
  380. package/skills/dws/dingtalk-misc/references/sheet/sheet-dimension-operations.md +60 -0
  381. package/skills/dws/dingtalk-misc/references/sheet/sheet-dropdown.md +103 -0
  382. package/skills/dws/dingtalk-misc/references/sheet/sheet-export.md +135 -0
  383. package/skills/dws/dingtalk-misc/references/sheet/sheet-filter-view.md +51 -0
  384. package/skills/dws/dingtalk-misc/references/sheet/sheet-filter.md +181 -0
  385. package/skills/dws/dingtalk-misc/references/sheet/sheet-formula.md +192 -0
  386. package/skills/dws/dingtalk-misc/references/sheet/sheet-import.md +76 -0
  387. package/skills/dws/dingtalk-misc/references/sheet/sheet-media-image.md +263 -0
  388. package/skills/dws/dingtalk-misc/references/sheet/sheet-pivot-table.md +292 -0
  389. package/skills/dws/dingtalk-misc/references/sheet/sheet-range-operations.md +161 -0
  390. package/skills/dws/dingtalk-misc/references/sheet/sheet-read-data.md +60 -0
  391. package/skills/dws/dingtalk-misc/references/sheet/sheet-revision-changeset.md +46 -0
  392. package/skills/dws/dingtalk-misc/references/sheet/sheet-search-replace.md +120 -0
  393. package/skills/dws/dingtalk-misc/references/sheet/sheet-style-format.md +71 -0
  394. package/skills/dws/dingtalk-misc/references/sheet/sheet-version.md +84 -0
  395. package/skills/dws/dingtalk-misc/references/sheet/sheet-workbook.md +59 -0
  396. package/skills/dws/dingtalk-misc/references/sheet/sheet-write-data.md +93 -0
  397. package/skills/dws/dingtalk-misc/references/sheet-conventions.md +44 -0
  398. package/skills/dws/dingtalk-misc/references/sheet-intent-guide.md +12 -0
  399. package/skills/dws/dingtalk-misc/references/sheet.md +166 -0
  400. package/skills/dws/dingtalk-misc/references/skill.md +98 -0
  401. package/skills/dws/dingtalk-misc/references/unsupported-scripts.md +49 -0
  402. package/skills/dws/dingtalk-misc/references/url-patterns.md +144 -0
  403. package/skills/dws/dingtalk-misc/references/whiteboard/compose.md +200 -0
  404. package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/01-overview.md +57 -0
  405. package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/02-query.md +288 -0
  406. package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/03-update.md +249 -0
  407. package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/04-text-style.md +395 -0
  408. package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/05-shape-frame-group-connector.md +186 -0
  409. package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/06-vector-icon-path.md +313 -0
  410. package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/07-examples-errors-write-support.md +270 -0
  411. package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/08-catalogs.md +61 -0
  412. package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1.md +54 -0
  413. package/skills/dws/dingtalk-misc/references/whiteboard/replace.md +41 -0
  414. package/skills/dws/dingtalk-misc/references/whiteboard/vector.md +51 -0
  415. package/skills/dws/dingtalk-misc/references/whiteboard.md +115 -0
  416. package/skills/dws/dingtalk-misc/scripts/aiapp_create_and_poll.py +138 -0
  417. package/skills/dws/dingtalk-misc/scripts/attendance_my_record.py +91 -0
  418. package/skills/dws/dingtalk-misc/scripts/attendance_report_checkin.py +440 -0
  419. package/skills/dws/dingtalk-misc/scripts/attendance_report_common.py +1698 -0
  420. package/skills/dws/dingtalk-misc/scripts/attendance_report_daily.py +558 -0
  421. package/skills/dws/dingtalk-misc/scripts/attendance_report_detail.py +809 -0
  422. package/skills/dws/dingtalk-misc/scripts/attendance_report_monthly.py +758 -0
  423. package/skills/dws/dingtalk-misc/scripts/attendance_report_record.py +947 -0
  424. package/skills/dws/dingtalk-misc/scripts/attendance_schedule_export.py +344 -0
  425. package/skills/dws/dingtalk-misc/scripts/attendance_schedule_import.py +498 -0
  426. package/skills/dws/dingtalk-misc/scripts/attendance_team_shift.py +89 -0
  427. package/skills/dws/dingtalk-misc/scripts/attendance_vacation_balance.py +617 -0
  428. package/skills/dws/dingtalk-misc/scripts/finance_daily_cashflow.py +70 -0
  429. package/skills/dws/dingtalk-misc/scripts/finance_expense_flow.py +138 -0
  430. package/skills/dws/dingtalk-misc/scripts/oa_batch_approve.py +158 -0
  431. package/skills/dws/dingtalk-misc/scripts/oa_pending_review.py +132 -0
  432. package/skills/dws/dingtalk-misc/scripts/report_received_today.py +315 -0
  433. package/skills/dws/dingtalk-misc/scripts/yida_custom_page_update.py +401 -0
  434. package/skills/dws/dingtalk-misc/scripts/yida_form_builder.py +526 -0
  435. package/skills/dws/dingtalk-misc/scripts/yida_form_fields.py +315 -0
  436. package/skills/dws/dingtalk-misc/scripts/yida_form_inspector.py +311 -0
  437. package/skills/dws/dingtalk-misc/scripts/yida_form_update.py +185 -0
  438. package/skills/dws/dingtalk-misc/scripts/yida_jsx_pipeline.py +1588 -0
  439. package/skills/dws/dingtalk-misc/scripts/yida_page_compiler.py +1441 -0
  440. package/skills/dws/dingtalk-misc/scripts/yida_page_generate.py +514 -0
  441. package/skills/dws/dingtalk-misc/scripts/yida_page_schema.py +519 -0
  442. package/skills/dws/dingtalk-misc/scripts/yida_page_self_check.py +398 -0
  443. package/skills/dws/dingtalk-misc/scripts/yida_process_flow.py +1478 -0
  444. package/skills/dws/dingtalk-misc/scripts/yida_process_update.py +589 -0
  445. package/skills/dws/dingtalk-misc/scripts/yida_report_builder.py +494 -0
  446. package/skills/dws/dingtalk-misc/scripts/yida_report_charts.py +1434 -0
  447. package/skills/dws/dingtalk-misc/scripts/yida_report_update.py +244 -0
  448. package/skills/dws/dingtalk-misc/scripts/yida_schema_common.py +340 -0
  449. package/skills/dws/dingtalk-shared/SKILL.md +83 -0
  450. package/skills/dws/dingtalk-shared/references/capability-limits.md +31 -0
  451. package/skills/dws/dingtalk-shared/references/channel-login.md +26 -0
  452. package/skills/dws/dingtalk-shared/references/confirmation.md +59 -0
  453. package/skills/dws/dingtalk-shared/references/error-codes.md +155 -0
  454. package/skills/dws/dingtalk-shared/references/global-reference.md +122 -0
  455. package/skills/dws/dingtalk-shared/references/intent-guide.md +537 -0
  456. package/skills/dws/dingtalk-shared/references/recipes/conventions.md +45 -0
  457. package/skills/dws/dingtalk-shared/references/recipes/lite-catalog.md +275 -0
  458. package/skills/dws/dingtalk-shared/references/recipes/meta.md +67 -0
  459. package/skills/dws/dingtalk-shared/references/routing.md +52 -0
  460. package/skills/dws/dingtalk-shared/references/runtime-contract.md +11 -0
  461. package/skills/dws/dingtalk-shared/references/schema-usage.md +88 -0
  462. package/skills/dws/dingtalk-shared/references/url-patterns.md +144 -0
  463. package/skills/dws/dingtalk-shared/references/workflow-routing.md +45 -0
  464. package/skills/dws/dingtalk-todo/SKILL.md +119 -0
  465. package/skills/dws/dingtalk-todo/references/02-task.md +70 -0
  466. package/skills/dws/dingtalk-todo/references/intent-guide.md +15 -0
  467. package/skills/dws/dingtalk-todo/references/lite-recipes.md +68 -0
  468. package/skills/dws/dingtalk-todo/references/recipes/conventions.md +44 -0
  469. package/skills/dws/dingtalk-todo/references/todo.md +517 -0
  470. package/skills/dws/dingtalk-todo/scripts/todo_batch_create.py +354 -0
  471. package/skills/dws/dingtalk-todo/scripts/todo_daily_summary.py +153 -0
  472. package/skills/dws/dingtalk-todo/scripts/todo_overdue_check.py +101 -0
  473. package/skills/dws/dingtalk-wiki/SKILL.md +117 -0
  474. package/skills/dws/dingtalk-wiki/references/intent-guide.md +14 -0
  475. package/skills/dws/dingtalk-wiki/references/lite-recipes.md +44 -0
  476. package/skills/dws/dingtalk-wiki/references/wiki-members.md +32 -0
  477. package/skills/dws/dingtalk-wiki/references/wiki-node-ops.md +46 -0
  478. package/skills/dws/dingtalk-wiki/references/wiki.md +65 -0
  479. package/skills/dws/provenance.json +27 -0
@@ -0,0 +1,1441 @@
1
+ #!/usr/bin/env python3
2
+ """yida_page_compiler.py — 纯 Python 宜搭自定义页面编译管线。
3
+
4
+ 两块能力按 region 分段维护:
5
+ 1) [region: page_compat] 现代 JSX (export default + useState/useEffect)
6
+ → 宜搭 runtime 兼容格式 (export function renderJsx
7
+ + _customState + didMount/didUnmount)
8
+ 2) [region: compiler] page-compat → JSX→createElement → bind-this
9
+ → ESM→CJS → minify 的端到端编译。
10
+
11
+ 公开 API(被 yida_custom_page_update.py 直接 import):
12
+ - compile_jsx(jsx_source, modern=None, minify=True) -> dict
13
+ - compile_jsx_to_schema(jsx_source, form_uuid=None, ...) -> dict
14
+ - build_page_source(source_code) -> dict
15
+ - ensure_runtime_contract(source_code) -> dict
16
+ - minify_js(source) -> str
17
+
18
+ 外部依赖:无(纯 Python 标准库)。JSX 转换从 yida_jsx_pipeline.transform_jsx;
19
+ schema 外壳从 yida_page_schema.build_schema_content。
20
+ """
21
+
22
+ import json
23
+ import re
24
+ import sys
25
+ from pathlib import Path
26
+
27
+ _SCRIPT_DIR = Path(__file__).resolve().parent
28
+ if str(_SCRIPT_DIR) not in sys.path:
29
+ sys.path.insert(0, str(_SCRIPT_DIR))
30
+
31
+ # JSX 转换与 schema 外壳从其它聚合文件 import(避免重复实现)
32
+ from yida_jsx_pipeline import transform_jsx
33
+ from yida_page_schema import build_schema_content
34
+
35
+
36
+ # ===========================================================================
37
+ # region: page_compat —— modern JSX → 宜搭 runtime 格式
38
+ # ===========================================================================
39
+ #
40
+ # 支持的转换:
41
+ # - export default function Page() → export function renderJsx()
42
+ # - var [x, setX] = useState(init) → this.getCustomState('x') / this.setCustomState({x: val})
43
+ # - setX(function(prev) { ... }) → setCustomState calls updater with previous value
44
+ # - useEffect(fn, []) → didMount 生命周期
45
+ # - useEffect cleanup return → didUnmount 生命周期
46
+ # - 移除 React/react-dom import
47
+ # - const/let → var
48
+ # - 补齐 runtime contract exports
49
+ # ---------------------------------------------------------------------------
50
+
51
+ _REMOVABLE_IMPORTS = frozenset(['react', 'react-dom', 'lodash', 'moment'])
52
+
53
+ _RUNTIME_EXPORTS = {
54
+ 'getCustomState': (
55
+ 'export function getCustomState(key) {\n'
56
+ ' if (typeof _customState === "undefined") {\n'
57
+ ' return key ? undefined : {};\n'
58
+ ' }\n'
59
+ ' if (key) {\n'
60
+ ' return _customState[key];\n'
61
+ ' }\n'
62
+ ' return Object.assign({}, _customState);\n'
63
+ '}'
64
+ ),
65
+ 'setCustomState': (
66
+ 'export function setCustomState(newState) {\n'
67
+ ' if (typeof _customState === "undefined") {\n'
68
+ ' return;\n'
69
+ ' }\n'
70
+ ' Object.keys(newState || {}).forEach(function(key) {\n'
71
+ ' var value = newState[key];\n'
72
+ ' if (typeof value === "function") {\n'
73
+ ' value = value(_customState[key]);\n'
74
+ ' }\n'
75
+ ' _customState[key] = value;\n'
76
+ ' });\n'
77
+ ' this.forceUpdate();\n'
78
+ '}'
79
+ ),
80
+ 'forceUpdate': (
81
+ 'export function forceUpdate() {\n'
82
+ ' this.setState({ timestamp: new Date().getTime() });\n'
83
+ '}'
84
+ ),
85
+ 'didMount': 'export function didMount() {}',
86
+ 'didUnmount': 'export function didUnmount() {}',
87
+ }
88
+
89
+ _TIMESTAMP_NODE = (
90
+ "React.createElement('div', {style: {display: 'none'}}, "
91
+ "this.state && this.state.timestamp)"
92
+ )
93
+
94
+ _CUSTOM_STATE_DECL_RE = re.compile(r'\bvar\s+_customState\b')
95
+
96
+
97
+ def _build_render_jsx_with_marker(render_body):
98
+ """把 render_body 包进 IIFE,外层加 timestamp marker。
99
+
100
+ 产出:
101
+ export function renderJsx() {
102
+ var __yidaTs = this.state && this.state.timestamp;
103
+ var __yidaContent = (function() {
104
+ ...原 render_body...
105
+ }).call(this);
106
+ return React.createElement('div', null,
107
+ React.createElement('div', {style: {display: 'none'}}, __yidaTs),
108
+ __yidaContent
109
+ );
110
+ }
111
+
112
+ 作用:__yidaTs 本体在 IIFE 之外被读取使 setState({timestamp}) 能被 React diff、
113
+ 隐藏 div 把 __yidaTs 放到 JSX 树里使其参与重渲染;原 content 不动。
114
+ 多一层匿名 <div> 包裹,不影响布局,仅 body > div > xxx 选择器会失效。
115
+ """
116
+ if not render_body:
117
+ render_body = ' return null;'
118
+ indented = '\n'.join(
119
+ (' ' + line) if line else line
120
+ for line in render_body.split('\n')
121
+ )
122
+ return (
123
+ 'export function renderJsx() {\n'
124
+ ' var __yidaTs = this.state && this.state.timestamp;\n'
125
+ ' var __yidaContent = (function() {\n'
126
+ + indented + '\n'
127
+ ' }).call(this);\n'
128
+ " return React.createElement('div', null,\n"
129
+ " React.createElement('div', {style: {display: 'none'}}, __yidaTs),\n"
130
+ ' __yidaContent\n'
131
+ ' );\n'
132
+ '}'
133
+ )
134
+
135
+
136
+ def build_page_source(source_code):
137
+ """将现代 JSX 页面源码转换为宜搭 runtime 兼容格式。
138
+
139
+ Args:
140
+ source_code: 现代格式的 JSX 源码(export default function Page)
141
+
142
+ Returns:
143
+ dict: {
144
+ 'code': str, 转换后的代码
145
+ 'lint': {'errors': [], 'warnings': []},
146
+ 'fixes': [str], 应用的修复描述
147
+ 'errors': [{'code': str, 'message': str}],
148
+ }
149
+ """
150
+ if not _should_transform(source_code):
151
+ result = ensure_runtime_contract(source_code)
152
+ return {
153
+ 'code': result['code'],
154
+ 'lint': {'errors': [], 'warnings': []},
155
+ 'fixes': result['fixes'],
156
+ 'errors': [],
157
+ }
158
+
159
+ fixes = []
160
+ errors = []
161
+
162
+ # Step 0: 剥离 JSX 表达式注释 {/* ... */}
163
+ source_code = _strip_jsx_comments(source_code, fixes)
164
+
165
+ lines = source_code.split('\n')
166
+
167
+ # Step 1: 移除 React 等 import
168
+ lines = _remove_imports(lines, fixes, errors)
169
+
170
+ # Step 2: const/let → var
171
+ lines = _fix_variable_declarations(lines, fixes)
172
+
173
+ # Step 3: 提取 Page 函数体
174
+ page_info = _extract_page_function(lines, errors)
175
+ if page_info is None:
176
+ code = '\n'.join(lines)
177
+ result = ensure_runtime_contract(code)
178
+ return {
179
+ 'code': result['code'],
180
+ 'lint': {'errors': errors, 'warnings': []},
181
+ 'fixes': fixes + result['fixes'],
182
+ 'errors': errors,
183
+ }
184
+
185
+ # Step 4: 从函数体中提取 useState 和 useEffect
186
+ body_lines = page_info['body_lines']
187
+ state_info = _extract_use_state(body_lines, fixes, errors)
188
+ effect_info = _extract_use_effect(body_lines, fixes, errors)
189
+
190
+ # Step 5: 构建 renderJsx 函数
191
+ remaining_body = _remove_hooks_from_body(body_lines, state_info, effect_info)
192
+
193
+ # Step 5.5: 分离函数体为三类
194
+ separated = _separate_body_parts(remaining_body, state_info)
195
+
196
+ # Step 6: 构建输出
197
+ output_parts = []
198
+ output_parts.append('\n'.join(page_info['before_lines']))
199
+
200
+ # _customState 初始化
201
+ state_inits = []
202
+ for st in state_info['states']:
203
+ state_inits.append(f" {st['name']}: {st['init']},")
204
+ if state_inits:
205
+ output_parts.append('var _customState = {\n' + '\n'.join(state_inits) + '\n};')
206
+ else:
207
+ output_parts.append('var _customState = {};')
208
+
209
+ # 模块顶层变量
210
+ if separated['top_level_vars']:
211
+ top_vars = _replace_state_references(separated['top_level_vars'], state_info)
212
+ output_parts.append(top_vars)
213
+
214
+ # 纯工具函数
215
+ for fn_code in separated['utility_functions']:
216
+ output_parts.append(fn_code.strip())
217
+
218
+ # 导出函数声明(变为组件方法)
219
+ function_names = separated['function_names']
220
+ for fn_code in separated['functions']:
221
+ fn_transformed = _replace_state_references(fn_code, state_info)
222
+ fn_transformed = _add_bind_this_to_then_catch(fn_transformed)
223
+ fn_transformed = _replace_fn_calls_with_this(fn_transformed, function_names)
224
+ output_parts.append('export ' + fn_transformed.strip())
225
+
226
+ # renderJsx
227
+ render_body = _replace_state_references(separated['render_body'], state_info, receiver='self')
228
+ render_body = _replace_fn_calls_with_this(render_body, function_names)
229
+ if ('self.getCustomState' in render_body or 'self.setCustomState' in render_body) \
230
+ and not re.search(r'\bvar\s+self\s*=\s*this\b', render_body):
231
+ render_body = 'var self = this;\n' + render_body
232
+ render_fn = _build_render_jsx_with_marker(render_body)
233
+ output_parts.append(render_fn)
234
+ fixes.append('Converted export default function Page → export function renderJsx (with re-render marker)')
235
+
236
+ # didMount/didUnmount
237
+ mount_code = _replace_state_references(effect_info['mount_code'], state_info) if effect_info['mount_code'] else ''
238
+ unmount_code = _replace_state_references(effect_info['unmount_code'], state_info) if effect_info['unmount_code'] else ''
239
+
240
+ if mount_code:
241
+ mount_code = _replace_fn_calls_with_this(mount_code, function_names)
242
+ mount_code = _add_bind_this_to_then_catch(mount_code)
243
+ if unmount_code:
244
+ unmount_code = _replace_fn_calls_with_this(unmount_code, function_names)
245
+ unmount_code = _add_bind_this_to_then_catch(unmount_code)
246
+
247
+ if mount_code:
248
+ mount_fn = 'export function didMount() {\n' + mount_code + '\n}'
249
+ output_parts.append(mount_fn)
250
+ fixes.append('Extracted useEffect(fn, []) → didMount')
251
+ else:
252
+ output_parts.append('export function didMount() {}')
253
+
254
+ if unmount_code:
255
+ unmount_fn = 'export function didUnmount() {\n' + unmount_code + '\n}'
256
+ output_parts.append(unmount_fn)
257
+ fixes.append('Extracted useEffect cleanup → didUnmount')
258
+ else:
259
+ output_parts.append('export function didUnmount() {}')
260
+
261
+ if page_info['after_lines']:
262
+ output_parts.append('\n'.join(page_info['after_lines']))
263
+
264
+ code = '\n\n'.join(p for p in output_parts if p.strip())
265
+ result = ensure_runtime_contract(code)
266
+
267
+ return {
268
+ 'code': result['code'],
269
+ 'lint': {'errors': errors, 'warnings': []},
270
+ 'fixes': fixes + result['fixes'],
271
+ 'errors': errors,
272
+ }
273
+
274
+
275
+ def ensure_runtime_contract(source_code):
276
+ """确保代码包含宜搭 runtime 必需的 export 函数。
277
+
278
+ Args:
279
+ source_code: 已转换的源码
280
+
281
+ Returns:
282
+ dict: {'code': str, 'fixes': [str]}
283
+ """
284
+ fixes = []
285
+ append_parts = []
286
+
287
+ exported_names = set(re.findall(r'export\s+function\s+(\w+)', source_code))
288
+
289
+ if '_customState' in source_code and not _CUSTOM_STATE_DECL_RE.search(source_code):
290
+ append_parts.insert(0, 'var _customState = {};')
291
+ fixes.append('Inserted missing _customState store')
292
+
293
+ if 'getCustomState' not in exported_names or 'setCustomState' not in exported_names:
294
+ if '_customState' not in source_code:
295
+ append_parts.insert(0, 'var _customState = {};')
296
+ fixes.append('Inserted default _customState store')
297
+
298
+ for name, body in _RUNTIME_EXPORTS.items():
299
+ if name not in exported_names:
300
+ append_parts.append(body)
301
+ fixes.append(f'Inserted missing export function {name}')
302
+
303
+ if not append_parts:
304
+ return {'code': source_code, 'fixes': fixes}
305
+
306
+ return {
307
+ 'code': source_code.rstrip() + '\n\n' + '\n\n'.join(append_parts) + '\n',
308
+ 'fixes': fixes,
309
+ }
310
+
311
+
312
+ def _should_transform(source_code):
313
+ """判断是否需要做 modern → yida 转换"""
314
+ if re.search(r'export\s+function\s+renderJsx\s*\(', source_code):
315
+ return False
316
+ return bool(re.search(r'export\s+default\b', source_code))
317
+
318
+
319
+ def _strip_jsx_comments(source_code, fixes):
320
+ """剥离 JSX 表达式容器注释 {/* ... */}(避免 ,, 双逗号)。"""
321
+ if not source_code or '/*' not in source_code:
322
+ return source_code
323
+ original = source_code
324
+ source_code = re.sub(
325
+ r'(?m)^[ \t]*\{\s*/\*[\s\S]*?\*/\s*\}[ \t]*\r?\n',
326
+ '',
327
+ source_code,
328
+ )
329
+ source_code = re.sub(
330
+ r'(>)\s*\{\s*/\*[\s\S]*?\*/\s*\}\s*(<)',
331
+ r'\1\2',
332
+ source_code,
333
+ )
334
+ if source_code != original:
335
+ fixes.append('Stripped JSX expression comments (avoid ",," double-comma)')
336
+ return source_code
337
+
338
+
339
+ def _remove_imports(lines, fixes, errors):
340
+ """移除已知的 removable imports,对未知的报错"""
341
+ result = []
342
+ for line in lines:
343
+ match = re.match(r"^\s*import\s+.*?from\s+['\"]([^'\"]+)['\"]", line)
344
+ if not match:
345
+ match = re.match(r"^\s*import\s+['\"]([^'\"]+)['\"]", line)
346
+ if match:
347
+ module_name = match.group(1).lower()
348
+ if module_name in _REMOVABLE_IMPORTS:
349
+ fixes.append(f'Removed {module_name} import')
350
+ continue
351
+ else:
352
+ errors.append({
353
+ 'code': 'UNSUPPORTED_IMPORT',
354
+ 'message': f'Unsupported import "{match.group(1)}". '
355
+ 'Use this.utils.loadScript for external libraries.',
356
+ })
357
+ result.append(line)
358
+ return result
359
+
360
+
361
+ def _fix_variable_declarations(lines, fixes):
362
+ """const/let → var"""
363
+ result = []
364
+ pattern = re.compile(r'^(\s*)(const|let)\b(.*)$')
365
+ changed = False
366
+ for line in lines:
367
+ m = pattern.match(line)
368
+ if m:
369
+ result.append(f'{m.group(1)}var{m.group(3)}')
370
+ changed = True
371
+ else:
372
+ result.append(line)
373
+ if changed:
374
+ fixes.append('Replaced const/let with var')
375
+ return result
376
+
377
+
378
+ def _extract_page_function(lines, errors):
379
+ """提取 export default function Page() { ... } 的位置和内容"""
380
+ pattern = re.compile(r'^(\s*)export\s+default\s+function\s+\w*\s*\([^)]*\)\s*\{?\s*$')
381
+ start_idx = None
382
+
383
+ for i, line in enumerate(lines):
384
+ if pattern.match(line):
385
+ start_idx = i
386
+ break
387
+ if re.match(r'^\s*export\s+default\s+function\b', line):
388
+ start_idx = i
389
+ break
390
+
391
+ if start_idx is None:
392
+ errors.append({
393
+ 'code': 'NO_DEFAULT_EXPORT',
394
+ 'message': 'Cannot find export default function Page().',
395
+ })
396
+ return None
397
+
398
+ brace_start = None
399
+ text_from_start = '\n'.join(lines[start_idx:])
400
+ for i, ch in enumerate(text_from_start):
401
+ if ch == '{':
402
+ brace_start = i
403
+ break
404
+
405
+ if brace_start is None:
406
+ errors.append({'code': 'PARSE_ERROR', 'message': 'Cannot find opening brace of Page function.'})
407
+ return None
408
+
409
+ depth = 0
410
+ all_text = '\n'.join(lines)
411
+ char_pos = len('\n'.join(lines[:start_idx])) + (1 if start_idx > 0 else 0) + brace_start
412
+
413
+ end_char = None
414
+ pos = char_pos
415
+ while pos < len(all_text):
416
+ ch = all_text[pos]
417
+ if ch in ('"', "'", '`'):
418
+ pos = _skip_string_in_source(all_text, pos) + 1
419
+ continue
420
+ if ch == '{':
421
+ depth += 1
422
+ elif ch == '}':
423
+ depth -= 1
424
+ if depth == 0:
425
+ end_char = pos
426
+ break
427
+ pos += 1
428
+
429
+ if end_char is None:
430
+ errors.append({'code': 'PARSE_ERROR', 'message': 'Cannot find closing brace of Page function.'})
431
+ return None
432
+
433
+ body_content = all_text[char_pos + 1:end_char]
434
+ end_line = all_text[:end_char + 1].count('\n')
435
+
436
+ before_lines = lines[:start_idx]
437
+ after_lines = lines[end_line + 1:] if end_line + 1 < len(lines) else []
438
+ body_lines = body_content.split('\n')
439
+
440
+ return {
441
+ 'before_lines': before_lines,
442
+ 'body_lines': body_lines,
443
+ 'after_lines': after_lines,
444
+ }
445
+
446
+
447
+ def _skip_string_in_source(text, pos):
448
+ """跳过字符串字面量"""
449
+ quote = text[pos]
450
+ pos += 1
451
+ while pos < len(text):
452
+ ch = text[pos]
453
+ if ch == '\\':
454
+ pos += 2
455
+ continue
456
+ if ch == quote:
457
+ return pos
458
+ pos += 1
459
+ return pos
460
+
461
+
462
+ def _extract_use_state(body_lines, fixes, errors):
463
+ """提取所有 useState 声明(支持跨行初值)"""
464
+ states = []
465
+ body_text = '\n'.join(body_lines)
466
+ decl_pattern = re.compile(
467
+ r'(?m)^([ \t]*)var\s+\[(\w+)\s*,\s*(\w+)\]\s*=\s*useState\s*\('
468
+ )
469
+ for m in decl_pattern.finditer(body_text):
470
+ name = m.group(2)
471
+ setter = m.group(3)
472
+ paren_start = m.end() - 1
473
+ depth = 0
474
+ pos = paren_start
475
+ while pos < len(body_text):
476
+ ch = body_text[pos]
477
+ if ch in ('"', "'", '`'):
478
+ pos = _skip_string_in_source(body_text, pos) + 1
479
+ continue
480
+ if ch == '(':
481
+ depth += 1
482
+ elif ch == ')':
483
+ depth -= 1
484
+ if depth == 0:
485
+ break
486
+ pos += 1
487
+ if depth != 0:
488
+ errors.append({
489
+ 'code': 'UNSUPPORTED_USE_STATE',
490
+ 'message': f'Unbalanced parentheses in useState({name}) declaration.',
491
+ })
492
+ continue
493
+ init_text = body_text[paren_start + 1:pos].strip()
494
+ decl_end = pos + 1
495
+ while decl_end < len(body_text) and body_text[decl_end] in ' \t':
496
+ decl_end += 1
497
+ if decl_end < len(body_text) and body_text[decl_end] == ';':
498
+ decl_end += 1
499
+ states.append({
500
+ 'name': name,
501
+ 'setter': setter,
502
+ 'init': init_text,
503
+ 'raw_text': body_text[m.start():decl_end],
504
+ })
505
+
506
+ for line in body_lines:
507
+ hook_match = re.search(r'\buse([A-Z]\w*)\b', line)
508
+ if hook_match:
509
+ hook_name = 'use' + hook_match.group(1)
510
+ if hook_name not in ('useState', 'useEffect'):
511
+ errors.append({
512
+ 'code': 'UNSUPPORTED_HOOK',
513
+ 'message': f'{hook_name} is not supported.',
514
+ })
515
+
516
+ return {'states': states}
517
+
518
+
519
+ def _extract_use_effect(body_lines, fixes, errors):
520
+ """提取 useEffect(fn, []) 调用"""
521
+ mount_code = ''
522
+ unmount_code = ''
523
+
524
+ body_text = '\n'.join(body_lines)
525
+ effect_pattern = re.compile(r'useEffect\s*\(\s*function\s*\(\)\s*\{')
526
+
527
+ for match in effect_pattern.finditer(body_text):
528
+ start = match.end()
529
+ depth = 1
530
+ pos = start
531
+ while pos < len(body_text) and depth > 0:
532
+ ch = body_text[pos]
533
+ if ch in ('"', "'", '`'):
534
+ pos = _skip_string_in_source(body_text, pos) + 1
535
+ continue
536
+ if ch == '{':
537
+ depth += 1
538
+ elif ch == '}':
539
+ depth -= 1
540
+ pos += 1
541
+
542
+ fn_body = body_text[start:pos - 1]
543
+
544
+ remaining_after_fn = body_text[pos:].lstrip()
545
+ if not remaining_after_fn.startswith(','):
546
+ errors.append({'code': 'UNSUPPORTED_EFFECT_DEPS', 'message': 'useEffect must have [], as second arg.'})
547
+ continue
548
+ deps_start = body_text.index(',', pos) + 1
549
+ deps_text = body_text[deps_start:].lstrip()
550
+ if not deps_text.startswith('[]') and not deps_text.startswith('[ ]'):
551
+ errors.append({'code': 'UNSUPPORTED_EFFECT_DEPS', 'message': 'Only useEffect(fn, []) is supported.'})
552
+ continue
553
+
554
+ mount_lines, unmount_lines = _split_effect_body(fn_body)
555
+ if mount_lines:
556
+ mount_code += mount_lines + '\n'
557
+ if unmount_lines:
558
+ unmount_code += unmount_lines + '\n'
559
+
560
+ return {
561
+ 'mount_code': mount_code.strip(),
562
+ 'unmount_code': unmount_code.strip(),
563
+ }
564
+
565
+
566
+ def _split_effect_body(fn_body):
567
+ """将 effect 函数体分为 mount 代码和 unmount(return 的 cleanup 函数)"""
568
+ lines = fn_body.strip().split('\n')
569
+ mount_lines = []
570
+ unmount_lines = []
571
+
572
+ return_idx = None
573
+ for i in range(len(lines) - 1, -1, -1):
574
+ if re.match(r'^\s*return\s+function\b', lines[i]):
575
+ return_idx = i
576
+ break
577
+ if re.match(r'^\s*return\s*\(\s*function\b', lines[i]):
578
+ return_idx = i
579
+ break
580
+
581
+ if return_idx is None:
582
+ return '\n'.join(lines), ''
583
+
584
+ mount_lines = lines[:return_idx]
585
+
586
+ return_text = '\n'.join(lines[return_idx:])
587
+ brace_match = return_text.find('{')
588
+ if brace_match == -1:
589
+ return '\n'.join(mount_lines), ''
590
+
591
+ depth = 0
592
+ end_pos = None
593
+ for i in range(brace_match, len(return_text)):
594
+ if return_text[i] == '{':
595
+ depth += 1
596
+ elif return_text[i] == '}':
597
+ depth -= 1
598
+ if depth == 0:
599
+ end_pos = i
600
+ break
601
+
602
+ if end_pos is not None:
603
+ cleanup_body = return_text[brace_match + 1:end_pos]
604
+ unmount_lines = cleanup_body.strip()
605
+
606
+ return '\n'.join(mount_lines), unmount_lines
607
+
608
+
609
+ def _remove_hooks_from_body(body_lines, state_info, effect_info):
610
+ """从函数体中移除 useState 和 useEffect 声明行"""
611
+ body_text = '\n'.join(body_lines)
612
+
613
+ for st in state_info['states']:
614
+ raw = st.get('raw_text') or st.get('line') or ''
615
+ if raw:
616
+ body_text = body_text.replace(raw, '')
617
+
618
+ effect_pattern = re.compile(r'\s*useEffect\s*\(')
619
+ while True:
620
+ m = effect_pattern.search(body_text)
621
+ if not m:
622
+ break
623
+ start = m.start()
624
+ paren_start = body_text.index('(', m.start())
625
+ depth = 0
626
+ pos = paren_start
627
+ while pos < len(body_text):
628
+ ch = body_text[pos]
629
+ if ch in ('"', "'", '`'):
630
+ pos = _skip_string_in_source(body_text, pos) + 1
631
+ continue
632
+ if ch == '(':
633
+ depth += 1
634
+ elif ch == ')':
635
+ depth -= 1
636
+ if depth == 0:
637
+ break
638
+ pos += 1
639
+ end = pos + 1
640
+ if end < len(body_text) and body_text[end] == ';':
641
+ end += 1
642
+ body_text = body_text[:start] + body_text[end:]
643
+
644
+ body_text = re.sub(r'\n{3,}', '\n\n', body_text)
645
+ return body_text.strip()
646
+
647
+
648
+ def _replace_state_references(body_text, state_info, receiver='this'):
649
+ """将 state 变量引用替换为 this.getCustomState/setCustomState(跳过字符串)。"""
650
+ for st in state_info['states']:
651
+ name = st['name']
652
+ setter = st['setter']
653
+ body_text = _replace_setter_calls(body_text, setter, name, receiver=receiver)
654
+ body_text = _replace_state_reads(body_text, name, receiver=receiver)
655
+ return body_text
656
+
657
+
658
+ def _replace_state_reads(text, state_name, receiver='this'):
659
+ """替换裸变量名引用为 this.getCustomState('name'),跳过字符串内容和对象属性 key。"""
660
+ pattern = re.compile(r'(?<![.\w])' + re.escape(state_name) + r'(?!\w)')
661
+ result = []
662
+ pos = 0
663
+ while pos < len(text):
664
+ ch = text[pos]
665
+ if ch in ('"', "'", '`'):
666
+ end = _find_string_end(text, pos)
667
+ result.append(text[pos:end + 1])
668
+ pos = end + 1
669
+ continue
670
+ m = pattern.match(text, pos)
671
+ if m:
672
+ after_end = m.end()
673
+ preceding = ''.join(result).rstrip()
674
+
675
+ if preceding and preceding[-1] == '>':
676
+ prev_prev = preceding[-2] if len(preceding) >= 2 else ''
677
+ if prev_prev not in ('=', '!', '<', '>'):
678
+ result.append(text[pos:m.end()])
679
+ pos = m.end()
680
+ continue
681
+
682
+ rest_after = text[after_end:].lstrip()
683
+ if rest_after and rest_after[0] == ':' and not rest_after.startswith('::'):
684
+ if preceding and preceding[-1] in ('{', ',', '\n'):
685
+ result.append(text[pos:m.end()])
686
+ pos = m.end()
687
+ continue
688
+ result.append(f"{receiver}.getCustomState('{state_name}')")
689
+ pos = m.end()
690
+ else:
691
+ result.append(ch)
692
+ pos += 1
693
+ return ''.join(result)
694
+
695
+
696
+ def _replace_setter_calls(text, setter_name, state_name, receiver='this'):
697
+ """替换 setX(value) 为 this.setCustomState({'x': value}),跳过字符串内容。"""
698
+ setter_pattern = re.compile(r'(?<![.\w])' + re.escape(setter_name) + r'\s*\(')
699
+ result_parts = []
700
+ search_start = 0
701
+
702
+ while search_start < len(text):
703
+ ch = text[search_start] if search_start < len(text) else ''
704
+ if ch in ('"', "'", '`'):
705
+ end = _find_string_end(text, search_start)
706
+ result_parts.append(text[search_start:end + 1])
707
+ search_start = end + 1
708
+ continue
709
+
710
+ m = setter_pattern.search(text, search_start)
711
+ if not m:
712
+ result_parts.append(text[search_start:])
713
+ break
714
+
715
+ result_parts.append(text[search_start:m.start()])
716
+
717
+ paren_start = m.end() - 1
718
+ depth = 0
719
+ pos = paren_start
720
+ while pos < len(text):
721
+ c = text[pos]
722
+ if c in ('"', "'", '`'):
723
+ pos = _find_string_end(text, pos) + 1
724
+ continue
725
+ if c == '(':
726
+ depth += 1
727
+ elif c == ')':
728
+ depth -= 1
729
+ if depth == 0:
730
+ break
731
+ pos += 1
732
+ arg_content = text[paren_start + 1:pos]
733
+ replacement = f"{receiver}.setCustomState({{ '{state_name}': {arg_content} }})"
734
+ result_parts.append(replacement)
735
+ search_start = pos + 1
736
+
737
+ return ''.join(result_parts)
738
+
739
+
740
+ def _find_string_end(text, start):
741
+ """找到从 start 位置开始的字符串字面量结束位置(含闭合引号)"""
742
+ quote = text[start]
743
+ pos = start + 1
744
+ if quote == '`':
745
+ while pos < len(text):
746
+ ch = text[pos]
747
+ if ch == '\\':
748
+ pos += 2
749
+ continue
750
+ if ch == '`':
751
+ return pos
752
+ if ch == '$' and pos + 1 < len(text) and text[pos + 1] == '{':
753
+ pos += 2
754
+ depth = 1
755
+ while pos < len(text) and depth > 0:
756
+ if text[pos] == '{':
757
+ depth += 1
758
+ elif text[pos] == '}':
759
+ depth -= 1
760
+ pos += 1
761
+ continue
762
+ pos += 1
763
+ return len(text) - 1
764
+ while pos < len(text):
765
+ ch = text[pos]
766
+ if ch == '\\':
767
+ pos += 2
768
+ continue
769
+ if ch == quote:
770
+ return pos
771
+ pos += 1
772
+ return len(text) - 1
773
+
774
+
775
+
776
+ def _consume_var_declaration(lines, start_index):
777
+ """从 lines[start_index] 起读一条完整的 var 声明,允许初值跨多行。
778
+
779
+ 跟踪 {} [] () 括号深度(跳过字符串、模板字符串、单/多行注释),
780
+ 括号深度归零的那一行视为声明结束。返回 (end_index_inclusive, full_text)。
781
+ """
782
+ depth = 0
783
+ in_string = False
784
+ string_quote = ''
785
+ n = len(lines)
786
+ end = start_index
787
+ i = start_index
788
+ while i < n:
789
+ line = lines[i]
790
+ j = 0
791
+ L = len(line)
792
+ while j < L:
793
+ ch = line[j]
794
+ if in_string:
795
+ if string_quote == '`':
796
+ if ch == '\\' and j + 1 < L:
797
+ j += 2
798
+ continue
799
+ if ch == '`':
800
+ in_string = False
801
+ j += 1
802
+ continue
803
+ if ch == '\\' and j + 1 < L:
804
+ j += 2
805
+ continue
806
+ if ch == string_quote:
807
+ in_string = False
808
+ j += 1
809
+ continue
810
+ if ch == '/' and j + 1 < L and line[j + 1] == '/':
811
+ break
812
+ if ch == '/' and j + 1 < L and line[j + 1] == '*':
813
+ end_block = line.find('*/', j + 2)
814
+ if end_block == -1:
815
+ j = L
816
+ break
817
+ j = end_block + 2
818
+ continue
819
+ if ch in ('"', "'", '`'):
820
+ in_string = True
821
+ string_quote = ch
822
+ j += 1
823
+ continue
824
+ if ch in '{[(':
825
+ depth += 1
826
+ elif ch in '}])':
827
+ depth -= 1
828
+ j += 1
829
+ if depth <= 0:
830
+ end = i
831
+ break
832
+ i += 1
833
+ else:
834
+ end = n - 1
835
+ return end, '\n'.join(lines[start_index:end + 1])
836
+
837
+
838
+ def _var_function_to_declaration(decl_text):
839
+ """Convert `var name = function(args) { ... };` to `function name(args) { ... }`."""
840
+ header = re.match(r'^\s*var\s+(\w+)\s*=\s*function\s*(\([^)]*\))\s*\{', decl_text, re.S)
841
+ if not header:
842
+ return None, None
843
+ name = header.group(1)
844
+ args = header.group(2)
845
+ open_brace = decl_text.find('{', header.start())
846
+ close_brace = decl_text.rfind('}')
847
+ if open_brace < 0 or close_brace <= open_brace:
848
+ return None, None
849
+ body = decl_text[open_brace + 1:close_brace]
850
+ return name, f"function {name}{args} {{{body}\n}}"
851
+
852
+
853
+ def _extract_declared_var_names(decl_text):
854
+ """Return declared identifiers from a `var ...` declaration.
855
+
856
+ The modern authoring splitter needs these names to keep chained render
857
+ derivations together, e.g. `filtered` followed by `amount =
858
+ filtered.reduce(...)`. This parser only handles normal identifiers because
859
+ useState destructuring is removed before this phase.
860
+ """
861
+ m = re.match(r'^\s*var\s+([\s\S]*)$', decl_text.strip())
862
+ if not m:
863
+ return []
864
+ tail = m.group(1)
865
+ names = []
866
+ pos = 0
867
+ n = len(tail)
868
+ while pos < n:
869
+ while pos < n and tail[pos].isspace():
870
+ pos += 1
871
+ ident = re.match(r'[A-Za-z_$][\w$]*', tail[pos:])
872
+ if ident:
873
+ names.append(ident.group(0))
874
+ pos += len(ident.group(0))
875
+ depth = 0
876
+ while pos < n:
877
+ ch = tail[pos]
878
+ if ch in ('"', "'", '`'):
879
+ pos = _find_string_end(tail, pos) + 1
880
+ continue
881
+ if ch in '([{':
882
+ depth += 1
883
+ elif ch in ')]}':
884
+ depth -= 1
885
+ elif ch == ',' and depth == 0:
886
+ pos += 1
887
+ break
888
+ pos += 1
889
+ else:
890
+ break
891
+ return names
892
+
893
+
894
+ def _references_any_identifier(text, names):
895
+ """Whether text references any identifier in names outside strings."""
896
+ if not names:
897
+ return False
898
+ clean_parts = []
899
+ pos = 0
900
+ while pos < len(text):
901
+ ch = text[pos]
902
+ if ch in ('"', "'", '`'):
903
+ end = _find_string_end(text, pos)
904
+ clean_parts.append(' ')
905
+ pos = end + 1
906
+ else:
907
+ clean_parts.append(ch)
908
+ pos += 1
909
+ clean = ''.join(clean_parts)
910
+ return any(re.search(r'\b' + re.escape(name) + r'\b', clean) for name in names)
911
+
912
+
913
+ def _separate_body_parts(body_text, state_info):
914
+ """将函数体分离为:需导出的方法、模块级工具函数、顶层常量、render 部分。
915
+
916
+ 分类规则:
917
+ - 函数内含 setState/getState 调用 → 导出为组件方法(用 this.xxx() 调用)
918
+ - 纯工具函数(不依赖 state)→ 模块级普通函数(直接调用)
919
+ - var 声明依赖 state → 放入 renderJsx
920
+ - var 声明不依赖 state → 模块顶层常量
921
+ - return 语句及之后 → render_body
922
+ """
923
+ lines = body_text.split('\n')
924
+ exported_functions = []
925
+ exported_fn_names = []
926
+ utility_functions = []
927
+ utility_fn_names = []
928
+ top_level_vars = []
929
+ render_lines = []
930
+ render_var_names = set()
931
+
932
+ state_setters = set(st['setter'] for st in state_info['states'])
933
+ state_names = set(st['name'] for st in state_info['states'])
934
+
935
+ i = 0
936
+ found_return = False
937
+ while i < len(lines):
938
+ line = lines[i]
939
+ stripped = line.strip()
940
+
941
+ if found_return:
942
+ render_lines.append(line)
943
+ i += 1
944
+ continue
945
+
946
+ if re.match(r'^\s*return\s*[\(]', stripped) or stripped == 'return (' or stripped == 'return(':
947
+ found_return = True
948
+ render_lines.append(line)
949
+ i += 1
950
+ continue
951
+
952
+ fn_match = re.match(r'^(\s*)function\s+(\w+)\s*\(', line)
953
+ if fn_match:
954
+ fn_name = fn_match.group(2)
955
+ fn_start = i
956
+ brace_depth = 0
957
+ fn_end = i
958
+ for j in range(i, len(lines)):
959
+ for ch in lines[j]:
960
+ if ch == '{':
961
+ brace_depth += 1
962
+ elif ch == '}':
963
+ brace_depth -= 1
964
+ if brace_depth == 0 and j > i:
965
+ fn_end = j
966
+ break
967
+ if brace_depth == 0 and '{' in lines[j]:
968
+ fn_end = j
969
+ break
970
+ fn_code = '\n'.join(lines[fn_start:fn_end + 1])
971
+
972
+ uses_state = (
973
+ any(s + '(' in fn_code for s in state_setters)
974
+ or any(re.search(r'\b' + re.escape(sn) + r'\b', fn_code) for sn in state_names)
975
+ or bool(re.search(r'\bthis\.', fn_code))
976
+ )
977
+ if uses_state:
978
+ exported_functions.append(fn_code)
979
+ exported_fn_names.append(fn_name)
980
+ else:
981
+ utility_functions.append(fn_code)
982
+ utility_fn_names.append(fn_name)
983
+ i = fn_end + 1
984
+ continue
985
+
986
+ if re.match(r'^\s*var\s+\w+\s*=\s*function\s*\(', line):
987
+ decl_end, decl_text = _consume_var_declaration(lines, i)
988
+ fn_name, fn_code = _var_function_to_declaration(decl_text)
989
+ if fn_name and fn_code:
990
+ exported_functions.append(fn_code)
991
+ exported_fn_names.append(fn_name)
992
+ else:
993
+ render_lines.extend(lines[i:decl_end + 1])
994
+ i = decl_end + 1
995
+ continue
996
+
997
+ if stripped.startswith('var ') and not stripped.startswith('var ['):
998
+ decl_end, decl_text = _consume_var_declaration(lines, i)
999
+ decl_names = _extract_declared_var_names(decl_text)
1000
+ uses_state_var = _references_any_identifier(decl_text, state_names)
1001
+ uses_render_var = _references_any_identifier(decl_text, render_var_names)
1002
+ if uses_state_var or uses_render_var:
1003
+ render_lines.extend(lines[i:decl_end + 1])
1004
+ render_var_names.update(decl_names)
1005
+ else:
1006
+ top_level_vars.extend(lines[i:decl_end + 1])
1007
+ i = decl_end + 1
1008
+ continue
1009
+
1010
+ if stripped:
1011
+ render_lines.append(line)
1012
+ i += 1
1013
+
1014
+ return {
1015
+ 'functions': exported_functions,
1016
+ 'function_names': exported_fn_names,
1017
+ 'utility_functions': utility_functions,
1018
+ 'utility_fn_names': utility_fn_names,
1019
+ 'top_level_vars': '\n'.join(top_level_vars),
1020
+ 'render_body': '\n'.join(render_lines).strip(),
1021
+ }
1022
+
1023
+
1024
+ def _replace_fn_calls_with_this(code, function_names):
1025
+ """将裸函数调用 xxx() 替换为 this.xxx()(仅对导出为组件方法的函数)。"""
1026
+ for fn_name in function_names:
1027
+ pattern = re.compile(r'(?<!function )(?<![.\w])' + re.escape(fn_name) + r'(?=\s*\()')
1028
+ code = pattern.sub('this.' + fn_name, code)
1029
+ return code
1030
+
1031
+
1032
+ def _add_bind_this_to_then_catch(code):
1033
+ """在 .then(function(...) { ... }) 和 .catch(function(...) { ... }) 回调中,
1034
+ 如果内部使用了 this.,则在闭合 } 后添加 .bind(this)。
1035
+ """
1036
+ result = []
1037
+ pos = 0
1038
+ text = code
1039
+
1040
+ while pos < len(text):
1041
+ match = None
1042
+ for method in ('then', 'catch'):
1043
+ pattern = '.' + method + '(function'
1044
+ if text[pos:pos + len(pattern)] == pattern:
1045
+ match = method
1046
+ break
1047
+
1048
+ if match is None:
1049
+ result.append(text[pos])
1050
+ pos += 1
1051
+ continue
1052
+
1053
+ method_text = '.' + match + '('
1054
+ result.append(method_text)
1055
+ pos += len(method_text)
1056
+
1057
+ brace_pos = text.find('{', pos)
1058
+ if brace_pos == -1:
1059
+ result.append(text[pos:])
1060
+ break
1061
+
1062
+ result.append(text[pos:brace_pos + 1])
1063
+ pos = brace_pos + 1
1064
+
1065
+ depth = 1
1066
+ body_start = pos
1067
+ while pos < len(text) and depth > 0:
1068
+ ch = text[pos]
1069
+ if ch in ('"', "'", '`'):
1070
+ pos = _find_string_end(text, pos) + 1
1071
+ continue
1072
+ if ch == '{':
1073
+ depth += 1
1074
+ elif ch == '}':
1075
+ depth -= 1
1076
+ pos += 1
1077
+
1078
+ fn_body = text[body_start:pos - 1]
1079
+ result.append(fn_body)
1080
+
1081
+ if 'this.' in fn_body:
1082
+ result.append('}.bind(this)')
1083
+ else:
1084
+ result.append('}')
1085
+
1086
+ return ''.join(result)
1087
+
1088
+
1089
+ # ===========================================================================
1090
+ # region: compiler —— page-compat → JSX→createElement → bind-this → ESM→CJS → minify
1091
+ # ===========================================================================
1092
+
1093
+ def compile_jsx(jsx_source, modern=None, minify=True):
1094
+ """编译 JSX 源码为宜搭 runtime 兼容的 JS。
1095
+
1096
+ Args:
1097
+ jsx_source: JSX/JS 源码字符串
1098
+ modern: True 强制走 page-compat 转换;None 自动判断
1099
+ minify: 是否压缩(默认 True)
1100
+
1101
+ Returns:
1102
+ dict: {
1103
+ 'source_code': str, 原始输入
1104
+ 'intermediate_code': str, page-compat 改写后的中间产物
1105
+ 'compiled_code': str, JSX 转换 + minify 后的最终 JS
1106
+ 'lint': {'errors': [], 'warnings': []},
1107
+ 'ok': bool,
1108
+ 'errors': [{'message': str, ...}],
1109
+ }
1110
+ """
1111
+ errors = []
1112
+
1113
+ # Step 1: page-compat 转换
1114
+ compat_result = build_page_source(jsx_source)
1115
+ intermediate_code = compat_result['code']
1116
+ lint = compat_result.get('lint', {'errors': [], 'warnings': []})
1117
+
1118
+ if compat_result.get('errors'):
1119
+ for err in compat_result['errors']:
1120
+ errors.append(err)
1121
+
1122
+ if lint.get('errors'):
1123
+ return {
1124
+ 'source_code': jsx_source,
1125
+ 'intermediate_code': intermediate_code,
1126
+ 'compiled_code': '',
1127
+ 'lint': lint,
1128
+ 'ok': False,
1129
+ 'errors': errors + lint['errors'],
1130
+ }
1131
+
1132
+ # Step 2: JSX → React.createElement
1133
+ try:
1134
+ transformed_code = transform_jsx(intermediate_code)
1135
+ except Exception as exc:
1136
+ errors.append({'code': 'JSX_TRANSFORM_ERROR', 'message': str(exc)})
1137
+ return {
1138
+ 'source_code': jsx_source,
1139
+ 'intermediate_code': intermediate_code,
1140
+ 'compiled_code': '',
1141
+ 'lint': lint,
1142
+ 'ok': False,
1143
+ 'errors': errors,
1144
+ }
1145
+
1146
+ # Step 2.5: 给含 this. 的匿名函数加 .bind(this)
1147
+ transformed_code = _bind_this_to_anonymous_fns(transformed_code)
1148
+
1149
+ # Step 2.6: ESM exports → CommonJS(宜搭 runtime 只认 CommonJS)
1150
+ transformed_code = _convert_esm_to_commonjs(transformed_code)
1151
+
1152
+ # Step 3: minify
1153
+ if minify:
1154
+ compiled_code = minify_js(transformed_code)
1155
+ else:
1156
+ compiled_code = transformed_code
1157
+
1158
+ ok = len(errors) == 0
1159
+ return {
1160
+ 'source_code': jsx_source,
1161
+ 'intermediate_code': intermediate_code,
1162
+ 'compiled_code': compiled_code,
1163
+ 'lint': lint,
1164
+ 'ok': ok,
1165
+ 'errors': errors,
1166
+ }
1167
+
1168
+
1169
+ def compile_jsx_to_schema(jsx_source, form_uuid=None, existing_data_source=None,
1170
+ modern=None, minify=True):
1171
+ """编译 JSX 源码,可选生成完整自定义页面 schema。"""
1172
+ result = compile_jsx(jsx_source, modern=modern, minify=minify)
1173
+
1174
+ if form_uuid and result.get('ok'):
1175
+ result['schema'] = build_schema_content(
1176
+ result['source_code'],
1177
+ result['compiled_code'],
1178
+ form_uuid,
1179
+ existing_data_source=existing_data_source,
1180
+ )
1181
+ else:
1182
+ result['schema'] = None
1183
+
1184
+ return result
1185
+
1186
+
1187
+ def minify_js(source):
1188
+ """简单 JS minifier:移除注释、压缩连续空白、保留字符串内容。"""
1189
+ result = []
1190
+ pos = 0
1191
+ length = len(source)
1192
+
1193
+ while pos < length:
1194
+ ch = source[pos]
1195
+
1196
+ if ch in ('"', "'"):
1197
+ end = _consume_string(source, pos)
1198
+ result.append(source[pos:end + 1])
1199
+ pos = end + 1
1200
+ continue
1201
+
1202
+ if ch == '`':
1203
+ end = _consume_template(source, pos)
1204
+ result.append(source[pos:end + 1])
1205
+ pos = end + 1
1206
+ continue
1207
+
1208
+ if ch == '/' and pos + 1 < length and source[pos + 1] == '/':
1209
+ while pos < length and source[pos] != '\n':
1210
+ pos += 1
1211
+ continue
1212
+
1213
+ if ch == '/' and pos + 1 < length and source[pos + 1] == '*':
1214
+ pos += 2
1215
+ while pos < length - 1:
1216
+ if source[pos] == '*' and source[pos + 1] == '/':
1217
+ pos += 2
1218
+ break
1219
+ pos += 1
1220
+ else:
1221
+ pos = length
1222
+ continue
1223
+
1224
+ if ch in (' ', '\t', '\r'):
1225
+ while pos < length and source[pos] in (' ', '\t', '\r'):
1226
+ pos += 1
1227
+ if result and result[-1] and result[-1][-1:].isalnum() or (result and result[-1][-1:] == '_'):
1228
+ if pos < length and (source[pos].isalnum() or source[pos] == '_'):
1229
+ result.append(' ')
1230
+ continue
1231
+
1232
+ if ch == '\n':
1233
+ while pos < length and source[pos] in ('\n', '\r', ' ', '\t'):
1234
+ pos += 1
1235
+ if result and result[-1] and result[-1][-1:] not in ('{', '}', ';', ',', '(', '[', '\n', ''):
1236
+ result.append('\n')
1237
+ continue
1238
+
1239
+ result.append(ch)
1240
+ pos += 1
1241
+
1242
+ return ''.join(result)
1243
+
1244
+
1245
+ def _consume_string(source, start):
1246
+ """找到字符串字面量的结束位置(含闭合引号)"""
1247
+ quote = source[start]
1248
+ pos = start + 1
1249
+ while pos < len(source):
1250
+ ch = source[pos]
1251
+ if ch == '\\':
1252
+ pos += 2
1253
+ continue
1254
+ if ch == quote:
1255
+ return pos
1256
+ pos += 1
1257
+ return len(source) - 1
1258
+
1259
+
1260
+ def _consume_template(source, start):
1261
+ """找到模板字面量的结束位置(含闭合反引号)"""
1262
+ pos = start + 1
1263
+ while pos < len(source):
1264
+ ch = source[pos]
1265
+ if ch == '\\':
1266
+ pos += 2
1267
+ continue
1268
+ if ch == '`':
1269
+ return pos
1270
+ if ch == '$' and pos + 1 < len(source) and source[pos + 1] == '{':
1271
+ pos += 2
1272
+ depth = 1
1273
+ while pos < len(source) and depth > 0:
1274
+ if source[pos] == '{':
1275
+ depth += 1
1276
+ elif source[pos] == '}':
1277
+ depth -= 1
1278
+ elif source[pos] == '\\':
1279
+ pos += 1
1280
+ pos += 1
1281
+ continue
1282
+ pos += 1
1283
+ return len(source) - 1
1284
+
1285
+
1286
+ def _bind_this_to_anonymous_fns(code):
1287
+ """给含有 this. 引用的匿名函数表达式自动添加 .bind(this)。"""
1288
+ result = []
1289
+ pos = 0
1290
+ length = len(code)
1291
+
1292
+ while pos < length:
1293
+ fn_idx = code.find('function', pos)
1294
+ if fn_idx == -1:
1295
+ result.append(code[pos:])
1296
+ break
1297
+
1298
+ if fn_idx > 0 and (code[fn_idx - 1].isalnum() or code[fn_idx - 1] == '_'):
1299
+ result.append(code[pos:fn_idx + 8])
1300
+ pos = fn_idx + 8
1301
+ continue
1302
+ after_fn = fn_idx + 8
1303
+ if after_fn < length and (code[after_fn].isalnum() or code[after_fn] == '_'):
1304
+ pass
1305
+
1306
+ result.append(code[pos:fn_idx])
1307
+
1308
+ line_start = code.rfind('\n', 0, fn_idx)
1309
+ line_start = line_start + 1 if line_start != -1 else 0
1310
+ prefix = code[line_start:fn_idx].strip()
1311
+ is_declaration = (prefix == '' or prefix == 'export')
1312
+
1313
+ scan = fn_idx + 8
1314
+ while scan < length and code[scan] in (' ', '\t'):
1315
+ scan += 1
1316
+
1317
+ if scan < length and (code[scan].isalpha() or code[scan] == '_'):
1318
+ while scan < length and (code[scan].isalnum() or code[scan] == '_'):
1319
+ scan += 1
1320
+ while scan < length and code[scan] in (' ', '\t'):
1321
+ scan += 1
1322
+
1323
+ if scan >= length or code[scan] != '(':
1324
+ result.append('function')
1325
+ pos = fn_idx + 8
1326
+ continue
1327
+
1328
+ depth = 1
1329
+ scan += 1
1330
+ while scan < length and depth > 0:
1331
+ ch = code[scan]
1332
+ if ch == '(':
1333
+ depth += 1
1334
+ elif ch == ')':
1335
+ depth -= 1
1336
+ elif ch in ('"', "'", '`'):
1337
+ scan = _skip_str_for_bind(code, scan)
1338
+ scan += 1
1339
+ paren_end = scan
1340
+
1341
+ while scan < length and code[scan] in (' ', '\t', '\n', '\r'):
1342
+ scan += 1
1343
+
1344
+ if scan >= length or code[scan] != '{':
1345
+ result.append(code[fn_idx:paren_end])
1346
+ pos = paren_end
1347
+ continue
1348
+
1349
+ brace_start = scan
1350
+
1351
+ if is_declaration:
1352
+ result.append(code[fn_idx:brace_start + 1])
1353
+ pos = brace_start + 1
1354
+ continue
1355
+
1356
+ depth = 1
1357
+ scan += 1
1358
+ while scan < length and depth > 0:
1359
+ ch = code[scan]
1360
+ if ch in ('"', "'", '`'):
1361
+ scan = _skip_str_for_bind(code, scan)
1362
+ scan += 1
1363
+ continue
1364
+ if ch == '{':
1365
+ depth += 1
1366
+ elif ch == '}':
1367
+ depth -= 1
1368
+ scan += 1
1369
+ brace_end = scan
1370
+
1371
+ fn_body = code[brace_start + 1:brace_end - 1]
1372
+ result.append(code[fn_idx:brace_end])
1373
+
1374
+ if 'this.' in fn_body:
1375
+ rest = code[brace_end:brace_end + 30]
1376
+ if not rest.lstrip(' \t').startswith('.bind(this)'):
1377
+ result.append('.bind(this)')
1378
+
1379
+ pos = brace_end
1380
+
1381
+ return ''.join(result)
1382
+
1383
+
1384
+ def _skip_str_for_bind(code, pos):
1385
+ """跳过字符串/模板字面量,返回闭合引号的位置"""
1386
+ quote = code[pos]
1387
+ pos += 1
1388
+ while pos < len(code):
1389
+ ch = code[pos]
1390
+ if ch == '\\':
1391
+ pos += 2
1392
+ continue
1393
+ if quote == '`' and ch == '$' and pos + 1 < len(code) and code[pos + 1] == '{':
1394
+ pos += 2
1395
+ depth = 1
1396
+ while pos < len(code) and depth > 0:
1397
+ if code[pos] == '{':
1398
+ depth += 1
1399
+ elif code[pos] == '}':
1400
+ depth -= 1
1401
+ pos += 1
1402
+ continue
1403
+ if ch == quote:
1404
+ return pos
1405
+ pos += 1
1406
+ return pos - 1
1407
+
1408
+
1409
+ def _convert_esm_to_commonjs(code):
1410
+ """将 export function xxx 转换为宜搭 runtime 能识别的格式。
1411
+
1412
+ 宜搭 runtime(render-engine)期望代码是一个包裹在 function 中的块,
1413
+ 通过 Babel 编译后产出 CommonJS 风格:
1414
+ "use strict";
1415
+ Object.defineProperty(exports, "__esModule", { value: true });
1416
+ exports.renderJsx = renderJsx;
1417
+ exports.didMount = didMount;
1418
+ ...
1419
+ function renderJsx() { ... }
1420
+
1421
+ 但实际上宜搭更简单:它直接 eval 代码后从 exports 对象取函数。
1422
+ """
1423
+ lines = code.split('\n')
1424
+ result_lines = ['"use strict";', '']
1425
+ export_names = []
1426
+
1427
+ for line in lines:
1428
+ m = re.match(r'^export\s+function\s+(\w+)', line)
1429
+ if m:
1430
+ fn_name = m.group(1)
1431
+ export_names.append(fn_name)
1432
+ result_lines.append(line.replace('export ', '', 1))
1433
+ else:
1434
+ result_lines.append(line)
1435
+
1436
+ if export_names:
1437
+ result_lines.append('')
1438
+ for name in export_names:
1439
+ result_lines.append(f'exports.{name} = {name};')
1440
+
1441
+ return '\n'.join(result_lines)