@datamoc/mw_games 0.42.0 → 0.44.2

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 (333) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/README.md +84 -1
  3. package/REFERENCE.md +134 -6
  4. package/api-surface.json +1814 -116
  5. package/data/microgm-essential.PROVENANCE.md +13 -0
  6. package/data/microgm-essential.sf2 +0 -0
  7. package/data/soundfont-catalog.json +8 -0
  8. package/dist/actors/Resource.d.ts.map +1 -1
  9. package/dist/actors/Resource.js +2 -3
  10. package/dist/actors/Resource.js.map +1 -1
  11. package/dist/audio/AdaptiveMusic.d.ts +85 -0
  12. package/dist/audio/AdaptiveMusic.d.ts.map +1 -0
  13. package/dist/audio/AdaptiveMusic.js +230 -0
  14. package/dist/audio/AdaptiveMusic.js.map +1 -0
  15. package/dist/audio/AudioLoader.d.ts +33 -0
  16. package/dist/audio/AudioLoader.d.ts.map +1 -0
  17. package/dist/audio/AudioLoader.js +58 -0
  18. package/dist/audio/AudioLoader.js.map +1 -0
  19. package/dist/audio/AudioViews.d.ts +70 -0
  20. package/dist/audio/AudioViews.d.ts.map +1 -0
  21. package/dist/audio/AudioViews.js +119 -0
  22. package/dist/audio/AudioViews.js.map +1 -0
  23. package/dist/audio/BusBudget.d.ts +89 -0
  24. package/dist/audio/BusBudget.d.ts.map +1 -0
  25. package/dist/audio/BusBudget.js +120 -0
  26. package/dist/audio/BusBudget.js.map +1 -0
  27. package/dist/audio/Channels.d.ts +66 -1
  28. package/dist/audio/Channels.d.ts.map +1 -1
  29. package/dist/audio/Channels.js +119 -31
  30. package/dist/audio/Channels.js.map +1 -1
  31. package/dist/audio/CoreHost.d.ts +114 -0
  32. package/dist/audio/CoreHost.d.ts.map +1 -0
  33. package/dist/audio/CoreHost.js +335 -0
  34. package/dist/audio/CoreHost.js.map +1 -0
  35. package/dist/audio/DecoderGuard.d.ts +139 -0
  36. package/dist/audio/DecoderGuard.d.ts.map +1 -0
  37. package/dist/audio/DecoderGuard.js +208 -0
  38. package/dist/audio/DecoderGuard.js.map +1 -0
  39. package/dist/audio/Fft.d.ts +3 -0
  40. package/dist/audio/Fft.d.ts.map +1 -0
  41. package/dist/audio/Fft.js +45 -0
  42. package/dist/audio/Fft.js.map +1 -0
  43. package/dist/audio/LoopTags.d.ts +18 -0
  44. package/dist/audio/LoopTags.d.ts.map +1 -1
  45. package/dist/audio/LoopTags.js +20 -0
  46. package/dist/audio/LoopTags.js.map +1 -1
  47. package/dist/audio/Midi.d.ts +35 -0
  48. package/dist/audio/Midi.d.ts.map +1 -1
  49. package/dist/audio/Midi.js +71 -1
  50. package/dist/audio/Midi.js.map +1 -1
  51. package/dist/audio/MidiRender.d.ts +47 -0
  52. package/dist/audio/MidiRender.d.ts.map +1 -1
  53. package/dist/audio/MidiRender.js +100 -2
  54. package/dist/audio/MidiRender.js.map +1 -1
  55. package/dist/audio/MidiSynth.d.ts +147 -0
  56. package/dist/audio/MidiSynth.d.ts.map +1 -0
  57. package/dist/audio/MidiSynth.js +205 -0
  58. package/dist/audio/MidiSynth.js.map +1 -0
  59. package/dist/audio/MusicPlayer.d.ts +80 -0
  60. package/dist/audio/MusicPlayer.d.ts.map +1 -0
  61. package/dist/audio/MusicPlayer.js +147 -0
  62. package/dist/audio/MusicPlayer.js.map +1 -0
  63. package/dist/audio/Playable.d.ts +7 -0
  64. package/dist/audio/Playable.d.ts.map +1 -1
  65. package/dist/audio/Playable.js.map +1 -1
  66. package/dist/audio/Positional.d.ts +12 -4
  67. package/dist/audio/Positional.d.ts.map +1 -1
  68. package/dist/audio/Positional.js +5 -2
  69. package/dist/audio/Positional.js.map +1 -1
  70. package/dist/audio/Resample.d.ts +61 -0
  71. package/dist/audio/Resample.d.ts.map +1 -0
  72. package/dist/audio/Resample.js +97 -0
  73. package/dist/audio/Resample.js.map +1 -0
  74. package/dist/audio/SampleDecoder.d.ts +16 -2
  75. package/dist/audio/SampleDecoder.d.ts.map +1 -1
  76. package/dist/audio/SampleDecoder.js +10 -6
  77. package/dist/audio/SampleDecoder.js.map +1 -1
  78. package/dist/audio/Settings.d.ts +99 -0
  79. package/dist/audio/Settings.d.ts.map +1 -0
  80. package/dist/audio/Settings.js +134 -0
  81. package/dist/audio/Settings.js.map +1 -0
  82. package/dist/audio/Sfx.d.ts +148 -0
  83. package/dist/audio/Sfx.d.ts.map +1 -0
  84. package/dist/audio/Sfx.js +213 -0
  85. package/dist/audio/Sfx.js.map +1 -0
  86. package/dist/audio/Sound.d.ts +2 -0
  87. package/dist/audio/Sound.d.ts.map +1 -1
  88. package/dist/audio/Sound.js +3 -0
  89. package/dist/audio/Sound.js.map +1 -1
  90. package/dist/audio/SoundFont.d.ts +5 -3
  91. package/dist/audio/SoundFont.d.ts.map +1 -1
  92. package/dist/audio/SoundFont.js +10 -8
  93. package/dist/audio/SoundFont.js.map +1 -1
  94. package/dist/audio/SpectralDistance.d.ts +34 -0
  95. package/dist/audio/SpectralDistance.d.ts.map +1 -0
  96. package/dist/audio/SpectralDistance.js +64 -0
  97. package/dist/audio/SpectralDistance.js.map +1 -0
  98. package/dist/audio/StreamChannel.d.ts +150 -0
  99. package/dist/audio/StreamChannel.d.ts.map +1 -0
  100. package/dist/audio/StreamChannel.js +390 -0
  101. package/dist/audio/StreamChannel.js.map +1 -0
  102. package/dist/audio/StreamPlayable.d.ts +73 -0
  103. package/dist/audio/StreamPlayable.d.ts.map +1 -0
  104. package/dist/audio/StreamPlayable.js +130 -0
  105. package/dist/audio/StreamPlayable.js.map +1 -0
  106. package/dist/audio/Synth.d.ts +1 -1
  107. package/dist/audio/Synth.d.ts.map +1 -1
  108. package/dist/audio/Synth.js +4 -0
  109. package/dist/audio/Synth.js.map +1 -1
  110. package/dist/audio/SyxBank.d.ts +73 -0
  111. package/dist/audio/SyxBank.d.ts.map +1 -0
  112. package/dist/audio/SyxBank.js +130 -0
  113. package/dist/audio/SyxBank.js.map +1 -0
  114. package/dist/audio/Underrun.d.ts +25 -0
  115. package/dist/audio/Underrun.d.ts.map +1 -0
  116. package/dist/audio/Underrun.js +18 -0
  117. package/dist/audio/Underrun.js.map +1 -0
  118. package/dist/audio/Wav.d.ts +56 -0
  119. package/dist/audio/Wav.d.ts.map +1 -0
  120. package/dist/audio/Wav.js +164 -0
  121. package/dist/audio/Wav.js.map +1 -0
  122. package/dist/audio/WebMidi.d.ts +58 -0
  123. package/dist/audio/WebMidi.d.ts.map +1 -0
  124. package/dist/audio/WebMidi.js +79 -0
  125. package/dist/audio/WebMidi.js.map +1 -0
  126. package/dist/audio/humanize/Onsets.d.ts.map +1 -1
  127. package/dist/audio/humanize/Onsets.js +1 -44
  128. package/dist/audio/humanize/Onsets.js.map +1 -1
  129. package/dist/audio/index.d.ts +43 -7
  130. package/dist/audio/index.d.ts.map +1 -1
  131. package/dist/audio/index.js +21 -3
  132. package/dist/audio/index.js.map +1 -1
  133. package/dist/battle/AttackPreview.d.ts.map +1 -1
  134. package/dist/battle/AttackPreview.js +8 -8
  135. package/dist/battle/AttackPreview.js.map +1 -1
  136. package/dist/battle/StatStages.d.ts.map +1 -1
  137. package/dist/battle/StatStages.js +2 -1
  138. package/dist/battle/StatStages.js.map +1 -1
  139. package/dist/board/Engine.d.ts +2 -0
  140. package/dist/board/Engine.d.ts.map +1 -1
  141. package/dist/board/Engine.js +1 -1
  142. package/dist/board/Engine.js.map +1 -1
  143. package/dist/board/HexSkirmish.d.ts.map +1 -1
  144. package/dist/board/HexSkirmish.js +11 -72
  145. package/dist/board/HexSkirmish.js.map +1 -1
  146. package/dist/board/Tactics.d.ts.map +1 -1
  147. package/dist/board/Tactics.js +3 -5
  148. package/dist/board/Tactics.js.map +1 -1
  149. package/dist/board/tourney.d.ts.map +1 -1
  150. package/dist/board/tourney.js +2 -19
  151. package/dist/board/tourney.js.map +1 -1
  152. package/dist/core/Handles.js +7 -7
  153. package/dist/core/Handles.js.map +1 -1
  154. package/dist/mw_games.global.js +25 -22
  155. package/dist/mwl/conditions.d.ts +12 -0
  156. package/dist/mwl/conditions.d.ts.map +1 -1
  157. package/dist/mwl/conditions.js +49 -8
  158. package/dist/mwl/conditions.js.map +1 -1
  159. package/dist/mwl/index.d.ts +13 -0
  160. package/dist/mwl/index.d.ts.map +1 -1
  161. package/dist/mwl/index.js +7 -0
  162. package/dist/mwl/index.js.map +1 -1
  163. package/dist/mwl/objects.d.ts +31 -0
  164. package/dist/mwl/objects.d.ts.map +1 -0
  165. package/dist/mwl/objects.js +83 -0
  166. package/dist/mwl/objects.js.map +1 -0
  167. package/dist/mwl/package-container.d.ts +185 -0
  168. package/dist/mwl/package-container.d.ts.map +1 -0
  169. package/dist/mwl/package-container.js +446 -0
  170. package/dist/mwl/package-container.js.map +1 -0
  171. package/dist/mwl/package-document.d.ts +217 -0
  172. package/dist/mwl/package-document.d.ts.map +1 -0
  173. package/dist/mwl/package-document.js +639 -0
  174. package/dist/mwl/package-document.js.map +1 -0
  175. package/dist/mwl/package-rom.d.ts +37 -0
  176. package/dist/mwl/package-rom.d.ts.map +1 -0
  177. package/dist/mwl/package-rom.js +96 -0
  178. package/dist/mwl/package-rom.js.map +1 -0
  179. package/dist/mwl/package-verify.d.ts +34 -0
  180. package/dist/mwl/package-verify.d.ts.map +1 -0
  181. package/dist/mwl/package-verify.js +111 -0
  182. package/dist/mwl/package-verify.js.map +1 -0
  183. package/dist/mwl/package.d.ts +184 -0
  184. package/dist/mwl/package.d.ts.map +1 -0
  185. package/dist/mwl/package.js +382 -0
  186. package/dist/mwl/package.js.map +1 -0
  187. package/dist/mwl/schema.d.ts +1 -1
  188. package/dist/mwl/schema.d.ts.map +1 -1
  189. package/dist/mwl/tables.d.ts +2 -0
  190. package/dist/mwl/tables.d.ts.map +1 -1
  191. package/dist/mwl/tables.js +13 -0
  192. package/dist/mwl/tables.js.map +1 -1
  193. package/dist/physics/PhysicsWorld.d.ts.map +1 -1
  194. package/dist/physics/PhysicsWorld.js +4 -3
  195. package/dist/physics/PhysicsWorld.js.map +1 -1
  196. package/dist/physics/XpbdWorld.d.ts +23 -5
  197. package/dist/physics/XpbdWorld.d.ts.map +1 -1
  198. package/dist/physics/XpbdWorld.js +91 -6
  199. package/dist/physics/XpbdWorld.js.map +1 -1
  200. package/dist/testing/FakeAudio.d.ts +149 -0
  201. package/dist/testing/FakeAudio.d.ts.map +1 -0
  202. package/dist/testing/FakeAudio.js +219 -0
  203. package/dist/testing/FakeAudio.js.map +1 -0
  204. package/dist/testing/index.d.ts +18 -0
  205. package/dist/testing/index.d.ts.map +1 -1
  206. package/dist/testing/index.js +22 -0
  207. package/dist/testing/index.js.map +1 -1
  208. package/dist/threads/index.d.ts +2 -2
  209. package/dist/threads/index.d.ts.map +1 -1
  210. package/dist/threads/index.js +1 -1
  211. package/dist/threads/index.js.map +1 -1
  212. package/dist/threads/spawn.d.ts +32 -0
  213. package/dist/threads/spawn.d.ts.map +1 -1
  214. package/dist/threads/spawn.js +35 -11
  215. package/dist/threads/spawn.js.map +1 -1
  216. package/dist/three-d/Collision3D.d.ts +46 -2
  217. package/dist/three-d/Collision3D.d.ts.map +1 -1
  218. package/dist/three-d/Collision3D.js +46 -2
  219. package/dist/three-d/Collision3D.js.map +1 -1
  220. package/dist/three-d/Grid.d.ts +12 -1
  221. package/dist/three-d/Grid.d.ts.map +1 -1
  222. package/dist/three-d/Grid.js +12 -1
  223. package/dist/three-d/Grid.js.map +1 -1
  224. package/dist/three-d/Vox.d.ts +13 -1
  225. package/dist/three-d/Vox.d.ts.map +1 -1
  226. package/dist/three-d/Vox.js +13 -1
  227. package/dist/three-d/Vox.js.map +1 -1
  228. package/dist/two-d/Game.d.ts +32 -1
  229. package/dist/two-d/Game.d.ts.map +1 -1
  230. package/dist/two-d/Game.js +105 -14
  231. package/dist/two-d/Game.js.map +1 -1
  232. package/dist/two-d/Scene2D.d.ts +24 -0
  233. package/dist/two-d/Scene2D.d.ts.map +1 -1
  234. package/dist/two-d/Scene2D.js +30 -0
  235. package/dist/two-d/Scene2D.js.map +1 -1
  236. package/dist/two-d/render/HazeLayer.d.ts.map +1 -1
  237. package/dist/two-d/render/HazeLayer.js +2 -1
  238. package/dist/two-d/render/HazeLayer.js.map +1 -1
  239. package/dist/two-d/render/ImageModifiers.d.ts +4 -2
  240. package/dist/two-d/render/ImageModifiers.d.ts.map +1 -1
  241. package/dist/two-d/render/ImageModifiers.js +13 -8
  242. package/dist/two-d/render/ImageModifiers.js.map +1 -1
  243. package/dist/two-d/render/NeonGlow.d.ts.map +1 -1
  244. package/dist/two-d/render/NeonGlow.js +2 -1
  245. package/dist/two-d/render/NeonGlow.js.map +1 -1
  246. package/dist/two-d/render/Particles.d.ts.map +1 -1
  247. package/dist/two-d/render/Particles.js +2 -1
  248. package/dist/two-d/render/Particles.js.map +1 -1
  249. package/dist/two-d/render/SceneTransition.d.ts +301 -0
  250. package/dist/two-d/render/SceneTransition.d.ts.map +1 -0
  251. package/dist/two-d/render/SceneTransition.js +458 -0
  252. package/dist/two-d/render/SceneTransition.js.map +1 -0
  253. package/dist/two-d/render/ScreenEffects.d.ts.map +1 -1
  254. package/dist/two-d/render/ScreenEffects.js +2 -1
  255. package/dist/two-d/render/ScreenEffects.js.map +1 -1
  256. package/dist/two-d/render/SkeletalSprite.d.ts.map +1 -1
  257. package/dist/two-d/render/SkeletalSprite.js +2 -1
  258. package/dist/two-d/render/SkeletalSprite.js.map +1 -1
  259. package/dist/two-d/render/Types2D.d.ts +2 -2
  260. package/dist/two-d/render/Types2D.d.ts.map +1 -1
  261. package/dist/two-d/render/Types2D.js +2 -2
  262. package/dist/two-d/render/Types2D.js.map +1 -1
  263. package/dist/two-d/render/index.d.ts +2 -0
  264. package/dist/two-d/render/index.d.ts.map +1 -1
  265. package/dist/two-d/render/index.js +1 -0
  266. package/dist/two-d/render/index.js.map +1 -1
  267. package/dist/two-d/ui/Bar.d.ts.map +1 -1
  268. package/dist/two-d/ui/Bar.js +3 -5
  269. package/dist/two-d/ui/Bar.js.map +1 -1
  270. package/dist/two-d/ui/Button.d.ts +23 -0
  271. package/dist/two-d/ui/Button.d.ts.map +1 -1
  272. package/dist/two-d/ui/Button.js +37 -0
  273. package/dist/two-d/ui/Button.js.map +1 -1
  274. package/dist/two-d/ui/Dropdown.d.ts +5 -1
  275. package/dist/two-d/ui/Dropdown.d.ts.map +1 -1
  276. package/dist/two-d/ui/Dropdown.js +8 -1
  277. package/dist/two-d/ui/Dropdown.js.map +1 -1
  278. package/dist/two-d/ui/DropdownView.d.ts +63 -3
  279. package/dist/two-d/ui/DropdownView.d.ts.map +1 -1
  280. package/dist/two-d/ui/DropdownView.js +168 -9
  281. package/dist/two-d/ui/DropdownView.js.map +1 -1
  282. package/dist/two-d/ui/FloatingText.d.ts.map +1 -1
  283. package/dist/two-d/ui/FloatingText.js +2 -1
  284. package/dist/two-d/ui/FloatingText.js.map +1 -1
  285. package/dist/two-d/ui/IconGrid.d.ts.map +1 -1
  286. package/dist/two-d/ui/IconGrid.js +2 -1
  287. package/dist/two-d/ui/IconGrid.js.map +1 -1
  288. package/dist/two-d/ui/ListView.d.ts.map +1 -1
  289. package/dist/two-d/ui/ListView.js +2 -1
  290. package/dist/two-d/ui/ListView.js.map +1 -1
  291. package/dist/two-d/ui/ScrollBox.d.ts +96 -0
  292. package/dist/two-d/ui/ScrollBox.d.ts.map +1 -1
  293. package/dist/two-d/ui/ScrollBox.js +218 -5
  294. package/dist/two-d/ui/ScrollBox.js.map +1 -1
  295. package/dist/two-d/ui/ScrollText.d.ts +131 -0
  296. package/dist/two-d/ui/ScrollText.d.ts.map +1 -0
  297. package/dist/two-d/ui/ScrollText.js +218 -0
  298. package/dist/two-d/ui/ScrollText.js.map +1 -0
  299. package/dist/two-d/ui/Slider.d.ts.map +1 -1
  300. package/dist/two-d/ui/Slider.js +4 -4
  301. package/dist/two-d/ui/Slider.js.map +1 -1
  302. package/dist/two-d/ui/Tooltip.d.ts +70 -1
  303. package/dist/two-d/ui/Tooltip.d.ts.map +1 -1
  304. package/dist/two-d/ui/Tooltip.js +115 -3
  305. package/dist/two-d/ui/Tooltip.js.map +1 -1
  306. package/dist/two-d/ui/Window.d.ts +35 -2
  307. package/dist/two-d/ui/Window.d.ts.map +1 -1
  308. package/dist/two-d/ui/Window.js +58 -3
  309. package/dist/two-d/ui/Window.js.map +1 -1
  310. package/dist/two-d/ui/index.d.ts +7 -5
  311. package/dist/two-d/ui/index.d.ts.map +1 -1
  312. package/dist/two-d/ui/index.js +3 -2
  313. package/dist/two-d/ui/index.js.map +1 -1
  314. package/dist/version.d.ts +1 -1
  315. package/dist/version.js +1 -1
  316. package/package.json +14 -3
  317. package/sbom.cdx.json +19 -260
  318. package/tools/audio.d.mts +75 -0
  319. package/tools/audio.mjs +437 -0
  320. package/tools/browser-driver.mjs +3 -3
  321. package/tools/browser-smoke.mjs +19 -2
  322. package/tools/build-tablebases.mjs +7 -3
  323. package/tools/compile-openings.mjs +7 -3
  324. package/tools/compile-resources.d.mts +2 -0
  325. package/tools/compile-resources.mjs +13 -1
  326. package/tools/emit-page.d.mts +6 -0
  327. package/tools/emit-page.mjs +43 -3
  328. package/tools/find-chrome.mjs +4 -2
  329. package/tools/init.mjs +11 -3
  330. package/tools/mwl.mjs +144 -2
  331. package/tools/play-uci.mjs +9 -5
  332. package/tools/vite.mjs +9 -1
  333. package/tools.md +2 -1
