@playcraft/devkit 1.0.1-8.beta.1

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 (209) hide show
  1. package/LICENSE +21 -0
  2. package/cli/bin/playable-scripts.js +2 -0
  3. package/cli/commands/build.js +27 -0
  4. package/cli/commands/builds.js +16 -0
  5. package/cli/commands/dev.js +1 -0
  6. package/cli/commands/pack.js +81 -0
  7. package/cli/commands/vite-dev.js +1 -0
  8. package/core/batch-build.js +181 -0
  9. package/core/cos-uploader.js +49 -0
  10. package/core/index.d.ts +185 -0
  11. package/core/index.js +6 -0
  12. package/core/loaders/gltf-loader.js +45 -0
  13. package/core/options.js +26 -0
  14. package/core/plugins/AdikteevInjectorPlugin.js +47 -0
  15. package/core/plugins/BigoAdsInjectorPlugin.js +7 -0
  16. package/core/plugins/DAPIInjectorPlugin.js +5 -0
  17. package/core/plugins/DebuggerInjectionPlugin.js +6 -0
  18. package/core/plugins/ExitAPIInjectorPlugin.js +10 -0
  19. package/core/plugins/FflateCompressionPlugin.js +225 -0
  20. package/core/plugins/FontInjectionWebpackPlugin.js +17 -0
  21. package/core/plugins/LiftoffInjectorPlugin.js +14 -0
  22. package/core/plugins/MRAIDInjectorPlugin.js +4 -0
  23. package/core/plugins/MintegralInjectorPlugin.js +5 -0
  24. package/core/plugins/PangleInjectorPlugin.js +4 -0
  25. package/core/plugins/SnapchatInjectorPlugin.js +20 -0
  26. package/core/plugins/TikTokInjectorPlugin.js +4 -0
  27. package/core/plugins/UnityInjectorPlugin.js +20 -0
  28. package/core/plugins/ZipPlugin.js +19 -0
  29. package/core/resources/bigoads-config.json +1 -0
  30. package/core/resources/fonts/README.md +31 -0
  31. package/core/resources/fonts/maoken/04e60956bbe055746aa7bca5c229ca8c.woff2 +0 -0
  32. package/core/resources/fonts/maoken/086a30eeaa0f5558062389361dca8685.woff2 +0 -0
  33. package/core/resources/fonts/maoken/1b1c9545aa1da01979d240f38f979b9e.woff2 +0 -0
  34. package/core/resources/fonts/maoken/1dd31fae60d98fcad0812cb4dc54afdd.woff2 +0 -0
  35. package/core/resources/fonts/maoken/1f08966635ca0dc4a64f519863df0dcc.woff2 +0 -0
  36. package/core/resources/fonts/maoken/26aab6a388b04c37fd5a4d066a512e14.woff2 +0 -0
  37. package/core/resources/fonts/maoken/29390db71394eb0d298fa1c339590e45.woff2 +0 -0
  38. package/core/resources/fonts/maoken/2b743d9f736c5cda9250fc8b85f69db8.woff2 +0 -0
  39. package/core/resources/fonts/maoken/2f16b1f8881909c4e65404610460797f.woff2 +0 -0
  40. package/core/resources/fonts/maoken/339dc88f6fd1b09336410154252f858f.woff2 +0 -0
  41. package/core/resources/fonts/maoken/3665591d2bd9a3f31a302a34a76c74dc.woff2 +0 -0
  42. package/core/resources/fonts/maoken/3b58ece5ff16a5addc9adc1c74717d12.woff2 +0 -0
  43. package/core/resources/fonts/maoken/3e3aacd6cd2255be39c97c4ed6eb978b.woff2 +0 -0
  44. package/core/resources/fonts/maoken/3e8cdd750fcbbfcf9a1101c7418eeb36.woff2 +0 -0
  45. package/core/resources/fonts/maoken/3f71b3c0ecb555f9fa88d1b2ae0ec77f.woff2 +0 -0
  46. package/core/resources/fonts/maoken/40f956d892d89a442992737448f0330b.woff2 +0 -0
  47. package/core/resources/fonts/maoken/47ecbb41501b6cb923aaa8b8324bb22f.woff2 +0 -0
  48. package/core/resources/fonts/maoken/4a6e704952e05d217e4f4d2c2a330094.woff2 +0 -0
  49. package/core/resources/fonts/maoken/4ec9fc653a6aaabd2296fac3fe906416.woff2 +0 -0
  50. package/core/resources/fonts/maoken/5d950afcbaac4bb8f44083e5f4572957.woff2 +0 -0
  51. package/core/resources/fonts/maoken/6505b718e31b5b2b29c4f50a2a2b5fe6.woff2 +0 -0
  52. package/core/resources/fonts/maoken/673405ebfdfebae1104f36efa778714d.woff2 +0 -0
  53. package/core/resources/fonts/maoken/695a686e27152d8e81b4459b64d7b6a1.woff2 +0 -0
  54. package/core/resources/fonts/maoken/6b5fb0c960eed7a53f39adb18d0ab28b.woff2 +0 -0
  55. package/core/resources/fonts/maoken/6f53ca6c761ff540e11d5f7111dad67c.woff2 +0 -0
  56. package/core/resources/fonts/maoken/7125e9df28437d053e56f85a908a7343.woff2 +0 -0
  57. package/core/resources/fonts/maoken/731865983ae51e2a4bdc6c194da0b82e.woff2 +0 -0
  58. package/core/resources/fonts/maoken/74d993e9aa68ef636eb1287439d06bb0.woff2 +0 -0
  59. package/core/resources/fonts/maoken/797653f0a72360cd584f625972b5d217.woff2 +0 -0
  60. package/core/resources/fonts/maoken/7af5930fe5e6dade3cf31d3287d6414c.woff2 +0 -0
  61. package/core/resources/fonts/maoken/7c6fe2ee55f924f00b097da208852a34.woff2 +0 -0
  62. package/core/resources/fonts/maoken/8cff8ef36a011ca7e6d103f29b22f98b.woff2 +0 -0
  63. package/core/resources/fonts/maoken/8ff8566257c5df17ddde67b2b48243a4.woff2 +0 -0
  64. package/core/resources/fonts/maoken/93988344f6df526a0033a05d2d927d76.woff2 +0 -0
  65. package/core/resources/fonts/maoken/99d1c5647f1ce540674bd32354c2038f.woff2 +0 -0
  66. package/core/resources/fonts/maoken/a328b132aac2a3f53d97b8e7ad940e1a.woff2 +0 -0
  67. package/core/resources/fonts/maoken/a7dc00c95dd1e7a8eca8053975b0c07c.woff2 +0 -0
  68. package/core/resources/fonts/maoken/ab93ca86fb3e7ad8e95b93f7de5d8f97.woff2 +0 -0
  69. package/core/resources/fonts/maoken/b1b83e931f7e2187786b87545a6ee842.woff2 +0 -0
  70. package/core/resources/fonts/maoken/bb31b8c0ccf2d93af7cc25f19a576970.woff2 +0 -0
  71. package/core/resources/fonts/maoken/bd4a61534e4a0f426dd0d396362b211d.woff2 +0 -0
  72. package/core/resources/fonts/maoken/bec7534be141c6473023f18f5aee7a0e.woff2 +0 -0
  73. package/core/resources/fonts/maoken/c133a76c68c645b46b20e7faf9894ab6.woff2 +0 -0
  74. package/core/resources/fonts/maoken/c2deab25bd23b63c794da537e208c61a.woff2 +0 -0
  75. package/core/resources/fonts/maoken/cf13611d9749dd23fd9737073d2110f6.woff2 +0 -0
  76. package/core/resources/fonts/maoken/d01182e5dd9f51dce713916e8f872bbf.woff2 +0 -0
  77. package/core/resources/fonts/maoken/d1d4c88b2a14e679d3cc9d0ad8e8abdb.woff2 +0 -0
  78. package/core/resources/fonts/maoken/df61c7f5fa45aa6156122c09653f6700.woff2 +0 -0
  79. package/core/resources/fonts/maoken/e1867d1454d8c903ba29bac70f88c211.woff2 +0 -0
  80. package/core/resources/fonts/maoken/ef912f41f9109c5d936005af5552f521.woff2 +0 -0
  81. package/core/resources/fonts/maoken/f0f119744871bb6bec7e0eef4e1ce8ba.woff2 +0 -0
  82. package/core/resources/fonts/maoken/f32a268245b50f6c19bd946239fd8cc0.woff2 +0 -0
  83. package/core/resources/fonts/maoken/f4735851e08da122f471100ecff5c4ae.woff2 +0 -0
  84. package/core/resources/fonts/maoken/f85b8f6994600622f767506ae55d6280.woff2 +0 -0
  85. package/core/resources/fonts/maoken/fc145bdf9c20b67db44156382155b465.woff2 +0 -0
  86. package/core/resources/fonts/maoken/fcd8571ec88b953c893d77c4fd5c6ebc.woff2 +0 -0
  87. package/core/resources/fonts/maoken/fcdcb3f4b26b5c1f45fdd151f45e0c6d.woff2 +0 -0
  88. package/core/resources/fonts/maoken/index.json +59 -0
  89. package/core/resources/fonts/noto-sans-sc-regular.woff2 +0 -0
  90. package/core/resources/fonts/pangmen/0047cd3053f8c06117a653b73e100391.woff2 +0 -0
  91. package/core/resources/fonts/pangmen/008870151036457b39ee8794f5bc7a64.woff2 +0 -0
  92. package/core/resources/fonts/pangmen/01b5355e45274055ad794cc1b1305e69.woff2 +0 -0
  93. package/core/resources/fonts/pangmen/0756361ea2445ec1c500d354fa0867d0.woff2 +0 -0
  94. package/core/resources/fonts/pangmen/091cf1201fbbda8dc98c01e775991bab.woff2 +0 -0
  95. package/core/resources/fonts/pangmen/0c854072dc9998bd02ea8b29b5f1f55e.woff2 +0 -0
  96. package/core/resources/fonts/pangmen/10f3f7f8da6ab5f882e816c97532a4c6.woff2 +0 -0
  97. package/core/resources/fonts/pangmen/141229a7039c633c894ea5168d0ae849.woff2 +0 -0
  98. package/core/resources/fonts/pangmen/1b0b982276f5f93b3c71b3f6b1b81184.woff2 +0 -0
  99. package/core/resources/fonts/pangmen/1e415e51c4efb3182b87665d726fc423.woff2 +0 -0
  100. package/core/resources/fonts/pangmen/1f5fb0c8d785813dc765ff889b491361.woff2 +0 -0
  101. package/core/resources/fonts/pangmen/1fe8065222947d67d4177fe3bdd782cd.woff2 +0 -0
  102. package/core/resources/fonts/pangmen/229674de0b8d208f607468537d10ca48.woff2 +0 -0
  103. package/core/resources/fonts/pangmen/27230f128a3708090bed2eb5bb6ad437.woff2 +0 -0
  104. package/core/resources/fonts/pangmen/2d8f78526cd5d63e22274b3f0e5c4e50.woff2 +0 -0
  105. package/core/resources/fonts/pangmen/2f441859584ca1d060378be9f1173a6a.woff2 +0 -0
  106. package/core/resources/fonts/pangmen/319ed7be5128268f3cf297464b305a55.woff2 +0 -0
  107. package/core/resources/fonts/pangmen/35a41344eec91ef694ca59fda4dc9ccc.woff2 +0 -0
  108. package/core/resources/fonts/pangmen/3715cbec78511ff67e44fdd363b5fd79.woff2 +0 -0
  109. package/core/resources/fonts/pangmen/37d468e8e898644f8fddc9f83f04a5b1.woff2 +0 -0
  110. package/core/resources/fonts/pangmen/384b560d66242e5c1062489020d8749c.woff2 +0 -0
  111. package/core/resources/fonts/pangmen/38ef10dd61436ebaeb8dada7b827595c.woff2 +0 -0
  112. package/core/resources/fonts/pangmen/3e8bdfd8597f723100ff7d6b54e803f9.woff2 +0 -0
  113. package/core/resources/fonts/pangmen/43d57e12e1ec85c68a289d88f920f62e.woff2 +0 -0
  114. package/core/resources/fonts/pangmen/4c4ee1006529ddcc67e7c195d5ddb2e0.woff2 +0 -0
  115. package/core/resources/fonts/pangmen/4f65210dd5014352f4c6eb1a791e56c9.woff2 +0 -0
  116. package/core/resources/fonts/pangmen/5179f5b92b71d655b9094b282ca39161.woff2 +0 -0
  117. package/core/resources/fonts/pangmen/5282fc1cc80a878d744621a36279a1fa.woff2 +0 -0
  118. package/core/resources/fonts/pangmen/57604d7bad494a118e0fa9a6c0f1035a.woff2 +0 -0
  119. package/core/resources/fonts/pangmen/5891eaedeb9999f6b71434efc42407db.woff2 +0 -0
  120. package/core/resources/fonts/pangmen/592e3b1817378c607c8b6b183c89019a.woff2 +0 -0
  121. package/core/resources/fonts/pangmen/5c7c16778cf226e25ef39e475a19afa0.woff2 +0 -0
  122. package/core/resources/fonts/pangmen/5eccbf1ee87f73db786ee0a8441df8a3.woff2 +0 -0
  123. package/core/resources/fonts/pangmen/606a4091be7a3307a2d9adc4d2206b04.woff2 +0 -0
  124. package/core/resources/fonts/pangmen/62da615f3cbb7d19ce02973b009ec5b9.woff2 +0 -0
  125. package/core/resources/fonts/pangmen/63f9972982226af9df16006408160e8a.woff2 +0 -0
  126. package/core/resources/fonts/pangmen/6702bbfff577fce5bc621a91fdcb78bc.woff2 +0 -0
  127. package/core/resources/fonts/pangmen/6cd32dbf440a9b7711cc293b19f4c2d7.woff2 +0 -0
  128. package/core/resources/fonts/pangmen/6ef49a0811c0a0892dfbca6602365e6f.woff2 +0 -0
  129. package/core/resources/fonts/pangmen/7194cb1fa135d58fee1611bb8915fe25.woff2 +0 -0
  130. package/core/resources/fonts/pangmen/71baf3daab3248b0748dd187ab2fbc45.woff2 +0 -0
  131. package/core/resources/fonts/pangmen/7457db009eff249ec587c7d912c5d347.woff2 +0 -0
  132. package/core/resources/fonts/pangmen/762d1c7a28837c08180123e98dbe8cbf.woff2 +0 -0
  133. package/core/resources/fonts/pangmen/76e3d855d8684a1ee4d12cf7a572ca78.woff2 +0 -0
  134. package/core/resources/fonts/pangmen/78a97efdf675a6dcf903b4725de01219.woff2 +0 -0
  135. package/core/resources/fonts/pangmen/7b42a8cbf34bd2a0820ed81bb4730b6d.woff2 +0 -0
  136. package/core/resources/fonts/pangmen/7d07b13e8fc6502f0d2e21a7bb87ddb1.woff2 +0 -0
  137. package/core/resources/fonts/pangmen/7e710c007a48e4410861c67a9a26d4fc.woff2 +0 -0
  138. package/core/resources/fonts/pangmen/7eb8fe8685f6f2d6f0e223337f3a2b2b.woff2 +0 -0
  139. package/core/resources/fonts/pangmen/7ec1b00b541c2c2817cf352e2634bf5b.woff2 +0 -0
  140. package/core/resources/fonts/pangmen/7ffdd7059669ad52af7995b515889eeb.woff2 +0 -0
  141. package/core/resources/fonts/pangmen/818c6b2f12623b5a43d1036cb7f71b61.woff2 +0 -0
  142. package/core/resources/fonts/pangmen/81983731abfefd6a400b83b93d9e3665.woff2 +0 -0
  143. package/core/resources/fonts/pangmen/821ad173f8fd8eccd214241cc651657b.woff2 +0 -0
  144. package/core/resources/fonts/pangmen/83ee6dce60a5f156da3ad18e5cfb3e6e.woff2 +0 -0
  145. package/core/resources/fonts/pangmen/87db73da676a2e7b3e640e84187a5c86.woff2 +0 -0
  146. package/core/resources/fonts/pangmen/89b2edc17dd7ab4332f18b038d268068.woff2 +0 -0
  147. package/core/resources/fonts/pangmen/8b3f89232252b80f6e12ab91d93fda96.woff2 +0 -0
  148. package/core/resources/fonts/pangmen/928a7b39a7c4efdca10f70dfcdb917ac.woff2 +0 -0
  149. package/core/resources/fonts/pangmen/945d03601ca6e8be5f7e6043ecb716a5.woff2 +0 -0
  150. package/core/resources/fonts/pangmen/97de16fa122ebcbff22956b7b2de2f77.woff2 +0 -0
  151. package/core/resources/fonts/pangmen/9882677dd482462a6c1c0a1d7efbb15d.woff2 +0 -0
  152. package/core/resources/fonts/pangmen/9c46188cc1e1711f860fa06d2c29dcc1.woff2 +0 -0
  153. package/core/resources/fonts/pangmen/a279650fd2f2f115ab30cbbf748f85f9.woff2 +0 -0
  154. package/core/resources/fonts/pangmen/a3bfd69906b38ab891501ac997bd577d.woff2 +0 -0
  155. package/core/resources/fonts/pangmen/a4cc4f73c30d1dca3f7abd656705e239.woff2 +0 -0
  156. package/core/resources/fonts/pangmen/a54d94633b077f678d15b96ad9e6a9c9.woff2 +0 -0
  157. package/core/resources/fonts/pangmen/ab9bd513cba626dee079d3f8f35da118.woff2 +0 -0
  158. package/core/resources/fonts/pangmen/b4b057c12d1e44681bdb5c5e92f8e070.woff2 +0 -0
  159. package/core/resources/fonts/pangmen/b8c1e4b1ab3beb334fa773a50c9221b0.woff2 +0 -0
  160. package/core/resources/fonts/pangmen/b93617eda1294f0696193c5237320892.woff2 +0 -0
  161. package/core/resources/fonts/pangmen/bac50a33e26632667ed330c817b37dcf.woff2 +0 -0
  162. package/core/resources/fonts/pangmen/c31e6efe815ccefa90bebc1fa64aba8b.woff2 +0 -0
  163. package/core/resources/fonts/pangmen/c75ff5aa44a05db009b0a38d05f4040b.woff2 +0 -0
  164. package/core/resources/fonts/pangmen/ca70255a9a388b3f33cff49d5abc31f2.woff2 +0 -0
  165. package/core/resources/fonts/pangmen/cb249c8c31a2bee3d6a68fd1c74c061a.woff2 +0 -0
  166. package/core/resources/fonts/pangmen/cb898d32532efe5a7933672228bc620c.woff2 +0 -0
  167. package/core/resources/fonts/pangmen/cc3c33c20e8abd03b9e5acc637985d63.woff2 +0 -0
  168. package/core/resources/fonts/pangmen/cca20bec40d1a11ede1ec496d22cd026.woff2 +0 -0
  169. package/core/resources/fonts/pangmen/cedee24be6ea34e35c53c0ce5edd7af0.woff2 +0 -0
  170. package/core/resources/fonts/pangmen/d42c580643039640037f5c63750ac711.woff2 +0 -0
  171. package/core/resources/fonts/pangmen/dbe1c94b8d92f8915c33fff55b625716.woff2 +0 -0
  172. package/core/resources/fonts/pangmen/dced2d67eceeefa7f45ba44854174b8b.woff2 +0 -0
  173. package/core/resources/fonts/pangmen/dee98b9b0545a42fd3aaa87ec1fa213e.woff2 +0 -0
  174. package/core/resources/fonts/pangmen/e6ac6bb1654d431fe62556a342b16e52.woff2 +0 -0
  175. package/core/resources/fonts/pangmen/eb9be6e3e450bf5706308fcec576c6df.woff2 +0 -0
  176. package/core/resources/fonts/pangmen/ee8cb027810ba5b5d9016774789f747b.woff2 +0 -0
  177. package/core/resources/fonts/pangmen/f0ecb29938306af8e9117a24eefad319.woff2 +0 -0
  178. package/core/resources/fonts/pangmen/f1a1a697d45d7f13a71a0a38424a98a7.woff2 +0 -0
  179. package/core/resources/fonts/pangmen/f203894451810a009d6795f1b0493989.woff2 +0 -0
  180. package/core/resources/fonts/pangmen/f53b808c0d5afb527499049ba225198e.woff2 +0 -0
  181. package/core/resources/fonts/pangmen/f6f69823eda331486d1d1a6e676e1d4a.woff2 +0 -0
  182. package/core/resources/fonts/pangmen/fb1ad0e1ba9ca84b66aa88867ce4109c.woff2 +0 -0
  183. package/core/resources/fonts/pangmen/index.json +95 -0
  184. package/core/resources/fonts/poppins-regular.woff2 +0 -0
  185. package/core/resources/snapchat-config.json +1 -0
  186. package/core/resources/tiktok-config.json +1 -0
  187. package/core/utils/buildDefines.js +3 -0
  188. package/core/utils/buildTemplateString.js +6 -0
  189. package/core/utils/date.js +1 -0
  190. package/core/utils/fontRegistry.js +28 -0
  191. package/core/utils/fontResolver.js +97 -0
  192. package/core/utils/generateAdikteevHtmlWebpackPluginConfig.js +36 -0
  193. package/core/utils/generateBigabidHtmlWebpackPluginConfig.js +13 -0
  194. package/core/utils/generateInMobiHtmlWebpackPluginConfig.js +36 -0
  195. package/core/utils/injectSDKPlugins.js +19 -0
  196. package/core/utils/logOptions.js +5 -0
  197. package/core/utils/mergeOptions.js +6 -0
  198. package/core/utils/parseArgvOptions.js +8 -0
  199. package/core/utils/resolveChannelFold2Zip.js +14 -0
  200. package/core/utils/validateThemeData.js +97 -0
  201. package/core/validators/index.d.ts +242 -0
  202. package/core/validators/pre-build-checker.js +89 -0
  203. package/core/validators/tracking-validator.js +113 -0
  204. package/core/vite.dev.js +27 -0
  205. package/core/webpack.build.js +71 -0
  206. package/core/webpack.common.js +11 -0
  207. package/core/webpack.dev.js +15 -0
  208. package/defines.d.ts +54 -0
  209. package/package.json +85 -0
