@triflare/fx 2.1.0-beta.1 → 2.1.0-beta.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 (198) hide show
  1. package/package.json +15 -6
  2. package/product.yml +1 -1
  3. package/src/build.js +23 -6
  4. package/src/cli/commands/check.js +3 -1
  5. package/src/cli/commands/feature.js +54 -5
  6. package/src/cli/commands/index.js +2 -0
  7. package/src/cli/commands/lint.js +4 -0
  8. package/src/cli/commands/test.js +186 -0
  9. package/src/cli/context.js +3 -1
  10. package/src/config-edit.js +78 -0
  11. package/src/config.js +77 -0
  12. package/src/features.js +89 -3
  13. package/src/formatter.js +21 -1
  14. package/src/index.js +21 -0
  15. package/src/lint/analyze.js +25 -5
  16. package/src/lint/config-file.js +14 -0
  17. package/src/merge.js +7 -0
  18. package/src/parser.js +78 -3
  19. package/src/project.js +14 -3
  20. package/src/test/discover.js +82 -0
  21. package/src/test/parse.js +72 -0
  22. package/src/test/pattern.js +84 -0
  23. package/src/test/reporters.js +332 -0
  24. package/src/test/runner.js +1457 -0
  25. package/src/vm.js +353 -0
  26. package/vendor/pm-vm-dist/.fx-build.json +7 -0
  27. package/vendor/pm-vm-dist/LICENSE +373 -0
  28. package/vendor/pm-vm-dist/NOTICE +6 -0
  29. package/vendor/pm-vm-dist/dist/iframe-extension-worker.js +1 -0
  30. package/vendor/pm-vm-dist/dist/js/extension-worker/extension-worker.7aa98c98b43d49535069.js +1 -0
  31. package/vendor/pm-vm-dist/dist/media/music/00765be80e1ec0073c7d855eac123b96.mp3 +1 -0
  32. package/vendor/pm-vm-dist/dist/media/music/040abc33bc843ccea91acc82b370a2a5.mp3 +1 -0
  33. package/vendor/pm-vm-dist/dist/media/music/059f32937ff4e15834ad94972c058386.mp3 +1 -0
  34. package/vendor/pm-vm-dist/dist/media/music/0ae12e3be02439395202850a9c469930.mp3 +1 -0
  35. package/vendor/pm-vm-dist/dist/media/music/0ae841a4034e4847205ff93589fe38b7.mp3 +1 -0
  36. package/vendor/pm-vm-dist/dist/media/music/101c38be253a79f22164915a7b41a485.mp3 +1 -0
  37. package/vendor/pm-vm-dist/dist/media/music/1c0f11f5e3bc3a290e3011a863acbf19.mp3 +1 -0
  38. package/vendor/pm-vm-dist/dist/media/music/1e35fc4bf222acbb373be13bef7b494c.mp3 +1 -0
  39. package/vendor/pm-vm-dist/dist/media/music/27470aa23024c67716d745d09d1eeb0d.mp3 +1 -0
  40. package/vendor/pm-vm-dist/dist/media/music/2a2c19ccfb24fd1f611133f46859f697.mp3 +1 -0
  41. package/vendor/pm-vm-dist/dist/media/music/2f42c08774845c95c5afe6aa2011660d.mp3 +1 -0
  42. package/vendor/pm-vm-dist/dist/media/music/317cfa5845ae8684414e36059aa217cf.mp3 +1 -0
  43. package/vendor/pm-vm-dist/dist/media/music/32ab039554155f1cc982d06b17c1b268.mp3 +1 -0
  44. package/vendor/pm-vm-dist/dist/media/music/368b67706d2601ccdb29ba0ae4a06c7d.mp3 +1 -0
  45. package/vendor/pm-vm-dist/dist/media/music/418e78ff0f0202a85f8e51ddceb6383b.mp3 +1 -0
  46. package/vendor/pm-vm-dist/dist/media/music/44fede772a621f62516dafeec7ba6e76.mp3 +1 -0
  47. package/vendor/pm-vm-dist/dist/media/music/45b502e33be78aca1c76962d8f0c9799.mp3 +1 -0
  48. package/vendor/pm-vm-dist/dist/media/music/4efb38b83c696fde2043ad3770fd6cf5.mp3 +1 -0
  49. package/vendor/pm-vm-dist/dist/media/music/555849284aa06b4716e47d7e12341ab4.mp3 +1 -0
  50. package/vendor/pm-vm-dist/dist/media/music/6072de860d1aed016b4b82379510efc7.mp3 +1 -0
  51. package/vendor/pm-vm-dist/dist/media/music/612b52d7a8538850a26cc9a6adda0451.mp3 +1 -0
  52. package/vendor/pm-vm-dist/dist/media/music/6469f82ff2d352bc85e7528635ca6b98.mp3 +1 -0
  53. package/vendor/pm-vm-dist/dist/media/music/66f88d71bbebdff3c79c315946b77516.mp3 +1 -0
  54. package/vendor/pm-vm-dist/dist/media/music/70ce80e25dd12856990938c55f576862.mp3 +1 -0
  55. package/vendor/pm-vm-dist/dist/media/music/79fb30eddc4dd8362cabe7dc80b59da6.mp3 +1 -0
  56. package/vendor/pm-vm-dist/dist/media/music/7f0e67fcb804175c4169eabf81d21040.mp3 +1 -0
  57. package/vendor/pm-vm-dist/dist/media/music/832a2a264fdeb69057ad7208584a49b5.mp3 +1 -0
  58. package/vendor/pm-vm-dist/dist/media/music/83f40654a5eb9dc81f854c2b6f83422c.mp3 +1 -0
  59. package/vendor/pm-vm-dist/dist/media/music/8414e157e2b98d7d4462330c8c4b1cc2.mp3 +1 -0
  60. package/vendor/pm-vm-dist/dist/media/music/8d02b8a9547b47c8506d6c15c913acc1.mp3 +1 -0
  61. package/vendor/pm-vm-dist/dist/media/music/912efc3e11a27b50b879a7ad3bad2eab.mp3 +1 -0
  62. package/vendor/pm-vm-dist/dist/media/music/92771972e8ea5e5e76566218bd193471.mp3 +1 -0
  63. package/vendor/pm-vm-dist/dist/media/music/92e9bc4ec854ae2c3157169fc5ca47a4.mp3 +1 -0
  64. package/vendor/pm-vm-dist/dist/media/music/9b36fd7513f2eeab9876bc75c98262c6.mp3 +1 -0
  65. package/vendor/pm-vm-dist/dist/media/music/9c571ca77e0ff0d1071c39941677febc.mp3 +1 -0
  66. package/vendor/pm-vm-dist/dist/media/music/9d3f26ebd92e02a921b031785dafd6d6.mp3 +1 -0
  67. package/vendor/pm-vm-dist/dist/media/music/9f769e9f239b0a1c048486b24cd9e15b.mp3 +1 -0
  68. package/vendor/pm-vm-dist/dist/media/music/a5a68e9168e8e677cc15494e8680538b.mp3 +1 -0
  69. package/vendor/pm-vm-dist/dist/media/music/a6a3f99085f00a94f2fb5aebfeee2879.mp3 +1 -0
  70. package/vendor/pm-vm-dist/dist/media/music/a6caebbce7ac630fa45cd12fedc920c0.mp3 +1 -0
  71. package/vendor/pm-vm-dist/dist/media/music/b5e23b90e61aaf37f5de98e9ca81fdbb.mp3 +1 -0
  72. package/vendor/pm-vm-dist/dist/media/music/bc4d077743d9bc06d58065fd764f4019.mp3 +1 -0
  73. package/vendor/pm-vm-dist/dist/media/music/bef29ef069663beb6280b70aac6705de.mp3 +1 -0
  74. package/vendor/pm-vm-dist/dist/media/music/c2ba808baa06dac52c1787790396e54c.mp3 +1 -0
  75. package/vendor/pm-vm-dist/dist/media/music/ca1b5393c07468dc2be541c24c3cc687.mp3 +1 -0
  76. package/vendor/pm-vm-dist/dist/media/music/cad1541317d9ef4443a8c6325c7df3be.mp3 +1 -0
  77. package/vendor/pm-vm-dist/dist/media/music/cde6740cc2c1d45a800e1237cc86f9a1.mp3 +1 -0
  78. package/vendor/pm-vm-dist/dist/media/music/d448a74cb6caebc801e921ecdfd0c5d8.mp3 +1 -0
  79. package/vendor/pm-vm-dist/dist/media/music/d5e7e578ff2d4a9b14c0d51f33a24299.mp3 +1 -0
  80. package/vendor/pm-vm-dist/dist/media/music/df6c9e67211654be18df433951b021b0.mp3 +1 -0
  81. package/vendor/pm-vm-dist/dist/media/music/dfe095471b15f0935cde612b52c91a3c.mp3 +1 -0
  82. package/vendor/pm-vm-dist/dist/media/music/e64996bbc174611c2ad3d04a1434f7a0.mp3 +1 -0
  83. package/vendor/pm-vm-dist/dist/media/music/e9e0e78519e67c9a17563711949e4684.mp3 +1 -0
  84. package/vendor/pm-vm-dist/dist/media/music/eb290db5208d65da146b41d34f22f008.mp3 +1 -0
  85. package/vendor/pm-vm-dist/dist/media/music/ebe739a8e09e50ea00c5f1afacc15d6a.mp3 +1 -0
  86. package/vendor/pm-vm-dist/dist/media/music/ed88e2831000ca0e9dceabb95a346597.mp3 +1 -0
  87. package/vendor/pm-vm-dist/dist/media/music/f059a67cea01cb6e49ba358c05d658c9.mp3 +1 -0
  88. package/vendor/pm-vm-dist/dist/media/music/f3a9a9ae967dd5a330f29aa94d5c4680.mp3 +1 -0
  89. package/vendor/pm-vm-dist/dist/media/music/f7e43cdb9928ae9999bc623f2087ee1a.mp3 +1 -0
  90. package/vendor/pm-vm-dist/dist/media/music/fc4058e26c5069cbf08e741771d1e210.mp3 +1 -0
  91. package/vendor/pm-vm-dist/dist/media/music/fd598784d15d0a4673327e341eb40d3b.mp3 +1 -0
  92. package/vendor/pm-vm-dist/dist/scratch-vm.js +187 -0
  93. package/vendor/pm-vm-dist/dist/static/assets/0043f23189fb3cbf2021a88959961ece.mp3 +0 -0
  94. package/vendor/pm-vm-dist/dist/static/assets/00d2dff9d5ece15a67074374b2027590.mp3 +0 -0
  95. package/vendor/pm-vm-dist/dist/static/assets/02996322134b61a0344527cddc61f9bd.png +0 -0
  96. package/vendor/pm-vm-dist/dist/static/assets/06e9863f95061349f2ba2e0fb0b58ddc.png +0 -0
  97. package/vendor/pm-vm-dist/dist/static/assets/0966e5defb1ae50e569fbbc7ebe7f22a.png +0 -0
  98. package/vendor/pm-vm-dist/dist/static/assets/09995475180591a6c64c8744fbfdf4d8.mp3 +0 -0
  99. package/vendor/pm-vm-dist/dist/static/assets/0b18f02669375ad2ee80bef8d07ea7ec.mp3 +0 -0
  100. package/vendor/pm-vm-dist/dist/static/assets/0dcd27b60c185811b19f59d56eaa12f6.mp3 +0 -0
  101. package/vendor/pm-vm-dist/dist/static/assets/0e8ed599d9875c77d52db36de2b2b33f.mp3 +0 -0
  102. package/vendor/pm-vm-dist/dist/static/assets/103fe904046c35630c600cb122492d78.mp3 +0 -0
  103. package/vendor/pm-vm-dist/dist/static/assets/10af80546707afae5507edb828dd35a0.mp3 +0 -0
  104. package/vendor/pm-vm-dist/dist/static/assets/11c35683a60415bf62ee1a7324a25848.png +0 -0
  105. package/vendor/pm-vm-dist/dist/static/assets/129b99ac19024c35228d855f42fb852b.mp3 +0 -0
  106. package/vendor/pm-vm-dist/dist/static/assets/1980303ffc6f5c17c601a9e5992aea77.mp3 +0 -0
  107. package/vendor/pm-vm-dist/dist/static/assets/1a064f9a752824e781ccf79ab66850ed.mp3 +0 -0
  108. package/vendor/pm-vm-dist/dist/static/assets/1a87a73db1ba0193283680e37b35754e.mp3 +0 -0
  109. package/vendor/pm-vm-dist/dist/static/assets/1c4f445589ea1ce7d954fc601f787f95.svg +58 -0
  110. package/vendor/pm-vm-dist/dist/static/assets/1c8f54c61adf1cee25299833093395f3.png +0 -0
  111. package/vendor/pm-vm-dist/dist/static/assets/1c9630b745711a0b829841e8555d76e4.mp3 +0 -0
  112. package/vendor/pm-vm-dist/dist/static/assets/1d1e22600289d252ebda0b9924bf7d3a.png +0 -0
  113. package/vendor/pm-vm-dist/dist/static/assets/1e7ed7b14b056ebd80f434143846fb4e.mp3 +0 -0
  114. package/vendor/pm-vm-dist/dist/static/assets/2b0cfb89b2c8d1badb03136fcf911c3b.mp3 +0 -0
  115. package/vendor/pm-vm-dist/dist/static/assets/2b7b80de8884cc3cf584aa0deb86a59b.mp3 +0 -0
  116. package/vendor/pm-vm-dist/dist/static/assets/3682b35e452a7f4a5a61167d1632e24a.ttf +0 -0
  117. package/vendor/pm-vm-dist/dist/static/assets/368c582b289e75a3c5299e954c2ad735.ttf +0 -0
  118. package/vendor/pm-vm-dist/dist/static/assets/36ed01ff8cd7eccf19f2606b56f6cd9f.svg +18 -0
  119. package/vendor/pm-vm-dist/dist/static/assets/39563f3b38b1fdb805323b1f69dace54.mp3 +0 -0
  120. package/vendor/pm-vm-dist/dist/static/assets/3fbd690bdae84637e34b8b933a164e3d.mp3 +0 -0
  121. package/vendor/pm-vm-dist/dist/static/assets/4240a79d13ecf2792f825457194c96d9.mp3 +0 -0
  122. package/vendor/pm-vm-dist/dist/static/assets/44c364962f4cb85aef2cdec4f0e01fd4.png +0 -0
  123. package/vendor/pm-vm-dist/dist/static/assets/48d66af328a669a1c84c5dc87a4e9a3b.ttf +0 -0
  124. package/vendor/pm-vm-dist/dist/static/assets/4c5757837d14e9fca439064c62617ba1.png +0 -0
  125. package/vendor/pm-vm-dist/dist/static/assets/4c768843c9fa6593074231cd762b83f8.ttf +0 -0
  126. package/vendor/pm-vm-dist/dist/static/assets/4ec6148233578e86736548eb8b9952e7.png +0 -0
  127. package/vendor/pm-vm-dist/dist/static/assets/51a75dd50afd0bc6022dfc86455be19c.mp3 +0 -0
  128. package/vendor/pm-vm-dist/dist/static/assets/57f0f65052d9dac78ee66b11cc696b6a.mp3 +0 -0
  129. package/vendor/pm-vm-dist/dist/static/assets/5888c4ba6b8a0767f8be5bbc81994397.mp3 +0 -0
  130. package/vendor/pm-vm-dist/dist/static/assets/58d62765bbaa69620eb5bcd2c1cbbebb.png +0 -0
  131. package/vendor/pm-vm-dist/dist/static/assets/5c6533a34175f61a9cab627f671111e1.mp3 +0 -0
  132. package/vendor/pm-vm-dist/dist/static/assets/5e0704b815761d38242f1b961ce9ef69.mp3 +0 -0
  133. package/vendor/pm-vm-dist/dist/static/assets/5e80f6caeb6323606a203fbb248fe18a.png +0 -0
  134. package/vendor/pm-vm-dist/dist/static/assets/5f46c8dc3b8cc2296eb73f013dfce3db.ttf +0 -0
  135. package/vendor/pm-vm-dist/dist/static/assets/5fe2f38d9b66e0a55b49e717fcd5c001.png +0 -0
  136. package/vendor/pm-vm-dist/dist/static/assets/61edf73198617b2326d37f232b8abd6f.otf +0 -0
  137. package/vendor/pm-vm-dist/dist/static/assets/638bc6bde253241454993ae12273970b.png +0 -0
  138. package/vendor/pm-vm-dist/dist/static/assets/64bcfe1f13fce38e2ea26280157ff2ce.ttf +0 -0
  139. package/vendor/pm-vm-dist/dist/static/assets/7337f194b0a727f4be98902487dfb615.mp3 +0 -0
  140. package/vendor/pm-vm-dist/dist/static/assets/74496d9086d97aaeeafb3085e9957668.ttf +0 -0
  141. package/vendor/pm-vm-dist/dist/static/assets/76e8f03f3f2ac00ab16dd4f2afee63c4.png +0 -0
  142. package/vendor/pm-vm-dist/dist/static/assets/78a74345ee778afbd6abe637b4f69efc.mp3 +0 -0
  143. package/vendor/pm-vm-dist/dist/static/assets/7a7fdb40997804d4778e2a12bd7599f7.mp3 +0 -0
  144. package/vendor/pm-vm-dist/dist/static/assets/7c5a16e0627c64fd62ec597bb72b879d.mp3 +0 -0
  145. package/vendor/pm-vm-dist/dist/static/assets/827c57667d965940792a82d82345e075.png +0 -0
  146. package/vendor/pm-vm-dist/dist/static/assets/83857346b7c2e6a351c69377e82a3606.mp3 +0 -0
  147. package/vendor/pm-vm-dist/dist/static/assets/83f2d48d0c7d1e6be8a509d4d2c6d0e5.mp3 +0 -0
  148. package/vendor/pm-vm-dist/dist/static/assets/87a3a62c0627e925b92d3ede2f62b04f.ttf +0 -0
  149. package/vendor/pm-vm-dist/dist/static/assets/8996930d8e83f63cc3341172205df460.otf +0 -0
  150. package/vendor/pm-vm-dist/dist/static/assets/8bad9bd9d9a0d53b4a0f4e69bf02764a.png +0 -0
  151. package/vendor/pm-vm-dist/dist/static/assets/8c0c02f16572b730d422f96a3ce53e7f.mp3 +0 -0
  152. package/vendor/pm-vm-dist/dist/static/assets/8ca9997e9ed7534a8d0f349a6125bd24.mp3 +0 -0
  153. package/vendor/pm-vm-dist/dist/static/assets/8d83d454d2c42614a203322393c23a5b.ttf +0 -0
  154. package/vendor/pm-vm-dist/dist/static/assets/8f3259bb74d811177e8e65dcbea39ed4.png +0 -0
  155. package/vendor/pm-vm-dist/dist/static/assets/8fdeee6df0b53509b7f9d623cab482b3.svg +1 -0
  156. package/vendor/pm-vm-dist/dist/static/assets/90eaf6469dbf3a808a3d9739cc77358d.mp3 +0 -0
  157. package/vendor/pm-vm-dist/dist/static/assets/942d5ca1a892e7dc5156d0ae7b438c1b.png +0 -0
  158. package/vendor/pm-vm-dist/dist/static/assets/94b3a496d095dd3b3cfbaabb84a190fd.png +0 -0
  159. package/vendor/pm-vm-dist/dist/static/assets/961bfad11616b6f5c951c58499cf267a.mp3 +0 -0
  160. package/vendor/pm-vm-dist/dist/static/assets/97aa37df83e2c5c2effa09d6622cd3a7.png +0 -0
  161. package/vendor/pm-vm-dist/dist/static/assets/9ad12a9de73f49fb645902987027eb2b.mp3 +0 -0
  162. package/vendor/pm-vm-dist/dist/static/assets/9ec3449723269a806ec6eaba0f8f6cef.ttf +0 -0
  163. package/vendor/pm-vm-dist/dist/static/assets/9f8e3a1db63c7a8fd976beca7e3546c3.mp3 +0 -0
  164. package/vendor/pm-vm-dist/dist/static/assets/a02821bc5d19d616d43d55ac10779101.png +0 -0
  165. package/vendor/pm-vm-dist/dist/static/assets/a05bbf6359407ad7950e115edab178d9.mp3 +0 -0
  166. package/vendor/pm-vm-dist/dist/static/assets/a0b130dbafdc7958b73d162239053173.mp3 +0 -0
  167. package/vendor/pm-vm-dist/dist/static/assets/a61fc8393abec3c0d1e813fc01f252ee.mp3 +0 -0
  168. package/vendor/pm-vm-dist/dist/static/assets/a7c08a472cc8b1c4e0c45ae9b030b414.mp3 +0 -0
  169. package/vendor/pm-vm-dist/dist/static/assets/b52b83f63e8482255d46f40ea454fa01.mp3 +0 -0
  170. package/vendor/pm-vm-dist/dist/static/assets/b6a8e4a96ffcd83154ad2c5cbcb52751.mp3 +0 -0
  171. package/vendor/pm-vm-dist/dist/static/assets/b700cf6f9c368ce49851d287ddd3b63c.mp3 +0 -0
  172. package/vendor/pm-vm-dist/dist/static/assets/bacc13779ca01580b89f39dd2118c22a.mp3 +0 -0
  173. package/vendor/pm-vm-dist/dist/static/assets/bb59f04e7f0aabe8e142ac1607a03dd6.otf +0 -0
  174. package/vendor/pm-vm-dist/dist/static/assets/bd16b79cacee7f23835efba88ed4aa67.png +0 -0
  175. package/vendor/pm-vm-dist/dist/static/assets/c36dd943607003a3229aca77b49d32de.mp3 +0 -0
  176. package/vendor/pm-vm-dist/dist/static/assets/c44ce3bfa465281e765f197d6c3ee7ea.mp3 +0 -0
  177. package/vendor/pm-vm-dist/dist/static/assets/c60f3f06c5fb4d49df7236aeb2f31a9d.png +0 -0
  178. package/vendor/pm-vm-dist/dist/static/assets/d434438e5d193118e4aad3a846e1c7ba.mp3 +0 -0
  179. package/vendor/pm-vm-dist/dist/static/assets/d4b3ce804680eb7d58f6accc7436e807.mp3 +0 -0
  180. package/vendor/pm-vm-dist/dist/static/assets/d6214ab9abf22f5d5dee060cb72f637f.png +0 -0
  181. package/vendor/pm-vm-dist/dist/static/assets/d72b98474fc83d6268886779339e242e.mp3 +0 -0
  182. package/vendor/pm-vm-dist/dist/static/assets/da1a6ebcd442e9d867b31fb21ce6a289.mp3 +0 -0
  183. package/vendor/pm-vm-dist/dist/static/assets/da3126c6a70c4ef84192c33f99996354.mp3 +0 -0
  184. package/vendor/pm-vm-dist/dist/static/assets/e27f7cdf42df8b768345ae83fecffe4b.mp3 +0 -0
  185. package/vendor/pm-vm-dist/dist/static/assets/e6180253665b8248f685c6d96881f75b.mp3 +0 -0
  186. package/vendor/pm-vm-dist/dist/static/assets/eb64e706eb7a3b324ba50d3279a980b1.ttf +0 -0
  187. package/vendor/pm-vm-dist/dist/static/assets/ecb1cbda3b9c4f29ab971734d97e3faf.mp3 +0 -0
  188. package/vendor/pm-vm-dist/dist/static/assets/ed2a96fbdb476efe419246982e357121.mp3 +0 -0
  189. package/vendor/pm-vm-dist/dist/static/assets/edd84717633475a8d3db34087123314a.png +0 -0
  190. package/vendor/pm-vm-dist/dist/static/assets/f04f7b0f673ced51710157ce16e49276.png +0 -0
  191. package/vendor/pm-vm-dist/dist/static/assets/f28b1ecd6fccd6218a837c14b1109da7.mp3 +0 -0
  192. package/vendor/pm-vm-dist/dist/static/assets/f382f43d667b784d1bcd8cfe7aa6fff0.mp3 +0 -0
  193. package/vendor/pm-vm-dist/dist/static/assets/f8d8b9d9f1aa45900b786980418219b1.mp3 +0 -0
  194. package/vendor/pm-vm-dist/dist/static/assets/f974293a11768662b53444715e3439ed.png +0 -0
  195. package/vendor/pm-vm-dist/dist/static/assets/f9868658d8483a6dbcd600e0226dfeef.mp3 +0 -0
  196. package/vendor/pm-vm-dist/dist/static/assets/fad59dd220562492e6722aa8a97f9369.mp3 +0 -0
  197. package/vendor/pm-vm-dist/dist/static/assets/fc383956e315b7ee2bd8dd09de311701.mp3 +0 -0
  198. package/vendor/pm-vm-dist/package.json +3 -0
