@fps-games/vfx 0.1.1 → 0.3.3

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 (250) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/README.md +41 -0
  3. package/dist/effects/coin-burst/index.js +6 -2
  4. package/dist/effects/comet-trail-flame/index.js +3 -1
  5. package/dist/effects/debris-smoke/index.js +1 -0
  6. package/dist/effects/electric-arc/index.js +1 -0
  7. package/dist/effects/energy-shield/index.js +1 -0
  8. package/dist/effects/explosion/index.js +3 -2
  9. package/dist/effects/hit-flash/index.js +12 -4
  10. package/dist/effects/laser-beam/index.js +14 -2
  11. package/dist/effects/lumber_vehicle_upgrade_aura/index.js +1 -0
  12. package/dist/effects/monster-hit/index.js +2 -1
  13. package/dist/effects/player-upgrade/index.js +5 -3
  14. package/dist/effects/pulse-laser-volley/index.js +14 -4
  15. package/dist/effects/thruster-flame/index.js +3 -1
  16. package/dist/effects/treasure-attention/index.d.ts +54 -0
  17. package/dist/effects/treasure-attention/index.js +535 -0
  18. package/dist/effects/ufo-thread-flame/index.js +1 -0
  19. package/dist/effects/vehicle-upgrade/index.js +4 -1
  20. package/dist/effects/weapon-trail/index.js +8 -3
  21. package/dist/effects/wood_crusher_sawdust/index.d.ts +4 -0
  22. package/dist/effects/wood_cutter_sawdust/index.d.ts +4 -0
  23. package/dist/index.js +85 -17
  24. package/dist/types.d.ts +80 -0
  25. package/index.json +19 -1
  26. package/package.json +9 -4
  27. package/packages/effects/treasure-attention/vfx-params.json +49 -0
  28. package/tooling/vfx-cli.mjs +8 -1
  29. package/docs/AUTHORING_EFFECTS.md +0 -297
  30. package/docs/VFX_LIBRARY_ARCHITECTURE_chatgpt.md +0 -1580
  31. package/docs/VFX_LIBRARY_EXECUTION_PLAN.md +0 -229
  32. package/docs/VFX_LIBRARY_PLAN.md +0 -546
  33. package/docs/vfx-ab/RESULTS.md +0 -35
  34. package/docs/vfx-ab/lumber-order/vehicle-exhaust-smoke/after.png +0 -0
  35. package/docs/vfx-ab/lumber-order/vehicle-exhaust-smoke/before.png +0 -0
  36. package/docs/vfx-ab/lumber-truck/vehicle-exhaust-smoke/after.png +0 -0
  37. package/docs/vfx-ab/lumber-truck/vehicle-exhaust-smoke/before.png +0 -0
  38. package/docs/vfx-integration_chatgpt.md +0 -462
  39. package/docs/weapon-trail-visualization.html +0 -241
  40. package/packages/EffectPackageService.ts +0 -240
  41. package/packages/SceneVfxService.ts +0 -96
  42. package/packages/build.mesh.yellow_build_upgrade_indicator/assets/runtime/simpleMeshEffect.v1.js +0 -333
  43. package/packages/build.mesh.yellow_build_upgrade_indicator/assets/shaders/mesh-effect-preview.frag.glsl +0 -25
  44. package/packages/build.mesh.yellow_build_upgrade_indicator/assets/shaders/mesh-effect-preview.vert.glsl +0 -11
  45. package/packages/build.mesh.yellow_build_upgrade_indicator/assets/textures/effect_jianbian.png +0 -0
  46. package/packages/build.mesh.yellow_build_upgrade_indicator/effect.json +0 -927
  47. package/packages/build.mesh.yellow_build_upgrade_indicator/media/contact-sheet.png +0 -0
  48. package/packages/build.mesh.yellow_build_upgrade_indicator/media/preview.gif +0 -0
  49. package/packages/build.mesh.yellow_build_upgrade_indicator/media/preview.mp4 +0 -0
  50. package/packages/build.mesh.yellow_build_upgrade_indicator/media/preview.webm +0 -0
  51. package/packages/build.mesh.yellow_build_upgrade_indicator/media/thumbnail.png +0 -0
  52. package/packages/build.mesh.yellow_build_upgrade_indicator/metadata/description.json +0 -197
  53. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/03-effect.original.json +0 -895
  54. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/03-normalized-vfx-to-babylon-report.json +0 -241
  55. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/04-preview-overrides.json +0 -542
  56. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/04-preview-tuning-report.json +0 -69
  57. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/asset-map.json +0 -125
  58. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/lineage.json +0 -44
  59. package/packages/build.particle.golden_upgrade_starburst/assets/runtime/particleEmitter.v1.js +0 -210
  60. package/packages/build.particle.golden_upgrade_starburst/assets/textures/effect_guangdian.png +0 -0
  61. package/packages/build.particle.golden_upgrade_starburst/effect.json +0 -880
  62. package/packages/build.particle.golden_upgrade_starburst/media/contact-sheet.png +0 -0
  63. package/packages/build.particle.golden_upgrade_starburst/media/preview.gif +0 -0
  64. package/packages/build.particle.golden_upgrade_starburst/media/preview.mp4 +0 -0
  65. package/packages/build.particle.golden_upgrade_starburst/media/preview.webm +0 -0
  66. package/packages/build.particle.golden_upgrade_starburst/media/thumbnail.png +0 -0
  67. package/packages/build.particle.golden_upgrade_starburst/metadata/description.json +0 -161
  68. package/packages/build.particle.golden_upgrade_starburst/provenance/03-effect.original.json +0 -762
  69. package/packages/build.particle.golden_upgrade_starburst/provenance/03-normalized-vfx-to-babylon-report.json +0 -260
  70. package/packages/build.particle.golden_upgrade_starburst/provenance/04-preview-overrides.json +0 -436
  71. package/packages/build.particle.golden_upgrade_starburst/provenance/04-preview-tuning-report.json +0 -40
  72. package/packages/build.particle.golden_upgrade_starburst/provenance/asset-map.json +0 -115
  73. package/packages/build.particle.golden_upgrade_starburst/provenance/lineage.json +0 -40
  74. package/packages/build.particle.orange_upgrade_transform_sparks/assets/runtime/particleEmitter.v1.js +0 -210
  75. package/packages/build.particle.orange_upgrade_transform_sparks/assets/textures/b5b27ab1-e740-4398-b407-848fc2b2c897.png +0 -0
  76. package/packages/build.particle.orange_upgrade_transform_sparks/effect.json +0 -887
  77. package/packages/build.particle.orange_upgrade_transform_sparks/media/preview.gif +0 -0
  78. package/packages/build.particle.orange_upgrade_transform_sparks/media/preview.mp4 +0 -0
  79. package/packages/build.particle.orange_upgrade_transform_sparks/media/preview.webm +0 -0
  80. package/packages/build.particle.orange_upgrade_transform_sparks/media/thumbnail.png +0 -0
  81. package/packages/build.particle.orange_upgrade_transform_sparks/metadata/description.json +0 -179
  82. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/03-effect.original.json +0 -859
  83. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/03-normalized-vfx-to-babylon-report.json +0 -260
  84. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/04-preview-overrides.json +0 -478
  85. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/04-preview-tuning-report.json +0 -40
  86. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/asset-map.json +0 -114
  87. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/lineage.json +0 -41
  88. package/packages/build.particle.wood_chip_dust_burst/assets/runtime/particleEmitter.v1.js +0 -210
  89. package/packages/build.particle.wood_chip_dust_burst/assets/textures/effect_yanwu4.png +0 -0
  90. package/packages/build.particle.wood_chip_dust_burst/assets/textures/suipian3.png +0 -0
  91. package/packages/build.particle.wood_chip_dust_burst/effect.json +0 -1400
  92. package/packages/build.particle.wood_chip_dust_burst/media/contact-sheet.png +0 -0
  93. package/packages/build.particle.wood_chip_dust_burst/media/preview.gif +0 -0
  94. package/packages/build.particle.wood_chip_dust_burst/media/preview.mp4 +0 -0
  95. package/packages/build.particle.wood_chip_dust_burst/media/preview.webm +0 -0
  96. package/packages/build.particle.wood_chip_dust_burst/media/thumbnail.png +0 -0
  97. package/packages/build.particle.wood_chip_dust_burst/metadata/description.json +0 -173
  98. package/packages/build.particle.wood_chip_dust_burst/provenance/03-effect.original.json +0 -1277
  99. package/packages/build.particle.wood_chip_dust_burst/provenance/03-normalized-vfx-to-babylon-report.json +0 -329
  100. package/packages/build.particle.wood_chip_dust_burst/provenance/04-preview-overrides.json +0 -749
  101. package/packages/build.particle.wood_chip_dust_burst/provenance/04-preview-tuning-report.json +0 -60
  102. package/packages/build.particle.wood_chip_dust_burst/provenance/asset-map.json +0 -146
  103. package/packages/build.particle.wood_chip_dust_burst/provenance/lineage.json +0 -69
  104. package/packages/effects/building-upgrade/effect.json +0 -6
  105. package/packages/effects/building-upgrade/index.ts +0 -387
  106. package/packages/effects/character-entrance/effect.json +0 -6
  107. package/packages/effects/character-entrance/index.ts +0 -308
  108. package/packages/effects/coin-burst/index.ts +0 -43
  109. package/packages/effects/comet-trail-flame/assets/textures/trail_5x4.webp +0 -0
  110. package/packages/effects/comet-trail-flame/index.ts +0 -106
  111. package/packages/effects/conveyor-upgrade/effect.json +0 -6
  112. package/packages/effects/conveyor-upgrade/index.ts +0 -401
  113. package/packages/effects/debris-smoke/assets/models/rock_debris.glb +0 -0
  114. package/packages/effects/debris-smoke/assets/textures/smoke22.webp +0 -0
  115. package/packages/effects/debris-smoke/index.ts +0 -101
  116. package/packages/effects/door_open_gold_flakes/assets/runtime/goldFlakes.v1.js +0 -397
  117. package/packages/effects/door_open_gold_flakes/door_open_gold_flakes.json +0 -498
  118. package/packages/effects/door_open_gold_flakes/index.ts +0 -11
  119. package/packages/effects/electric-arc/assets/textures/spark_4x4.webp +0 -0
  120. package/packages/effects/electric-arc/index.ts +0 -62
  121. package/packages/effects/energy-shield/effect.json +0 -7
  122. package/packages/effects/energy-shield/index.ts +0 -431
  123. package/packages/effects/explosion/assets/textures/baozha29.webp +0 -0
  124. package/packages/effects/explosion/assets/textures/glow01.webp +0 -0
  125. package/packages/effects/explosion/assets/textures/lizi01.webp +0 -0
  126. package/packages/effects/explosion/assets/textures/xulie_lizibaozha02_5x5.webp +0 -0
  127. package/packages/effects/explosion/index.ts +0 -114
  128. package/packages/effects/ground-tile-upgrade/effect.json +0 -6
  129. package/packages/effects/ground-tile-upgrade/index.ts +0 -410
  130. package/packages/effects/hit-flash/index.ts +0 -69
  131. package/packages/effects/laser-beam/assets/textures/laser_glow.webp +0 -0
  132. package/packages/effects/laser-beam/assets/textures/laser_noise.webp +0 -0
  133. package/packages/effects/laser-beam/index.ts +0 -6
  134. package/packages/effects/laser-beam-factory.ts +0 -135
  135. package/packages/effects/lumber-cart-suction/effect.json +0 -6
  136. package/packages/effects/lumber-cart-suction/index.ts +0 -399
  137. package/packages/effects/lumber-debris-scatter/assets/models/lumber_prop_model.glb +0 -0
  138. package/packages/effects/lumber-debris-scatter/assets/models/lumber_prop_model.glb.versions/.active +0 -1
  139. package/packages/effects/lumber-debris-scatter/assets/models/lumber_prop_model.glb.versions/1781589063890_upload.glb +0 -0
  140. package/packages/effects/lumber-debris-scatter/assets/runtime/solidParticleScatter.v1.js +0 -236
  141. package/packages/effects/lumber-debris-scatter/effect.json +0 -228
  142. package/packages/effects/lumber-debris-scatter/index.ts +0 -780
  143. package/packages/effects/lumber_vehicle_gear_spin/assets/runtime/gearSpin.v1.js +0 -202
  144. package/packages/effects/lumber_vehicle_gear_spin/index.ts +0 -11
  145. package/packages/effects/lumber_vehicle_gear_spin/lumber_vehicle_gear_spin.json +0 -338
  146. package/packages/effects/lumber_vehicle_upgrade_aura/assets/runtime/upgradeAura.v1.js +0 -353
  147. package/packages/effects/lumber_vehicle_upgrade_aura/index.ts +0 -11
  148. package/packages/effects/lumber_vehicle_upgrade_aura/lumber_vehicle_upgrade_aura.json +0 -365
  149. package/packages/effects/lumber_vehicle_wood_chips/assets/runtime/solidParticleScatter.v1.js +0 -310
  150. package/packages/effects/lumber_vehicle_wood_chips/index.ts +0 -11
  151. package/packages/effects/lumber_vehicle_wood_chips/lumber_vehicle_wood_chips.json +0 -335
  152. package/packages/effects/monster-hit/index.ts +0 -128
  153. package/packages/effects/player-upgrade/effect.json +0 -6
  154. package/packages/effects/player-upgrade/index.ts +0 -635
  155. package/packages/effects/pulse-laser-volley/assets/textures/bolt_guangshu.webp +0 -0
  156. package/packages/effects/pulse-laser-volley/index.ts +0 -204
  157. package/packages/effects/resource-collect-sparkles/effect.json +0 -6
  158. package/packages/effects/resource-collect-sparkles/index.ts +0 -254
  159. package/packages/effects/run-footstep-dust/assets/runtime/runFootstepDust.v1.js +0 -215
  160. package/packages/effects/run-footstep-dust/effect.json +0 -183
  161. package/packages/effects/run-footstep-dust/index.ts +0 -377
  162. package/packages/effects/space-shared.ts +0 -120
  163. package/packages/effects/thruster-flame/index.ts +0 -305
  164. package/packages/effects/tree-chop-dust/assets/textures/tree_chop_dust_atlas.png +0 -0
  165. package/packages/effects/tree-chop-dust/effect.json +0 -8
  166. package/packages/effects/tree-chop-dust/index.ts +0 -202
  167. package/packages/effects/tree-leaf-chop-burst/assets/runtime/particleEmitter.v1.js +0 -303
  168. package/packages/effects/tree-leaf-chop-burst/assets/textures/tree_leaf_chop_burst_leaf_atlas.png +0 -0
  169. package/packages/effects/tree-leaf-chop-burst/effect.json +0 -498
  170. package/packages/effects/tree-leaf-chop-burst/index.ts +0 -294
  171. package/packages/effects/tree_chop_dust_burst/assets/runtime/particleEmitter.v1.js +0 -318
  172. package/packages/effects/tree_chop_dust_burst/assets/textures/tree_chop_dust_burst_dust.png +0 -0
  173. package/packages/effects/tree_chop_dust_burst/index.ts +0 -13
  174. package/packages/effects/tree_chop_dust_burst/tree_chop_dust_burst.json +0 -412
  175. package/packages/effects/ufo-thread-flame/assets/textures/flame_flow.webp +0 -0
  176. package/packages/effects/ufo-thread-flame/assets/textures/flame_spike.webp +0 -0
  177. package/packages/effects/ufo-thread-flame/index.ts +0 -152
  178. package/packages/effects/vehicle-exhaust-smoke/assets/textures/vehicle_exhaust_smoke_atlas.png +0 -0
  179. package/packages/effects/vehicle-exhaust-smoke/effect.json +0 -8
  180. package/packages/effects/vehicle-exhaust-smoke/index.ts +0 -298
  181. package/packages/effects/vehicle-upgrade/effect.json +0 -6
  182. package/packages/effects/vehicle-upgrade/index.ts +0 -401
  183. package/packages/effects/weapon-trail/assets/textures/trail_wedge.png +0 -0
  184. package/packages/effects/weapon-trail/index.ts +0 -313
  185. package/packages/effects/wood-crusher-sawdust/assets/runtime/particleEmitter.v1.js +0 -312
  186. package/packages/effects/wood-crusher-sawdust/assets/textures/wood_crusher_sawdust_dust.png +0 -0
  187. package/packages/effects/wood-crusher-sawdust/assets/textures/wood_crusher_sawdust_fiber.png +0 -0
  188. package/packages/effects/wood-crusher-sawdust/index.ts +0 -43
  189. package/packages/effects/wood-crusher-sawdust/wood_crusher_sawdust.json +0 -592
  190. package/packages/effects/wood-cutter-sawdust/assets/runtime/particleEmitter.v1.js +0 -312
  191. package/packages/effects/wood-cutter-sawdust/assets/textures/wood_cutter_sawdust_chip.png +0 -0
  192. package/packages/effects/wood-cutter-sawdust/assets/textures/wood_cutter_sawdust_dust.png +0 -0
  193. package/packages/effects/wood-cutter-sawdust/index.ts +0 -43
  194. package/packages/effects/wood-cutter-sawdust/wood_cutter_sawdust.json +0 -592
  195. package/packages/effects/wood-sawdust-effect-factory.ts +0 -194
  196. package/packages/effects/wood_crusher_sawdust/assets/runtime/particleEmitter.v1.js +0 -318
  197. package/packages/effects/wood_crusher_sawdust/assets/textures/wood_crusher_sawdust_dust.png +0 -0
  198. package/packages/effects/wood_crusher_sawdust/assets/textures/wood_crusher_sawdust_fiber.png +0 -0
  199. package/packages/effects/wood_crusher_sawdust/index.ts +0 -29
  200. package/packages/effects/wood_crusher_sawdust/wood_crusher_sawdust.json +0 -592
  201. package/packages/effects/wood_cutter_sawdust/assets/runtime/particleEmitter.v1.js +0 -318
  202. package/packages/effects/wood_cutter_sawdust/assets/textures/wood_cutter_sawdust_chip.png +0 -0
  203. package/packages/effects/wood_cutter_sawdust/assets/textures/wood_cutter_sawdust_dust.png +0 -0
  204. package/packages/effects/wood_cutter_sawdust/index.ts +0 -29
  205. package/packages/effects/wood_cutter_sawdust/wood_cutter_sawdust.json +0 -592
  206. package/packages/effects/wood_peel_material_test/assets/runtime/woodPeelMaterialTest.v1.js +0 -196
  207. package/packages/effects/wood_peel_material_test/index.ts +0 -11
  208. package/packages/effects/wood_peel_material_test/wood_peel_material_test.json +0 -136
  209. package/packages/env.particle.forest_edge_fog/assets/runtime/particleEmitter.v1.js +0 -210
  210. package/packages/env.particle.forest_edge_fog/assets/textures/f12a23c4-b924-4322-a260-3d982428f1e8.png +0 -0
  211. package/packages/env.particle.forest_edge_fog/assets/textures/yuan.png +0 -0
  212. package/packages/env.particle.forest_edge_fog/effect.json +0 -854
  213. package/packages/env.particle.forest_edge_fog/media/preview.gif +0 -0
  214. package/packages/env.particle.forest_edge_fog/media/preview.mp4 +0 -0
  215. package/packages/env.particle.forest_edge_fog/media/preview.webm +0 -0
  216. package/packages/env.particle.forest_edge_fog/media/thumbnail.png +0 -0
  217. package/packages/env.particle.forest_edge_fog/metadata/description.json +0 -148
  218. package/packages/env.particle.forest_edge_fog/provenance/03-effect.original.json +0 -827
  219. package/packages/env.particle.forest_edge_fog/provenance/03-normalized-vfx-to-babylon-report.json +0 -196
  220. package/packages/env.particle.forest_edge_fog/provenance/04-preview-overrides.json +0 -432
  221. package/packages/env.particle.forest_edge_fog/provenance/04-preview-tuning-report.json +0 -28
  222. package/packages/env.particle.forest_edge_fog/provenance/asset-map.json +0 -124
  223. package/packages/env.particle.forest_edge_fog/provenance/lineage.json +0 -41
  224. package/packages/generation.ts +0 -14
  225. package/packages/index.ts +0 -14
  226. package/packages/legacy-particle-runtime.ts +0 -113
  227. package/packages/manifestRuntimePackage.ts +0 -519
  228. package/packages/registry.ts +0 -85
  229. package/packages/rendering.ts +0 -13
  230. package/packages/resource.particle.viscous_oil_suction/assets/runtime/top_disk_suction/particleEmitter.v1.js +0 -424
  231. package/packages/resource.particle.viscous_oil_suction/assets/textures/Glow ad.png +0 -0
  232. package/packages/resource.particle.viscous_oil_suction/effect.json +0 -1254
  233. package/packages/resource.particle.viscous_oil_suction/media/preview.gif +0 -0
  234. package/packages/resource.particle.viscous_oil_suction/media/preview.mp4 +0 -0
  235. package/packages/resource.particle.viscous_oil_suction/media/preview.webm +0 -0
  236. package/packages/resource.particle.viscous_oil_suction/media/thumbnail.png +0 -0
  237. package/packages/resource.particle.viscous_oil_suction/metadata/description.json +0 -122
  238. package/packages/resource.particle.viscous_oil_suction/provenance/03-effect.original.json +0 -1051
  239. package/packages/resource.particle.viscous_oil_suction/provenance/03-normalized-vfx-to-babylon-report.json +0 -200
  240. package/packages/resource.particle.viscous_oil_suction/provenance/04-preview-overrides.json +0 -581
  241. package/packages/resource.particle.viscous_oil_suction/provenance/04-preview-tuning-report.json +0 -28
  242. package/packages/resource.particle.viscous_oil_suction/provenance/asset-map.json +0 -111
  243. package/packages/resource.particle.viscous_oil_suction/provenance/lineage.json +0 -45
  244. package/packages/types.ts +0 -107
  245. package/tooling/check-dist-runtime.mjs +0 -166
  246. package/tooling/generate-index.mjs +0 -55
  247. package/tooling/test-guardrails.mjs +0 -157
  248. package/tooling/validate-boundary.mjs +0 -92
  249. package/tooling/validate-effects.mjs +0 -304
  250. package/tooling/validate.mjs +0 -53