@@ -0,0 +1,113 @@
1
+ "use strict";const fs=require("fs");const path=require("path");// ============================================================
2
+ // 埋点方法配置
3
+ // ============================================================
4
+ /**
5
+ * 埋点方法配置
6
+ * @typedef {Object} TrackingMethod
7
+ * @property {string} name - 方法名
8
+ * @property {boolean} required - 是否必须调用
9
+ * @property {string} description - 方法描述
10
+ * @property {string[]} channels - 对应的渠道埋点
11
+ *//**
12
+ * 所有埋点方法配置
13
+ * @type {TrackingMethod[]}
14
+ */const TRACKING_METHODS=[{name:"onPlaycraftTrackingInit",required:true,description:"SDK \u521D\u59CB\u5316\u5F00\u59CB",channels:["Bigabid:mraid_viewable","InMobi:Ad_Load_Start","AppLovin:LOADING"]},{name:"onPlaycraftTrackingLoading",required:true,description:"\u6E38\u620F\u8D44\u6E90\u52A0\u8F7D\u4E2D",channels:["Bigabid:game_viewable","InMobi:Ad_Viewable","AppLovin:LOADED"]},{name:"onPlaycraftTrackingLoaded",required:true,description:"\u4E3B\u573A\u666F\u5C31\u7EEA\uFF0C\u7528\u6237\u53EF\u4EA4\u4E92",channels:["AppLovin:DISPLAYED"]},{name:"onPlaycraftChallengeStart",required:true,description:"\u6311\u6218\u5F00\u59CB\uFF08\u9996\u6B21\u7528\u6237\u4EA4\u4E92\uFF09",channels:["Bigabid:engagement","InMobi:First_Engagement","AppLovin:CHALLENGE_STARTED"]},{name:"onPlaycraftChallengeProgress",required:false,description:"\u8FDB\u5EA6\u767E\u5206\u6BD4\u8FFD\u8E2A\uFF08\u53EF\u9009\uFF09",channels:["AppLovin:CHALLENGE_PASS_*"]},{name:"onPlaycraftChallengeSuccess",required:true,description:"\u6E38\u620F\u6311\u6218\u6210\u529F",channels:["AppLovin:CHALLENGE_SOLVED","Bigabid:complete","InMobi:Gameplay_Complete"]},{name:"onPlaycraftChallengeFinish",required:false,description:"\u6E38\u620F\u7ED3\u675F\uFF08\u53EF\u9009\uFF09",channels:["Bigabid:complete","InMobi:Gameplay_Complete","AppLovin:ENDCARD_SHOWN"]},{name:"onPlaycraftInstall",required:true,description:"\u70B9\u51FB\u5B89\u88C5",channels:["Bigabid:click","InMobi:DSP_Click","AppLovin:CTA_CLICKED"]},{name:"onPlaycraftRetry",required:false,description:"\u91CD\u8BD5\u6E38\u620F\uFF08\u53EF\u9009\uFF09",channels:["AppLovin:CHALLENGE_RETRY"]},{name:"onPlaycraftChallengeFailed",required:true,description:"\u6E38\u620F\u6311\u6218\u5931\u8D25",channels:["AppLovin:CHALLENGE_FAILED"]}];/**
15
+ * SDK 静态方法映射到 tracking 方法
16
+ * 这些 SDK 方法内部会调用对应的 tracking 方法
17
+ */const SDK_TO_TRACKING_MAP={"playcraftInit":"onPlaycraftTrackingInit","playcraftStart":"onPlaycraftTrackingLoading","playcraftInteractive":"onPlaycraftTrackingLoaded","playcraftChallengeStart":"onPlaycraftChallengeStart","recordPlaycraftChallengeProgress":"onPlaycraftChallengeProgress","playcraftRecordSuccess":"onPlaycraftChallengeSuccess","playcraftFinish":"onPlaycraftChallengeFinish","playcraftInstall":"onPlaycraftInstall","playcraftRetry":"onPlaycraftRetry","playcraftRecordFailed":"onPlaycraftChallengeFailed"};// ============================================================
18
+ // 文件扫描工具
19
+ // ============================================================
20
+ /**
21
+ * 递归扫描目录获取所有源码文件
22
+ * @param {string} dir - 目录路径
23
+ * @param {string[]} extensions - 文件扩展名列表
24
+ * @param {string[]} excludeDirs - 排除的目录名列表
25
+ * @returns {string[]} 文件路径列表
26
+ */function scanSourceFiles(dir,extensions=[".ts",".tsx",".js",".jsx"],excludeDirs=["node_modules","dist","build",".git",".claude"]){const files=[];function scan(currentDir){if(!fs.existsSync(currentDir))return;const entries=fs.readdirSync(currentDir,{withFileTypes:true});for(const entry of entries){const fullPath=path.join(currentDir,entry.name);if(entry.isDirectory()){// 跳过排除的目录
27
+ if(excludeDirs.includes(entry.name))continue;scan(fullPath)}else if(entry.isFile()){// 检查文件扩展名
28
+ const ext=path.extname(entry.name);if(extensions.includes(ext)){files.push(fullPath)}}}}scan(dir);return files}/**
29
+ * 读取文件内容(带错误处理)
30
+ * @param {string} filePath - 文件路径
31
+ * @returns {string|null} 文件内容或 null
32
+ */function readFileContent(filePath){try{return fs.readFileSync(filePath,"utf-8")}catch(err){console.warn(`[警告] 无法读取文件: ${filePath}`,err.message);return null}}// ============================================================
33
+ // 方法调用检测
34
+ // ============================================================
35
+ /**
36
+ * 检测代码中是否包含 tracking 方法的直接调用
37
+ * @param {string} content - 文件内容
38
+ * @param {string} methodName - 方法名
39
+ * @returns {boolean} 是否找到调用
40
+ *
41
+ * 注意:不依赖对象名称,只检测方法调用模式
42
+ * 匹配:xxx.onPlaycraftXxx( 或 xxx['onPlaycraftXxx']( 或 xxx["onPlaycraftXxx"](
43
+ */function detectDirectTrackingCall(content,methodName){const patterns=[// 匹配 .方法名(
44
+ new RegExp(`\\.${methodName}\\s*\\(`),// 匹配 ['方法名'](
45
+ new RegExp(`\\['${methodName}'\\]\\s*\\(`),// 匹配 ["方法名"](
46
+ new RegExp(`\\["${methodName}"\\]\\s*\\(`)];return patterns.some(pattern=>pattern.test(content))}/**
47
+ * 检测代码中是否包含 SDK 方法调用(会间接触发 tracking)
48
+ * @param {string} content - 文件内容
49
+ * @param {string} sdkMethod - SDK 方法名
50
+ * @returns {boolean} 是否找到调用
51
+ *
52
+ * 注意:不依赖对象名称,只检测方法调用模式
53
+ * 匹配:xxx.playcraftXxx( 或 xxx['playcraftXxx']( 或 xxx["playcraftXxx"](
54
+ */function detectSDKCall(content,sdkMethod){const patterns=[// 匹配 .方法名(
55
+ new RegExp(`\\.${sdkMethod}\\s*\\(`),// 匹配 ['方法名'](
56
+ new RegExp(`\\['${sdkMethod}'\\]\\s*\\(`),// 匹配 ["方法名"](
57
+ new RegExp(`\\["${sdkMethod}"\\]\\s*\\(`)];return patterns.some(pattern=>pattern.test(content))}/**
58
+ * 检测是否导入了 @playcraft/ads-tracking
59
+ * @param {string} content - 文件内容
60
+ * @returns {boolean} 是否导入了 tracking
61
+ */function hasTrackingImport(content){return /from\s+['"]@playcraft\/ads-tracking['"]/.test(content)||/require\s*\(\s*['"]@playcraft\/ads-tracking['"]\s*\)/.test(content)}/**
62
+ * 检测是否导入了 @playcraft/adsdk
63
+ * @param {string} content - 文件内容
64
+ * @returns {boolean} 是否导入了 SDK
65
+ */function hasSDKImport(content){return /from\s+['"]@playcraft\/adsdk['"]/.test(content)||/require\s*\(\s*['"]@playcraft\/adsdk['"]\s*\)/.test(content)}// ============================================================
66
+ // 验证核心逻辑
67
+ // ============================================================
68
+ /**
69
+ * 验证结果
70
+ * @typedef {Object} ValidationResult
71
+ * @property {boolean} success - 验证是否通过
72
+ * @property {'sdk'|'tracking'|'none'} integrationType - 集成类型
73
+ * @property {Array<{method: string, found: boolean, required: boolean, description: string}>} methods - 方法检测结果
74
+ * @property {string[]} errors - 错误信息列表
75
+ * @property {string[]} warnings - 警告信息列表
76
+ * @property {Object} stats - 统计信息
77
+ *//**
78
+ * 验证项目中的埋点调用
79
+ * @param {string} projectRoot - 项目根目录
80
+ * @param {Object} [options] - 验证选项
81
+ * @param {string[]} [options.extensions] - 文件扩展名
82
+ * @param {string[]} [options.excludeDirs] - 排除的目录
83
+ * @param {boolean} [options.verbose] - 是否输出详细信息
84
+ * @returns {ValidationResult} 验证结果
85
+ */function validateTracking(projectRoot,options={}){const{extensions=[".ts",".tsx",".js",".jsx"],excludeDirs=["node_modules","dist","build",".git"],verbose=false}=options;const result={success:false,integrationType:"none",methods:[],errors:[],warnings:[],stats:{totalFiles:0,scannedFiles:0,hasTrackingImport:false,hasSDKImport:false}};// 1. 扫描所有源码文件
86
+ if(verbose)console.log(`\n[扫描] 开始扫描项目: ${projectRoot}`);const files=scanSourceFiles(projectRoot,extensions,excludeDirs);result.stats.totalFiles=files.length;if(files.length===0){result.errors.push("\u672A\u627E\u5230\u4EFB\u4F55\u6E90\u7801\u6587\u4EF6");return result}if(verbose)console.log(`[扫描] 找到 ${files.length} 个源码文件\n`);// 2. 读取所有文件内容并合并(用于全局检测)
87
+ let allContent="";for(const file of files){const content=readFileContent(file);if(content){allContent+=content+"\n";result.stats.scannedFiles++}}// 3. 判断集成类型
88
+ result.stats.hasTrackingImport=hasTrackingImport(allContent);result.stats.hasSDKImport=hasSDKImport(allContent);if(result.stats.hasTrackingImport&&result.stats.hasSDKImport){result.integrationType="sdk";// SDK 优先
89
+ }else if(result.stats.hasSDKImport){result.integrationType="sdk"}else if(result.stats.hasTrackingImport){result.integrationType="tracking"}else{result.errors.push("\u672A\u68C0\u6D4B\u5230 @playcraft/adsdk \u6216 @playcraft/ads-tracking \u7684\u5BFC\u5165");return result}if(verbose){console.log(`[集成类型] ${result.integrationType==="sdk"?"SDK \u96C6\u6210\uFF08\u901A\u8FC7 sdk \u9759\u6001\u65B9\u6CD5\u8C03\u7528\uFF09":"\u76F4\u63A5 tracking \u96C6\u6210"}`);console.log(`[导入检测] SDK: ${result.stats.hasSDKImport?"\u2713":"\u2717"} | Tracking: ${result.stats.hasTrackingImport?"\u2713":"\u2717"}\n`)}// 4. 检测每个埋点方法
90
+ for(const method of TRACKING_METHODS){let found=false;if(result.integrationType==="sdk"){// SDK 集成:检测 SDK 方法调用
91
+ const sdkMethod=Object.keys(SDK_TO_TRACKING_MAP).find(key=>SDK_TO_TRACKING_MAP[key]===method.name);if(sdkMethod){found=detectSDKCall(allContent,sdkMethod)}}else if(result.integrationType==="tracking"){// 直接 tracking 集成:检测 tracking 方法调用
92
+ found=detectDirectTrackingCall(allContent,method.name)}result.methods.push({method:method.name,found,required:method.required,description:method.description});// 必须方法未找到 -> 错误
93
+ if(method.required&&!found){result.errors.push(`❌ 缺少必须的埋点方法: ${method.name} (${method.description})`)}// 可选方法未找到 -> 警告
94
+ if(!method.required&&!found){result.warnings.push(`⚠️ 未找到可选埋点方法: ${method.name} (${method.description})`)}}// 5. 判断验证结果
95
+ result.success=result.errors.length===0;return result}// ============================================================
96
+ // 格式化输出
97
+ // ============================================================
98
+ /**
99
+ * 格式化验证结果并输出到控制台
100
+ * @param {ValidationResult} result - 验证结果
101
+ * @param {boolean} [showDetails=true] - 是否显示详细信息
102
+ */function printValidationResult(result,showDetails=true){console.log("\n"+"=".repeat(60));console.log("\uD83D\uDCCA \u57CB\u70B9\u9A8C\u8BC1\u62A5\u544A");console.log("=".repeat(60));// 统计信息
103
+ console.log(`\n📁 文件统计: 扫描 ${result.stats.scannedFiles}/${result.stats.totalFiles} 个文件`);console.log(`🔗 集成类型: ${result.integrationType==="sdk"?"SDK \u96C6\u6210":result.integrationType==="tracking"?"Tracking \u76F4\u63A5\u96C6\u6210":"\u672A\u68C0\u6D4B\u5230"}`);// 方法检测结果
104
+ if(showDetails){console.log("\n\uD83D\uDCCB \u65B9\u6CD5\u68C0\u6D4B\u660E\u7EC6:");console.log("-".repeat(60));const requiredMethods=result.methods.filter(m=>m.required);const optionalMethods=result.methods.filter(m=>!m.required);console.log("\n\u5FC5\u987B\u65B9\u6CD5:");for(const m of requiredMethods){const status=m.found?"\u2705":"\u274C";console.log(` ${status} ${m.method.padEnd(35)} ${m.found?"\u5DF2\u8C03\u7528":"\u672A\u8C03\u7528"}`)}console.log("\n\u53EF\u9009\u65B9\u6CD5:");for(const m of optionalMethods){const status=m.found?"\u2705":"\u26A0\uFE0F ";console.log(` ${status} ${m.method.padEnd(35)} ${m.found?"\u5DF2\u8C03\u7528":"\u672A\u8C03\u7528"}`)}}// 错误信息
105
+ if(result.errors.length>0){console.log("\n\u274C \u9519\u8BEF:");result.errors.forEach(err=>console.log(` ${err}`))}// 警告信息
106
+ if(result.warnings.length>0){console.log("\n\u26A0\uFE0F \u8B66\u544A:");result.warnings.forEach(warn=>console.log(` ${warn}`))}// 最终结果
107
+ console.log("\n"+"=".repeat(60));if(result.success){console.log("\u2705 \u9A8C\u8BC1\u901A\u8FC7\uFF01\u6240\u6709\u5FC5\u987B\u7684\u57CB\u70B9\u65B9\u6CD5\u90FD\u5DF2\u6B63\u786E\u8C03\u7528\u3002")}else{console.log("\u274C \u9A8C\u8BC1\u5931\u8D25\uFF01\u5B58\u5728\u7F3A\u5931\u7684\u5FC5\u987B\u57CB\u70B9\u65B9\u6CD5\uFF0C\u8BF7\u68C0\u67E5\u4EE3\u7801\u3002")}console.log("=".repeat(60)+"\n")}// ============================================================
108
+ // 导出
109
+ // ============================================================
110
+ module.exports={// 核心验证函数
111
+ validateTracking,printValidationResult,// 配置数据(供外部使用)
112
+ TRACKING_METHODS,SDK_TO_TRACKING_MAP,// 工具函数(供外部使用)
113
+ scanSourceFiles,detectDirectTrackingCall,detectSDKCall,hasTrackingImport,hasSDKImport};
@@ -0,0 +1,27 @@
1
+ "use strict";const path=require("path");const fs=require("fs");const{options}=require("./options.js");const{mergeOptions}=require("./utils/mergeOptions.js");const{buildDefines}=require("./utils/buildDefines.js");const{logOptions}=require("./utils/logOptions.js");/**
2
+ * 创建 Vite 开发配置
3
+ * @param {Partial<import('./index').CLIOptions>} [customOptions] - 自定义选项,会与默认选项合并
4
+ * @param {Record<string, any>} [customDefines] - 额外的 define 常量
5
+ * @param {import('vite').UserConfig} [viteCustomConfig] - 自定义 Vite 配置,会与默认配置合并
6
+ * @returns {import('vite').UserConfig} 最终的 Vite 开发配置
7
+ */function makeViteDevConfig(customOptions,customDefines,viteCustomConfig){const devOptions=mergeOptions(options,customOptions);logOptions(devOptions);customDefines=customDefines||{};viteCustomConfig=viteCustomConfig||{};// 构建 define 常量(复用 webpack 的 buildDefines 逻辑)
8
+ const defines={...buildDefines(),...devOptions.defines,__DEV__:JSON.stringify(devOptions["dev"]===undefined?true:devOptions["dev"]),...customDefines};// 解析 tsconfig/jsconfig 路径别名
9
+ const tsConfigPath=path.resolve(devOptions.tsConfig||"tsconfig.json");const jsConfigPath=path.resolve(devOptions.jsConfig||"jsconfig.json");let aliasConfig={};// 尝试从 tsconfig/jsconfig 中读取 paths 配置
10
+ let configFilePath=null;if(fs.existsSync(tsConfigPath)){configFilePath=tsConfigPath}else if(fs.existsSync(jsConfigPath)){configFilePath=jsConfigPath}if(configFilePath){try{var _config$compilerOptio;const JSON5=require("json5");const configContent=fs.readFileSync(configFilePath,"utf-8");const config=JSON5.parse(configContent);const paths=((_config$compilerOptio=config.compilerOptions)===null||_config$compilerOptio===void 0?void 0:_config$compilerOptio.paths)||{};for(const[alias,targets]of Object.entries(paths)){var _targets$;// 将 tsconfig paths 格式 "foo/*" -> ["src/foo/*"] 转换为 vite alias
11
+ const cleanAlias=alias.replace("/*","");const cleanTarget=((_targets$=targets[0])===null||_targets$===void 0?void 0:_targets$.replace("/*",""))||"";if(cleanAlias&&cleanTarget){aliasConfig[cleanAlias]=path.resolve(cleanTarget)}}}catch(err){console.log("\x1B[33m\u26A0\uFE0F \u8BFB\u53D6\u8DEF\u5F84\u914D\u7F6E\u5931\u8D25: "+err.message+"\x1B[0m")}}// 默认别名
12
+ aliasConfig={assets:path.resolve("assets"),"@":path.resolve("src"),...aliasConfig};// JSON5 插件
13
+ function json5Plugin(){return{name:"vite-plugin-json5",transform(code,id){if(id.endsWith(".json5")){const JSON5=require("json5");const raw=fs.readFileSync(id,"utf-8");const parsed=JSON5.parse(raw);return{code:`export default ${JSON.stringify(parsed)};`,map:null}}}}}// 资源内联插件(模拟 webpack 的 asset/inline 行为)
14
+ function assetInlinePlugin(){return{name:"vite-plugin-asset-inline",enforce:"pre",transform(code,id){// .atlas 文件 -> base64 data URL
15
+ if(id.endsWith(".atlas")){const content=fs.readFileSync(id);const base64=content.toString("base64");return{code:`export default "data:text/atlas;base64,${base64}";`,map:null}}// .fnt 文件 -> base64 data URL
16
+ if(id.endsWith(".fnt")){const content=fs.readFileSync(id);const base64=content.toString("base64");return{code:`export default "data:text/plain;base64,${base64}";`,map:null}}// .xml 文件 -> 原始文本
17
+ if(id.endsWith(".xml")){const raw=fs.readFileSync(id,"utf-8");return{code:`export default ${JSON.stringify(raw)};`,map:null}}}}}// 字体注入插件(dev 模式:读取字体文件 base64 内联到 index.html)
18
+ function fontFamilyPlugin(){const{getOrGenerateFontCSS}=require("./utils/fontResolver");let _themeName=null;let _themeConfig=null;// 尝试检测当前主题名(与 build.js 中 detectCurrentTheme 逻辑一致)
19
+ function detectTheme(){const root=process.cwd();const buildsConfigPath=path.join(root,"builds.config.js");let config={sourceDir:"src/theme",entryFile:"src/theme/index.ts"};if(fs.existsSync(buildsConfigPath)){try{delete require.cache[require.resolve(buildsConfigPath)];const buildsConfig=require(buildsConfigPath);if(buildsConfig.themes){config={...config,...buildsConfig.themes}}}catch(_){/* ignore */}}let name=null;const entryPath=path.join(root,config.entryFile);if(fs.existsSync(entryPath)){const content=fs.readFileSync(entryPath,"utf8");const match=content.match(/from\s+['"]\.\/([^'"]+)['"]/);if(match)name=match[1]}return{themeName:name,themeConfig:config}}return{name:"vite-plugin-font-family",apply:"serve",transformIndexHtml:{order:"pre",handler(){if(_themeName===null){const detected=detectTheme();_themeName=detected.themeName;_themeConfig=detected.themeConfig}if(!_themeName)return"";const css=getOrGenerateFontCSS({projectRoot:process.cwd(),themeName:_themeName,themeConfig:_themeConfig});return css?[{tag:"style",attrs:{"data-playable-fonts":""},children:css.replace(/<\/?style[^>]*>/g,""),injectTo:"head"}]:[]}}}}/** @type {import('vite').UserConfig} */const viteConfig={root:".",resolve:{alias:aliasConfig},plugins:[json5Plugin(),assetInlinePlugin(),fontFamilyPlugin()],define:defines,server:{port:devOptions["port"]||3000,open:devOptions["open"]||false},build:{outDir:devOptions["outDir"]||"dist",sourcemap:true}};// 合并自定义配置
20
+ if(viteCustomConfig){// 简单合并:自定义配置中的字段会覆盖默认配置
21
+ const{plugins:customPlugins,define:customDefine,resolve:customResolve,server:customServer,build:customBuild,...rest}=viteCustomConfig;if(customPlugins){viteConfig.plugins=[...viteConfig.plugins,...customPlugins]}if(customDefine){viteConfig.define={...viteConfig.define,...customDefine}}if(customResolve){viteConfig.resolve={...viteConfig.resolve,...customResolve,alias:{...viteConfig.resolve.alias,...(customResolve.alias||{})}}}if(customServer){viteConfig.server={...viteConfig.server,...customServer}}if(customBuild){viteConfig.build={...viteConfig.build,...customBuild}}Object.assign(viteConfig,rest)}return viteConfig}/**
22
+ * 启动 Vite 开发服务器
23
+ * @param {import('vite').UserConfig} [viteConfig] - Vite 配置,不传则自动创建
24
+ * @param {Partial<import('./index').CLIOptions>} [customOptions] - 自定义选项
25
+ * @param {Record<string, any>} [customDefines] - 额外的 define 常量
26
+ * @param {import('vite').UserConfig} [viteCustomConfig] - 自定义 Vite 配置
27
+ */async function runViteDev(viteConfig,customOptions,customDefines,viteCustomConfig){if(!viteConfig){viteConfig=makeViteDevConfig(customOptions,customDefines,viteCustomConfig)}const{createServer}=require("vite");const server=await createServer(viteConfig);await server.listen();server.printUrls()}exports.makeViteDevConfig=makeViteDevConfig;exports.runViteDev=runViteDev;
@@ -0,0 +1,71 @@
1
+ "use strict";const{merge}=require("webpack-merge");const{webpackCommonConfig}=require("./webpack.common.js");const webpack=require("webpack");const TerserPlugin=require("terser-webpack-plugin");const CopyWebpackPlugin=require("copy-webpack-plugin");const HtmlInlineScriptPlugin=require("html-inline-script-webpack-plugin");const HtmlWebpackPlugin=require("html-webpack-plugin");const MiniCssExtractPlugin=require("mini-css-extract-plugin");const{ZipPlugin}=require("./plugins/ZipPlugin.js");const{FflateCompressionPlugin}=require("./plugins/FflateCompressionPlugin.js");const{generateAdikteevHtmlWebpackPluginConfig}=require("./utils/generateAdikteevHtmlWebpackPluginConfig.js");const{generateBigabidHtmlWebpackPluginConfig}=require("./utils/generateBigabidHtmlWebpackPluginConfig.js");const{generateInMobiHtmlWebpackPluginConfig}=require("./utils/generateInMobiHtmlWebpackPluginConfig.js");const path=require("path");const{mergeOptions}=require("./utils/mergeOptions.js");const{options}=require("./options.js");const{buildDefines}=require("./utils/buildDefines.js");const{buildTemplateString}=require("./utils/buildTemplateString.js");const{logOptions}=require("./utils/logOptions.js");const{injectSDKPlugins}=require("./utils/injectSDKPlugins.js");const{resolveChannelFold2Zip}=require("./utils/resolveChannelFold2Zip.js");const META={COMMON_CHARSET:"utf-8",COMMON_VIEWPORT:"width=device-width,initial-scale=1.0,viewport-fit=cover,maximum-scale=1.0,user-scalable=no",MINTEGRAL_VIEWPORT:"width=device-width,user-scalable=no,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0"};const FontFamily={LIFTOFF:"<style>body{font-family:Roboto,\"Helvetica Neue\",-apple-system,BlinkMacSystemFont,sans-serif}</style>"};/**
2
+ * HtmlWebpackPlugin beforeEmit 钩子。
3
+ * 在最终 HTML 输出前统一修正 meta 标签:
4
+ * - charset → 统一小写 utf-8
5
+ * - viewport → 统一替换为渠道通道规范值
6
+ * 项目模板无需关心 meta 标签,全部由 devkit 构建时自动处理。
7
+ */class MetaTagOverridePlugin{constructor(adNetwork){this.adNetwork=adNetwork}apply(compiler){compiler.hooks.compilation.tap("MetaTagOverridePlugin",compilation=>{HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tap("MetaTagOverridePlugin",data=>{// charset: 统一小写
8
+ data.html=data.html.replace(/<meta\s+charset=["'][^"']*["']\s*\/?>/i,"<meta charset=\"utf-8\" />");// viewport: Mintegral 需要 minimum-scale,其他渠道通用
9
+ const viewportContent=this.adNetwork==="mintegral"?META.MINTEGRAL_VIEWPORT:META.COMMON_VIEWPORT;data.html=data.html.replace(/<meta\s+name=["']viewport["'][^>]*\/?>/i,`<meta name="viewport" content="${viewportContent}" />`);// font-family: Liftoff 渠道需要指定系统字体
10
+ if(this.adNetwork==="liftoff"){const fontStyle=FontFamily.LIFTOFF;data.html=data.html.replace("</head>",fontStyle+"</head>")}return data})})}}/** @type {AD_NETWORK[]} *//** @type {AD_NETWORK[]} */const zipOutputNetworks=["google","pangle","tiktok","vungle","mytarget","mintegral","adikteev","bigabid","inmobi",// InMobi 支持 ZIP 格式(包含 index.html + 资源文件)
11
+ "snapchat","bigoads","liftoff"];/** @type {AD_NETWORK[]} */const zipOutputAllowedNetworks=["facebook","moloco"];/**
12
+ * Creates webpack configuration for production build
13
+ * @param {Partial<import('./index').CLIOptions>} [customOptions] - Custom options to merge with default options
14
+ * @param {Record<string, any>} [customDefines] - Additional defines for webpack.DefinePlugin
15
+ * @param {import('webpack').Configuration} [webpackCustomConfig] - Custom webpack config to merge
16
+ * @returns {import('webpack').Configuration} Final webpack production configuration
17
+ */function makeWebpackBuildConfig(customOptions,customDefines,webpackCustomConfig){const buildOptions=mergeOptions(options,customOptions);logOptions(buildOptions);customDefines=customDefines||{};webpackCustomConfig=webpackCustomConfig||{};/** @type {AD_NETWORK} */const adNetwork=buildOptions["network"];/** @type {AD_PROTOCOL} */const adProtocol=buildOptions["protocol"];let outDir=buildTemplateString(buildOptions.outDir,buildOptions);function getFileName(){let filename=buildTemplateString(buildOptions.filename,buildOptions);if(adNetwork==="mintegral")return filename.replace(/\s+/g,"").replace(/[^a-zA-Z0-9]/g,"_").replace("_fullhash_6_","[fullhash:6]");return filename}const isZipOutput=zipOutputNetworks.includes(adNetwork)||buildOptions.zip&&zipOutputAllowedNetworks.includes(adNetwork);const{isChannelFold2Zip,isZipOutputFinal}=resolveChannelFold2Zip({isChannelFold2ZipOption:buildOptions.isChannelFold2Zip,adNetwork,isZipOutput});let htmlFileName="";if(adNetwork==="mintegral")htmlFileName="index.html";// 修改为 index.html
18
+ else if(isZipOutputFinal)htmlFileName="index.html";else htmlFileName=`${getFileName()}.html`;const metaTags={viewport:META.COMMON_VIEWPORT};if(adNetwork==="mintegral"){metaTags["viewport"]=META.MINTEGRAL_VIEWPORT}if(!buildOptions.skipRecommendedMeta){metaTags["HandheldFriendly"]="True";metaTags["cleartype"]={"http-equiv":"cleartype",content:"on"};metaTags["apple-mobile-web-app-capable"]="yes";metaTags["mobile-web-app-capable"]="yes";metaTags["X-UA-Compatible"]={"http-equiv":"X-UA-Compatible",content:"IE=10"}}let htmlWebpackPluginConfig={template:path.resolve("src/index.html"),filename:htmlFileName,title:`${buildOptions.name} - ${buildOptions.app}`,inlineSource:".(js|css|png|jpg|webp|svg|xml|atlas|mp3|gif|glb|fbx|obj)$",meta:metaTags};if(adNetwork==="adikteev")htmlWebpackPluginConfig=generateAdikteevHtmlWebpackPluginConfig("src/index.html");else if(adNetwork==="bigabid")htmlWebpackPluginConfig=generateBigabidHtmlWebpackPluginConfig("src/index.html");else if(adNetwork==="inmobi")htmlWebpackPluginConfig=generateInMobiHtmlWebpackPluginConfig("src/index.html",buildOptions);const webpackConfig=merge(webpackCommonConfig,{mode:"production",stats:"errors-only",optimization:{usedExports:true,minimize:(buildOptions.obfuscateLevel!==undefined?buildOptions.obfuscateLevel:2)>=1,minimizer:[new TerserPlugin({extractComments:false,terserOptions:{safari10:true,mangle:{reserved:["$"],// ⚠️ 不要开启 properties: true,会混淆 Phaser 等框架的属性名导致运行时错误
19
+ // properties: true,
20
+ toplevel:true},compress:{drop_console:true,arrows:false,drop_debugger:true,unused:true,dead_code:true,// 🔥 增加优化轮数
21
+ passes:3,// 🔥 激进压缩选项
22
+ inline:3,// 最大内联
23
+ collapse_vars:true,// 折叠变量
24
+ reduce_vars:true,// 减少变量
25
+ hoist_vars:true,// 提升变量
26
+ hoist_funs:true,// 提升函数
27
+ sequences:20,// 更长的序列化
28
+ properties:true,// 优化属性访问
29
+ conditionals:true,// 优化条件
30
+ comparisons:true,// 优化比较
31
+ booleans:true,// 优化布尔
32
+ loops:true,// 优化循环
33
+ if_return:true,// 优化 if-return
34
+ join_vars:true,// 合并变量
35
+ negate_iife:true,// 否定 IIFE
36
+ side_effects:true,// 移除副作用
37
+ switches:true,// 优化 switch
38
+ typeofs:true,// 优化 typeof
39
+ unsafe:false,// 不使用 unsafe(避免破坏框架)
40
+ keep_fargs:false,// 移除未使用参数
41
+ drop_console:true},output:{comments:false,quote_style:3,beautify:false,wrap_iife:true,ecma:2015},toplevel:true,keep_classnames:false,keep_fnames:false}})]},plugins:[new HtmlWebpackPlugin(htmlWebpackPluginConfig),new MetaTagOverridePlugin(adNetwork),new webpack.DefinePlugin({...buildDefines(),...buildOptions.defines,...customDefines}),// For some reason needed for PIXI v8
42
+ new webpack.optimize.LimitChunkCountPlugin({maxChunks:1})]},webpackCustomConfig);webpackConfig.output.path=path.resolve(outDir);// 根据混淆等级注入 obfuscator loader
43
+ const obfuscateLevel=buildOptions.obfuscateLevel!==undefined?buildOptions.obfuscateLevel:2;// 获取需要排除压缩的目录列表
44
+ const excludeCompressDirs=buildOptions.excludeCompressDirs||[];if(obfuscateLevel>=2){// 构建各等级的 obfuscator 配置
45
+ const obfuscatorOptions={// 通用基础配置
46
+ target:"browser",sourceMap:false,selfDefending:false,renameProperties:false,disableConsoleOutput:true,splitStrings:false,unicodeEscapeSequence:false};if(obfuscateLevel===2){// 等级2:基础混淆(控制流平坦化 + 字符串数组)
47
+ Object.assign(obfuscatorOptions,{controlFlowFlattening:true,controlFlowFlatteningThreshold:0.75,deadCodeInjection:false,stringArray:true,stringArrayThreshold:0.75,stringArrayEncoding:["base64"],stringArrayIndexShift:true,stringArrayRotate:true,stringArrayShuffle:true,stringArrayWrappersCount:2,stringArrayWrappersChainedCalls:true,stringArrayWrappersType:"function",stringArrayCallsTransform:true,stringArrayCallsTransformThreshold:0.75,transformObjectKeys:true,identifierNamesGenerator:"hexadecimal"})}else if(obfuscateLevel===3){// 等级3:在等级2基础上增加死代码注入
48
+ Object.assign(obfuscatorOptions,{controlFlowFlattening:true,controlFlowFlatteningThreshold:0.75,deadCodeInjection:true,deadCodeInjectionThreshold:0.4,stringArray:true,stringArrayThreshold:0.75,stringArrayEncoding:["base64"],stringArrayIndexShift:true,stringArrayRotate:true,stringArrayShuffle:true,stringArrayWrappersCount:2,stringArrayWrappersChainedCalls:true,stringArrayWrappersType:"function",stringArrayCallsTransform:true,stringArrayCallsTransformThreshold:0.75,transformObjectKeys:true,identifierNamesGenerator:"hexadecimal"})}else if(obfuscateLevel===4){// 等级4:最高强度混淆
49
+ Object.assign(obfuscatorOptions,{controlFlowFlattening:true,controlFlowFlatteningThreshold:1,deadCodeInjection:true,deadCodeInjectionThreshold:0.5,stringArray:true,stringArrayThreshold:1,stringArrayEncoding:["rc4"],stringArrayIndexShift:true,stringArrayRotate:true,stringArrayShuffle:true,stringArrayWrappersCount:3,stringArrayWrappersChainedCalls:true,stringArrayWrappersType:"function",stringArrayCallsTransform:true,stringArrayCallsTransformThreshold:1,transformObjectKeys:true,identifierNamesGenerator:"hexadecimal",splitStrings:true,splitStringsChunkLength:5,unicodeEscapeSequence:true})}console.log(`Obfuscation level: ${obfuscateLevel} (${["","terser-only","basic","dead-code","maximum"][obfuscateLevel]})`);// 构建exclude数组,包含node_modules和用户指定的排除目录
50
+ const excludePatterns=[];if(excludeCompressDirs.length>0){excludeCompressDirs.forEach(dir=>{excludePatterns.push(path.resolve(dir))});console.log(`ℹ️ Excluding directories from compression: ${excludeCompressDirs.join(", ")}`)}webpackConfig.module.rules.push({test:/\.(js|ts)$/,include:[path.resolve("src")],exclude:excludePatterns,enforce:"post",use:{loader:require.resolve("webpack-obfuscator/dist/loader"),options:obfuscatorOptions}})}else if(obfuscateLevel===1){console.log("Obfuscation level: 1 (terser-only)")}else{console.log("Obfuscation level: 0 (no minimize/obfuscate)")}// Adikteev 需要单独提取 CSS 为 style.css,替换默认的 style-loader
51
+ if(adNetwork==="adikteev"){const cssRuleIndex=webpackConfig.module.rules.findIndex(rule=>rule.test&&rule.test.toString().includes("\\.css"));if(cssRuleIndex!==-1){webpackConfig.module.rules[cssRuleIndex]={test:/\.css$/,use:[MiniCssExtractPlugin.loader,"css-loader"]};console.log("\u2705 Adikteev: CSS will be extracted to style.css")}}// 决定是否内联 JS 到 HTML
52
+ // 以下渠道需要分离 JS 文件,不使用 HtmlInlineScriptPlugin:
53
+ // - mintegral: 需要独立的 JS 文件
54
+ // - adikteev: 需要 creative.js 上传到 CDN
55
+ // - bigabid: 需要 main.js 上传到 CDN
56
+ // - facebook (ZIP 模式): Facebook ZIP 格式需要分离文件
57
+ if(adNetwork!=="mintegral"&&adNetwork!=="adikteev"&&adNetwork!=="bigabid"&&!(isZipOutputFinal&&adNetwork==="facebook"))webpackConfig.plugins.push(new HtmlInlineScriptPlugin);// 注入渠道 SDK 插件(公共逻辑)
58
+ injectSDKPlugins(webpackConfig,{network:adNetwork,protocol:adProtocol,orientation:buildOptions.orientation});if(isZipOutputFinal){if(adNetwork==="tiktok"){webpackConfig.plugins.push(new CopyWebpackPlugin({patterns:[{from:path.join(__dirname,"resources","tiktok-config.json"),to:"config.json"}]}))}else if(adNetwork==="snapchat"){const tmpConfigPath=path.join(__dirname,"resources","snapchat-config.json");webpackConfig.plugins.push(new CopyWebpackPlugin({patterns:[{from:tmpConfigPath,to:"config.json"}]}))}else if(adNetwork==="bigoads"){// BigoAds 要求输出 ZIP 包,包含 index.html 和 config.json
59
+ // SDK 注入已在 injectSDKPlugins 中处理
60
+ webpackConfig.plugins.push(new CopyWebpackPlugin({patterns:[{from:path.join(__dirname,"resources","bigoads-config.json"),to:"config.json"}]}))}webpackConfig.plugins.push(new ZipPlugin({filename:isChannelFold2Zip?adNetwork:getFileName(),path:path.resolve(outDir),cleanTempFolder:true// 🗑️ 生成 zip 后删除临时文件夹
61
+ }));webpackConfig.output.path=path.resolve(outDir,adNetwork);if(adNetwork==="adikteev"){webpackConfig.output.filename="creative.js";// Adikteev 需要将 CSS 单独提取为 style.css 文件
62
+ webpackConfig.plugins.push(new MiniCssExtractPlugin({filename:"style.css"}))}else if(adNetwork==="bigabid")webpackConfig.output.filename="main.js";else if(adNetwork==="inmobi")webpackConfig.output.filename="main.js"}// 添加 fflate 压缩插件(仅在打正式包时生效)
63
+ if(buildOptions.fflateCompression){console.log("\u2705 fflate compression enabled (production build only)");webpackConfig.plugins.push(new FflateCompressionPlugin({enabled:true,level:9,keepOriginal:false,logStats:true,googlePlayUrl:buildOptions.googlePlayUrl,appStoreUrl:buildOptions.appStoreUrl}))}return webpackConfig}/**
64
+ * Runs webpack production build
65
+ * @param {import('webpack').Configuration} [webpackConfig] - Webpack configuration to use, creates default if not provided
66
+ * @param {Partial<import('./index').CLIOptions>} [customOptions] - Custom options to merge with default options
67
+ * @param {Record<string, any>} [customDefines] - Additional defines for webpack.DefinePlugin
68
+ * @param {import('webpack').Configuration} [webpackCustomConfig] - Custom webpack config to merge
69
+ */function runBuild(webpackConfig,customOptions,customDefines,webpackCustomConfig){return new Promise((resolve,reject)=>{if(!webpackConfig)webpackConfig=makeWebpackBuildConfig(customOptions,customDefines,webpackCustomConfig);const compiler=webpack(webpackConfig);compiler.run((err,stats)=>{if(err){console.error("Build failed:",err.stack||err);if(err.details){console.error("Error details:",err.details)}return reject(err)}if(stats.hasErrors()){const errors=stats.compilation.errors;console.error(`\n❌ Build finished with ${errors.length} error(s):\n`);// 打印每个错误的详细信息
70
+ errors.forEach((error,index)=>{console.error(`\n[Error ${index+1}/${errors.length}]`);console.error(error.message||error);if(error.stack){console.error(error.stack)}});// 拒绝 Promise 时传递格式化的错误消息
71
+ const errorMessage=errors.map((e,i)=>`[${i+1}] ${e.message||e}`).join("\n");reject(new Error(`Webpack build failed with ${errors.length} error(s):\n${errorMessage}`))}else{console.log(`Build successful!`);resolve()}})})}exports.makeWebpackBuildConfig=makeWebpackBuildConfig;exports.runBuild=runBuild;
@@ -0,0 +1,11 @@
1
+ "use strict";const path=require("path");const fs=require("fs");const TsconfigPathsPlugin=require("tsconfig-paths-webpack-plugin");const{VueLoaderPlugin}=require("vue-loader");const{options}=require("./options");const tsConfigPath=path.resolve(options.tsConfig);const jsConfigPath=path.resolve(options.jsConfig);let jsTsConfigPath=null;if(fs.existsSync(tsConfigPath)){jsTsConfigPath=tsConfigPath}else if(fs.existsSync(jsConfigPath)){jsTsConfigPath=jsConfigPath}/** @type {string[]} List of file extensions that webpack will resolve */const allowedExtensions=[".vue",".ts",".tsx",".js",".json",".json5",".png",".glb",".fbx",".obj",".jpg",".webp",".mp3",".svg",".xml",".atlas",".fnt",".css",".gif",".mp4",".woff",".woff2",".ttf",".otf"];/** @type {import('webpack').Configuration} Base webpack configuration used by both development and build configs */const webpackConfig={entry:path.resolve("src/index"),resolve:{extensions:allowedExtensions,alias:{assets:path.resolve("assets"),vue:"vue/dist/vue.esm-bundler.js","@":path.resolve("src")},plugins:jsTsConfigPath?[new TsconfigPathsPlugin({configFile:jsTsConfigPath,extensions:allowedExtensions})]:[]},output:{filename:"build.js",path:path.resolve("dist")},module:{rules:[// Vue 单文件组件
2
+ {test:/\.vue$/,loader:"vue-loader"},// {
3
+ // test: /\.tsx?$/,
4
+ // use: 'ts-loader',
5
+ // exclude: /node_modules/,
6
+ // },
7
+ {test:/\.ts?$/,exclude:/node_modules/,use:[{loader:"babel-loader",options:{// presets: ['@babel/preset-env'],
8
+ plugins:options.compilation.allowTemplateLiterals===false?["@babel/plugin-transform-template-literals"]:[]}},{loader:"esbuild-loader",options:{loader:"ts",target:"es2015"}}]},{test:/\.css$/,use:["style-loader","css-loader"]},{test:/\.(js|mjs)$/,// exclude: /node_modules/,
9
+ use:{loader:"babel-loader",options:{// presets: ['@babel/preset-env'],
10
+ plugins:options.compilation.allowTemplateLiterals===false?["@babel/plugin-transform-template-literals"]:[]}}},{test:/\.(gltf)$/,loader:path.join(__dirname,"loaders/gltf-loader.js")},{test:/\.(gif|png|jpe?g|webp|mp3|mp4|m4a|ogg|wav|glb|fbx|obj)$/i,type:"asset/inline"},{test:/\.json5$/i,type:"javascript/auto",use:{loader:"json5-loader",options:{esModule:false}}},// 处理 json?url → base64
11
+ {test:/\.json$/i,resourceQuery:/url/,type:"asset/inline",generator:{dataUrl:content=>`data:application/json;base64,${content.toString("base64")}`}},{test:/\.(svg|xml)$/i,type:"asset/source"},{test:/\.atlas$/,type:"asset/inline",generator:{dataUrl:content=>{return`data:text/atlas;base64,${content.toString("base64")}`}}},{test:/\.fnt$/i,type:"asset/inline",generator:{dataUrl:content=>{return`data:text/plain;base64,${content.toString("base64")}`}}},{test:/\.(woff|woff2|eot|ttf|otf)$/i,type:"asset/inline"}]},plugins:[new VueLoaderPlugin]};exports.allowedExtensions=allowedExtensions;exports.webpackCommonConfig=webpackConfig;
@@ -0,0 +1,15 @@
1
+ "use strict";const{merge}=require("webpack-merge");const webpack=require("webpack");const HtmlWebpackPlugin=require("html-webpack-plugin");const WebpackDevServer=require("webpack-dev-server");const{webpackCommonConfig}=require("./webpack.common.js");const path=require("path");const{DebuggerInjectionPlugin}=require("./plugins/DebuggerInjectionPlugin.js");const{options}=require("./options.js");const{mergeOptions}=require("./utils/mergeOptions.js");const{buildDefines}=require("./utils/buildDefines.js");const{logOptions}=require("./utils/logOptions.js");const{injectSDKPlugins}=require("./utils/injectSDKPlugins.js");/**
2
+ * Creates webpack configuration for development
3
+ * @param {Partial<import('./index').CLIOptions>} [customOptions] - Custom options to merge with default options
4
+ * @param {Record<string, any>} [customDefines] - Additional defines for webpack.DefinePlugin
5
+ * @param {import('webpack').Configuration} [webpackCustomConfig] - Custom webpack config to merge
6
+ * @returns {import('webpack').Configuration} Final webpack development configuration
7
+ */function makeWebpackDevConfig(customOptions,customDefines,webpackCustomConfig){const devOptions=mergeOptions(options,customOptions);logOptions(devOptions);customDefines=customDefines||{};webpackCustomConfig=webpackCustomConfig||{};const webpackConfig=merge(webpackCommonConfig,{mode:"development",devtool:"inline-source-map",devServer:{static:{directory:path.resolve("dist")},hot:true,// compress: true,
8
+ port:devOptions["port"],allowedHosts:"all",open:devOptions["open"]},plugins:[new HtmlWebpackPlugin({template:path.resolve("src/index.html"),inlineSource:".(js|css|png|jpg|webp|svg|xml|atlas|mp3|gif|glb|fbx|obj)$",meta:{viewport:"width=device-width,initial-scale=1.0,viewport-fit=cover,maximum-scale=1.0,user-scalable=no"}}),new webpack.DefinePlugin({...buildDefines(),...devOptions.defines,__DEV__:JSON.stringify(devOptions["dev"]===undefined?true:devOptions["dev"]),...customDefines})]},webpackCustomConfig);if(devOptions["debugger"]){webpackConfig.plugins.push(new DebuggerInjectionPlugin(devOptions["debugger"]))}// 注入渠道 SDK 插件(与 build 保持一致)
9
+ injectSDKPlugins(webpackConfig,{network:devOptions["network"],protocol:devOptions["protocol"],orientation:devOptions["orientation"]});return webpackConfig}/**
10
+ * Starts webpack development server
11
+ * @param {import('webpack').Configuration} [webpackConfig] - Webpack configuration to use, creates default if not provided
12
+ * @param {Partial<import('./index').CLIOptions>} [customOptions] - Custom options to merge with default options
13
+ * @param {Record<string, any>} [customDefines] - Additional defines for webpack.DefinePlugin
14
+ * @param {import('webpack').Configuration} [webpackCustomConfig] - Custom webpack config to merge
15
+ */function runDev(webpackConfig,customOptions,customDefines,webpackCustomConfig){if(!webpackConfig)webpackConfig=makeWebpackDevConfig(customOptions,customDefines,webpackCustomConfig);const compiler=webpack(webpackConfig);const server=new WebpackDevServer(webpackConfig.devServer,compiler);server.start()}exports.makeWebpackDevConfig=makeWebpackDevConfig;exports.runDev=runDev;
package/defines.d.ts ADDED
@@ -0,0 +1,54 @@
1
+ declare module '*.png';
2
+ declare module '*.jpg';
3
+ declare module '*.svg';
4
+ declare module '*.webp';
5
+ declare module '*.xml';
6
+ declare module '*.atlas';
7
+ declare module '*.glb';
8
+ declare module '*.gltf';
9
+ declare module '*.fbx';
10
+ declare module '*.obj';
11
+ declare module '*.mp3';
12
+ declare module '*.mp4';
13
+ declare module '*.otf';
14
+ declare module '*.ttf';
15
+ declare module '*.woff';
16
+ declare module '*.woff2';
17
+
18
+ declare const AD_NETWORK:
19
+ | 'preview'
20
+ | 'applovin'
21
+ | 'unity'
22
+ | 'google'
23
+ | 'ironsource'
24
+ | 'facebook'
25
+ | 'moloco'
26
+ | 'mintegral'
27
+ | 'vungle'
28
+ | 'adcolony'
29
+ | 'tapjoy'
30
+ | 'snapchat'
31
+ | 'tiktok'
32
+ | 'appreciate'
33
+ | 'chartboost'
34
+ | 'pangle'
35
+ | 'mytarget'
36
+ | 'liftoff'
37
+ | 'smadex'
38
+ | 'adikteev'
39
+ | 'bigabid'
40
+ | 'inmobi'
41
+ | 'bigoads';
42
+
43
+ declare const AD_PROTOCOL: 'mraid' | 'dapi' | 'nucleo' | 'none';
44
+
45
+ declare const GOOGLE_PLAY_URL: string;
46
+ declare const APP_STORE_URL: string;
47
+ declare const APP: string;
48
+ declare const NAME: string;
49
+ declare const VERSION: string;
50
+ declare const LANGUAGE: 'auto' | 'en' | 'es' | 'zh' | 'hi' | 'ar' | 'fr' | 'de' | 'ja' | 'pt';
51
+ declare const ORIENTATION: 'both' | 'portrait' | 'landscape' | 'square';
52
+ declare const BUILD_HASH: string;
53
+
54
+ declare const __DEV__: boolean;
package/package.json ADDED
@@ -0,0 +1,85 @@
1
+ {
2
+ "name": "@playcraft/devkit",
3
+ "version": "1.0.18.beta.1",
4
+ "description": "HTML5 Playable Ads 构建工具,支持多广告渠道打包",
5
+ "main": "core/index.js",
6
+ "module": "core/index.js",
7
+ "types": "core/index.d.ts",
8
+ "bin": {
9
+ "playable-scripts": "cli/bin/playable-scripts.js"
10
+ },
11
+ "files": [
12
+ "cli",
13
+ "core",
14
+ "LICENSE",
15
+ "defines.d.ts"
16
+ ],
17
+ "scripts": {
18
+ "deploy": "npm run build && cd dist && npm publish --registry https://registry.npmjs.org/ --access public",
19
+ "deploy:beta": "npm run build && cd dist && npm publish --registry https://registry.npmjs.org/ --access public --tag beta"
20
+ },
21
+ "author": "Tencent ADS Team",
22
+ "license": "MIT",
23
+ "dependencies": {
24
+ "@babel/cli": "^7.28.6",
25
+ "@babel/core": "^7.26.0",
26
+ "@babel/preset-env": "^7.26.0",
27
+ "@vue/compiler-sfc": "^3.4.0",
28
+ "ajv": "^8.18.0",
29
+ "babel-loader": "^9.2.1",
30
+ "copy-webpack-plugin": "^13.0.0",
31
+ "cos-nodejs-sdk-v5": "^2.14.4",
32
+ "cross-spawn": "^7.0.3",
33
+ "css-loader": "^7.1.2",
34
+ "esbuild-loader": "^4.2.2",
35
+ "fflate": "^0.8.2",
36
+ "html-inline-script-webpack-plugin": "^3.2.1",
37
+ "html-webpack-plugin": "^5.6.0",
38
+ "json5": "^2.2.3",
39
+ "json5-loader": "^4.0.0",
40
+ "mini-css-extract-plugin": "^2.7.6",
41
+ "prettyjson": "^1.2.5",
42
+ "style-loader": "^4.0.0",
43
+ "tencentcloud-sdk-nodejs-cdn": "^4.1.161",
44
+ "terser-webpack-plugin": "^5.3.10",
45
+ "tsconfig-paths-webpack-plugin": "^4.2.0",
46
+ "vite": "^6.4.1",
47
+ "vue": "^3.4.0",
48
+ "vue-loader": "^17.4.2",
49
+ "webpack": "^5.91.0",
50
+ "webpack-dev-server": "^5.1.0",
51
+ "webpack-merge": "^5.10.0",
52
+ "webpack-obfuscator": "^3.6.0",
53
+ "yazl": "^2.5.1"
54
+ },
55
+ "engines": {
56
+ "node": ">=14.0.0"
57
+ },
58
+ "keywords": [
59
+ "playable",
60
+ "scripts",
61
+ "builder",
62
+ "playable-ads",
63
+ "html5-ads",
64
+ "ad-network",
65
+ "ad-builder",
66
+ "compression",
67
+ "fflate",
68
+ "optimization",
69
+ "tencent",
70
+ "bigoads",
71
+ "mraid",
72
+ "vungle",
73
+ "applovin",
74
+ "google-ads",
75
+ "meta-ads",
76
+ "unity-ads",
77
+ "ironsource",
78
+ "webpack",
79
+ "cli-tool",
80
+ "build-tool"
81
+ ],
82
+ "publishConfig": {
83
+ "registry": "https://www.npmjs.com/registry"
84
+ }
85
+ }