package/src/vm.js ADDED
@@ -0,0 +1,353 @@
1
+ /**
2
+ * Headless PenguinMod VM adapter.
3
+ *
4
+ * `fx test` runs projects against the real PenguinMod runtime. The VM is a
5
+ * browser module that touches a handful of DOM globals at import time, so this
6
+ * module provides minimal in-memory stand-ins for storage and rendering and
7
+ * requires the VM lazily. Loading a project never draws anything, so the
8
+ * no-op renderer is enough to deserialize targets, costumes, and blocks.
9
+ *
10
+ * The VM is vendored as a git submodule and built into `vendor/pm-vm-dist` by
11
+ * `scripts/build-vm.js`, much like the PenguinMod Packager. `fx test` reports
12
+ * a clear error when that build is missing instead of failing at import time.
13
+ */
14
+ import crypto from "node:crypto";
15
+ import fs from "node:fs";
16
+ import path from "node:path";
17
+ import { createRequire } from "node:module";
18
+ import { fileURLToPath } from "node:url";
19
+ import { runInThisContext } from "node:vm";
20
+
21
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
22
+ const VM_BUNDLE = path.join(
23
+ ROOT,
24
+ "vendor",
25
+ "pm-vm-dist",
26
+ "dist",
27
+ "scratch-vm.js",
28
+ );
29
+
30
+ let modules = null;
31
+
32
+ /** Blob URLs the VM created for project extension sources, keyed by URL. */
33
+ const extensionBlobs = new Map();
34
+
35
+ /** Install the DOM globals the VM reads while it is being imported. */
36
+ function installGlobals() {
37
+ globalThis.location ||= {
38
+ search: "",
39
+ href: "http://localhost/",
40
+ origin: "http://localhost",
41
+ protocol: "http:",
42
+ host: "localhost",
43
+ hostname: "localhost",
44
+ pathname: "/",
45
+ hash: "",
46
+ };
47
+ globalThis.DOMParser ||= class {
48
+ parseFromString() {
49
+ const empty = () => [];
50
+ return {
51
+ querySelectorAll: empty,
52
+ getElementsByTagName: empty,
53
+ querySelector: () => null,
54
+ };
55
+ }
56
+ };
57
+ globalThis.window ||= globalThis;
58
+ globalThis.self ||= globalThis;
59
+
60
+ // The VM loads a project extension like a page does: fetch its URL, wrap the
61
+ // result in a Blob, read that Blob with FileReader, then append a script.
62
+ // These shims keep that flow working without a DOM.
63
+ globalThis.FileReader ||= class {
64
+ readAsText(blob) {
65
+ Promise.resolve(blob.text()).then(
66
+ (result) => this.onload?.({ target: { result } }),
67
+ (error) => this.onerror?.(error),
68
+ );
69
+ }
70
+ };
71
+ if (!globalThis.URL.createObjectURL.__fxHeadless) {
72
+ const createObjectURL = globalThis.URL.createObjectURL.bind(globalThis.URL);
73
+ globalThis.URL.createObjectURL = (blob) => {
74
+ const url = createObjectURL(blob);
75
+ extensionBlobs.set(url, blob);
76
+ return url;
77
+ };
78
+ globalThis.URL.createObjectURL.__fxHeadless = true;
79
+ }
80
+
81
+ /** Run an appended `<script>` whose `src` is one of the VM's Blob URLs. */
82
+ const runScript = (node) => {
83
+ const blob = extensionBlobs.get(node.src);
84
+ if (!blob) {
85
+ node.onerror?.(new Error(`Cannot read extension script ${node.src}`));
86
+ return;
87
+ }
88
+ Promise.resolve(blob.text()).then(
89
+ (source) => {
90
+ try {
91
+ // The unsandboxed loader installs `global.Scratch` before appending
92
+ // the script, so evaluating here matches a browser script tag.
93
+ runInThisContext(source, { filename: node.src });
94
+ node.onload?.();
95
+ } catch (error) {
96
+ node.onerror?.(error);
97
+ }
98
+ },
99
+ (error) => node.onerror?.(error),
100
+ );
101
+ };
102
+
103
+ globalThis.document ||= {
104
+ createElement: (tagName) => ({
105
+ tagName: String(tagName).toUpperCase(),
106
+ style: {},
107
+ getContext: () => null,
108
+ appendChild() {},
109
+ setAttribute() {},
110
+ addEventListener() {},
111
+ remove() {},
112
+ }),
113
+ body: {
114
+ appendChild(node) {
115
+ if (node?.tagName === "SCRIPT") runScript(node);
116
+ },
117
+ },
118
+ querySelector: () => null,
119
+ addEventListener() {},
120
+ };
121
+ }
122
+
123
+ /**
124
+ * Load the vendored PenguinMod VM on first use.
125
+ * @returns {{
126
+ * VirtualMachine: any,
127
+ * centralDispatch: any,
128
+ * extensionApi: any,
129
+ * Cast: any,
130
+ * Color: any,
131
+ * }}
132
+ */
133
+ export function loadVmModules() {
134
+ if (modules) return modules;
135
+ installGlobals();
136
+ const require = createRequire(import.meta.url);
137
+ let api;
138
+ try {
139
+ api = require(VM_BUNDLE);
140
+ } catch (error) {
141
+ if (fs.existsSync(VM_BUNDLE)) throw error;
142
+ throw new Error(
143
+ "Running tests needs the PenguinMod VM build, which is missing. " +
144
+ "Run `npm run build:vm` from the Triflare Fx checkout (or reinstall " +
145
+ "the package).",
146
+ { cause: error },
147
+ );
148
+ }
149
+ if (
150
+ typeof api?.VirtualMachine !== "function" ||
151
+ !api.centralDispatch ||
152
+ !api.extensionApi
153
+ ) {
154
+ throw new Error(
155
+ `The PenguinMod VM at ${VM_BUNDLE} did not export the expected API.`,
156
+ );
157
+ }
158
+ modules = api;
159
+ return modules;
160
+ }
161
+
162
+ const AssetType = {
163
+ ImageVector: { name: "ImageVector", runtimeFormat: "svg" },
164
+ ImageBitmap: { name: "ImageBitmap", runtimeFormat: "png" },
165
+ Sound: { name: "Sound", runtimeFormat: "wav" },
166
+ };
167
+
168
+ const DataFormat = {
169
+ SVG: "svg",
170
+ PNG: "png",
171
+ JPG: "jpg",
172
+ BMP: "bmp",
173
+ GIF: "gif",
174
+ WAV: "wav",
175
+ MP3: "mp3",
176
+ OGG: "ogg",
177
+ FLAC: "flac",
178
+ };
179
+
180
+ const md5 = (data) =>
181
+ crypto.createHash("md5").update(Buffer.from(data)).digest("hex");
182
+
183
+ /** In-memory storage module so the VM can load assets headlessly. */
184
+ export function createStorage() {
185
+ return {
186
+ AssetType,
187
+ DataFormat,
188
+ createAsset(assetType, dataFormat, data, assetId, generateId) {
189
+ const bytes =
190
+ data instanceof Uint8Array ? data : new Uint8Array(data ?? []);
191
+ return {
192
+ assetType,
193
+ dataFormat,
194
+ data: bytes,
195
+ assetId: assetId ?? (generateId ? md5(bytes) : null),
196
+ clean: true,
197
+ encodeDataURI() {
198
+ const base64 = Buffer.from(this.data).toString("base64");
199
+ return `data:${dataFormat};base64,${base64}`;
200
+ },
201
+ decodeText() {
202
+ return Buffer.from(this.data).toString("utf8");
203
+ },
204
+ encodeTextData(text) {
205
+ this.data = new Uint8Array(Buffer.from(text, "utf8"));
206
+ this.assetId = md5(this.data);
207
+ },
208
+ };
209
+ },
210
+ };
211
+ }
212
+
213
+ /** No-op renderer: enough state for blocks that inspect or create skins. */
214
+ export function createRenderer() {
215
+ let nextId = 0;
216
+ const skins = {};
217
+ const createSkin = (size = [0, 0]) => {
218
+ const skinId = nextId++;
219
+ skins[skinId] = {
220
+ id: skinId,
221
+ renderQuality: 1,
222
+ _size: size,
223
+ _setTexture() {},
224
+ };
225
+ return skinId;
226
+ };
227
+ return {
228
+ _allSkins: skins,
229
+ _groupOrdering: [],
230
+ useHighQualityRender: true,
231
+ setLayerGroupOrdering() {},
232
+ setPrivateSkinAccess() {},
233
+ setUseHighQualityRender() {},
234
+ setRenderOffscreen() {},
235
+ setStageSize() {},
236
+ draw() {},
237
+ markDrawableAsNoninteractive() {},
238
+ createDrawable() {
239
+ return nextId++;
240
+ },
241
+ destroyDrawable() {},
242
+ updateDrawableSkinId() {},
243
+ updateDrawablePosition() {},
244
+ updateDrawableDirectionScale() {},
245
+ updateDrawableVisible() {},
246
+ updateDrawableEffect() {},
247
+ setDrawableOrder() {},
248
+ getDrawableOrder() {
249
+ return 0;
250
+ },
251
+ getFencedPositionOfDrawable(id, position) {
252
+ return position;
253
+ },
254
+ getBounds() {
255
+ return { left: 0, right: 0, top: 0, bottom: 0 };
256
+ },
257
+ getBoundsForBubble() {
258
+ return { left: 0, right: 0, top: 0, bottom: 0 };
259
+ },
260
+ isTouchingDrawables() {
261
+ return false;
262
+ },
263
+ drawableTouching() {
264
+ return false;
265
+ },
266
+ getTouchingDrawablesPoint() {
267
+ return [];
268
+ },
269
+ isTouchingColor() {
270
+ return false;
271
+ },
272
+ createSVGSkin() {
273
+ return createSkin();
274
+ },
275
+ createBitmapSkin() {
276
+ return createSkin();
277
+ },
278
+ createPenSkin() {
279
+ return createSkin();
280
+ },
281
+ getSkinSize(skinId) {
282
+ return skins[skinId] ? skins[skinId]._size : [0, 0];
283
+ },
284
+ getSkinRotationCenter() {
285
+ return [0, 0];
286
+ },
287
+ penClear() {},
288
+ penLine() {},
289
+ penPoint() {},
290
+ penStamp() {},
291
+ penTranslate() {},
292
+ };
293
+ }
294
+
295
+ /**
296
+ * Create a headless VM with in-memory storage and a no-op renderer.
297
+ *
298
+ * Project extensions are plain JavaScript, and the only way to run them here is
299
+ * unsandboxed in this Node process: loading one is equivalent to running the
300
+ * project's code with full filesystem and network access. Nothing is loaded
301
+ * unless `allowProjectExtensions` is set. Callers opt in explicitly (for `fx
302
+ * test` that means the user passes `--allow-project-extensions`) to state that
303
+ * the project's extensions are trusted for this run.
304
+ *
305
+ * The dispatch broker is a process-wide singleton; dropping its services keeps
306
+ * one VM's extensions from logging a provider conflict for the next.
307
+ *
308
+ * @param {{allowProjectExtensions?: boolean}} [options]
309
+ */
310
+ export function createVm({ allowProjectExtensions = false } = {}) {
311
+ const { VirtualMachine, centralDispatch } = loadVmModules();
312
+ centralDispatch.services = {};
313
+ const vm = new VirtualMachine();
314
+ vm.attachStorage(createStorage());
315
+ vm.attachRenderer(createRenderer());
316
+ // Denying the load keeps both the extension source and its top-level code
317
+ // from running, even when the project only configured it and no block uses
318
+ // it. The VM reports "Permission to load extension denied" for the project.
319
+ vm.securityManager.canLoadExtensionFromProject = () =>
320
+ Promise.resolve(allowProjectExtensions);
321
+ // Node has neither the web worker nor the iframe sandbox the VM would use, so
322
+ // a permitted extension runs in this process, exactly like PenguinMod's
323
+ // unsandboxed mode. Denied extensions never reach the sandbox selector.
324
+ vm.securityManager.getSandboxMode = () =>
325
+ Promise.resolve(allowProjectExtensions ? "unsandboxed" : "iframe");
326
+ return vm;
327
+ }
328
+
329
+ /**
330
+ * Build a fresh VM and load an sb3/pmp buffer into it.
331
+ * @param {Buffer} sb3
332
+ * @param {{allowProjectExtensions?: boolean}} [options]
333
+ */
334
+ export const loadProject = async (sb3, options = {}) => {
335
+ const vm = createVm(options);
336
+ // Loading an extension logs its whole source to the console; keep reporter
337
+ // output (especially JSON and JUnit) parseable.
338
+ const log = console.log;
339
+ console.log = () => {};
340
+ try {
341
+ await vm.loadProject(sb3);
342
+ } catch (error) {
343
+ try {
344
+ vm.stopAll();
345
+ } catch {
346
+ // Preserve the original load error if cleanup also fails.
347
+ }
348
+ throw error;
349
+ } finally {
350
+ console.log = log;
351
+ }
352
+ return vm;
353
+ };
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "scratch-vm",
3
+ "version": "0.2.0",
4
+ "commit": "75ef6994e55270b9c2a2f3cb2edf6a5bf31cdf2a",
5
+ "inputHash": "9557b90be391ad2f1deaccfd034fddf0a9591c354da50b13e4cf142a8e2654fc",
6
+ "builtAt": "2026-11-07T18:15:07.603Z"
7
+ }