@@ -1,92 +0,0 @@
1
- #!/usr/bin/env node
2
- // 库纯净契约(plan §17)的执行点:扫描库内所有 import,凡越过库边界 / 反向依赖项目 → 报违规。
3
- // 允许:@babylonjs/*、@fps/*、node: 内置、第三方裸包、库内相对路径(解析到库内存在的文件)。
4
- // 违规:相对路径解析后逃出库根或指向不存在的文件;命中项目模块特征(config/core/services/systems/gameplay/assets-index 等)。
5
- import { readdirSync, statSync, readFileSync, existsSync } from 'node:fs';
6
- import { resolve, dirname, join, relative } from 'node:path';
7
- import { fileURLToPath } from 'node:url';
8
-
9
- const LIB_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
10
- // 新包面 = packages/ 根的运行时 .ts + packages/effects/**(不含旧 Cocos 包 build.particle.* 等)
11
- const PKG_ROOT = join(LIB_ROOT, 'packages');
12
-
13
- // 项目模块特征(反向依赖项目的明显信号)
14
- const PROJECT_HINTS = [
15
- /(^|\/)config\//, /(^|\/)core\//, /(^|\/)services\//, /(^|\/)systems\//,
16
- /(^|\/)gameplay\//, /projectGameplayConfig/, /babylon-gltf-loader/,
17
- /(^|\/)assets$/, /(^|\/)assets\/index/, /UIImages/, /configService/,
18
- ];
19
- const ALLOWED_BARE = [/^@babylonjs\//, /^@fps\//, /^node:/];
20
-
21
- function walk(dir, out = []) {
22
- if (!existsSync(dir)) return out;
23
- for (const e of readdirSync(dir)) {
24
- if (e === 'node_modules' || e === 'dist' || e.startsWith('.')) continue;
25
- const p = join(dir, e);
26
- const s = statSync(p);
27
- if (s.isDirectory()) walk(p, out);
28
- else if (/\.(ts|tsx|js|mjs)$/.test(e)) out.push(p);
29
- }
30
- return out;
31
- }
32
-
33
- function specifiers(src) {
34
- const out = new Set();
35
- let m;
36
- // a) `... from '...'`(普通/重导出)
37
- const fromRe = /\bfrom\s*['"]([^'"]+)['"]/g;
38
- while ((m = fromRe.exec(src))) out.add(m[1]);
39
- // b) 副作用导入 `import '...'`(无 from)
40
- const sideRe = /\bimport\s+['"]([^'"]+)['"]/g;
41
- while ((m = sideRe.exec(src))) out.add(m[1]);
42
- // c) 动态 import('...')
43
- const dynRe = /\bimport\(\s*['"]([^'"]+)['"]\s*\)/g;
44
- while ((m = dynRe.exec(src))) out.add(m[1]);
45
- return [...out];
46
- }
47
-
48
- function resolvesInLib(fromFile, spec) {
49
- // 去掉 ?url / ?raw 等 query
50
- const clean = spec.replace(/\?.*$/, '');
51
- const base = resolve(dirname(fromFile), clean);
52
- if (!base.startsWith(LIB_ROOT)) return { ok: false, reason: '逃出库根' };
53
- const cands = [base, base + '.ts', base + '.tsx', base + '.js', base + '.mjs', base + '.json',
54
- join(base, 'index.ts'), join(base, 'index.js')];
55
- if (cands.some(existsSync)) return { ok: true };
56
- return { ok: false, reason: '指向库内不存在的文件(可能本应指向项目)' };
57
- }
58
-
59
- const violations = [];
60
- // 扫:packages/ 根的运行时 .ts(非递归)+ packages/effects/ 全树
61
- const rootTsFiles = existsSync(PKG_ROOT)
62
- ? readdirSync(PKG_ROOT).filter((e) => /\.(ts|tsx|js|mjs)$/.test(e)).map((e) => join(PKG_ROOT, e))
63
- : [];
64
- const files = [...rootTsFiles, ...walk(join(PKG_ROOT, 'effects'))];
65
- for (const f of files) {
66
- const src = readFileSync(f, 'utf8');
67
- for (const spec of specifiers(src)) {
68
- const rel = relative(LIB_ROOT, f);
69
- if (spec.startsWith('.')) {
70
- const r = resolvesInLib(f, spec);
71
- if (!r.ok) violations.push({ file: rel, spec, reason: r.reason });
72
- } else if (ALLOWED_BARE.some((re) => re.test(spec))) {
73
- continue;
74
- } else if (PROJECT_HINTS.some((re) => re.test(spec))) {
75
- violations.push({ file: rel, spec, reason: '命中项目模块特征' });
76
- }
77
- // 其它裸包(第三方)默认放行
78
- }
79
- }
80
-
81
- if (violations.length === 0) {
82
- console.log('✅ 库纯净契约:无越界 import。');
83
- process.exit(0);
84
- }
85
- console.log(`❌ 库纯净契约:发现 ${violations.length} 处越界/反向依赖 import:\n`);
86
- const byFile = {};
87
- for (const v of violations) (byFile[v.file] ??= []).push(v);
88
- for (const [file, vs] of Object.entries(byFile)) {
89
- console.log(` ${file}`);
90
- for (const v of vs) console.log(` → "${v.spec}" [${v.reason}]`);
91
- }
92
- process.exit(1);
@@ -1,304 +0,0 @@
1
- #!/usr/bin/env node
2
- // 新增特效 guardrail · Layer 1(源码静态 lint,见 issue #3)。
3
- // 对每个 packages/effects/<id>/ 静态扫描,在「进库前 / 发版前」拦住会坑消费方的模式:
4
- // 1. 引用策略 —— 禁全量 glTF barrel / meshopt / draco / ktx2 等会被急切拉进 consumer 包的重运行时
5
- // 扩展(根治 v0.1.1 的 meshopt 事故);要核心 loader `@babylonjs/loaders/glTF/2.0/glTFLoader`。
6
- // 2. 资源内联 —— 资源用 `?url` 导入(库 build 才会内联成 base64);禁 `new URL(...,import.meta.url)`;
7
- // 引用的 .glb/.png/.js 资源必须存在。
8
- // 3. 契约 —— index.ts 导出合法 VfxEffectPackage(create+id+nameZh+defaultParams+debugParams,或 data 工厂)。
9
- // 4. 结构 —— 有 index.ts、vfx-params.json 合法 JSON;目录名 vs 导出 id(软提示)。
10
- //
11
- // 用法:
12
- // node tooling/validate-effects.mjs # 扫全部特效
13
- // node tooling/validate-effects.mjs --effect <id> # 只扫一个(发版 gate / 回归用)
14
- // node tooling/validate-effects.mjs --effects-dir <path> # 换扫描根(回归 fixture 用)
15
- // 退出码:有 error → 1;只有 warning 或全过 → 0。
16
- import { readdirSync, statSync, readFileSync, existsSync } from 'node:fs';
17
- import { resolve, dirname, join, relative } from 'node:path';
18
- import { fileURLToPath } from 'node:url';
19
-
20
- const LIB_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
21
-
22
- // ── CLI ──────────────────────────────────────────────────────────────────────
23
- const argv = process.argv.slice(2);
24
- function flag(name) {
25
- const i = argv.indexOf(name);
26
- return i >= 0 ? argv[i + 1] : undefined;
27
- }
28
- const onlyEffect = flag('--effect');
29
- const effectsDir = resolve(flag('--effects-dir') ?? join(LIB_ROOT, 'packages', 'effects'));
30
-
31
- // ── 引用策略 ──────────────────────────────────────────────────────────────────
32
- // 唯一允许的 glTF loader 入口(只注册 2.0 核心,不引扩展)。
33
- const ALLOWED_LOADER_SPECIFIERS = new Set(['@babylonjs/loaders/glTF/2.0/glTFLoader']);
34
- // 轻量扩展白名单(按需扩;默认空 → 任何 @babylonjs/loaders 扩展都需显式审查后加入)。
35
- const ALLOWED_LOADER_EXTENSION_PREFIXES = [];
36
- // 明确点名的重 barrel/扩展(命中给定向修复提示)。
37
- const HEAVY_LOADER_HINTS = [
38
- { re: /^@babylonjs\/loaders$/, why: '全量 loaders barrel(注册所有格式 + 全部 glTF 扩展)' },
39
- { re: /^@babylonjs\/loaders\/glTF$/, why: '全量 glTF barrel(急切注册 EXT_meshopt_compression / KHR_draco 等扩展)' },
40
- { re: /^@babylonjs\/loaders\/glTF\/2\.0$/, why: 'glTF 2.0 barrel(index,会拉入扩展注册)' },
41
- { re: /EXT_meshopt_compression/, why: 'meshopt 扩展(把 MeshoptDecoder 拉进 consumer 包)' },
42
- { re: /KHR_draco_mesh_compression/, why: 'draco 扩展(把 Draco 解码器拉进 consumer 包)' },
43
- { re: /KHR_texture_basisu/, why: 'ktx2/basisu 扩展(把 basis transcoder 拉进 consumer 包)' },
44
- ];
45
- const LOADER_FIX = '改用核心加载器 `import \'@babylonjs/loaders/glTF/2.0/glTFLoader\'`(只注册 2.0 核心,不引扩展);GLB 须为 plain(零扩展)。';
46
-
47
- // 在已剥注释的源码里直接出现 = 危险解码器引用(即便不是通过 loaders barrel 引入,如直接 import @babylonjs/core 的解码器)。
48
- const FORBIDDEN_RUNTIME_TOKENS = [
49
- 'EXT_meshopt_compression', 'MeshoptDecoder', 'meshopt_decoder',
50
- 'KHR_draco_mesh_compression', 'DracoCompression', 'DracoDecoder',
51
- 'KHR_texture_basisu', 'KhronosTextureContainer2', '_KTX2Decoder', 'MSCTranscoder', 'basis_transcoder',
52
- 'application/x-glb-brotli', 'brotli-dec-wasm',
53
- ];
54
-
55
- // ── 工具 ──────────────────────────────────────────────────────────────────────
56
- function stripComments(src) {
57
- // 先去块注释,再去行注释(用 (?<!:) 避免吃掉 URL 里的 `://`)。
58
- return src.replace(/\/\*[\s\S]*?\*\//g, '').replace(/(?<!:)\/\/[^\n]*/g, '');
59
- }
60
-
61
- // import/require specifier(含无空格副作用导入、反引号动态 import、require)。
62
- function specifiers(src) {
63
- const out = new Set();
64
- let m;
65
- const fromRe = /\bfrom\s*[`'"]([^`'"]+)[`'"]/g;
66
- while ((m = fromRe.exec(src))) out.add(m[1]);
67
- const sideRe = /\bimport\s*[`'"]([^`'"]+)[`'"]/g; // \s* :允许 import'x'(零空格)
68
- while ((m = sideRe.exec(src))) out.add(m[1]);
69
- const dynRe = /\bimport\(\s*[`'"]([^`'"]+)[`'"]\s*\)/g; // [`'"] :含模板字符串动态 import
70
- while ((m = dynRe.exec(src))) out.add(m[1]);
71
- const reqRe = /\brequire\(\s*[`'"]([^`'"]+)[`'"]\s*\)/g;
72
- while ((m = reqRe.exec(src))) out.add(m[1]);
73
- return [...out];
74
- }
75
-
76
- // 所有字符串字面量(单/双引号 + 不含插值的反引号)。用于在任意位置(import / require /
77
- // 字符串拼接 fragment)抓 heavy loader specifier —— 比只看 import 位置更难绕过。
78
- function stringLiterals(src) {
79
- const out = [];
80
- let m;
81
- const re = /(['"])((?:[^'"\\]|\\.)*)\1|`([^`$\\]*)`/g;
82
- while ((m = re.exec(src))) out.push(m[2] ?? m[3] ?? '');
83
- return out;
84
- }
85
-
86
- // ── key 一致性(面板↔参数)用的轻量括号配平解析 ────────────────────────────────
87
- // 从 startIdx(open 字符 { 或 [ 处)取平衡子串(处理字符串/转义)。
88
- function balancedFrom(src, startIdx) {
89
- const open = src[startIdx];
90
- const close = open === '{' ? '}' : open === '[' ? ']' : null;
91
- if (!close) return '';
92
- let depth = 0, inStr = null;
93
- for (let i = startIdx; i < src.length; i++) {
94
- const c = src[i];
95
- if (inStr) { if (c === '\\') { i++; continue; } if (c === inStr) inStr = null; continue; }
96
- if (c === '"' || c === "'" || c === '`') { inStr = c; continue; }
97
- if (c === open) depth++;
98
- else if (c === close) { depth--; if (depth === 0) return src.slice(startIdx, i + 1); }
99
- }
100
- return '';
101
- }
102
- // 对象字面量(含外层 {})的顶层键名集合。
103
- function topLevelKeys(objStr) {
104
- const keys = new Set();
105
- let depth = 0, inStr = null, atProp = false;
106
- for (let i = 0; i < objStr.length; i++) {
107
- const c = objStr[i];
108
- if (inStr) { if (c === '\\') { i++; continue; } if (c === inStr) inStr = null; continue; }
109
- if (c === '"' || c === "'" || c === '`') {
110
- if (depth === 1 && atProp) { const m = objStr.slice(i).match(/^(['"])([A-Za-z_$][\w$]*)\1\s*:/); if (m) { keys.add(m[2]); atProp = false; } }
111
- inStr = c; continue;
112
- }
113
- if (c === '{' || c === '[' || c === '(') { depth++; if (depth === 1) atProp = true; continue; }
114
- if (c === '}' || c === ']' || c === ')') { depth--; continue; }
115
- if (depth === 1) {
116
- if (c === ',') { atProp = true; continue; }
117
- if (atProp && /[A-Za-z_$]/.test(c)) { const m = objStr.slice(i).match(/^([A-Za-z_$][\w$]*)\s*:/); if (m) { keys.add(m[1]); atProp = false; } }
118
- }
119
- }
120
- return keys;
121
- }
122
- function valueAfterProp(code, prop) {
123
- const m = new RegExp(`\\b${prop}\\s*:\\s*`).exec(code);
124
- if (!m) return null;
125
- let i = m.index + m[0].length;
126
- while (i < code.length && /\s/.test(code[i])) i++;
127
- return { idx: i, ch: code[i] };
128
- }
129
- // defaultParams 的键:支持内联 { } 或指向 const FOO = { }
130
- function defaultParamKeys(code) {
131
- const v = valueAfterProp(code, 'defaultParams');
132
- if (!v) return null;
133
- if (v.ch === '{') return topLevelKeys(balancedFrom(code, v.idx));
134
- const idm = code.slice(v.idx).match(/^([A-Za-z_$][\w$]*)/);
135
- if (!idm) return null;
136
- const cm = new RegExp(`\\b(?:const|let|var)\\s+${idm[1]}\\b[^=]*=\\s*`).exec(code);
137
- if (!cm) return null;
138
- let j = cm.index + cm[0].length;
139
- while (j < code.length && /\s/.test(code[j])) j++;
140
- if (code[j] !== '{') return null;
141
- return topLevelKeys(balancedFrom(code, j));
142
- }
143
- // debugParams 数组里每个条目的 key
144
- function debugParamKeys(code) {
145
- const v = valueAfterProp(code, 'debugParams');
146
- if (!v || v.ch !== '[') return null;
147
- const arr = balancedFrom(code, v.idx);
148
- return new Set([...arr.matchAll(/\bkey\s*:\s*['"]([^'"]+)['"]/g)].map((m) => m[1]));
149
- }
150
-
151
- // 扫特效目录下"会被打包成模块"的代码文件;assets/ 是 ?url 运行时资产(非静态 import),排除。
152
- const CODE_EXT = /\.(tsx?|mts|cts|jsx?|mjs|cjs)$/;
153
- function codeFilesIn(dir, isRoot = true) {
154
- const out = [];
155
- for (const e of readdirSync(dir)) {
156
- if (e === 'node_modules' || e.startsWith('.')) continue;
157
- if (e === 'assets') continue; // ?url 资产目录,运行时按 URL 加载,不参与模块打包
158
- const p = join(dir, e);
159
- const s = statSync(p);
160
- if (s.isDirectory()) out.push(...codeFilesIn(p, false));
161
- else if (CODE_EXT.test(e)) out.push(p);
162
- }
163
- return out;
164
- }
165
-
166
- // ── 单特效校验 ────────────────────────────────────────────────────────────────
167
- function validateEffect(id) {
168
- const dir = join(effectsDir, id);
169
- const errors = [];
170
- const warnings = [];
171
- const indexPath = join(dir, 'index.ts');
172
-
173
- if (!existsSync(indexPath)) {
174
- errors.push(`缺少 index.ts(每个特效目录必须有 index.ts 导出 VfxEffectPackage)。`);
175
- return { id, errors, warnings };
176
- }
177
-
178
- const files = codeFilesIn(dir);
179
- const indexSrcRaw = readFileSync(indexPath, 'utf8');
180
- const indexCode = stripComments(indexSrcRaw);
181
-
182
- for (const file of files) {
183
- const rel = relative(dirname(effectsDir), file);
184
- const raw = readFileSync(file, 'utf8');
185
- const code = stripComments(raw);
186
-
187
- // 1) 引用策略:扫所有字符串字面量(覆盖 import / require / 字符串拼接 fragment / 反引号)。
188
- // 凡以 @babylonjs/loaders 开头、又不是唯一允许的核心 loader / 白名单扩展 → 拦。
189
- for (const lit of stringLiterals(code)) {
190
- if (!lit.startsWith('@babylonjs/loaders')) continue;
191
- if (ALLOWED_LOADER_SPECIFIERS.has(lit)) continue;
192
- if (ALLOWED_LOADER_EXTENSION_PREFIXES.some((p) => lit.startsWith(p))) continue;
193
- const hint = HEAVY_LOADER_HINTS.find((h) => h.re.test(lit));
194
- errors.push(`[引用策略] ${rel} 引用 "${lit}"${hint ? `(${hint.why})` : '(非白名单 loaders 子路径)'} → ${LOADER_FIX}`);
195
- }
196
-
197
- // 1b) 危险解码器 token(剥注释后,覆盖 helper .js/.mjs 等)
198
- for (const token of FORBIDDEN_RUNTIME_TOKENS) {
199
- if (code.includes(token)) {
200
- errors.push(`[引用策略] ${rel} 出现禁运行时标识 "${token}" → 会把重解码器拉进 consumer 包(违反 Playable bundle 策略,见 #2 v0.1.1)。`);
201
- }
202
- }
203
-
204
- // 2) 资源:禁 new URL(...,import.meta.url)(含模板字符串/单双引号)
205
- if (/new\s+URL\s*\(\s*[`'"][^`'"]+[`'"]\s*,\s*import\.meta\.url\s*\)/.test(code)) {
206
- errors.push(`[资源] ${rel} 用了 \`new URL(..., import.meta.url)\` → 库 build 不会内联它。改用 \`import X from './path?url'\`(Phase1 已统一,见 #2)。`);
207
- }
208
-
209
- // 2b) ?url / ?raw 资源必须存在(query 里出现 url/raw 标志即算,容忍 ?url&inline 等附加 query)
210
- for (const spec of specifiers(code)) {
211
- if (!/[?&](url|raw)(?=&|=|$)/.test(spec)) continue;
212
- if (!spec.startsWith('.')) continue; // 只校验相对资源
213
- const cleaned = spec.replace(/\?.*$/, '');
214
- const assetPath = resolve(dirname(file), cleaned);
215
- if (!existsSync(assetPath)) {
216
- errors.push(`[资源] ${rel} 引用的资源不存在:"${spec}"(解析为 ${relative(dirname(effectsDir), assetPath)})。`);
217
- }
218
- }
219
- }
220
-
221
- // 3) 契约:导出合法 VfxEffectPackage
222
- // data 类用 createManifestRuntimeEffectPackage / createDataDrivenEffectPackage;
223
- // 另有共享工厂(如 createWoodSawdustEffectPackage)。统一识别 create*EffectPackage( 调用。
224
- const usesFactory = /\bcreate\w*EffectPackage\s*\(/.test(indexCode);
225
- // create 可为方法 create( / 泛型 create<T>( / 箭头属性 create: (ctx)=>
226
- const hasInlineShape = /\bcreate\s*[(:<]/.test(indexCode)
227
- && /\bid\s*:/.test(indexCode)
228
- && /\bnameZh\s*:/.test(indexCode)
229
- && /\bdefaultParams\b/.test(indexCode)
230
- && /\bdebugParams\b/.test(indexCode);
231
- // index.ts 仅做 re-export(package 定义在 ./impl 等同目录模块)也算合规
232
- const reExportsRelative = /export\s+(?:\*|\{[^}]*\}|.+\s+as\s+default)\s+from\s*[`'"]\.[^`'"]*[`'"]/.test(indexCode);
233
- const hasExport = /\bexport\b/.test(indexCode);
234
- if (!hasExport) {
235
- errors.push(`[契约] index.ts 没有任何 export(需导出 VfxEffectPackage)。`);
236
- } else if (!usesFactory && !hasInlineShape && !reExportsRelative) {
237
- errors.push(`[契约] index.ts 未检测到合法 VfxEffectPackage:code 类需 create+id+nameZh+defaultParams+debugParams;data 类用 create*EffectPackage 工厂;或 re-export 自同目录模块。`);
238
- }
239
-
240
- // 3b) 面板↔参数 key 一致性:每个 debugParams[].key 必须在 defaultParams 里有默认值,
241
- // 否则面板生成的旋钮调它不生效(以 undefined 起步)。仅对 inline code 特效;
242
- // 工厂/data 类的 default/debug 由工厂生成,跳过(解析不到也跳过,不误报)。
243
- if (hasInlineShape && !usesFactory && !reExportsRelative) {
244
- const defaultKeys = defaultParamKeys(indexCode);
245
- const debugKeys = debugParamKeys(indexCode);
246
- if (defaultKeys && debugKeys) {
247
- const orphan = [...debugKeys].filter((k) => !defaultKeys.has(k));
248
- if (orphan.length) {
249
- errors.push(`[参数] debugParams 旋钮 [${orphan.join(', ')}] 在 defaultParams 里没有对应默认值 → 面板调它不生效(以 undefined 起步)。把这些 key 补进 defaultParams,或修正 debugParams 的 key 使其与 defaultParams/create() 读取的 key 一致。`);
250
- }
251
- }
252
- }
253
-
254
- // 4) 结构:vfx-params.json
255
- const paramsPath = join(dir, 'vfx-params.json');
256
- if (existsSync(paramsPath)) {
257
- try {
258
- JSON.parse(readFileSync(paramsPath, 'utf8'));
259
- } catch (e) {
260
- errors.push(`[结构] vfx-params.json 不是合法 JSON:${e.message}`);
261
- }
262
- } else {
263
- warnings.push(`[结构] 缺 vfx-params.json(项目调参种子;建议补一个 {schemaVersion,effectId,params:{}})。`);
264
- }
265
- // 注:本库导出 id 可带命名空间(如 run-footstep-dust → build.particle.run_footstep_dust),
266
- // 故不强制「目录名==导出 id」;tooling(generate-index/vfx-cli)统一以目录名作 dir key。
267
-
268
- return { id, errors, warnings };
269
- }
270
-
271
- // ── 主流程 ────────────────────────────────────────────────────────────────────
272
- if (!existsSync(effectsDir)) {
273
- console.error(`✗ effects 目录不存在:${effectsDir}`);
274
- process.exit(1);
275
- }
276
- const ids = (onlyEffect ? [onlyEffect] : readdirSync(effectsDir))
277
- .filter((id) => {
278
- try { return statSync(join(effectsDir, id)).isDirectory(); } catch { return false; }
279
- })
280
- .sort();
281
-
282
- let errorCount = 0;
283
- let warnCount = 0;
284
- const failed = [];
285
- for (const id of ids) {
286
- const { errors, warnings } = validateEffect(id);
287
- errorCount += errors.length;
288
- warnCount += warnings.length;
289
- if (errors.length || warnings.length) {
290
- const mark = errors.length ? '✗' : '⚠';
291
- console.log(`${mark} ${id}`);
292
- for (const e of errors) console.log(` ✗ ${e}`);
293
- for (const w of warnings) console.log(` ⚠ ${w}`);
294
- }
295
- if (errors.length) failed.push(id);
296
- }
297
-
298
- console.log('');
299
- if (errorCount === 0) {
300
- console.log(`✅ Layer 1 特效 lint 通过:${ids.length} 个特效,0 error${warnCount ? `,${warnCount} warning(不阻断)` : ''}。`);
301
- process.exit(0);
302
- }
303
- console.log(`❌ Layer 1 特效 lint:${failed.length}/${ids.length} 个特效有问题,共 ${errorCount} error。`);
304
- process.exit(1);
@@ -1,53 +0,0 @@
1
- #!/usr/bin/env node
2
- // `pnpm validate` 聚合闸(issue #3 串联):一条命令跑完所有 guardrail。
3
- // boundary(纯净契约) → effects(Layer 1 源码 lint) → typecheck → build → check-dist(Layer 2)
4
- // 任一步失败即中止并以非 0 退出(作者本地 / CI PR / 发版 gate 共用,见 release.mjs)。
5
- //
6
- // 用法:
7
- // node tooling/validate.mjs # 全量(含 build + dist 扫描)
8
- // node tooling/validate.mjs --no-build # 快速:只 boundary + effects + typecheck(跳过 build/dist)
9
- import { spawnSync } from 'node:child_process';
10
- import { existsSync } from 'node:fs';
11
- import { resolve, dirname, join } from 'node:path';
12
- import { fileURLToPath } from 'node:url';
13
-
14
- const LIB_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
15
- const noBuild = process.argv.includes('--no-build');
16
-
17
- function bin(name) {
18
- const local = join(LIB_ROOT, 'node_modules', '.bin', name);
19
- return existsSync(local) ? local : name; // 退回 PATH(如 CI 已全局装)
20
- }
21
-
22
- const steps = [
23
- { name: '纯净契约 (validate-boundary)', cmd: 'node', args: ['tooling/validate-boundary.mjs'] },
24
- { name: 'Layer 1 特效 lint (validate-effects)', cmd: 'node', args: ['tooling/validate-effects.mjs'] },
25
- { name: 'typecheck (tsc --noEmit)', cmd: bin('tsc'), args: ['-p', 'tsconfig.json', '--noEmit'] },
26
- ];
27
- if (!noBuild) {
28
- steps.push({ name: 'build (vite)', cmd: bin('vite'), args: ['build'] });
29
- steps.push({ name: 'Layer 2 dist 禁运行时 (check-dist-runtime)', cmd: 'node', args: ['tooling/check-dist-runtime.mjs'] });
30
- }
31
-
32
- console.log(`▶ pnpm validate${noBuild ? ' --no-build' : ''} —— ${steps.length} 步\n`);
33
- const results = [];
34
- for (const [i, step] of steps.entries()) {
35
- console.log(`── [${i + 1}/${steps.length}] ${step.name} ──────────────────────────`);
36
- const r = spawnSync(step.cmd, step.args, { cwd: LIB_ROOT, stdio: 'inherit' });
37
- const ok = r.status === 0;
38
- results.push({ name: step.name, ok });
39
- console.log('');
40
- if (!ok) {
41
- console.log('❌ validate 中止于:' + step.name + (r.error ? `(${r.error.message})` : ` (exit ${r.status})`));
42
- summarize(results, i + 1, steps.length);
43
- process.exit(1);
44
- }
45
- }
46
- console.log('✅ validate 全部通过。');
47
- summarize(results, steps.length, steps.length);
48
-
49
- function summarize(results, ran, total) {
50
- console.log('\n摘要:');
51
- for (const r of results) console.log(` ${r.ok ? '✅' : '❌'} ${r.name}`);
52
- if (ran < total) console.log(` ⋯ 剩余 ${total - ran} 步未执行(已中止)。`);
53
- }