@@ -0,0 +1,639 @@
1
+ import JSON5 from 'json5';
2
+ import { checkConditionSyntax } from './conditions.js';
3
+ import { preprocess } from './grammar.js';
4
+ import { checkPackagePath, PackageFormatError } from './package-container.js';
5
+ /**
6
+ * The `kind` values an `asset` tag accepts.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { PACKAGE_ASSET_KINDS } from '@datamoc/mw_games/mwl';
11
+ *
12
+ * console.log(PACKAGE_ASSET_KINDS.includes('synth-patch')); // true
13
+ * ```
14
+ */
15
+ export const PACKAGE_ASSET_KINDS = [
16
+ 'image',
17
+ 'audio',
18
+ 'music',
19
+ 'font',
20
+ 'data',
21
+ 'synth-patch',
22
+ ];
23
+ /**
24
+ * Kinds that are somebody's creative work: `license` and `source` are mandatory for these.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * import { PACKAGE_MEDIA_KINDS } from '@datamoc/mw_games/mwl';
29
+ *
30
+ * console.log(PACKAGE_MEDIA_KINDS.includes('data')); // false
31
+ * ```
32
+ */
33
+ export const PACKAGE_MEDIA_KINDS = ['image', 'audio', 'music', 'font'];
34
+ /**
35
+ * The MWL tags a package document may hold, and the only ones. Standard game tags (`unit_type`,
36
+ * `event`, ...) are refused in a package: a character expands into them, and keeping the two
37
+ * apart means nothing in a shared file can reach the rules of the game it is dropped into.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * import { PACKAGE_TAGS } from '@datamoc/mw_games/mwl';
42
+ *
43
+ * console.log(PACKAGE_TAGS.includes('character')); // true
44
+ * ```
45
+ */
46
+ export const PACKAGE_TAGS = [
47
+ 'package',
48
+ 'asset',
49
+ 'character',
50
+ 'animation',
51
+ 'sound_effect',
52
+ 'music',
53
+ 'script',
54
+ 'import',
55
+ ];
56
+ /**
57
+ * The event vocabulary a package script may `do`. It is the data-only subset of the MWL event
58
+ * actions plus `play` and `give`; `hook` (game-author code), `endlevel`, `win`, `lose`,
59
+ * `spawn`, `kill` and anything else that decides the outcome of a scenario are not on it.
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * import { PACKAGE_SCRIPT_ACTIONS } from '@datamoc/mw_games/mwl';
64
+ *
65
+ * console.log(PACKAGE_SCRIPT_ACTIONS.includes('hook')); // false
66
+ * ```
67
+ */
68
+ export const PACKAGE_SCRIPT_ACTIONS = [
69
+ 'message',
70
+ 'say',
71
+ 'play',
72
+ 'give',
73
+ 'set_variable',
74
+ 'gold',
75
+ 'heal_unit',
76
+ 'fire_event',
77
+ ];
78
+ /**
79
+ * The capabilities mwg itself knows how to grant; a package may name others, a policy decides.
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * import { KNOWN_PACKAGE_CAPABILITIES } from '@datamoc/mw_games/mwl';
84
+ *
85
+ * console.log(KNOWN_PACKAGE_CAPABILITIES); // ['audio', 'script']
86
+ * ```
87
+ */
88
+ export const KNOWN_PACKAGE_CAPABILITIES = ['audio', 'script'];
89
+ const ID = /^[A-Za-z_][\w.-]*$/;
90
+ const VERSION = /^\d+\.\d+\.\d+(?:[-+][\w.+-]*)?$/;
91
+ const MAX_FRAMES = 4096;
92
+ const MAX_CODE = 65536;
93
+ const MAX_CONDITION = 512;
94
+ const MAX_ACTIONS = 64;
95
+ const MAX_JSON_DEPTH = 16;
96
+ const MAX_IMPORT_DEPTH = 4;
97
+ /** every import read counts, so a fan-out of repeated imports cannot multiply within the depth bound */
98
+ const MAX_IMPORT_READS = 64;
99
+ /**
100
+ * Reads a package document: the MWL tags `package`, `asset`, `character`, `animation`,
101
+ * `sound_effect`, `music`, `script` and `import`, into plain typed data. Macros and
102
+ * `#include`s are expanded first, as for any MWL file. Nothing is executed: a script's `when` is
103
+ * only checked for syntax and its `do` only for membership of the safe vocabulary.
104
+ *
105
+ * `character` is a macro, not a compiled type of its own: it stays one record here and expands
106
+ * into a `unit_type` (plus the animations, effects and scripts it names) when merged. A new
107
+ * compiled table would need its own schema, validator and loader beside the one `unit_type`
108
+ * already has; the macro reuses all of them, and the fields only a renderer or a player of
109
+ * sound reads (portrait, sprite, theme) have no consumer in the rules core anyway.
110
+ *
111
+ * @example
112
+ * ```ts
113
+ * import { parsePackageDocument } from '@datamoc/mw_games/mwl';
114
+ *
115
+ * const document = parsePackageDocument(
116
+ * "[{ tag: 'package', id: 'marta', version: '1.0.0' }, { tag: 'character', id: 'marta', hitpoints: 28 }]",
117
+ * );
118
+ * console.log(document.characters[0]?.stats.hitpoints); // 28
119
+ * ```
120
+ */
121
+ export function parsePackageDocument(source, options = {}) {
122
+ const sections = emptySections();
123
+ readInto(sections, source, options, 0, '', { reads: 0 });
124
+ if (!sections.meta)
125
+ throw new PackageFormatError(`${options.file ?? 'package'}: needs a [package] tag`);
126
+ return finish(sections, sections.meta);
127
+ }
128
+ function emptySections() {
129
+ return {
130
+ assets: [],
131
+ characters: [],
132
+ animations: [],
133
+ soundEffects: [],
134
+ music: [],
135
+ scripts: [],
136
+ imports: [],
137
+ messages: new Set(),
138
+ };
139
+ }
140
+ function finish(sections, meta) {
141
+ return {
142
+ meta,
143
+ assets: sections.assets,
144
+ characters: sections.characters,
145
+ animations: sections.animations,
146
+ soundEffects: sections.soundEffects,
147
+ music: sections.music,
148
+ scripts: sections.scripts,
149
+ imports: sections.imports,
150
+ messages: [...sections.messages].sort(),
151
+ };
152
+ }
153
+ /** parses one source into `sections`; a file `import` recurses with a prefix and a depth bound */
154
+ function readInto(sections, source, options, depth, prefix, budget) {
155
+ const file = options.file ?? 'package.mwl';
156
+ const fail = (message) => {
157
+ throw new PackageFormatError(`${file}: ${message}`);
158
+ };
159
+ let value;
160
+ try {
161
+ value = JSON5.parse(preprocess(source, options));
162
+ }
163
+ catch (error) {
164
+ return fail(error instanceof Error ? error.message : String(error));
165
+ }
166
+ if (!Array.isArray(value))
167
+ return fail('a package document is a top-level array of nodes');
168
+ const text = (raw, where) => {
169
+ if (typeof raw === 'string')
170
+ return raw;
171
+ const gettext = raw?.$gettext;
172
+ if (typeof gettext === 'string' && Object.keys(raw).length === 1) {
173
+ sections.messages.add(gettext);
174
+ return gettext;
175
+ }
176
+ return fail(`${where} must be a string or _("...")`);
177
+ };
178
+ const plain = (raw, where) => {
179
+ if (typeof raw !== 'string')
180
+ return fail(`${where} must be a string`);
181
+ return raw;
182
+ };
183
+ const idOf = (raw, where) => {
184
+ if (typeof raw !== 'string' || !ID.test(raw))
185
+ return fail(`${where} must be an id (letters, digits, _ . -)`);
186
+ return raw;
187
+ };
188
+ const finite = (raw, where) => {
189
+ if (typeof raw !== 'number' || !Number.isFinite(raw))
190
+ return fail(`${where} must be a number`);
191
+ return raw;
192
+ };
193
+ const record = (raw, where) => {
194
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
195
+ return fail(`${where} must be an object`);
196
+ return raw;
197
+ };
198
+ const only = (body, allowed, where) => {
199
+ for (const key of Object.keys(body))
200
+ if (!allowed.includes(key))
201
+ fail(`${where}: unknown field "${key}"`);
202
+ };
203
+ const unique = (list, id, where) => {
204
+ if (list.some((item) => item.id === id))
205
+ fail(`${where}: duplicate id "${id}"`);
206
+ };
207
+ const local = (id) => (prefix ? `${prefix}.${id}` : id);
208
+ const json = (raw, where, level = 0) => {
209
+ if (level > MAX_JSON_DEPTH)
210
+ return fail(`${where} is nested too deep`);
211
+ if (raw === null || typeof raw === 'boolean' || typeof raw === 'string')
212
+ return raw;
213
+ if (typeof raw === 'number')
214
+ return Number.isFinite(raw) ? raw : fail(`${where} must be finite`);
215
+ if (Array.isArray(raw))
216
+ return raw.map((item) => json(item, where, level + 1));
217
+ if (typeof raw === 'object') {
218
+ const out = {};
219
+ for (const [key, item] of Object.entries(raw)) {
220
+ if (key === '__proto__')
221
+ fail(`${where} has a forbidden key`);
222
+ out[key] =
223
+ key === '$gettext' && typeof item === 'string'
224
+ ? (sections.messages.add(item), item)
225
+ : json(item, where, level + 1);
226
+ }
227
+ return out;
228
+ }
229
+ return fail(`${where} must be plain data`);
230
+ };
231
+ const animationOf = (id, body, where) => {
232
+ only(body, ['tag', 'id', 'frames', 'fps', 'loop', 'events'], where);
233
+ const frames = body.frames;
234
+ if (!Array.isArray(frames) || frames.length === 0 || frames.length > MAX_FRAMES)
235
+ fail(`${where}: frames must be a list of 1 to ${MAX_FRAMES} frame numbers`);
236
+ frames.forEach((frame) => {
237
+ if (!Number.isInteger(frame) || frame < 0)
238
+ fail(`${where}: a frame is a non-negative integer`);
239
+ });
240
+ const events = {};
241
+ for (const [frame, event] of Object.entries(record(body.events ?? {}, `${where}.events`))) {
242
+ if (!/^\d+$/.test(frame))
243
+ fail(`${where}: event keys are frame numbers`);
244
+ const entry = record(event, `${where}.events.${frame}`);
245
+ only(entry, ['sound'], `${where}.events.${frame}`);
246
+ events[frame] = { sound: idOrRef(entry.sound, `${where}.events.${frame}.sound`) };
247
+ }
248
+ const fps = body.fps === undefined ? undefined : finite(body.fps, `${where}.fps`);
249
+ if (fps !== undefined && (fps <= 0 || fps > 240))
250
+ fail(`${where}: fps must be between 0 and 240`);
251
+ if (body.loop !== undefined && typeof body.loop !== 'boolean')
252
+ fail(`${where}: loop must be true or false`);
253
+ return {
254
+ id,
255
+ frames: frames,
256
+ ...(fps === undefined ? {} : { fps }),
257
+ ...(body.loop === undefined ? {} : { loop: body.loop }),
258
+ events,
259
+ };
260
+ };
261
+ /** a reference is an id, optionally qualified `package:id` */
262
+ const idOrRef = (raw, where) => {
263
+ if (typeof raw !== 'string' || !/^[A-Za-z_][\w.-]*(?:@[\w.+-]+)?(?::[A-Za-z_][\w.-]*)?$/.test(raw))
264
+ return fail(`${where} must be an id or package:id`);
265
+ return raw;
266
+ };
267
+ const scriptOf = (id, body, where) => {
268
+ only(body, ['tag', 'id', 'on', 'when', 'do', 'code'], where);
269
+ const on = plain(body.on, `${where}.on`);
270
+ if (!ID.test(on))
271
+ fail(`${where}: on must be an event name`);
272
+ let when;
273
+ if (body.when !== undefined) {
274
+ when = plain(body.when, `${where}.when`);
275
+ if (when.length > MAX_CONDITION)
276
+ fail(`${where}: when is longer than ${MAX_CONDITION} characters`);
277
+ try {
278
+ checkConditionSyntax(when);
279
+ }
280
+ catch (error) {
281
+ fail(`${where}: when: ${error instanceof Error ? error.message : String(error)}`);
282
+ }
283
+ }
284
+ const rawDo = body.do ?? [];
285
+ if (!Array.isArray(rawDo) || rawDo.length > MAX_ACTIONS)
286
+ fail(`${where}: do is a list of at most ${MAX_ACTIONS} actions`);
287
+ const actions = rawDo.map((item, index) => {
288
+ const action = record(item, `${where}.do[${index}]`);
289
+ let tag = action.tag;
290
+ let fields = action;
291
+ if (typeof tag !== 'string') {
292
+ // the named form `{ message: { text: ... } }`
293
+ const keys = Object.keys(action);
294
+ tag = keys.length === 1 ? keys[0] : undefined;
295
+ fields = tag ? record(action[tag], `${where}.do[${index}]`) : action;
296
+ }
297
+ if (typeof tag !== 'string' || !PACKAGE_SCRIPT_ACTIONS.includes(tag))
298
+ return fail(`${where}.do[${index}]: "${String(tag)}" is not an allowed action (${PACKAGE_SCRIPT_ACTIONS.join(', ')})`);
299
+ const attributes = {};
300
+ for (const [key, raw] of Object.entries(fields)) {
301
+ if (key === 'tag')
302
+ continue;
303
+ attributes[key] =
304
+ typeof raw === 'number' || typeof raw === 'boolean'
305
+ ? raw
306
+ : text(raw, `${where}.do[${index}].${key}`);
307
+ }
308
+ return { tag, attributes };
309
+ });
310
+ let code;
311
+ if (body.code !== undefined) {
312
+ code = plain(body.code, `${where}.code`);
313
+ if (code.length > MAX_CODE)
314
+ fail(`${where}: code is longer than ${MAX_CODE} characters`);
315
+ }
316
+ return {
317
+ id,
318
+ on,
319
+ ...(when === undefined ? {} : { when }),
320
+ do: actions,
321
+ ...(code === undefined ? {} : { code }),
322
+ };
323
+ };
324
+ const characterOf = (id, body) => {
325
+ const where = `[character] ${id}`;
326
+ unique(sections.characters, id, where);
327
+ const animations = {};
328
+ for (const [name, item] of Object.entries(record(body.animations ?? {}, `${where}.animations`))) {
329
+ if (typeof item === 'string')
330
+ animations[name] = idOrRef(item, `${where}.animations.${name}`);
331
+ else {
332
+ // inline: becomes an animation of its own, named after the character
333
+ const animationId = `${id}.${name}`;
334
+ unique(sections.animations, animationId, where);
335
+ sections.animations.push(animationOf(animationId, record(item, `${where}.animations.${name}`), `${where}.animations.${name}`));
336
+ animations[name] = animationId;
337
+ }
338
+ }
339
+ const sounds = {};
340
+ for (const [name, item] of Object.entries(record(body.sounds ?? {}, `${where}.sounds`))) {
341
+ if (typeof item === 'string')
342
+ sounds[name] = idOrRef(item, `${where}.sounds.${name}`);
343
+ else {
344
+ const soundId = `${id}.${name}`;
345
+ unique(sections.soundEffects, soundId, where);
346
+ const definition = record(item, `${where}.sounds.${name}`);
347
+ sections.soundEffects.push({
348
+ id: soundId,
349
+ definition: JSON.parse(JSON.stringify(definition)),
350
+ });
351
+ sounds[name] = soundId;
352
+ }
353
+ }
354
+ const scripts = [];
355
+ const rawScripts = body.scripts ?? [];
356
+ if (!Array.isArray(rawScripts))
357
+ fail(`${where}.scripts must be a list`);
358
+ rawScripts.forEach((item, index) => {
359
+ if (typeof item === 'string')
360
+ scripts.push(idOrRef(item, `${where}.scripts[${index}]`));
361
+ else {
362
+ const scriptId = `${id}.script${index}`;
363
+ unique(sections.scripts, scriptId, where);
364
+ sections.scripts.push(scriptOf(scriptId, record(item, `${where}.scripts[${index}]`), `${where}.scripts[${index}]`));
365
+ scripts.push(scriptId);
366
+ }
367
+ });
368
+ let sprite;
369
+ if (body.sprite !== undefined) {
370
+ const raw = record(body.sprite, `${where}.sprite`);
371
+ only(raw, ['image', 'frame'], `${where}.sprite`);
372
+ const frame = raw.frame;
373
+ if (frame !== undefined &&
374
+ (!Array.isArray(frame) || frame.length !== 2 || frame.some((n) => !Number.isInteger(n) || n <= 0)))
375
+ fail(`${where}.sprite.frame is [width, height]`);
376
+ sprite = {
377
+ image: idOrRef(raw.image, `${where}.sprite.image`),
378
+ ...(frame === undefined ? {} : { frame: frame }),
379
+ };
380
+ }
381
+ const stats = {};
382
+ for (const [key, raw] of Object.entries(body)) {
383
+ if (CHARACTER_FIELDS.includes(key))
384
+ continue;
385
+ if (!ID.test(key))
386
+ fail(`${where}: bad field name "${key}"`);
387
+ stats[key] = typeof raw === 'number' || typeof raw === 'boolean' ? raw : text(raw, `${where}.${key}`);
388
+ }
389
+ sections.characters.push({
390
+ id,
391
+ ...(body.name === undefined ? {} : { name: text(body.name, `${where}.name`) }),
392
+ ...(body.description === undefined ? {} : { description: text(body.description, `${where}.description`) }),
393
+ ...(body.portrait === undefined ? {} : { portrait: idOrRef(body.portrait, `${where}.portrait`) }),
394
+ ...(sprite === undefined ? {} : { sprite }),
395
+ ...(body.theme === undefined ? {} : { theme: idOrRef(body.theme, `${where}.theme`) }),
396
+ animations,
397
+ sounds,
398
+ scripts,
399
+ stats,
400
+ });
401
+ };
402
+ const entries = [];
403
+ for (const entry of value) {
404
+ const body = record(entry, 'a node');
405
+ if (typeof body.tag === 'string')
406
+ entries.push([body.tag, body]);
407
+ else {
408
+ const keys = Object.keys(body);
409
+ if (keys.length !== 1)
410
+ fail('a node needs a tag');
411
+ const items = Array.isArray(body[keys[0]]) ? body[keys[0]] : [body[keys[0]]];
412
+ for (const item of items)
413
+ entries.push([keys[0], record(item, keys[0])]);
414
+ }
415
+ }
416
+ for (const [tag, body] of entries) {
417
+ const where = `[${tag}]`;
418
+ if (!PACKAGE_TAGS.includes(tag))
419
+ fail(`${where} is not allowed in a package; use [character] and the other package tags`);
420
+ if (tag === 'package') {
421
+ if (depth > 0)
422
+ fail('an imported file cannot hold a [package] tag');
423
+ if (sections.meta)
424
+ fail('a second [package] tag');
425
+ only(body, ['tag', 'id', 'version', 'author', 'license', 'requires'], where);
426
+ const version = plain(body.version, `${where}.version`);
427
+ if (!VERSION.test(version))
428
+ fail(`${where}: version must look like 1.2.3`);
429
+ const requires = record(body.requires ?? {}, `${where}.requires`);
430
+ only(requires, ['mwg', 'retroAudio', 'capabilities'], `${where}.requires`);
431
+ const capabilities = requires.capabilities ?? [];
432
+ if (!Array.isArray(capabilities) || capabilities.some((item) => typeof item !== 'string' || !ID.test(item)))
433
+ fail(`${where}: requires.capabilities is a list of names`);
434
+ sections.meta = {
435
+ id: idOf(body.id, `${where}.id`),
436
+ version,
437
+ ...(body.author === undefined ? {} : { author: text(body.author, `${where}.author`) }),
438
+ ...(body.license === undefined ? {} : { license: plain(body.license, `${where}.license`) }),
439
+ requires: {
440
+ ...(requires.mwg === undefined ? {} : { mwg: plain(requires.mwg, `${where}.requires.mwg`) }),
441
+ ...(requires.retroAudio === undefined
442
+ ? {}
443
+ : { retroAudio: plain(requires.retroAudio, `${where}.requires.retroAudio`) }),
444
+ capabilities: [...new Set(capabilities)].sort(),
445
+ },
446
+ };
447
+ continue;
448
+ }
449
+ if (tag === 'import') {
450
+ only(body, ['tag', 'src', 'package', 'version', 'as'], where);
451
+ if ((body.src === undefined) === (body.package === undefined))
452
+ fail(`${where}: needs exactly one of src and package`);
453
+ const imported = {
454
+ ...(body.src === undefined ? {} : { src: checkPackagePath(plain(body.src, `${where}.src`)) }),
455
+ ...(body.package === undefined ? {} : { package: idOf(body.package, `${where}.package`) }),
456
+ ...(body.version === undefined ? {} : { version: plain(body.version, `${where}.version`) }),
457
+ ...(body.as === undefined ? {} : { as: idOf(body.as, `${where}.as`) }),
458
+ };
459
+ sections.imports.push(imported);
460
+ if (imported.src !== undefined) {
461
+ if (depth + 1 > MAX_IMPORT_DEPTH)
462
+ fail(`${where}: imports are nested more than ${MAX_IMPORT_DEPTH} deep`);
463
+ if (++budget.reads > MAX_IMPORT_READS)
464
+ fail(`${where}: more than ${MAX_IMPORT_READS} imports in one package`);
465
+ const files = options.files;
466
+ const imported_source = files && Object.hasOwn(files, imported.src) ? files[imported.src] : undefined;
467
+ if (imported_source === undefined)
468
+ fail(`${where}: "${imported.src}" is not in the package`);
469
+ readInto(sections, imported_source, { ...options, file: imported.src }, depth + 1, imported.as ? local(imported.as) : prefix, budget);
470
+ }
471
+ continue;
472
+ }
473
+ const id = local(idOf(body.id, `${where}.id`));
474
+ if (tag === 'asset') {
475
+ only(body, ['tag', 'id', 'kind', 'path', 'mime', 'sha256', 'license', 'source'], where);
476
+ const kind = plain(body.kind, `${where}.kind`);
477
+ if (!PACKAGE_ASSET_KINDS.includes(kind))
478
+ fail(`${where}: kind must be one of ${PACKAGE_ASSET_KINDS.join(', ')}`);
479
+ unique(sections.assets, id, where);
480
+ const sha256 = body.sha256 === undefined ? undefined : plain(body.sha256, `${where}.sha256`);
481
+ if (sha256 !== undefined && !/^[0-9a-f]{64}$/.test(sha256))
482
+ fail(`${where}: sha256 is 64 lower-case hex digits`);
483
+ sections.assets.push({
484
+ id,
485
+ kind,
486
+ path: checkPackagePath(plain(body.path, `${where}.path`)),
487
+ ...(body.mime === undefined ? {} : { mime: plain(body.mime, `${where}.mime`) }),
488
+ ...(sha256 === undefined ? {} : { sha256 }),
489
+ ...(body.license === undefined ? {} : { license: plain(body.license, `${where}.license`) }),
490
+ ...(body.source === undefined ? {} : { source: plain(body.source, `${where}.source`) }),
491
+ });
492
+ }
493
+ else if (tag === 'animation') {
494
+ unique(sections.animations, id, where);
495
+ sections.animations.push(animationOf(id, body, `${where} ${id}`));
496
+ }
497
+ else if (tag === 'sound_effect') {
498
+ unique(sections.soundEffects, id, where);
499
+ const { tag: _tag, id: _id, ...rest } = body;
500
+ void _tag;
501
+ void _id;
502
+ sections.soundEffects.push({
503
+ id,
504
+ definition: json(rest, `${where} ${id}`),
505
+ });
506
+ }
507
+ else if (tag === 'music') {
508
+ only(body, ['tag', 'id', 'src', 'loop', 'region', 'fallback'], where);
509
+ unique(sections.music, id, where);
510
+ const region = body.region;
511
+ if (region !== undefined &&
512
+ (!Array.isArray(region) || region.length !== 2 || region.some((n) => typeof n !== 'number' || n < 0)))
513
+ fail(`${where}: region is [start, end]`);
514
+ sections.music.push({
515
+ id,
516
+ src: idOrRef(body.src, `${where}.src`),
517
+ ...(body.loop === undefined ? {} : { loop: body.loop === true }),
518
+ ...(region === undefined ? {} : { region: region }),
519
+ ...(body.fallback === undefined ? {} : { fallback: idOrRef(body.fallback, `${where}.fallback`) }),
520
+ });
521
+ }
522
+ else if (tag === 'script') {
523
+ unique(sections.scripts, id, where);
524
+ sections.scripts.push(scriptOf(id, body, `${where} ${id}`));
525
+ }
526
+ else {
527
+ characterOf(id, body);
528
+ }
529
+ }
530
+ }
531
+ const CHARACTER_FIELDS = [
532
+ 'tag',
533
+ 'id',
534
+ 'name',
535
+ 'description',
536
+ 'portrait',
537
+ 'sprite',
538
+ 'theme',
539
+ 'animations',
540
+ 'sounds',
541
+ 'scripts',
542
+ ];
543
+ function parseTriple(text, partial) {
544
+ const parts = text.split(/[-+]/)[0].split('.');
545
+ if (parts.length > 3 || (!partial && parts.length !== 3))
546
+ throw new PackageFormatError(`bad version "${text}"`);
547
+ return parts.map((part) => {
548
+ if (!/^\d{1,9}$/.test(part))
549
+ throw new PackageFormatError(`bad version "${text}"`);
550
+ return Number(part);
551
+ });
552
+ }
553
+ function compareTriples(a, b) {
554
+ for (let index = 0; index < 3; index++) {
555
+ const difference = (a[index] ?? 0) - (b[index] ?? 0);
556
+ if (difference)
557
+ return difference;
558
+ }
559
+ return 0;
560
+ }
561
+ /** the first version a partial one does not cover: `1.2` -> `1.3.0`, `1` -> `2.0.0` */
562
+ function bump(parts) {
563
+ if (parts.length === 1)
564
+ return [parts[0] + 1, 0, 0];
565
+ if (parts.length === 2)
566
+ return [parts[0], parts[1] + 1, 0];
567
+ return [parts[0], parts[1], parts[2] + 1];
568
+ }
569
+ function comparator(text) {
570
+ const match = /^(>=|<=|>|<|\^|~|=)?\s*(.*)$/.exec(text.trim());
571
+ const operator = match?.[1] ?? '=';
572
+ const body = match?.[2] ?? '';
573
+ if (body === '' || body === '*' || /^[xX*]$/.test(body))
574
+ return undefined;
575
+ const parts = parseTriple(body.replace(/\.[xX*]/g, ''), true);
576
+ const low = [parts[0], parts[1] ?? 0, parts[2] ?? 0];
577
+ if (operator === '>=')
578
+ return { low, op: 'range' };
579
+ if (operator === '>')
580
+ return parts.length === 3 ? { low, op: 'gt' } : { low: bump(parts), op: 'range' };
581
+ if (operator === '<')
582
+ return { low: [0, 0, 0], high: low, op: 'range' };
583
+ if (operator === '<=')
584
+ return parts.length === 3 ? { low, op: 'lte' } : { low: [0, 0, 0], high: bump(parts), op: 'range' };
585
+ if (operator === '^') {
586
+ // a caret allows changes right of the first non-zero part
587
+ const significant = parts.findIndex((part) => part > 0);
588
+ return { low, high: bump(parts.slice(0, significant < 0 ? parts.length : significant + 1)), op: 'range' };
589
+ }
590
+ if (operator === '~')
591
+ return { low, high: bump(parts.slice(0, 2)), op: 'range' };
592
+ return { low, high: bump(parts), op: 'range' };
593
+ }
594
+ /**
595
+ * Checks a version against a small semver range: `>=`, `>`, `<=`, `<`, `^`, `~`, an exact
596
+ * version or a partial one (`0.42` is `0.42.x`), several separated by spaces (all must hold) and
597
+ * alternatives separated by `||`. A pre-release or build suffix on the version is ignored.
598
+ * A malformed range throws a `PackageFormatError`.
599
+ *
600
+ * @example
601
+ * ```ts
602
+ * import { satisfiesVersionRange } from '@datamoc/mw_games/mwl';
603
+ *
604
+ * console.log(satisfiesVersionRange('0.43.0', '>=0.42 <1')); // true
605
+ * console.log(satisfiesVersionRange('0.43.0', '^0.42.0')); // false
606
+ * ```
607
+ */
608
+ export function satisfiesVersionRange(version, range) {
609
+ const actual = parseTriple(version, false);
610
+ return range.split('||').some((alternative) => alternative
611
+ .replace(/([<>=^~])\s+/g, '$1')
612
+ .trim()
613
+ .split(/\s+/)
614
+ .every((part) => {
615
+ const rule = comparator(part);
616
+ if (!rule)
617
+ return true;
618
+ if (rule.op === 'gt')
619
+ return compareTriples(actual, rule.low) > 0;
620
+ if (rule.op === 'lte')
621
+ return compareTriples(actual, rule.low) <= 0;
622
+ return compareTriples(actual, rule.low) >= 0 && (!rule.high || compareTriples(actual, rule.high) < 0);
623
+ }));
624
+ }
625
+ /**
626
+ * Orders two `x.y.z` versions: negative when `a` is older, positive when newer. It decides which
627
+ * of two coexisting versions of a package owns the plain `packageId:id` name.
628
+ *
629
+ * @example
630
+ * ```ts
631
+ * import { compareVersions } from '@datamoc/mw_games/mwl';
632
+ *
633
+ * console.log(compareVersions('1.2.0', '1.10.0') < 0); // true
634
+ * ```
635
+ */
636
+ export function compareVersions(a, b) {
637
+ return compareTriples(parseTriple(a, false), parseTriple(b, false));
638
+ }
639
+ //# sourceMappingURL=package-document.js.map