@playdrop/playdrop-cli 0.16.5 → 0.16.11

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 (383) hide show
  1. package/bin/playdrop +1 -2
  2. package/config/client-meta.json +4 -4
  3. package/dist/apiClient.d.ts +2 -2
  4. package/dist/apiClient.js +16 -20
  5. package/dist/appUrls.d.ts +2 -2
  6. package/dist/appUrls.js +31 -33
  7. package/dist/apps/build.d.ts +2 -2
  8. package/dist/apps/build.js +83 -83
  9. package/dist/apps/index.d.ts +7 -7
  10. package/dist/apps/index.js +8 -8
  11. package/dist/apps/loadCheck.d.ts +28 -16
  12. package/dist/apps/loadCheck.js +411 -279
  13. package/dist/apps/registration.d.ts +3 -3
  14. package/dist/apps/registration.js +1 -1
  15. package/dist/apps/staticHtml.d.ts +3 -3
  16. package/dist/apps/staticHtml.js +90 -91
  17. package/dist/apps/surfaceProfiles.d.ts +2 -2
  18. package/dist/apps/surfaceProfiles.js +6 -6
  19. package/dist/apps/upload.d.ts +5 -5
  20. package/dist/apps/upload.js +75 -81
  21. package/dist/apps/validate.d.ts +2 -2
  22. package/dist/apps/validate.js +18 -22
  23. package/dist/assetSpecs.d.ts +2 -2
  24. package/dist/assetSpecs.js +35 -35
  25. package/dist/assets/model-artifacts.d.ts +1 -1
  26. package/dist/assets/model-artifacts.js +81 -77
  27. package/dist/assets/model-worker.d.ts +1 -1
  28. package/dist/assets/model-worker.js +6 -6
  29. package/dist/browser.js +9 -9
  30. package/dist/catalogue-utils.d.ts +1 -1
  31. package/dist/catalogue-utils.js +26 -26
  32. package/dist/catalogue.d.ts +21 -16
  33. package/dist/catalogue.js +509 -413
  34. package/dist/clientInfo.d.ts +1 -1
  35. package/dist/clientInfo.js +28 -28
  36. package/dist/commandContext.d.ts +5 -5
  37. package/dist/commandContext.js +19 -23
  38. package/dist/commands/accounts.js +6 -6
  39. package/dist/commands/ads.js +16 -16
  40. package/dist/commands/agents.d.ts +2 -2
  41. package/dist/commands/agents.js +131 -127
  42. package/dist/commands/boosts.js +41 -31
  43. package/dist/commands/browse.d.ts +2 -2
  44. package/dist/commands/browse.js +168 -128
  45. package/dist/commands/build.js +9 -9
  46. package/dist/commands/captureListing.d.ts +8 -8
  47. package/dist/commands/captureListing.js +292 -332
  48. package/dist/commands/changelog.js +1 -1
  49. package/dist/commands/chat.js +51 -53
  50. package/dist/commands/check.d.ts +1 -1
  51. package/dist/commands/check.js +44 -44
  52. package/dist/commands/clients.d.ts +3 -3
  53. package/dist/commands/clients.js +67 -64
  54. package/dist/commands/comments.js +36 -29
  55. package/dist/commands/create.js +305 -312
  56. package/dist/commands/createRemixContent.d.ts +1 -1
  57. package/dist/commands/createRemixContent.js +65 -60
  58. package/dist/commands/creations.js +205 -135
  59. package/dist/commands/credits.js +37 -27
  60. package/dist/commands/detail.js +40 -34
  61. package/dist/commands/dev.js +103 -126
  62. package/dist/commands/devBrowser.js +74 -65
  63. package/dist/commands/devRuntimeAssets.d.ts +4 -4
  64. package/dist/commands/devRuntimeAssets.js +38 -40
  65. package/dist/commands/devServer.d.ts +4 -4
  66. package/dist/commands/devServer.js +183 -174
  67. package/dist/commands/devShared.d.ts +3 -3
  68. package/dist/commands/devShared.js +31 -31
  69. package/dist/commands/doctor.d.ts +5 -5
  70. package/dist/commands/doctor.js +63 -57
  71. package/dist/commands/documentation.js +42 -32
  72. package/dist/commands/feedback.js +59 -42
  73. package/dist/commands/format.js +9 -9
  74. package/dist/commands/generation.d.ts +2 -2
  75. package/dist/commands/generation.js +344 -326
  76. package/dist/commands/init.d.ts +4 -4
  77. package/dist/commands/init.js +64 -61
  78. package/dist/commands/login.d.ts +2 -2
  79. package/dist/commands/login.js +42 -62
  80. package/dist/commands/logout.js +4 -4
  81. package/dist/commands/marketing.d.ts +8 -8
  82. package/dist/commands/marketing.js +246 -297
  83. package/dist/commands/notes.js +19 -19
  84. package/dist/commands/notifications.js +34 -22
  85. package/dist/commands/npmProcess.js +3 -3
  86. package/dist/commands/ownedRuntimeImageValidation.d.ts +1 -1
  87. package/dist/commands/ownedRuntimeImageValidation.js +19 -17
  88. package/dist/commands/play.js +12 -21
  89. package/dist/commands/publicPages.js +8 -4
  90. package/dist/commands/review.js +123 -110
  91. package/dist/commands/search.js +107 -88
  92. package/dist/commands/tags.js +10 -12
  93. package/dist/commands/taskCaptureSession.d.ts +3 -3
  94. package/dist/commands/taskCaptureSession.js +3 -3
  95. package/dist/commands/tweaks.js +25 -23
  96. package/dist/commands/upgrade.js +40 -31
  97. package/dist/commands/upload-content.d.ts +5 -5
  98. package/dist/commands/upload-content.js +130 -122
  99. package/dist/commands/upload-graph.d.ts +1 -1
  100. package/dist/commands/upload-graph.js +7 -11
  101. package/dist/commands/upload.d.ts +7 -7
  102. package/dist/commands/upload.js +196 -217
  103. package/dist/commands/validate.js +33 -32
  104. package/dist/commands/versionsBrowse.js +61 -51
  105. package/dist/commands/whoami.js +11 -14
  106. package/dist/commands/worker/archive-staging.d.ts +9 -0
  107. package/dist/commands/worker/archive-staging.js +257 -0
  108. package/dist/commands/worker/instrument-evidence.d.ts +1 -1
  109. package/dist/commands/worker/instrument-evidence.js +41 -43
  110. package/dist/commands/worker/provider-telemetry.d.ts +2 -1
  111. package/dist/commands/worker/provider-telemetry.js +181 -64
  112. package/dist/commands/worker/runtime.d.ts +18 -3
  113. package/dist/commands/worker/runtime.js +291 -239
  114. package/dist/commands/worker.d.ts +83 -22
  115. package/dist/commands/worker.js +1980 -1411
  116. package/dist/commands/workspaces.d.ts +2 -2
  117. package/dist/commands/workspaces.js +57 -57
  118. package/dist/config.js +23 -20
  119. package/dist/devAuth.d.ts +3 -3
  120. package/dist/devAuth.js +20 -17
  121. package/dist/environment.d.ts +1 -1
  122. package/dist/environment.js +34 -34
  123. package/dist/errors.d.ts +1 -1
  124. package/dist/errors.js +7 -7
  125. package/dist/externalAssetPackValidation.d.ts +1 -1
  126. package/dist/externalAssetPackValidation.js +9 -9
  127. package/dist/http.d.ts +1 -1
  128. package/dist/http.js +9 -9
  129. package/dist/index.js +657 -664
  130. package/dist/listingCaptureLease.js +23 -23
  131. package/dist/listingPreflight.d.ts +2 -2
  132. package/dist/listingPreflight.js +33 -33
  133. package/dist/messages.js +6 -14
  134. package/dist/output.js +9 -9
  135. package/dist/playwright.d.ts +1 -1
  136. package/dist/playwright.js +35 -40
  137. package/dist/proxyFetch.js +30 -27
  138. package/dist/recorderRelease.js +8 -8
  139. package/dist/refs.d.ts +5 -5
  140. package/dist/refs.js +40 -37
  141. package/dist/sessionCookie.js +15 -21
  142. package/dist/shellProbe.js +5 -5
  143. package/dist/taskSelection.d.ts +3 -3
  144. package/dist/taskSelection.js +67 -62
  145. package/dist/taskUtils.d.ts +1 -1
  146. package/dist/taskUtils.js +4 -4
  147. package/dist/tweakStaleness.d.ts +3 -3
  148. package/dist/tweakStaleness.js +8 -8
  149. package/dist/uploadLog.d.ts +3 -3
  150. package/dist/uploadLog.js +51 -51
  151. package/dist/versionCompare.js +3 -3
  152. package/dist/workerAppProject.d.ts +1 -1
  153. package/dist/workerAppProject.js +10 -17
  154. package/dist/workspaceAuth.d.ts +1 -1
  155. package/dist/workspaceAuth.js +28 -22
  156. package/node_modules/@playdrop/ai-client/dist/index.d.ts +35 -35
  157. package/node_modules/@playdrop/ai-client/dist/index.d.ts.map +1 -1
  158. package/node_modules/@playdrop/api-client/dist/client.d.ts +34 -33
  159. package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
  160. package/node_modules/@playdrop/api-client/dist/core/errors.d.ts +1 -1
  161. package/node_modules/@playdrop/api-client/dist/core/errors.d.ts.map +1 -1
  162. package/node_modules/@playdrop/api-client/dist/core/request.d.ts +2 -2
  163. package/node_modules/@playdrop/api-client/dist/core/request.d.ts.map +1 -1
  164. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts +2 -2
  165. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts.map +1 -1
  166. package/node_modules/@playdrop/api-client/dist/domains/admin.js +4 -1
  167. package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts +4 -3
  168. package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts.map +1 -1
  169. package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.js +12 -0
  170. package/node_modules/@playdrop/api-client/dist/domains/ai.d.ts +2 -2
  171. package/node_modules/@playdrop/api-client/dist/domains/ai.d.ts.map +1 -1
  172. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts +4 -4
  173. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -1
  174. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.d.ts +4 -4
  175. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.d.ts.map +1 -1
  176. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts +4 -4
  177. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts.map +1 -1
  178. package/node_modules/@playdrop/api-client/dist/domains/auth.d.ts +2 -2
  179. package/node_modules/@playdrop/api-client/dist/domains/auth.d.ts.map +1 -1
  180. package/node_modules/@playdrop/api-client/dist/domains/chat.d.ts +2 -2
  181. package/node_modules/@playdrop/api-client/dist/domains/chat.d.ts.map +1 -1
  182. package/node_modules/@playdrop/api-client/dist/domains/comments.d.ts +3 -3
  183. package/node_modules/@playdrop/api-client/dist/domains/comments.d.ts.map +1 -1
  184. package/node_modules/@playdrop/api-client/dist/domains/free-credits.d.ts +2 -2
  185. package/node_modules/@playdrop/api-client/dist/domains/free-credits.d.ts.map +1 -1
  186. package/node_modules/@playdrop/api-client/dist/domains/me.d.ts +2 -2
  187. package/node_modules/@playdrop/api-client/dist/domains/me.d.ts.map +1 -1
  188. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts +2 -2
  189. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts.map +1 -1
  190. package/node_modules/@playdrop/api-client/dist/domains/player-meta.d.ts +2 -2
  191. package/node_modules/@playdrop/api-client/dist/domains/player-meta.d.ts.map +1 -1
  192. package/node_modules/@playdrop/api-client/dist/domains/playtrade.d.ts +3 -3
  193. package/node_modules/@playdrop/api-client/dist/domains/playtrade.d.ts.map +1 -1
  194. package/node_modules/@playdrop/api-client/dist/domains/playtrade.js +2 -2
  195. package/node_modules/@playdrop/api-client/dist/domains/search.d.ts +2 -2
  196. package/node_modules/@playdrop/api-client/dist/domains/search.d.ts.map +1 -1
  197. package/node_modules/@playdrop/api-client/dist/domains/tags.d.ts +2 -2
  198. package/node_modules/@playdrop/api-client/dist/domains/tags.d.ts.map +1 -1
  199. package/node_modules/@playdrop/api-client/dist/index.d.ts +19 -18
  200. package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
  201. package/node_modules/@playdrop/api-client/dist/index.js +7 -2
  202. package/node_modules/@playdrop/boxel-core/dist/src/entity-cleaner.d.ts +1 -1
  203. package/node_modules/@playdrop/boxel-core/dist/src/entity-cleaner.js +47 -47
  204. package/node_modules/@playdrop/boxel-core/dist/src/entity-cleaner.js.map +1 -1
  205. package/node_modules/@playdrop/boxel-core/dist/src/entity-utils.d.ts +3 -3
  206. package/node_modules/@playdrop/boxel-core/dist/src/entity-utils.js +32 -47
  207. package/node_modules/@playdrop/boxel-core/dist/src/entity-utils.js.map +1 -1
  208. package/node_modules/@playdrop/boxel-core/dist/src/format-utils.js +16 -18
  209. package/node_modules/@playdrop/boxel-core/dist/src/format-utils.js.map +1 -1
  210. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/geometry.d.ts +2 -2
  211. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/index.d.ts +4 -4
  212. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/common.d.ts +1 -1
  213. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/index.d.ts +3 -3
  214. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/textured-builder.d.ts +2 -2
  215. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/textured-builder.js +44 -45
  216. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/textured-builder.js.map +1 -1
  217. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/types.d.ts +1 -1
  218. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/voxel-builder.d.ts +2 -2
  219. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/voxel-builder.js +40 -44
  220. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/voxel-builder.js.map +1 -1
  221. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/builder.d.ts +2 -2
  222. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/builder.js +30 -34
  223. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/builder.js.map +1 -1
  224. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/index.d.ts +4 -4
  225. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/index.js.map +1 -1
  226. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/scanner.d.ts +3 -3
  227. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/scanner.js +23 -20
  228. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/scanner.js.map +1 -1
  229. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/textures/artifacts.d.ts +3 -3
  230. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/textures/artifacts.js +3 -3
  231. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/textures/face-map.d.ts +1 -1
  232. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/textures/face-map.js +21 -21
  233. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/textures/face-map.js.map +1 -1
  234. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/templates/index.d.ts +1 -1
  235. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/templates/index.js +2 -2
  236. package/node_modules/@playdrop/boxel-core/dist/src/index.d.ts +19 -19
  237. package/node_modules/@playdrop/boxel-core/dist/src/materials.d.ts +1 -1
  238. package/node_modules/@playdrop/boxel-core/dist/src/materials.js +12 -16
  239. package/node_modules/@playdrop/boxel-core/dist/src/materials.js.map +1 -1
  240. package/node_modules/@playdrop/boxel-core/dist/src/palette_tools.d.ts +1 -1
  241. package/node_modules/@playdrop/boxel-core/dist/src/palette_tools.js +38 -41
  242. package/node_modules/@playdrop/boxel-core/dist/src/palette_tools.js.map +1 -1
  243. package/node_modules/@playdrop/boxel-core/dist/src/serialization.d.ts +1 -1
  244. package/node_modules/@playdrop/boxel-core/dist/src/serialization.js +54 -56
  245. package/node_modules/@playdrop/boxel-core/dist/src/serialization.js.map +1 -1
  246. package/node_modules/@playdrop/boxel-core/dist/src/textures.d.ts +3 -3
  247. package/node_modules/@playdrop/boxel-core/dist/src/textures.js +39 -45
  248. package/node_modules/@playdrop/boxel-core/dist/src/textures.js.map +1 -1
  249. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/infer-face.d.ts +2 -2
  250. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/infer-face.js +39 -43
  251. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/infer-face.js.map +1 -1
  252. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/layer-mode.d.ts +3 -3
  253. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/layer-mode.js +35 -35
  254. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/layer-mode.js.map +1 -1
  255. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/merge-overlay.d.ts +1 -1
  256. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/merge-overlay.js +18 -18
  257. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/merge-overlay.js.map +1 -1
  258. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/slice.d.ts +3 -3
  259. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/slice.js +50 -48
  260. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/slice.js.map +1 -1
  261. package/node_modules/@playdrop/boxel-core/dist/src/transforms/upscale.d.ts +1 -1
  262. package/node_modules/@playdrop/boxel-core/dist/src/transforms/upscale.js +22 -26
  263. package/node_modules/@playdrop/boxel-core/dist/src/transforms/upscale.js.map +1 -1
  264. package/node_modules/@playdrop/boxel-core/dist/src/transforms/voxels/slice.d.ts +2 -2
  265. package/node_modules/@playdrop/boxel-core/dist/src/transforms/voxels/slice.js +8 -8
  266. package/node_modules/@playdrop/boxel-core/dist/src/transforms/voxels/slice.js.map +1 -1
  267. package/node_modules/@playdrop/boxel-core/dist/src/transforms/voxels/textured-to-voxel.d.ts +2 -2
  268. package/node_modules/@playdrop/boxel-core/dist/src/transforms/voxels/textured-to-voxel.js +23 -28
  269. package/node_modules/@playdrop/boxel-core/dist/src/transforms/voxels/textured-to-voxel.js.map +1 -1
  270. package/node_modules/@playdrop/boxel-core/dist/src/types.d.ts +8 -8
  271. package/node_modules/@playdrop/boxel-core/dist/src/validation.d.ts +1 -1
  272. package/node_modules/@playdrop/boxel-core/dist/src/validation.js +93 -95
  273. package/node_modules/@playdrop/boxel-core/dist/src/validation.js.map +1 -1
  274. package/node_modules/@playdrop/boxel-core/dist/src/voxels/greedy-mesher.d.ts +1 -1
  275. package/node_modules/@playdrop/boxel-core/dist/src/voxels/greedy-mesher.js +2 -2
  276. package/node_modules/@playdrop/boxel-core/dist/src/voxels/greedy-mesher.js.map +1 -1
  277. package/node_modules/@playdrop/boxel-core/dist/src/voxels.d.ts +1 -1
  278. package/node_modules/@playdrop/boxel-core/dist/src/voxels.js +2 -7
  279. package/node_modules/@playdrop/boxel-core/dist/src/voxels.js.map +1 -1
  280. package/node_modules/@playdrop/boxel-three/dist/src/animations.d.ts +3 -3
  281. package/node_modules/@playdrop/boxel-three/dist/src/builders.d.ts +4 -4
  282. package/node_modules/@playdrop/boxel-three/dist/src/context.d.ts +3 -3
  283. package/node_modules/@playdrop/boxel-three/dist/src/exporters/glb.d.ts +6 -6
  284. package/node_modules/@playdrop/boxel-three/dist/src/exporters/glb.js +9 -1
  285. package/node_modules/@playdrop/boxel-three/dist/src/exporters/image.d.ts +3 -3
  286. package/node_modules/@playdrop/boxel-three/dist/src/index.d.ts +17 -17
  287. package/node_modules/@playdrop/boxel-three/dist/src/instantiate.d.ts +9 -9
  288. package/node_modules/@playdrop/boxel-three/dist/src/nodes.d.ts +3 -3
  289. package/node_modules/@playdrop/boxel-three/dist/src/nodes.js +1 -1
  290. package/node_modules/@playdrop/boxel-three/dist/src/overlays.d.ts +2 -2
  291. package/node_modules/@playdrop/boxel-three/dist/src/primitives.d.ts +1 -1
  292. package/node_modules/@playdrop/boxel-three/dist/src/scene.d.ts +1 -1
  293. package/node_modules/@playdrop/boxel-three/dist/src/skinned-mesh.d.ts +2 -2
  294. package/node_modules/@playdrop/boxel-three/dist/src/texture-atlas.d.ts +1 -1
  295. package/node_modules/@playdrop/boxel-three/dist/src/textures.d.ts +3 -3
  296. package/node_modules/@playdrop/boxel-three/dist/src/types.d.ts +4 -4
  297. package/node_modules/@playdrop/boxel-three/dist/src/voxels/faces.d.ts +2 -2
  298. package/node_modules/@playdrop/boxel-three/dist/src/voxels/mesher.d.ts +1 -1
  299. package/node_modules/@playdrop/boxel-three/dist/test/fixtures/render-worker.d.ts +1 -1
  300. package/node_modules/@playdrop/config/client-meta.json +4 -4
  301. package/node_modules/@playdrop/config/dist/src/client-meta.d.ts +1 -1
  302. package/node_modules/@playdrop/config/dist/src/client-meta.d.ts.map +1 -1
  303. package/node_modules/@playdrop/config/dist/src/client-meta.js +10 -10
  304. package/node_modules/@playdrop/config/dist/src/constants.js +10 -10
  305. package/node_modules/@playdrop/config/dist/src/creator-plans.d.ts +1 -1
  306. package/node_modules/@playdrop/config/dist/src/creator-plans.d.ts.map +1 -1
  307. package/node_modules/@playdrop/config/dist/src/creator-plans.js +27 -28
  308. package/node_modules/@playdrop/config/dist/src/deployment.d.ts +4 -4
  309. package/node_modules/@playdrop/config/dist/src/deployment.d.ts.map +1 -1
  310. package/node_modules/@playdrop/config/dist/src/deployment.js +42 -41
  311. package/node_modules/@playdrop/config/dist/src/game-arena.js +12 -12
  312. package/node_modules/@playdrop/config/dist/src/index.d.ts +9 -9
  313. package/node_modules/@playdrop/config/dist/src/index.d.ts.map +1 -1
  314. package/node_modules/@playdrop/config/dist/src/index.js +24 -25
  315. package/node_modules/@playdrop/config/dist/src/public-deployment.d.ts +4 -4
  316. package/node_modules/@playdrop/config/dist/src/public-deployment.d.ts.map +1 -1
  317. package/node_modules/@playdrop/config/dist/src/public-deployment.js +41 -39
  318. package/node_modules/@playdrop/config/dist/src/runtime-client-meta.js +1 -3
  319. package/node_modules/@playdrop/config/dist/src/runtime-service-origins.d.ts.map +1 -1
  320. package/node_modules/@playdrop/config/dist/src/runtime-service-origins.js +23 -25
  321. package/node_modules/@playdrop/config/dist/src/server/fastify.d.ts +5 -5
  322. package/node_modules/@playdrop/config/dist/src/server/fastify.d.ts.map +1 -1
  323. package/node_modules/@playdrop/config/dist/src/server/fastify.js +44 -46
  324. package/node_modules/@playdrop/config/dist/src/server/logging.d.ts +1 -1
  325. package/node_modules/@playdrop/config/dist/src/server/logging.d.ts.map +1 -1
  326. package/node_modules/@playdrop/config/dist/src/server/logging.js +6 -8
  327. package/node_modules/@playdrop/config/dist/src/types.d.ts +2 -2
  328. package/node_modules/@playdrop/config/dist/test/creator-plans.test.js +12 -12
  329. package/node_modules/@playdrop/config/dist/test/deployment.test.js +59 -59
  330. package/node_modules/@playdrop/config/dist/test/fastify.test.js +4 -4
  331. package/node_modules/@playdrop/config/dist/test/game-arena.test.js +8 -8
  332. package/node_modules/@playdrop/config/dist/test/logging.test.js +6 -6
  333. package/node_modules/@playdrop/config/dist/test/validateClientEnvironment.test.js +57 -57
  334. package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
  335. package/node_modules/@playdrop/types/dist/agent-task-playtest.d.ts.map +1 -1
  336. package/node_modules/@playdrop/types/dist/agent-task-terminal.d.ts +2 -2
  337. package/node_modules/@playdrop/types/dist/agent-task-terminal.d.ts.map +1 -1
  338. package/node_modules/@playdrop/types/dist/api.d.ts +314 -250
  339. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  340. package/node_modules/@playdrop/types/dist/api.js +40 -59
  341. package/node_modules/@playdrop/types/dist/app-capability-filters.d.ts +2 -2
  342. package/node_modules/@playdrop/types/dist/app-playtest-tape.d.ts +6 -6
  343. package/node_modules/@playdrop/types/dist/app-playtest-tape.d.ts.map +1 -1
  344. package/node_modules/@playdrop/types/dist/app-review.d.ts +2 -2
  345. package/node_modules/@playdrop/types/dist/app-review.d.ts.map +1 -1
  346. package/node_modules/@playdrop/types/dist/app-review.js +1 -6
  347. package/node_modules/@playdrop/types/dist/app.d.ts.map +1 -1
  348. package/node_modules/@playdrop/types/dist/asset-pack.d.ts +5 -5
  349. package/node_modules/@playdrop/types/dist/asset-spec.d.ts +4 -4
  350. package/node_modules/@playdrop/types/dist/asset.d.ts +11 -11
  351. package/node_modules/@playdrop/types/dist/attachment-policy.d.ts.map +1 -1
  352. package/node_modules/@playdrop/types/dist/chat.d.ts +3 -3
  353. package/node_modules/@playdrop/types/dist/creator-public-image.d.ts.map +1 -1
  354. package/node_modules/@playdrop/types/dist/graph.d.ts +4 -4
  355. package/node_modules/@playdrop/types/dist/index.d.ts +25 -25
  356. package/node_modules/@playdrop/types/dist/instrument-evidence.d.ts +2 -2
  357. package/node_modules/@playdrop/types/dist/instrument-evidence.d.ts.map +1 -1
  358. package/node_modules/@playdrop/types/dist/instrument-evidence.js +2 -11
  359. package/node_modules/@playdrop/types/dist/locale.d.ts +2 -2
  360. package/node_modules/@playdrop/types/dist/locale.d.ts.map +1 -1
  361. package/node_modules/@playdrop/types/dist/locale.js +2 -17
  362. package/node_modules/@playdrop/types/dist/owned-assets.d.ts +2 -2
  363. package/node_modules/@playdrop/types/dist/owned-assets.d.ts.map +1 -1
  364. package/node_modules/@playdrop/types/dist/player-meta.d.ts +3 -3
  365. package/node_modules/@playdrop/types/dist/player-meta.d.ts.map +1 -1
  366. package/node_modules/@playdrop/types/dist/playtest-notes.d.ts +11 -11
  367. package/node_modules/@playdrop/types/dist/playtest-notes.d.ts.map +1 -1
  368. package/node_modules/@playdrop/types/dist/playtrade.d.ts +8 -5
  369. package/node_modules/@playdrop/types/dist/playtrade.d.ts.map +1 -1
  370. package/node_modules/@playdrop/types/dist/realtime.d.ts +1 -1
  371. package/node_modules/@playdrop/types/dist/tweaks.d.ts +4 -4
  372. package/node_modules/@playdrop/types/dist/tweaks.d.ts.map +1 -1
  373. package/node_modules/@playdrop/types/dist/tweaks.js +12 -2
  374. package/node_modules/@playdrop/types/dist/version.d.ts +14 -14
  375. package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
  376. package/node_modules/@playdrop/types/dist/version.js +4 -1
  377. package/node_modules/@playdrop/vox-three/dist/src/index.d.ts +1 -1
  378. package/node_modules/@playdrop/vox-three/dist/src/vox.d.ts +2 -2
  379. package/node_modules/@playdrop/vox-three/dist/src/vox.js +20 -18
  380. package/node_modules/@playdrop/vox-three/dist/src/vox.js.map +1 -1
  381. package/node_modules/@playdrop/vox-three/dist/test/vox.test.js +11 -14
  382. package/node_modules/@playdrop/vox-three/dist/test/vox.test.js.map +1 -1
  383. package/package.json +1 -1
package/dist/catalogue.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.APP_DESCRIPTION_RECOMMENDED_MIN_WORDS = exports.APP_DESCRIPTION_RECOMMENDED_MIN_CHARACTERS = void 0;
3
+ exports.APP_RELEASE_NOTES_MAX_CHARACTERS = exports.APP_DESCRIPTION_RECOMMENDED_MIN_WORDS = exports.APP_DESCRIPTION_RECOMMENDED_MIN_CHARACTERS = void 0;
4
+ exports.normalizeAppReleaseNotes = normalizeAppReleaseNotes;
4
5
  exports.validateAppMetadata = validateAppMetadata;
5
6
  exports.resolveCatalogueTagGroups = resolveCatalogueTagGroups;
6
7
  exports.resolveCatalogueEntries = resolveCatalogueEntries;
@@ -15,19 +16,20 @@ const assetSpecs_1 = require("./assetSpecs");
15
16
  const catalogue_utils_1 = require("./catalogue-utils");
16
17
  const HEX_COLOR_REGEX = /^#?(?:[0-9a-fA-F]{6})$/;
17
18
  const SEMVER_REGEX = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
18
- const LEGACY_CATALOGUE_VERSION_KEY = ['schema', 'Version'].join('');
19
+ const LEGACY_CATALOGUE_VERSION_KEY = ["schema", "Version"].join("");
19
20
  const ASSET_CATEGORY_SET = new Set(types_1.ASSET_CATEGORY_VALUES);
20
- const LISTING_IMAGE_EXTENSIONS = new Set(['.png']);
21
- const LISTING_VIDEO_EXTENSIONS = new Set(['.mp4']);
21
+ const LISTING_IMAGE_EXTENSIONS = new Set([".png"]);
22
+ const LISTING_VIDEO_EXTENSIONS = new Set([".mp4"]);
22
23
  exports.APP_DESCRIPTION_RECOMMENDED_MIN_CHARACTERS = 120;
23
24
  exports.APP_DESCRIPTION_RECOMMENDED_MIN_WORDS = 20;
24
- const BOXEL_GENERATED_FILE_ROLES = new Set(['mesh', 'preview']);
25
- const BOXEL_REQUIRED_FILE_ROLES = ['primary', 'mesh', 'preview'];
26
- const VOX_GENERATED_FILE_ROLES = new Set(['boxel', 'mesh', 'preview']);
27
- const VOX_REQUIRED_FILE_ROLES = ['primary', 'boxel', 'mesh', 'preview'];
25
+ exports.APP_RELEASE_NOTES_MAX_CHARACTERS = 240;
26
+ const BOXEL_GENERATED_FILE_ROLES = new Set(["mesh", "preview"]);
27
+ const BOXEL_REQUIRED_FILE_ROLES = ["primary", "mesh", "preview"];
28
+ const VOX_GENERATED_FILE_ROLES = new Set(["boxel", "mesh", "preview"]);
29
+ const VOX_REQUIRED_FILE_ROLES = ["primary", "boxel", "mesh", "preview"];
28
30
  const MAX_CONTENT_TAGS_PER_ITEM = 20;
29
- const APP_ALLOWED_LICENSES = new Set(['CLOSED', 'PLAYDROP', 'MIT']);
30
- const ASSET_ALLOWED_LICENSES = new Set(['CLOSED', 'PLAYDROP', 'CC0']);
31
+ const APP_ALLOWED_LICENSES = new Set(["CLOSED", "PLAYDROP", "MIT"]);
32
+ const ASSET_ALLOWED_LICENSES = new Set(["CLOSED", "PLAYDROP", "CC0"]);
31
33
  const ASSET_SUBCATEGORY_SET_BY_CATEGORY = types_1.ASSET_SUBCATEGORY_DEFINITIONS.reduce((acc, definition) => {
32
34
  if (!acc[definition.category]) {
33
35
  acc[definition.category] = new Set();
@@ -35,28 +37,47 @@ const ASSET_SUBCATEGORY_SET_BY_CATEGORY = types_1.ASSET_SUBCATEGORY_DEFINITIONS.
35
37
  acc[definition.category].add(definition.subcategory);
36
38
  return acc;
37
39
  }, {});
40
+ function normalizeAppReleaseNotes(value, options = {}) {
41
+ if (value === undefined) {
42
+ if (options.required) {
43
+ throw new Error("agent_task_release_notes_missing: catalogue releaseNotes must contain one line with 1..240 characters.");
44
+ }
45
+ return undefined;
46
+ }
47
+ const releaseNotes = value.trim();
48
+ if (!releaseNotes) {
49
+ throw new Error("agent_task_release_notes_missing: catalogue releaseNotes must contain one line with 1..240 characters.");
50
+ }
51
+ if (/[\r\n]/.test(releaseNotes)) {
52
+ throw new Error(`agent_task_release_notes_multiline: releaseNotes has ${releaseNotes.split(/\r\n|\r|\n/).length} lines; expected one line with 1..240 characters.`);
53
+ }
54
+ if (releaseNotes.length > exports.APP_RELEASE_NOTES_MAX_CHARACTERS) {
55
+ throw new Error(`agent_task_release_notes_too_long: releaseNotes length=${releaseNotes.length}; expected 1..240 characters on one line.`);
56
+ }
57
+ return releaseNotes;
58
+ }
38
59
  function normalizeFileRole(role) {
39
60
  return role.trim().toLowerCase();
40
61
  }
41
62
  function inferGeneratedModel3DFormat(category, subcategory, format, files) {
42
- if (category !== 'MODEL_3D' || !(0, types_1.isModel3DAssetSubcategory)('MODEL_3D', subcategory)) {
63
+ if (category !== "MODEL_3D" || !(0, types_1.isModel3DAssetSubcategory)("MODEL_3D", subcategory)) {
43
64
  return null;
44
65
  }
45
- const normalizedFormat = typeof format === 'string' ? format.trim().toUpperCase() : '';
46
- if (normalizedFormat === 'PLAYDROP_BOXEL_JSON' || normalizedFormat === 'VOX') {
66
+ const normalizedFormat = typeof format === "string" ? format.trim().toUpperCase() : "";
67
+ if (normalizedFormat === "PLAYDROP_BOXEL_JSON" || normalizedFormat === "VOX") {
47
68
  return normalizedFormat;
48
69
  }
49
- const primary = typeof files?.primary === 'string' ? files.primary.trim().toLowerCase() : '';
50
- if (primary.endsWith('.boxel.json')) {
51
- return 'PLAYDROP_BOXEL_JSON';
70
+ const primary = typeof files?.primary === "string" ? files.primary.trim().toLowerCase() : "";
71
+ if (primary.endsWith(".boxel.json")) {
72
+ return "PLAYDROP_BOXEL_JSON";
52
73
  }
53
- if (primary.endsWith('.vox')) {
54
- return 'VOX';
74
+ if (primary.endsWith(".vox")) {
75
+ return "VOX";
55
76
  }
56
77
  return null;
57
78
  }
58
79
  function getGeneratedModel3DFileRoles(format) {
59
- if (format === 'VOX') {
80
+ if (format === "VOX") {
60
81
  return {
61
82
  generated: VOX_GENERATED_FILE_ROLES,
62
83
  required: VOX_REQUIRED_FILE_ROLES,
@@ -69,15 +90,15 @@ function getGeneratedModel3DFileRoles(format) {
69
90
  }
70
91
  function extractGlbJsonChunk(buffer) {
71
92
  if (buffer.length < 20) {
72
- throw new Error('invalid_glb_payload');
93
+ throw new Error("invalid_glb_payload");
73
94
  }
74
- if (buffer.toString('utf8', 0, 4) !== 'glTF') {
75
- throw new Error('invalid_glb_payload');
95
+ if (buffer.toString("utf8", 0, 4) !== "glTF") {
96
+ throw new Error("invalid_glb_payload");
76
97
  }
77
98
  const version = buffer.readUInt32LE(4);
78
99
  const declaredLength = buffer.readUInt32LE(8);
79
100
  if (version !== 2 || declaredLength !== buffer.length) {
80
- throw new Error('invalid_glb_payload');
101
+ throw new Error("invalid_glb_payload");
81
102
  }
82
103
  let offset = 12;
83
104
  while (offset + 8 <= buffer.length) {
@@ -85,45 +106,41 @@ function extractGlbJsonChunk(buffer) {
85
106
  const chunkType = buffer.readUInt32LE(offset + 4);
86
107
  offset += 8;
87
108
  if (offset + chunkLength > buffer.length) {
88
- throw new Error('invalid_glb_payload');
109
+ throw new Error("invalid_glb_payload");
89
110
  }
90
111
  const chunk = buffer.subarray(offset, offset + chunkLength);
91
112
  offset += chunkLength;
92
- if (chunkType !== 0x4E4F534A) {
113
+ if (chunkType !== 0x4e4f534a) {
93
114
  continue;
94
115
  }
95
- const payload = chunk.toString('utf8').replace(/\0+$/, '').trimEnd();
116
+ const payload = chunk.toString("utf8").replace(/\0+$/, "").trimEnd();
96
117
  try {
97
118
  const parsed = JSON.parse(payload);
98
- if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
99
- throw new Error('invalid_glb_payload');
119
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
120
+ throw new Error("invalid_glb_payload");
100
121
  }
101
122
  return parsed;
102
123
  }
103
124
  catch {
104
- throw new Error('invalid_glb_payload');
125
+ throw new Error("invalid_glb_payload");
105
126
  }
106
127
  }
107
- throw new Error('invalid_glb_payload');
128
+ throw new Error("invalid_glb_payload");
108
129
  }
109
130
  function validateSelfContainedGlb(filePath) {
110
131
  const gltf = extractGlbJsonChunk((0, node_fs_1.readFileSync)(filePath));
111
132
  const buffers = Array.isArray(gltf.buffers) ? gltf.buffers : [];
112
133
  for (const entry of buffers) {
113
- const uri = entry && typeof entry === 'object' && typeof entry.uri === 'string'
114
- ? entry.uri.trim()
115
- : '';
116
- if (uri && !uri.startsWith('data:')) {
117
- throw new Error('glb_external_dependency_unsupported');
134
+ const uri = entry && typeof entry === "object" && typeof entry.uri === "string" ? entry.uri.trim() : "";
135
+ if (uri && !uri.startsWith("data:")) {
136
+ throw new Error("glb_external_dependency_unsupported");
118
137
  }
119
138
  }
120
139
  const images = Array.isArray(gltf.images) ? gltf.images : [];
121
140
  for (const entry of images) {
122
- const uri = entry && typeof entry === 'object' && typeof entry.uri === 'string'
123
- ? entry.uri.trim()
124
- : '';
125
- if (uri && !uri.startsWith('data:')) {
126
- throw new Error('glb_external_dependency_unsupported');
141
+ const uri = entry && typeof entry === "object" && typeof entry.uri === "string" ? entry.uri.trim() : "";
142
+ if (uri && !uri.startsWith("data:")) {
143
+ throw new Error("glb_external_dependency_unsupported");
127
144
  }
128
145
  }
129
146
  }
@@ -166,10 +183,10 @@ function validateListingAssetPath(baseDir, relativePath, fieldName) {
166
183
  }
167
184
  function validateListingAssetExtension(relativePath, fieldName, kind) {
168
185
  const extension = (0, node_path_1.extname)(relativePath).toLowerCase();
169
- if (kind === 'image' && !LISTING_IMAGE_EXTENSIONS.has(extension)) {
186
+ if (kind === "image" && !LISTING_IMAGE_EXTENSIONS.has(extension)) {
170
187
  return `${fieldName} must use a .png file. Received "${relativePath}".`;
171
188
  }
172
- if (kind === 'video' && !LISTING_VIDEO_EXTENSIONS.has(extension)) {
189
+ if (kind === "video" && !LISTING_VIDEO_EXTENSIONS.has(extension)) {
173
190
  return `${fieldName} must use a .mp4 file. Received "${relativePath}".`;
174
191
  }
175
192
  return undefined;
@@ -199,14 +216,8 @@ function resolveListingMediaEntries(input) {
199
216
  for (let index = 0; index < input.value.length; index += 1) {
200
217
  const value = input.value[index];
201
218
  const fieldName = `${input.fieldName}[${index}]`;
202
- const objectValue = value && typeof value === 'object' && !Array.isArray(value)
203
- ? value
204
- : null;
205
- const relativePath = typeof value === 'string'
206
- ? value.trim()
207
- : typeof objectValue?.path === 'string'
208
- ? objectValue.path.trim()
209
- : '';
219
+ const objectValue = value && typeof value === "object" && !Array.isArray(value) ? value : null;
220
+ const relativePath = typeof value === "string" ? value.trim() : typeof objectValue?.path === "string" ? objectValue.path.trim() : "";
210
221
  if (!relativePath) {
211
222
  return { paths, metadata, error: `${fieldName} must be a path string or an object with a non-empty path` };
212
223
  }
@@ -222,22 +233,26 @@ function resolveListingMediaEntries(input) {
222
233
  fileKey: `${input.fileKeyPrefix}:${index}`,
223
234
  };
224
235
  if (objectValue) {
225
- for (const key of ['title', 'alt', 'caption', 'description']) {
236
+ for (const key of ["title", "alt", "caption", "description"]) {
226
237
  const raw = objectValue[key];
227
238
  if (raw === undefined) {
228
239
  continue;
229
240
  }
230
- if (typeof raw !== 'string' || raw.trim().length === 0) {
241
+ if (typeof raw !== "string" || raw.trim().length === 0) {
231
242
  return { paths, metadata, error: `${fieldName}.${key} must be a non-empty string when provided` };
232
243
  }
233
244
  const normalized = raw.trim();
234
245
  if (normalized.length > LISTING_MEDIA_TEXT_LIMITS[key]) {
235
- return { paths, metadata, error: `${fieldName}.${key} must be ${LISTING_MEDIA_TEXT_LIMITS[key]} characters or fewer` };
246
+ return {
247
+ paths,
248
+ metadata,
249
+ error: `${fieldName}.${key} must be ${LISTING_MEDIA_TEXT_LIMITS[key]} characters or fewer`,
250
+ };
236
251
  }
237
252
  item[key] = normalized;
238
253
  }
239
254
  if (objectValue.slug !== undefined) {
240
- if (typeof objectValue.slug !== 'string' || !LISTING_MEDIA_SLUG_REGEX.test(objectValue.slug.trim())) {
255
+ if (typeof objectValue.slug !== "string" || !LISTING_MEDIA_SLUG_REGEX.test(objectValue.slug.trim())) {
241
256
  return { paths, metadata, error: `${fieldName}.slug must use lowercase words separated by hyphens` };
242
257
  }
243
258
  item.slug = objectValue.slug.trim();
@@ -252,7 +267,7 @@ function normalizeCatalogueRemixRef(value, expectedKind, errors, context) {
252
267
  if (value === undefined || value === null) {
253
268
  return undefined;
254
269
  }
255
- if (typeof value !== 'string') {
270
+ if (typeof value !== "string") {
256
271
  errors.push(`${context} remix must be a canonical ${expectedKind} version ref string.`);
257
272
  return undefined;
258
273
  }
@@ -278,15 +293,15 @@ function normalizeCatalogueRelations(value, errors, context) {
278
293
  }
279
294
  const rows = [];
280
295
  for (const relation of value) {
281
- if (!relation || typeof relation !== 'object') {
296
+ if (!relation || typeof relation !== "object") {
282
297
  continue;
283
298
  }
284
- const type = typeof relation.type === 'string' ? relation.type.trim().toUpperCase() : '';
285
- const to = typeof relation.to === 'string' ? relation.to.trim() : '';
299
+ const type = typeof relation.type === "string" ? relation.type.trim().toUpperCase() : "";
300
+ const to = typeof relation.to === "string" ? relation.to.trim() : "";
286
301
  if (!type || !to) {
287
302
  continue;
288
303
  }
289
- if (type === 'REMIXES') {
304
+ if (type === "REMIXES") {
290
305
  errors.push(`${context} relations must not use REMIXES. Use the dedicated remix field instead.`);
291
306
  continue;
292
307
  }
@@ -303,17 +318,17 @@ function normalizeCatalogueAppDependencyAssets(value, errors, context) {
303
318
  const seenRuntimeKeys = new Set();
304
319
  for (let index = 0; index < value.length; index += 1) {
305
320
  const entry = value[index];
306
- const ref = typeof entry === 'string'
321
+ const ref = typeof entry === "string"
307
322
  ? entry.trim()
308
- : entry && typeof entry === 'object' && !Array.isArray(entry) && typeof entry.ref === 'string'
323
+ : entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.ref === "string"
309
324
  ? entry.ref.trim()
310
- : '';
325
+ : "";
311
326
  if (!ref) {
312
327
  errors.push(`${context} uses.assets[${index}] must be a non-empty asset ref string or { ref, runtimeKey } object.`);
313
328
  continue;
314
329
  }
315
330
  const parsed = (0, types_1.parseContentVersionRef)(ref);
316
- if (!parsed || parsed.kind !== 'asset') {
331
+ if (!parsed || parsed.kind !== "asset") {
317
332
  errors.push(`${context} uses.assets[${index}] must be a canonical asset version ref.`);
318
333
  continue;
319
334
  }
@@ -322,9 +337,9 @@ function normalizeCatalogueAppDependencyAssets(value, errors, context) {
322
337
  errors.push(`${context} uses.assets contains duplicate ref "${normalizedRef}".`);
323
338
  continue;
324
339
  }
325
- const runtimeKey = entry && typeof entry === 'object' && !Array.isArray(entry) && typeof entry.runtimeKey === 'string'
340
+ const runtimeKey = entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.runtimeKey === "string"
326
341
  ? entry.runtimeKey.trim()
327
- : '';
342
+ : "";
328
343
  if (runtimeKey) {
329
344
  if (seenRuntimeKeys.has(runtimeKey)) {
330
345
  errors.push(`${context} uses.assets contains duplicate runtimeKey "${runtimeKey}".`);
@@ -352,12 +367,12 @@ function normalizeCatalogueAppDependencyPacks(value, errors, context) {
352
367
  const seenRefs = new Set();
353
368
  for (let index = 0; index < value.length; index += 1) {
354
369
  const entry = value[index];
355
- if (typeof entry !== 'string' || entry.trim().length === 0) {
370
+ if (typeof entry !== "string" || entry.trim().length === 0) {
356
371
  errors.push(`${context} uses.packs[${index}] must be a non-empty pack version ref string. Use "pack:playdrop/name@version", not { ref, runtimeKey }.`);
357
372
  continue;
358
373
  }
359
374
  const parsed = (0, types_1.parseContentVersionRef)(entry.trim());
360
- if (!parsed || parsed.kind !== 'pack') {
375
+ if (!parsed || parsed.kind !== "pack") {
361
376
  errors.push(`${context} uses.packs[${index}] must be a canonical pack version ref.`);
362
377
  continue;
363
378
  }
@@ -372,7 +387,7 @@ function normalizeCatalogueAppDependencyPacks(value, errors, context) {
372
387
  return rows;
373
388
  }
374
389
  function rejectLegacyShopPriceCoinsField(value, errors, context) {
375
- if (Object.prototype.hasOwnProperty.call(value, 'shopPriceCoins')) {
390
+ if (Object.prototype.hasOwnProperty.call(value, "shopPriceCoins")) {
376
391
  errors.push(`${context} uses legacy shopPriceCoins. Rename it to shopPriceCredits.`);
377
392
  return true;
378
393
  }
@@ -390,11 +405,11 @@ function normalizeCatalogueTags(value, errors, context) {
390
405
  const seenRefs = new Set();
391
406
  for (let index = 0; index < value.length; index += 1) {
392
407
  const entry = value[index];
393
- if (typeof entry !== 'string' || entry.trim().length === 0) {
408
+ if (typeof entry !== "string" || entry.trim().length === 0) {
394
409
  errors.push(`${context} tags[${index}] must be a non-empty canonical tag ref string.`);
395
410
  continue;
396
411
  }
397
- let normalizedRef = '';
412
+ let normalizedRef = "";
398
413
  try {
399
414
  normalizedRef = (0, types_1.normalizeTagRef)(entry);
400
415
  }
@@ -426,9 +441,9 @@ function normalizeCatalogueAllowedTargetKinds(value, errors, context) {
426
441
  const seenKinds = new Set();
427
442
  for (let index = 0; index < value.length; index += 1) {
428
443
  const entry = value[index];
429
- const normalizedKind = typeof entry === 'string' ? entry.trim().toUpperCase() : '';
444
+ const normalizedKind = typeof entry === "string" ? entry.trim().toUpperCase() : "";
430
445
  if (!types_1.SAVED_ITEM_KIND_VALUES.includes(normalizedKind)) {
431
- errors.push(`${context} allowedTargetKinds[${index}] must be one of ${types_1.SAVED_ITEM_KIND_VALUES.join(', ')}.`);
446
+ errors.push(`${context} allowedTargetKinds[${index}] must be one of ${types_1.SAVED_ITEM_KIND_VALUES.join(", ")}.`);
432
447
  continue;
433
448
  }
434
449
  const savedItemKind = normalizedKind;
@@ -442,8 +457,8 @@ function normalizeCatalogueAllowedTargetKinds(value, errors, context) {
442
457
  return normalizedKinds;
443
458
  }
444
459
  function normalizeRelativePath(root, target) {
445
- const rel = (0, node_path_1.relative)(root, target).split(node_path_1.sep).join('/');
446
- return rel.replace(/^\/+/, '');
460
+ const rel = (0, node_path_1.relative)(root, target).split(node_path_1.sep).join("/");
461
+ return rel.replace(/^\/+/, "");
447
462
  }
448
463
  function tryRealpath(path) {
449
464
  try {
@@ -455,34 +470,34 @@ function tryRealpath(path) {
455
470
  }
456
471
  function normalizePathForComparison(path) {
457
472
  const actual = tryRealpath(path) ?? (0, node_path_1.resolve)(path);
458
- return actual.split(node_path_1.sep).join('/');
473
+ return actual.split(node_path_1.sep).join("/");
459
474
  }
460
475
  function shouldSkipDirectory(name) {
461
476
  if (!name)
462
477
  return true;
463
- if (name === '.' || name === '..')
478
+ if (name === "." || name === "..")
464
479
  return true;
465
- if (name.startsWith('.'))
480
+ if (name.startsWith("."))
466
481
  return true;
467
- return name === 'node_modules';
482
+ return name === "node_modules";
468
483
  }
469
484
  function readCatalogueFile(rootDir, filePath) {
470
485
  try {
471
- const raw = (0, node_fs_1.readFileSync)(filePath, 'utf8');
486
+ const raw = (0, node_fs_1.readFileSync)(filePath, "utf8");
472
487
  const parsed = JSON.parse(raw);
473
- if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
474
- return { error: 'catalogue.json must contain a JSON object' };
488
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
489
+ return { error: "catalogue.json must contain a JSON object" };
475
490
  }
476
491
  if (Object.prototype.hasOwnProperty.call(parsed, LEGACY_CATALOGUE_VERSION_KEY)) {
477
- return { error: 'catalogue.json must not define the legacy top-level version field. Remove it and retry.' };
492
+ return { error: "catalogue.json must not define the legacy top-level version field. Remove it and retry." };
478
493
  }
479
- const forbiddenKeys = ['avatars', 'humanoids', 'creatures', 'blocks', 'games'];
494
+ const forbiddenKeys = ["avatars", "humanoids", "creatures", "blocks", "games"];
480
495
  for (const key of forbiddenKeys) {
481
496
  if (Object.prototype.hasOwnProperty.call(parsed, key)) {
482
497
  return { error: `catalogue.json forbids top-level "${key}"` };
483
498
  }
484
499
  }
485
- const allowedKeys = new Set(['apps', 'tagGroups', 'assetSpecs', 'assets', 'assetPacks']);
500
+ const allowedKeys = new Set(["apps", "tagGroups", "assetSpecs", "assets", "assetPacks"]);
486
501
  const unknownKeys = Object.keys(parsed).filter((key) => !allowedKeys.has(key));
487
502
  if (unknownKeys.length > 0) {
488
503
  return {
@@ -490,18 +505,18 @@ function readCatalogueFile(rootDir, filePath) {
490
505
  };
491
506
  }
492
507
  const directory = (0, node_path_1.dirname)(filePath);
493
- const relativePath = (0, node_path_1.relative)(rootDir, filePath) || 'catalogue.json';
508
+ const relativePath = (0, node_path_1.relative)(rootDir, filePath) || "catalogue.json";
494
509
  return {
495
510
  file: {
496
511
  absolutePath: filePath,
497
- relativePath: relativePath.replace(/\\/g, '/'),
512
+ relativePath: relativePath.replace(/\\/g, "/"),
498
513
  directory,
499
514
  data: parsed,
500
515
  },
501
516
  };
502
517
  }
503
518
  catch (error) {
504
- return { error: `Invalid catalogue.json (${error?.message || 'failed to parse JSON'})` };
519
+ return { error: `Invalid catalogue.json (${error?.message || "failed to parse JSON"})` };
505
520
  }
506
521
  }
507
522
  function walkCatalogueTree(rootDir) {
@@ -521,11 +536,11 @@ function walkCatalogueTree(rootDir) {
521
536
  if (visited.has(realPath))
522
537
  continue;
523
538
  visited.add(realPath);
524
- const candidate = (0, node_path_1.join)(displayPath, 'catalogue.json');
539
+ const candidate = (0, node_path_1.join)(displayPath, "catalogue.json");
525
540
  if ((0, node_fs_1.existsSync)(candidate) && (0, node_fs_1.statSync)(candidate).isFile()) {
526
541
  const result = readCatalogueFile(rootDir, candidate);
527
542
  if (result.error) {
528
- errors.push(`[${normalizeRelativePath(rootDir, candidate) || 'catalogue.json'}] ${result.error}`);
543
+ errors.push(`[${normalizeRelativePath(rootDir, candidate) || "catalogue.json"}] ${result.error}`);
529
544
  }
530
545
  else if (result.file) {
531
546
  files.push(result.file);
@@ -565,7 +580,7 @@ function isCatalogueMarker(file) {
565
580
  }
566
581
  const packageJsonCache = new Map();
567
582
  function resolvePackageJson(projectDir) {
568
- const candidate = (0, node_path_1.join)(projectDir, 'package.json');
583
+ const candidate = (0, node_path_1.join)(projectDir, "package.json");
569
584
  if (!(0, node_fs_1.existsSync)(candidate)) {
570
585
  return null;
571
586
  }
@@ -582,44 +597,44 @@ function readPackageJsonManifest(packageJsonPath) {
582
597
  return packageJsonCache.get(packageJsonPath);
583
598
  }
584
599
  try {
585
- const raw = (0, node_fs_1.readFileSync)(packageJsonPath, 'utf8');
600
+ const raw = (0, node_fs_1.readFileSync)(packageJsonPath, "utf8");
586
601
  const parsed = raw.trim() ? JSON.parse(raw) : {};
587
602
  packageJsonCache.set(packageJsonPath, parsed);
588
603
  return parsed;
589
604
  }
590
605
  catch (error) {
591
- throw new Error(error?.message || 'failed to parse package.json');
606
+ throw new Error(error?.message || "failed to parse package.json");
592
607
  }
593
608
  }
594
609
  function collectMissingMetadata(entry) {
595
610
  const missing = [];
596
- if (typeof entry.displayName !== 'string' || !entry.displayName.trim()) {
597
- missing.push('displayName');
611
+ if (typeof entry.displayName !== "string" || !entry.displayName.trim()) {
612
+ missing.push("displayName");
598
613
  }
599
- if (typeof entry.description !== 'string' || !entry.description.trim()) {
600
- missing.push('description');
614
+ if (typeof entry.description !== "string" || !entry.description.trim()) {
615
+ missing.push("description");
601
616
  }
602
- const emojiProvided = Object.prototype.hasOwnProperty.call(entry, 'emoji');
617
+ const emojiProvided = Object.prototype.hasOwnProperty.call(entry, "emoji");
603
618
  if (!emojiProvided) {
604
- missing.push('emoji');
619
+ missing.push("emoji");
605
620
  }
606
621
  else {
607
622
  const emojiValue = entry.emoji;
608
623
  if (emojiValue !== null) {
609
- if (typeof emojiValue !== 'string' || !emojiValue.trim()) {
610
- missing.push('emoji');
624
+ if (typeof emojiValue !== "string" || !emojiValue.trim()) {
625
+ missing.push("emoji");
611
626
  }
612
627
  }
613
628
  }
614
- const colorProvided = Object.prototype.hasOwnProperty.call(entry, 'color');
629
+ const colorProvided = Object.prototype.hasOwnProperty.call(entry, "color");
615
630
  if (!colorProvided) {
616
- missing.push('color');
631
+ missing.push("color");
617
632
  }
618
633
  else {
619
634
  const colorValue = entry.color;
620
635
  if (colorValue !== null) {
621
- if (typeof colorValue !== 'string' || !colorValue.trim()) {
622
- missing.push('color');
636
+ if (typeof colorValue !== "string" || !colorValue.trim()) {
637
+ missing.push("color");
623
638
  }
624
639
  }
625
640
  }
@@ -627,45 +642,45 @@ function collectMissingMetadata(entry) {
627
642
  }
628
643
  function formatMissingMetadata(missing) {
629
644
  if (missing.length === 0)
630
- return '';
645
+ return "";
631
646
  if (missing.length === 1)
632
647
  return `no ${missing[0]} provided`;
633
648
  if (missing.length === 2)
634
649
  return `no ${missing[0]} or ${missing[1]} provided`;
635
650
  const last = missing[missing.length - 1];
636
- const initial = missing.slice(0, -1).join(', ');
651
+ const initial = missing.slice(0, -1).join(", ");
637
652
  return `no ${initial}, or ${last} provided`;
638
653
  }
639
654
  function normalizeAssetVisibilityValue(raw, errors, label) {
640
- if (typeof raw !== 'string' || raw.trim().length === 0) {
655
+ if (typeof raw !== "string" || raw.trim().length === 0) {
641
656
  return undefined;
642
657
  }
643
658
  const normalized = raw.trim().toUpperCase();
644
- if (normalized !== 'PUBLIC' && normalized !== 'PRIVATE') {
659
+ if (normalized !== "PUBLIC" && normalized !== "PRIVATE") {
645
660
  errors.push(`[${label}] visibility must be PUBLIC or PRIVATE.`);
646
661
  return undefined;
647
662
  }
648
663
  return normalized;
649
664
  }
650
665
  function normalizeContentLicenseValue(raw, errors, label, allowedValues, defaultValue = types_1.DEFAULT_CONTENT_LICENSE) {
651
- if (raw === undefined || raw === null || (typeof raw === 'string' && raw.trim().length === 0)) {
666
+ if (raw === undefined || raw === null || (typeof raw === "string" && raw.trim().length === 0)) {
652
667
  return defaultValue;
653
668
  }
654
669
  const parsed = (0, types_1.parseContentLicense)(raw);
655
670
  if (!parsed || !allowedValues.has(parsed)) {
656
- errors.push(`[${label}] license must be one of ${Array.from(allowedValues).join(', ')}.`);
671
+ errors.push(`[${label}] license must be one of ${Array.from(allowedValues).join(", ")}.`);
657
672
  return null;
658
673
  }
659
674
  return parsed;
660
675
  }
661
676
  function normalizeInheritedContentLicenseValue(raw, inheritedValue, errors, label, allowedValues) {
662
- if (raw === undefined || raw === null || (typeof raw === 'string' && raw.trim().length === 0)) {
677
+ if (raw === undefined || raw === null || (typeof raw === "string" && raw.trim().length === 0)) {
663
678
  return inheritedValue;
664
679
  }
665
680
  return normalizeContentLicenseValue(raw, errors, label, allowedValues);
666
681
  }
667
682
  function normalizeAssetCategoryValue(raw, errors, label) {
668
- if (typeof raw !== 'string' || raw.trim().length === 0) {
683
+ if (typeof raw !== "string" || raw.trim().length === 0) {
669
684
  errors.push(`${label} must define category.`);
670
685
  return null;
671
686
  }
@@ -677,17 +692,17 @@ function normalizeAssetCategoryValue(raw, errors, label) {
677
692
  return normalized;
678
693
  }
679
694
  function normalizeAssetSubcategoryValue(raw, category, errors, label) {
680
- if (category === 'CUSTOM') {
695
+ if (category === "CUSTOM") {
681
696
  if (raw === undefined || raw === null) {
682
697
  return null;
683
698
  }
684
- if (typeof raw === 'string' && raw.trim().length === 0) {
699
+ if (typeof raw === "string" && raw.trim().length === 0) {
685
700
  return null;
686
701
  }
687
702
  errors.push(`${label} must not define subcategory when category is CUSTOM.`);
688
703
  return null;
689
704
  }
690
- if (typeof raw !== 'string' || raw.trim().length === 0) {
705
+ if (typeof raw !== "string" || raw.trim().length === 0) {
691
706
  errors.push(`${label} must define subcategory.`);
692
707
  return null;
693
708
  }
@@ -708,19 +723,19 @@ function validateOptionalEmoji(value, hasEmojiField, errors) {
708
723
  if (!hasEmojiField) {
709
724
  return emoji;
710
725
  }
711
- if (emoji !== null && typeof emoji === 'string') {
726
+ if (emoji !== null && typeof emoji === "string") {
712
727
  const trimmed = emoji.trim();
713
728
  if (!trimmed) {
714
729
  return null;
715
730
  }
716
731
  if (Array.from(trimmed).length > 4) {
717
- errors.push('emoji must be a single character');
732
+ errors.push("emoji must be a single character");
718
733
  return emoji;
719
734
  }
720
735
  return trimmed;
721
736
  }
722
737
  if (emoji !== null) {
723
- errors.push('emoji must be a string or null');
738
+ errors.push("emoji must be a string or null");
724
739
  }
725
740
  return emoji;
726
741
  }
@@ -729,19 +744,19 @@ function validateOptionalColor(value, hasColorField, errors) {
729
744
  if (!hasColorField) {
730
745
  return color;
731
746
  }
732
- if (color !== null && typeof color === 'string') {
747
+ if (color !== null && typeof color === "string") {
733
748
  const trimmed = color.trim();
734
749
  if (!trimmed) {
735
750
  return null;
736
751
  }
737
752
  if (!HEX_COLOR_REGEX.test(trimmed)) {
738
- errors.push('color must be a hex RGB value like #FFAA00');
753
+ errors.push("color must be a hex RGB value like #FFAA00");
739
754
  return color;
740
755
  }
741
- return trimmed.startsWith('#') ? trimmed.toUpperCase() : `#${trimmed.toUpperCase()}`;
756
+ return trimmed.startsWith("#") ? trimmed.toUpperCase() : `#${trimmed.toUpperCase()}`;
742
757
  }
743
758
  if (color !== null) {
744
- errors.push('color must be a string or null');
759
+ errors.push("color must be a string or null");
745
760
  }
746
761
  return color;
747
762
  }
@@ -750,17 +765,17 @@ function validateAchievementDefinitions(value, catalogueDir, errors) {
750
765
  return [];
751
766
  }
752
767
  if (!Array.isArray(value)) {
753
- errors.push('achievements must be an array');
768
+ errors.push("achievements must be an array");
754
769
  return [];
755
770
  }
756
771
  const seenKeys = new Set();
757
772
  const definitions = [];
758
773
  value.forEach((entry, index) => {
759
- if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
774
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
760
775
  errors.push(`achievements[${index}] must be an object`);
761
776
  return;
762
777
  }
763
- const key = typeof entry.key === 'string' ? entry.key.trim() : '';
778
+ const key = typeof entry.key === "string" ? entry.key.trim() : "";
764
779
  if (!(0, types_1.isPlayerMetaKey)(key)) {
765
780
  errors.push(`achievements[${index}].key must match the player meta key format`);
766
781
  return;
@@ -770,33 +785,33 @@ function validateAchievementDefinitions(value, catalogueDir, errors) {
770
785
  return;
771
786
  }
772
787
  seenKeys.add(key);
773
- const displayName = typeof entry.displayName === 'string' ? entry.displayName.trim() : '';
788
+ const displayName = typeof entry.displayName === "string" ? entry.displayName.trim() : "";
774
789
  if (!displayName) {
775
790
  errors.push(`achievements[${index}].displayName must be a non-empty string`);
776
791
  return;
777
792
  }
778
- const description = typeof entry.description === 'string' ? entry.description.trim() : '';
793
+ const description = typeof entry.description === "string" ? entry.description.trim() : "";
779
794
  if (!description) {
780
795
  errors.push(`achievements[${index}].description must be a non-empty string`);
781
796
  return;
782
797
  }
783
798
  const kind = (0, types_1.parseAppAchievementKind)(entry.kind);
784
799
  if (!kind) {
785
- errors.push(`achievements[${index}].kind must be one of ${types_1.APP_ACHIEVEMENT_KIND_VALUES.join(', ')}`);
800
+ errors.push(`achievements[${index}].kind must be one of ${types_1.APP_ACHIEVEMENT_KIND_VALUES.join(", ")}`);
786
801
  return;
787
802
  }
788
803
  const hidden = Boolean(entry.hidden);
789
- const status = (0, types_1.parsePlayerMetaDefinitionStatus)(entry.status ?? 'ACTIVE');
804
+ const status = (0, types_1.parsePlayerMetaDefinitionStatus)(entry.status ?? "ACTIVE");
790
805
  if (!status) {
791
- errors.push(`achievements[${index}].status must be one of ${types_1.PLAYER_META_DEFINITION_STATUS_VALUES.join(', ')}`);
806
+ errors.push(`achievements[${index}].status must be one of ${types_1.PLAYER_META_DEFINITION_STATUS_VALUES.join(", ")}`);
792
807
  return;
793
808
  }
794
- const icon = typeof entry.icon === 'string' ? entry.icon.trim() : '';
809
+ const icon = typeof entry.icon === "string" ? entry.icon.trim() : "";
795
810
  if (!icon) {
796
811
  errors.push(`achievements[${index}].icon must be a non-empty PNG path`);
797
812
  return;
798
813
  }
799
- const iconExtensionError = validateListingAssetExtension(icon, `achievements[${index}].icon`, 'image');
814
+ const iconExtensionError = validateListingAssetExtension(icon, `achievements[${index}].icon`, "image");
800
815
  if (iconExtensionError) {
801
816
  errors.push(iconExtensionError);
802
817
  return;
@@ -808,7 +823,7 @@ function validateAchievementDefinitions(value, catalogueDir, errors) {
808
823
  }
809
824
  const iconPath = resolveListingAssetPath(catalogueDir, icon);
810
825
  let maxProgress;
811
- if (kind === 'INCREMENTAL') {
826
+ if (kind === "INCREMENTAL") {
812
827
  if (!Number.isSafeInteger(entry.maxProgress) || Number(entry.maxProgress) <= 0) {
813
828
  errors.push(`achievements[${index}].maxProgress must be a positive safe integer`);
814
829
  return;
@@ -838,17 +853,17 @@ function validateLeaderboardDefinitions(value, errors) {
838
853
  return [];
839
854
  }
840
855
  if (!Array.isArray(value)) {
841
- errors.push('leaderboards must be an array');
856
+ errors.push("leaderboards must be an array");
842
857
  return [];
843
858
  }
844
859
  const seenKeys = new Set();
845
860
  const definitions = [];
846
861
  value.forEach((entry, index) => {
847
- if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
862
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
848
863
  errors.push(`leaderboards[${index}] must be an object`);
849
864
  return;
850
865
  }
851
- const key = typeof entry.key === 'string' ? entry.key.trim() : '';
866
+ const key = typeof entry.key === "string" ? entry.key.trim() : "";
852
867
  if (!(0, types_1.isPlayerMetaKey)(key)) {
853
868
  errors.push(`leaderboards[${index}].key must match the player meta key format`);
854
869
  return;
@@ -858,36 +873,36 @@ function validateLeaderboardDefinitions(value, errors) {
858
873
  return;
859
874
  }
860
875
  seenKeys.add(key);
861
- const displayName = typeof entry.displayName === 'string' ? entry.displayName.trim() : '';
876
+ const displayName = typeof entry.displayName === "string" ? entry.displayName.trim() : "";
862
877
  if (!displayName) {
863
878
  errors.push(`leaderboards[${index}].displayName must be a non-empty string`);
864
879
  return;
865
880
  }
866
- const description = typeof entry.description === 'string' ? entry.description.trim() : '';
881
+ const description = typeof entry.description === "string" ? entry.description.trim() : "";
867
882
  if (!description) {
868
883
  errors.push(`leaderboards[${index}].description must be a non-empty string`);
869
884
  return;
870
885
  }
871
886
  const scoreType = (0, types_1.parseAppLeaderboardScoreType)(entry.scoreType);
872
887
  if (!scoreType) {
873
- errors.push(`leaderboards[${index}].scoreType must be one of ${types_1.APP_LEADERBOARD_SCORE_TYPE_VALUES.join(', ')}`);
888
+ errors.push(`leaderboards[${index}].scoreType must be one of ${types_1.APP_LEADERBOARD_SCORE_TYPE_VALUES.join(", ")}`);
874
889
  return;
875
890
  }
876
891
  const sort = (0, types_1.parseAppLeaderboardSort)(entry.sort);
877
892
  if (!sort) {
878
- errors.push(`leaderboards[${index}].sort must be one of ${types_1.APP_LEADERBOARD_SORT_VALUES.join(', ')}`);
893
+ errors.push(`leaderboards[${index}].sort must be one of ${types_1.APP_LEADERBOARD_SORT_VALUES.join(", ")}`);
879
894
  return;
880
895
  }
881
896
  if (!(0, types_1.isValidLeaderboardDefinitionCombination)(scoreType, sort)) {
882
897
  errors.push(`leaderboards[${index}] must use INTEGER + DESC or TIME_MS + ASC`);
883
898
  return;
884
899
  }
885
- const status = (0, types_1.parsePlayerMetaDefinitionStatus)(entry.status ?? 'ACTIVE');
900
+ const status = (0, types_1.parsePlayerMetaDefinitionStatus)(entry.status ?? "ACTIVE");
886
901
  if (!status) {
887
- errors.push(`leaderboards[${index}].status must be one of ${types_1.PLAYER_META_DEFINITION_STATUS_VALUES.join(', ')}`);
902
+ errors.push(`leaderboards[${index}].status must be one of ${types_1.PLAYER_META_DEFINITION_STATUS_VALUES.join(", ")}`);
888
903
  return;
889
904
  }
890
- const unitLabel = typeof entry.unitLabel === 'string' && entry.unitLabel.trim().length > 0
905
+ const unitLabel = typeof entry.unitLabel === "string" && entry.unitLabel.trim().length > 0
891
906
  ? entry.unitLabel.trim()
892
907
  : undefined;
893
908
  definitions.push({
@@ -909,26 +924,26 @@ function validateAppMetadata(entry) {
909
924
  if (missingFields.length > 0) {
910
925
  warnings.push(formatMissingMetadata(missingFields));
911
926
  }
912
- const emoji = validateOptionalEmoji(entry.emoji, Object.prototype.hasOwnProperty.call(entry, 'emoji'), errors);
913
- const color = validateOptionalColor(entry.color, Object.prototype.hasOwnProperty.call(entry, 'color'), errors);
927
+ const emoji = validateOptionalEmoji(entry.emoji, Object.prototype.hasOwnProperty.call(entry, "emoji"), errors);
928
+ const color = validateOptionalColor(entry.color, Object.prototype.hasOwnProperty.call(entry, "color"), errors);
914
929
  let type;
915
- if (typeof entry.type === 'string' && entry.type.trim()) {
930
+ if (typeof entry.type === "string" && entry.type.trim()) {
916
931
  const parsedType = (0, types_1.parseAppType)(entry.type.trim());
917
932
  if (!parsedType) {
918
- errors.push(`type must be one of ${types_1.APP_TYPE_VALUES.join(', ')}`);
933
+ errors.push(`type must be one of ${types_1.APP_TYPE_VALUES.join(", ")}`);
919
934
  }
920
935
  else {
921
936
  type = parsedType;
922
937
  }
923
938
  }
924
939
  let displayName;
925
- if (typeof entry.displayName === 'string' && entry.displayName.trim()) {
940
+ if (typeof entry.displayName === "string" && entry.displayName.trim()) {
926
941
  displayName = entry.displayName.trim();
927
942
  }
928
943
  let subtitle;
929
- if (Object.prototype.hasOwnProperty.call(entry, 'subtitle')) {
930
- if (typeof entry.subtitle !== 'string' || entry.subtitle.trim().length === 0) {
931
- errors.push('subtitle must be a non-empty string when provided');
944
+ if (Object.prototype.hasOwnProperty.call(entry, "subtitle")) {
945
+ if (typeof entry.subtitle !== "string" || entry.subtitle.trim().length === 0) {
946
+ errors.push("subtitle must be a non-empty string when provided");
932
947
  }
933
948
  else if (entry.subtitle.trim().length > types_1.APP_SUBTITLE_MAX_LENGTH) {
934
949
  errors.push(`subtitle must be ${types_1.APP_SUBTITLE_MAX_LENGTH} characters or fewer`);
@@ -938,25 +953,25 @@ function validateAppMetadata(entry) {
938
953
  }
939
954
  }
940
955
  let description;
941
- if (typeof entry.description === 'string') {
956
+ if (typeof entry.description === "string") {
942
957
  description = entry.description;
943
958
  const normalizedDescription = description.trim();
944
959
  const wordCount = normalizedDescription ? normalizedDescription.split(/\s+/).length : 0;
945
- if (normalizedDescription
946
- && (normalizedDescription.length < exports.APP_DESCRIPTION_RECOMMENDED_MIN_CHARACTERS
947
- || wordCount < exports.APP_DESCRIPTION_RECOMMENDED_MIN_WORDS)) {
960
+ if (normalizedDescription &&
961
+ (normalizedDescription.length < exports.APP_DESCRIPTION_RECOMMENDED_MIN_CHARACTERS ||
962
+ wordCount < exports.APP_DESCRIPTION_RECOMMENDED_MIN_WORDS)) {
948
963
  warnings.push(`description is ${normalizedDescription.length} characters and ${wordCount} words. Aim for at least ${exports.APP_DESCRIPTION_RECOMMENDED_MIN_CHARACTERS} characters and ${exports.APP_DESCRIPTION_RECOMMENDED_MIN_WORDS} words describing what the player does, the objective, and the game's distinctive hook.`);
949
964
  }
950
965
  }
951
966
  let editorSupported;
952
- if (Object.prototype.hasOwnProperty.call(entry, 'editorSupported')) {
953
- if (typeof entry.editorSupported !== 'boolean') {
954
- errors.push('editorSupported must be true or false');
967
+ if (Object.prototype.hasOwnProperty.call(entry, "editorSupported")) {
968
+ if (typeof entry.editorSupported !== "boolean") {
969
+ errors.push("editorSupported must be true or false");
955
970
  }
956
971
  else {
957
972
  editorSupported = entry.editorSupported;
958
- if (editorSupported && entry.hostingMode === 'EXTERNAL') {
959
- errors.push('editorSupported can only be true for HOSTED apps, not external apps');
973
+ if (editorSupported && entry.hostingMode === "EXTERNAL") {
974
+ errors.push("editorSupported can only be true for HOSTED apps, not external apps");
960
975
  }
961
976
  }
962
977
  }
@@ -968,20 +983,20 @@ function validateAppMetadata(entry) {
968
983
  surfaceTargetsMap = { ...parsed.map };
969
984
  }
970
985
  catch (error) {
971
- errors.push(error?.message || 'invalid surfaceTargets');
986
+ errors.push(error?.message || "invalid surfaceTargets");
972
987
  }
973
988
  let primarySurface;
974
- if (Object.prototype.hasOwnProperty.call(entry, 'primarySurface')) {
989
+ if (Object.prototype.hasOwnProperty.call(entry, "primarySurface")) {
975
990
  primarySurface = (0, types_1.parseAppSurface)(entry.primarySurface) ?? undefined;
976
991
  if (!primarySurface) {
977
- errors.push('primarySurface must be DESKTOP, MOBILE_LANDSCAPE, or MOBILE_PORTRAIT');
992
+ errors.push("primarySurface must be DESKTOP, MOBILE_LANDSCAPE, or MOBILE_PORTRAIT");
978
993
  }
979
994
  else if (!surfaceTargetsList.includes(primarySurface)) {
980
995
  errors.push(`primarySurface ${primarySurface} must also be enabled in surfaceTargets`);
981
996
  }
982
997
  }
983
998
  let playtestTapes;
984
- if (Object.prototype.hasOwnProperty.call(entry, 'playtestTapes')) {
999
+ if (Object.prototype.hasOwnProperty.call(entry, "playtestTapes")) {
985
1000
  const normalized = (0, types_1.normalizeAppPlaytestTapes)(entry.playtestTapes, {
986
1001
  surfaceTargets: surfaceTargetsList,
987
1002
  requireComplete: true,
@@ -1011,32 +1026,32 @@ function validateAppMetadata(entry) {
1011
1026
  };
1012
1027
  }
1013
1028
  function formatAppPlaytestTapeError(error) {
1014
- const parts = error.split(':');
1029
+ const parts = error.split(":");
1015
1030
  const code = parts[0] ?? error;
1016
- const surface = parts[1] ?? 'UNKNOWN';
1017
- if (code === 'playtest_tape_start_only_input_left_active') {
1018
- const count = parts[2] ?? 'unknown';
1031
+ const surface = parts[1] ?? "UNKNOWN";
1032
+ if (code === "playtest_tape_start_only_input_left_active") {
1033
+ const count = parts[2] ?? "unknown";
1019
1034
  return `${error}: playtestTapes.${surface}.startOnlyEventCount=${count} ends while an input is held; include the matching keyUp or pointerUp inside the startup prefix.`;
1020
1035
  }
1021
- if (code === 'playtest_tape_primary_verb_events_required') {
1022
- const verb = parts[2] ?? 'unknown';
1036
+ if (code === "playtest_tape_primary_verb_events_required") {
1037
+ const verb = parts[2] ?? "unknown";
1023
1038
  return `${error}: playtestTapes.${surface} must demonstrate primaryVerb "${verb}" at least once after startup; secondary controls are allowed.`;
1024
1039
  }
1025
- if (code === 'playtest_tape_primary_verb_gesture_required') {
1026
- const verb = parts[2] ?? 'unknown';
1040
+ if (code === "playtest_tape_primary_verb_gesture_required") {
1041
+ const verb = parts[2] ?? "unknown";
1027
1042
  return `${error}: playtestTapes.${surface} primaryVerb "${verb}" requires one complete pointerDown, pointerMove, pointerUp gesture after startup.`;
1028
1043
  }
1029
- if (code === 'invalid_playtest_tape_key') {
1030
- const index = parts[2] ?? 'unknown';
1044
+ if (code === "invalid_playtest_tape_key") {
1045
+ const index = parts[2] ?? "unknown";
1031
1046
  return `${error}: playtestTapes.${surface}.events[${index}].key must be a browser key string with 1..64 characters; use a single space " " for the space bar.`;
1032
1047
  }
1033
- if (code === 'invalid_playtest_tape_event_type') {
1034
- const index = parts[2] ?? 'unknown';
1035
- const actual = parts[3] ?? 'missing';
1048
+ if (code === "invalid_playtest_tape_event_type") {
1049
+ const index = parts[2] ?? "unknown";
1050
+ const actual = parts[3] ?? "missing";
1036
1051
  return `${error}: playtestTapes.${surface}.events[${index}].type="${actual}"; expected tap, pointerDown, pointerMove, pointerUp, keyDown, or keyUp.`;
1037
1052
  }
1038
- if (code === 'invalid_playtest_tape_success_signal_description') {
1039
- const index = parts[2] ?? 'unknown';
1053
+ if (code === "invalid_playtest_tape_success_signal_description") {
1054
+ const index = parts[2] ?? "unknown";
1040
1055
  return `${error}: playtestTapes.${surface}.successSignals[${index}].description must contain 1..240 characters.`;
1041
1056
  }
1042
1057
  return error;
@@ -1046,10 +1061,10 @@ function buildAppTasks(rootDir, catalogues, options) {
1046
1061
  const warnings = [];
1047
1062
  const errors = [];
1048
1063
  const seen = new Map();
1049
- const filterName = options.filterName ? options.filterName.trim() : '';
1050
- const filterFile = options.filterFile ? normalizePathForComparison(options.filterFile) : '';
1064
+ const filterName = options.filterName ? options.filterName.trim() : "";
1065
+ const filterFile = options.filterFile ? normalizePathForComparison(options.filterFile) : "";
1051
1066
  for (const file of catalogues) {
1052
- const label = file.relativePath || 'catalogue.json';
1067
+ const label = file.relativePath || "catalogue.json";
1053
1068
  const rawApps = Array.isArray(file.data.apps) ? file.data.apps : [];
1054
1069
  if (rawApps.length === 0) {
1055
1070
  const legacyGames = Array.isArray(file.data.games);
@@ -1059,23 +1074,23 @@ function buildAppTasks(rootDir, catalogues, options) {
1059
1074
  continue;
1060
1075
  }
1061
1076
  for (const entry of rawApps) {
1062
- if (!entry || typeof entry !== 'object') {
1077
+ if (!entry || typeof entry !== "object") {
1063
1078
  errors.push(`[${label}] Invalid app entry; expected object.`);
1064
1079
  continue;
1065
1080
  }
1066
- const rawName = typeof entry.name === 'string' ? entry.name.trim() : '';
1081
+ const rawName = typeof entry.name === "string" ? entry.name.trim() : "";
1067
1082
  if (!rawName) {
1068
1083
  errors.push(`[${label}] App entry is missing a name.`);
1069
1084
  continue;
1070
1085
  }
1071
1086
  const ownedAssetNameMatch = filterName
1072
- ? Array.isArray(entry.ownedAssets)
1073
- && entry.ownedAssets.some((ownedAsset) => ownedAsset
1074
- && typeof ownedAsset === 'object'
1075
- && typeof ownedAsset.name === 'string'
1076
- && ownedAsset.name.trim() === filterName)
1087
+ ? Array.isArray(entry.ownedAssets) &&
1088
+ entry.ownedAssets.some((ownedAsset) => ownedAsset &&
1089
+ typeof ownedAsset === "object" &&
1090
+ typeof ownedAsset.name === "string" &&
1091
+ ownedAsset.name.trim() === filterName)
1077
1092
  : true;
1078
- const fileField = typeof entry.file === 'string' && entry.file.trim().length > 0
1093
+ const fileField = typeof entry.file === "string" && entry.file.trim().length > 0
1079
1094
  ? entry.file.trim()
1080
1095
  : `${rawName}.html`;
1081
1096
  const absoluteFilePath = (0, node_path_1.resolve)(file.directory, fileField);
@@ -1091,13 +1106,13 @@ function buildAppTasks(rootDir, catalogues, options) {
1091
1106
  }
1092
1107
  seen.set(rawName, label);
1093
1108
  // Check for external mode early (before file validation)
1094
- const rawExternalUrlEarly = typeof entry.externalUrl === 'string' ? entry.externalUrl.trim() : '';
1095
- const rawHostingModeEarly = typeof entry.hostingMode === 'string' ? entry.hostingMode.trim().toUpperCase() : '';
1096
- const isExternalMode = rawHostingModeEarly === 'EXTERNAL' || (rawExternalUrlEarly && rawExternalUrlEarly.startsWith('https://'));
1109
+ const rawExternalUrlEarly = typeof entry.externalUrl === "string" ? entry.externalUrl.trim() : "";
1110
+ const rawHostingModeEarly = typeof entry.hostingMode === "string" ? entry.hostingMode.trim().toUpperCase() : "";
1111
+ const isExternalMode = rawHostingModeEarly === "EXTERNAL" || (rawExternalUrlEarly && rawExternalUrlEarly.startsWith("https://"));
1097
1112
  // Only validate file path for non-external apps
1098
1113
  if (!isExternalMode) {
1099
1114
  const relativeToProject = (0, node_path_1.relative)(file.directory, absoluteFilePath);
1100
- if (relativeToProject.startsWith('..')) {
1115
+ if (relativeToProject.startsWith("..")) {
1101
1116
  errors.push(`[${label}] Skipping ${rawName}: file path escapes project directory (${fileField}).`);
1102
1117
  continue;
1103
1118
  }
@@ -1111,15 +1126,15 @@ function buildAppTasks(rootDir, catalogues, options) {
1111
1126
  try {
1112
1127
  packageManifest = readPackageJsonManifest(packageJsonPath);
1113
1128
  const scripts = packageManifest?.scripts;
1114
- if (scripts && typeof scripts.validate === 'string' && scripts.validate.trim().length > 0) {
1129
+ if (scripts && typeof scripts.validate === "string" && scripts.validate.trim().length > 0) {
1115
1130
  hasValidateScript = true;
1116
1131
  }
1117
- if (scripts && typeof scripts.format === 'string' && scripts.format.trim().length > 0) {
1132
+ if (scripts && typeof scripts.format === "string" && scripts.format.trim().length > 0) {
1118
1133
  hasFormatScript = true;
1119
1134
  }
1120
1135
  }
1121
1136
  catch (error) {
1122
- errors.push(`[${label}] Skipping ${rawName}: failed to read package.json (${error?.message || 'unknown error'}).`);
1137
+ errors.push(`[${label}] Skipping ${rawName}: failed to read package.json (${error?.message || "unknown error"}).`);
1123
1138
  continue;
1124
1139
  }
1125
1140
  }
@@ -1142,11 +1157,11 @@ function buildAppTasks(rootDir, catalogues, options) {
1142
1157
  }
1143
1158
  const metadata = validateAppMetadata(entry);
1144
1159
  if (metadata.errors.length > 0) {
1145
- errors.push(`[${label}] Skipping ${rawName}: ${metadata.errors.join('; ')}`);
1160
+ errors.push(`[${label}] Skipping ${rawName}: ${metadata.errors.join("; ")}`);
1146
1161
  continue;
1147
1162
  }
1148
1163
  if (metadata.warnings.length > 0) {
1149
- warnings.push(`${label} (${metadata.warnings.join('; ')})`);
1164
+ warnings.push(`${label} (${metadata.warnings.join("; ")})`);
1150
1165
  }
1151
1166
  const errorCountBeforePlayerMeta = errors.length;
1152
1167
  const achievements = validateAchievementDefinitions(entry.achievements, file.directory, errors);
@@ -1162,7 +1177,7 @@ function buildAppTasks(rootDir, catalogues, options) {
1162
1177
  warnings.push(`${label} (${rawName}: package.json missing "format" script)`);
1163
1178
  }
1164
1179
  // Parse versioning fields.
1165
- const rawVersion = typeof entry.version === 'string' ? entry.version.trim() : '';
1180
+ const rawVersion = typeof entry.version === "string" ? entry.version.trim() : "";
1166
1181
  if (!rawVersion) {
1167
1182
  errors.push(`[${label}] Skipping ${rawName}: app version is required (e.g., "1.0.0").`);
1168
1183
  continue;
@@ -1172,93 +1187,106 @@ function buildAppTasks(rootDir, catalogues, options) {
1172
1187
  continue;
1173
1188
  }
1174
1189
  const version = rawVersion;
1175
- const releaseNotes = typeof entry.releaseNotes === 'string' ? entry.releaseNotes : undefined;
1190
+ let releaseNotes;
1191
+ if (Object.prototype.hasOwnProperty.call(entry, "releaseNotes")) {
1192
+ if (typeof entry.releaseNotes !== "string") {
1193
+ errors.push(`[${label}] Skipping ${rawName}: releaseNotes must be a string containing one line with 1..240 characters.`);
1194
+ continue;
1195
+ }
1196
+ try {
1197
+ releaseNotes = normalizeAppReleaseNotes(entry.releaseNotes);
1198
+ }
1199
+ catch (error) {
1200
+ errors.push(`[${label}] Skipping ${rawName}: ${error instanceof Error ? error.message : String(error)}`);
1201
+ continue;
1202
+ }
1203
+ }
1176
1204
  let versionVisibility;
1177
- const rawVisibility = typeof entry.visibility === 'string' ? entry.visibility.trim().toUpperCase() : '';
1205
+ const rawVisibility = typeof entry.visibility === "string" ? entry.visibility.trim().toUpperCase() : "";
1178
1206
  if (rawVisibility) {
1179
1207
  if (!types_1.APP_VERSION_VISIBILITY_VALUES.includes(rawVisibility)) {
1180
- errors.push(`[${label}] Skipping ${rawName}: visibility must be one of ${types_1.APP_VERSION_VISIBILITY_VALUES.join(', ')}.`);
1208
+ errors.push(`[${label}] Skipping ${rawName}: visibility must be one of ${types_1.APP_VERSION_VISIBILITY_VALUES.join(", ")}.`);
1181
1209
  continue;
1182
1210
  }
1183
1211
  versionVisibility = rawVisibility;
1184
1212
  }
1185
1213
  let hostingMode;
1186
- const rawHostingMode = typeof entry.hostingMode === 'string' ? entry.hostingMode.trim().toUpperCase() : '';
1214
+ const rawHostingMode = typeof entry.hostingMode === "string" ? entry.hostingMode.trim().toUpperCase() : "";
1187
1215
  if (rawHostingMode) {
1188
1216
  if (!types_1.APP_HOSTING_MODE_VALUES.includes(rawHostingMode)) {
1189
- errors.push(`[${label}] Skipping ${rawName}: hostingMode must be one of ${types_1.APP_HOSTING_MODE_VALUES.join(', ')}.`);
1217
+ errors.push(`[${label}] Skipping ${rawName}: hostingMode must be one of ${types_1.APP_HOSTING_MODE_VALUES.join(", ")}.`);
1190
1218
  continue;
1191
1219
  }
1192
1220
  hostingMode = rawHostingMode;
1193
1221
  }
1194
1222
  let authMode;
1195
- const rawAuthMode = typeof entry.authMode === 'string' ? entry.authMode.trim().toUpperCase() : '';
1223
+ const rawAuthMode = typeof entry.authMode === "string" ? entry.authMode.trim().toUpperCase() : "";
1196
1224
  if (rawAuthMode) {
1197
1225
  if (!types_1.APP_AUTH_MODE_VALUES.includes(rawAuthMode)) {
1198
- errors.push(`[${label}] Skipping ${rawName}: authMode must be one of ${types_1.APP_AUTH_MODE_VALUES.join(', ')}.`);
1226
+ errors.push(`[${label}] Skipping ${rawName}: authMode must be one of ${types_1.APP_AUTH_MODE_VALUES.join(", ")}.`);
1199
1227
  continue;
1200
1228
  }
1201
1229
  authMode = rawAuthMode;
1202
1230
  }
1203
1231
  let controllerMode;
1204
- const rawControllerMode = typeof entry.controllerMode === 'string' ? entry.controllerMode.trim().toUpperCase() : '';
1232
+ const rawControllerMode = typeof entry.controllerMode === "string" ? entry.controllerMode.trim().toUpperCase() : "";
1205
1233
  if (rawControllerMode) {
1206
1234
  if (!types_1.APP_CONTROLLER_MODE_VALUES.includes(rawControllerMode)) {
1207
- errors.push(`[${label}] Skipping ${rawName}: controllerMode must be one of ${types_1.APP_CONTROLLER_MODE_VALUES.join(', ')}.`);
1235
+ errors.push(`[${label}] Skipping ${rawName}: controllerMode must be one of ${types_1.APP_CONTROLLER_MODE_VALUES.join(", ")}.`);
1208
1236
  continue;
1209
1237
  }
1210
1238
  controllerMode = rawControllerMode;
1211
1239
  }
1212
1240
  let previewable;
1213
- if (Object.prototype.hasOwnProperty.call(entry, 'previewable')) {
1214
- if (typeof entry.previewable !== 'boolean') {
1241
+ if (Object.prototype.hasOwnProperty.call(entry, "previewable")) {
1242
+ if (typeof entry.previewable !== "boolean") {
1215
1243
  errors.push(`[${label}] Skipping ${rawName}: previewable must be true or false.`);
1216
1244
  continue;
1217
1245
  }
1218
1246
  previewable = Boolean(entry.previewable);
1219
1247
  }
1220
1248
  let editorSupported;
1221
- if (Object.prototype.hasOwnProperty.call(entry, 'editorSupported')) {
1222
- if (typeof entry.editorSupported !== 'boolean') {
1249
+ if (Object.prototype.hasOwnProperty.call(entry, "editorSupported")) {
1250
+ if (typeof entry.editorSupported !== "boolean") {
1223
1251
  errors.push(`[${label}] Skipping ${rawName}: editorSupported must be true or false.`);
1224
1252
  continue;
1225
1253
  }
1226
1254
  editorSupported = Boolean(entry.editorSupported);
1227
1255
  }
1228
1256
  let externalUrl;
1229
- const rawExternalUrl = typeof entry.externalUrl === 'string' ? entry.externalUrl.trim() : '';
1257
+ const rawExternalUrl = typeof entry.externalUrl === "string" ? entry.externalUrl.trim() : "";
1230
1258
  if (rawExternalUrl) {
1231
- if (!rawExternalUrl.startsWith('https://')) {
1259
+ if (!rawExternalUrl.startsWith("https://")) {
1232
1260
  errors.push(`[${label}] Skipping ${rawName}: externalUrl must be an HTTPS URL.`);
1233
1261
  continue;
1234
1262
  }
1235
1263
  externalUrl = rawExternalUrl;
1236
1264
  }
1237
1265
  // Validate hostingMode / externalUrl consistency
1238
- if (hostingMode === 'EXTERNAL' && !externalUrl) {
1266
+ if (hostingMode === "EXTERNAL" && !externalUrl) {
1239
1267
  errors.push(`[${label}] Skipping ${rawName}: externalUrl is required when hostingMode is EXTERNAL.`);
1240
1268
  continue;
1241
1269
  }
1242
- if (externalUrl && hostingMode !== 'EXTERNAL') {
1270
+ if (externalUrl && hostingMode !== "EXTERNAL") {
1243
1271
  // Auto-set hostingMode to EXTERNAL if externalUrl is provided
1244
- hostingMode = 'EXTERNAL';
1272
+ hostingMode = "EXTERNAL";
1245
1273
  }
1246
- if ((hostingMode ?? 'HOSTED') === 'EXTERNAL' && previewable) {
1274
+ if ((hostingMode ?? "HOSTED") === "EXTERNAL" && previewable) {
1247
1275
  errors.push(`[${label}] Skipping ${rawName}: previewable can only be true for HOSTED apps.`);
1248
1276
  continue;
1249
1277
  }
1250
- if ((hostingMode ?? 'HOSTED') === 'EXTERNAL' && editorSupported) {
1278
+ if ((hostingMode ?? "HOSTED") === "EXTERNAL" && editorSupported) {
1251
1279
  errors.push(`[${label}] Skipping ${rawName}: editorSupported can only be true for HOSTED apps.`);
1252
1280
  continue;
1253
1281
  }
1254
- const defaultLicense = (hostingMode ?? 'HOSTED') === 'EXTERNAL' ? 'CLOSED' : types_1.DEFAULT_CONTENT_LICENSE;
1282
+ const defaultLicense = (hostingMode ?? "HOSTED") === "EXTERNAL" ? "CLOSED" : types_1.DEFAULT_CONTENT_LICENSE;
1255
1283
  const license = normalizeContentLicenseValue(entry.license, errors, `${label} app "${rawName}"`, APP_ALLOWED_LICENSES, defaultLicense);
1256
1284
  if (!license) {
1257
1285
  continue;
1258
1286
  }
1259
1287
  // Parse username field (admin-only)
1260
1288
  let username;
1261
- const rawUsername = typeof entry.username === 'string' ? entry.username.trim() : '';
1289
+ const rawUsername = typeof entry.username === "string" ? entry.username.trim() : "";
1262
1290
  if (rawUsername) {
1263
1291
  username = rawUsername;
1264
1292
  }
@@ -1266,7 +1294,7 @@ function buildAppTasks(rootDir, catalogues, options) {
1266
1294
  let listing;
1267
1295
  const rawListing = entry.listing;
1268
1296
  let listingValidationFailed = false;
1269
- if (rawListing && typeof rawListing === 'object') {
1297
+ if (options.listingAssets !== "ignore" && rawListing && typeof rawListing === "object") {
1270
1298
  const resolvedListing = {
1271
1299
  screenshotPortraitPaths: [],
1272
1300
  screenshotLandscapePaths: [],
@@ -1275,15 +1303,15 @@ function buildAppTasks(rootDir, catalogues, options) {
1275
1303
  mediaMetadata: [],
1276
1304
  };
1277
1305
  // Icon
1278
- if (typeof rawListing.icon === 'string' && rawListing.icon.trim()) {
1306
+ if (typeof rawListing.icon === "string" && rawListing.icon.trim()) {
1279
1307
  const iconPath = rawListing.icon.trim();
1280
- const extensionError = validateListingAssetExtension(iconPath, 'listing.icon', 'image');
1308
+ const extensionError = validateListingAssetExtension(iconPath, "listing.icon", "image");
1281
1309
  if (extensionError) {
1282
1310
  errors.push(`[${label}] Skipping ${rawName}: ${extensionError}`);
1283
1311
  listingValidationFailed = true;
1284
1312
  }
1285
1313
  else {
1286
- const validationError = validateListingAssetPath(file.directory, iconPath, 'listing.icon');
1314
+ const validationError = validateListingAssetPath(file.directory, iconPath, "listing.icon");
1287
1315
  if (validationError) {
1288
1316
  errors.push(`[${label}] Skipping ${rawName}: ${validationError}`);
1289
1317
  listingValidationFailed = true;
@@ -1294,20 +1322,20 @@ function buildAppTasks(rootDir, catalogues, options) {
1294
1322
  }
1295
1323
  }
1296
1324
  // Hero images
1297
- const rawHeroPortrait = typeof rawListing.heroPortrait === 'string' && rawListing.heroPortrait.trim()
1325
+ const rawHeroPortrait = typeof rawListing.heroPortrait === "string" && rawListing.heroPortrait.trim()
1298
1326
  ? rawListing.heroPortrait.trim()
1299
- : typeof rawListing.heroPortraitPath === 'string' && rawListing.heroPortraitPath.trim()
1327
+ : typeof rawListing.heroPortraitPath === "string" && rawListing.heroPortraitPath.trim()
1300
1328
  ? rawListing.heroPortraitPath.trim()
1301
- : '';
1329
+ : "";
1302
1330
  if (!listingValidationFailed && rawHeroPortrait) {
1303
1331
  const heroPortraitPath = rawHeroPortrait;
1304
- const extensionError = validateListingAssetExtension(heroPortraitPath, 'listing.heroPortrait', 'image');
1332
+ const extensionError = validateListingAssetExtension(heroPortraitPath, "listing.heroPortrait", "image");
1305
1333
  if (extensionError) {
1306
1334
  errors.push(`[${label}] Skipping ${rawName}: ${extensionError}`);
1307
1335
  listingValidationFailed = true;
1308
1336
  }
1309
1337
  else {
1310
- const validationError = validateListingAssetPath(file.directory, heroPortraitPath, 'listing.heroPortrait');
1338
+ const validationError = validateListingAssetPath(file.directory, heroPortraitPath, "listing.heroPortrait");
1311
1339
  if (validationError) {
1312
1340
  errors.push(`[${label}] Skipping ${rawName}: ${validationError}`);
1313
1341
  listingValidationFailed = true;
@@ -1317,20 +1345,20 @@ function buildAppTasks(rootDir, catalogues, options) {
1317
1345
  }
1318
1346
  }
1319
1347
  }
1320
- const rawHeroLandscape = typeof rawListing.heroLandscape === 'string' && rawListing.heroLandscape.trim()
1348
+ const rawHeroLandscape = typeof rawListing.heroLandscape === "string" && rawListing.heroLandscape.trim()
1321
1349
  ? rawListing.heroLandscape.trim()
1322
- : typeof rawListing.heroLandscapePath === 'string' && rawListing.heroLandscapePath.trim()
1350
+ : typeof rawListing.heroLandscapePath === "string" && rawListing.heroLandscapePath.trim()
1323
1351
  ? rawListing.heroLandscapePath.trim()
1324
- : '';
1352
+ : "";
1325
1353
  if (!listingValidationFailed && rawHeroLandscape) {
1326
1354
  const heroLandscapePath = rawHeroLandscape;
1327
- const extensionError = validateListingAssetExtension(heroLandscapePath, 'listing.heroLandscape', 'image');
1355
+ const extensionError = validateListingAssetExtension(heroLandscapePath, "listing.heroLandscape", "image");
1328
1356
  if (extensionError) {
1329
1357
  errors.push(`[${label}] Skipping ${rawName}: ${extensionError}`);
1330
1358
  listingValidationFailed = true;
1331
1359
  }
1332
1360
  else {
1333
- const validationError = validateListingAssetPath(file.directory, heroLandscapePath, 'listing.heroLandscape');
1361
+ const validationError = validateListingAssetPath(file.directory, heroLandscapePath, "listing.heroLandscape");
1334
1362
  if (validationError) {
1335
1363
  errors.push(`[${label}] Skipping ${rawName}: ${validationError}`);
1336
1364
  listingValidationFailed = true;
@@ -1341,10 +1369,34 @@ function buildAppTasks(rootDir, catalogues, options) {
1341
1369
  }
1342
1370
  }
1343
1371
  for (const mediaField of [
1344
- { value: rawListing.screenshotsPortrait, fieldName: 'listing.screenshotsPortrait', kind: 'image', fileKeyPrefix: 'screenshotsPortrait', target: resolvedListing.screenshotPortraitPaths },
1345
- { value: rawListing.screenshotsLandscape, fieldName: 'listing.screenshotsLandscape', kind: 'image', fileKeyPrefix: 'screenshotsLandscape', target: resolvedListing.screenshotLandscapePaths },
1346
- { value: rawListing.videosPortrait, fieldName: 'listing.videosPortrait', kind: 'video', fileKeyPrefix: 'videosPortrait', target: resolvedListing.videoPortraitPaths },
1347
- { value: rawListing.videosLandscape, fieldName: 'listing.videosLandscape', kind: 'video', fileKeyPrefix: 'videosLandscape', target: resolvedListing.videoLandscapePaths },
1372
+ {
1373
+ value: rawListing.screenshotsPortrait,
1374
+ fieldName: "listing.screenshotsPortrait",
1375
+ kind: "image",
1376
+ fileKeyPrefix: "screenshotsPortrait",
1377
+ target: resolvedListing.screenshotPortraitPaths,
1378
+ },
1379
+ {
1380
+ value: rawListing.screenshotsLandscape,
1381
+ fieldName: "listing.screenshotsLandscape",
1382
+ kind: "image",
1383
+ fileKeyPrefix: "screenshotsLandscape",
1384
+ target: resolvedListing.screenshotLandscapePaths,
1385
+ },
1386
+ {
1387
+ value: rawListing.videosPortrait,
1388
+ fieldName: "listing.videosPortrait",
1389
+ kind: "video",
1390
+ fileKeyPrefix: "videosPortrait",
1391
+ target: resolvedListing.videoPortraitPaths,
1392
+ },
1393
+ {
1394
+ value: rawListing.videosLandscape,
1395
+ fieldName: "listing.videosLandscape",
1396
+ kind: "video",
1397
+ fileKeyPrefix: "videosLandscape",
1398
+ target: resolvedListing.videoLandscapePaths,
1399
+ },
1348
1400
  ]) {
1349
1401
  if (listingValidationFailed)
1350
1402
  break;
@@ -1363,14 +1415,16 @@ function buildAppTasks(rootDir, catalogues, options) {
1363
1415
  mediaField.target.push(...resolvedMedia.paths);
1364
1416
  resolvedListing.mediaMetadata.push(...resolvedMedia.metadata);
1365
1417
  }
1366
- if (!listingValidationFailed && typeof rawListing.captureReport === 'string' && rawListing.captureReport.trim()) {
1418
+ if (!listingValidationFailed &&
1419
+ typeof rawListing.captureReport === "string" &&
1420
+ rawListing.captureReport.trim()) {
1367
1421
  const captureReportPath = rawListing.captureReport.trim();
1368
- if ((0, node_path_1.extname)(captureReportPath).toLowerCase() !== '.json') {
1422
+ if ((0, node_path_1.extname)(captureReportPath).toLowerCase() !== ".json") {
1369
1423
  errors.push(`[${label}] Skipping ${rawName}: listing.captureReport must point to a JSON file.`);
1370
1424
  listingValidationFailed = true;
1371
1425
  }
1372
1426
  else {
1373
- const validationError = validateListingAssetPath(file.directory, captureReportPath, 'listing.captureReport');
1427
+ const validationError = validateListingAssetPath(file.directory, captureReportPath, "listing.captureReport");
1374
1428
  if (validationError) {
1375
1429
  errors.push(`[${label}] Skipping ${rawName}: ${validationError}`);
1376
1430
  listingValidationFailed = true;
@@ -1392,7 +1446,7 @@ function buildAppTasks(rootDir, catalogues, options) {
1392
1446
  continue;
1393
1447
  }
1394
1448
  let design;
1395
- if (Object.prototype.hasOwnProperty.call(entry, 'design')) {
1449
+ if (Object.prototype.hasOwnProperty.call(entry, "design")) {
1396
1450
  const parsedDesign = (0, types_1.normalizeAppVersionDesign)(entry.design);
1397
1451
  if (!parsedDesign.ok) {
1398
1452
  errors.push(`[${label}] App "${rawName}" ${parsedDesign.error}`);
@@ -1401,7 +1455,7 @@ function buildAppTasks(rootDir, catalogues, options) {
1401
1455
  design = parsedDesign.value;
1402
1456
  }
1403
1457
  let tweaks;
1404
- if (Object.prototype.hasOwnProperty.call(entry, 'tweaks')) {
1458
+ if (Object.prototype.hasOwnProperty.call(entry, "tweaks")) {
1405
1459
  const parsedTweaks = (0, types_1.normalizeAppTweaks)(entry.tweaks);
1406
1460
  if (!parsedTweaks.ok) {
1407
1461
  errors.push(`[${label}] App "${rawName}" ${parsedTweaks.error}`);
@@ -1410,13 +1464,13 @@ function buildAppTasks(rootDir, catalogues, options) {
1410
1464
  tweaks = parsedTweaks.value;
1411
1465
  }
1412
1466
  const errorCountBeforeTagMetadata = errors.length;
1413
- const remix = normalizeCatalogueRemixRef(entry.remix, 'app', errors, `[${label}] App "${rawName}"`);
1467
+ const remix = normalizeCatalogueRemixRef(entry.remix, "app", errors, `[${label}] App "${rawName}"`);
1414
1468
  const tags = normalizeCatalogueTags(entry.tags, errors, `[${label}] App "${rawName}"`);
1415
1469
  const graphRelations = normalizeCatalogueRelations(entry.relations, errors, `[${label}] App "${rawName}"`) ?? [];
1416
1470
  if (errors.length > errorCountBeforeTagMetadata) {
1417
1471
  continue;
1418
1472
  }
1419
- if (Object.prototype.hasOwnProperty.call(entry, 'embeddedAssets')) {
1473
+ if (Object.prototype.hasOwnProperty.call(entry, "embeddedAssets")) {
1420
1474
  errors.push(`[${label}] App "${rawName}" uses legacy embeddedAssets. Rename it to ownedAssets.`);
1421
1475
  continue;
1422
1476
  }
@@ -1426,12 +1480,12 @@ function buildAppTasks(rootDir, catalogues, options) {
1426
1480
  : [];
1427
1481
  for (let ownedIndex = 0; ownedIndex < rawOwnedAssets.length; ownedIndex++) {
1428
1482
  const owned = rawOwnedAssets[ownedIndex];
1429
- const ownedName = typeof owned?.name === 'string' ? owned.name.trim() : '';
1483
+ const ownedName = typeof owned?.name === "string" ? owned.name.trim() : "";
1430
1484
  if (!ownedName) {
1431
1485
  errors.push(`[${label}] Skipping ${rawName}: ownedAssets[${ownedIndex}] is missing a name.`);
1432
1486
  continue;
1433
1487
  }
1434
- const runtimeKey = typeof owned?.runtimeKey === 'string' && owned.runtimeKey.trim().length > 0
1488
+ const runtimeKey = typeof owned?.runtimeKey === "string" && owned.runtimeKey.trim().length > 0
1435
1489
  ? owned.runtimeKey.trim()
1436
1490
  : undefined;
1437
1491
  const category = normalizeAssetCategoryValue(owned?.category, errors, `[${label}] Skipping ${rawName}: owned asset "${ownedName}"`);
@@ -1439,20 +1493,22 @@ function buildAppTasks(rootDir, catalogues, options) {
1439
1493
  continue;
1440
1494
  }
1441
1495
  const subcategory = normalizeAssetSubcategoryValue(owned?.subcategory, category, errors, `[${label}] Skipping ${rawName}: owned asset "${ownedName}"`);
1442
- if (subcategory === null && category !== 'CUSTOM') {
1496
+ if (subcategory === null && category !== "CUSTOM") {
1443
1497
  continue;
1444
1498
  }
1445
- const fileEntries = owned?.files && typeof owned.files === 'object' && !Array.isArray(owned.files)
1499
+ const fileEntries = owned?.files && typeof owned.files === "object" && !Array.isArray(owned.files)
1446
1500
  ? owned.files
1447
1501
  : null;
1448
1502
  const assetSpec = (0, assetSpecs_1.normalizeAssetSpecVersionRef)(owned?.assetSpec, errors, `[${label}] App "${rawName}" owned asset "${ownedName}"`);
1449
- const ownedFormat = typeof owned?.format === 'string' && owned.format.trim().length > 0
1503
+ const ownedFormat = typeof owned?.format === "string" && owned.format.trim().length > 0
1450
1504
  ? owned.format.trim().toUpperCase()
1451
1505
  : undefined;
1452
1506
  const generatedModel3DFormat = subcategory
1453
1507
  ? inferGeneratedModel3DFormat(category, subcategory, ownedFormat, fileEntries)
1454
1508
  : null;
1455
- const generatedModel3DRoles = generatedModel3DFormat ? getGeneratedModel3DFileRoles(generatedModel3DFormat) : null;
1509
+ const generatedModel3DRoles = generatedModel3DFormat
1510
+ ? getGeneratedModel3DFileRoles(generatedModel3DFormat)
1511
+ : null;
1456
1512
  if (!fileEntries || Object.keys(fileEntries).length === 0) {
1457
1513
  errors.push(`[${label}] Skipping ${rawName}: owned asset "${ownedName}" must define files.`);
1458
1514
  continue;
@@ -1461,7 +1517,7 @@ function buildAppTasks(rootDir, catalogues, options) {
1461
1517
  const filePaths = {};
1462
1518
  let ownedAssetHasError = false;
1463
1519
  for (const [role, fileValue] of Object.entries(fileEntries)) {
1464
- if (typeof fileValue !== 'string' || !fileValue.trim()) {
1520
+ if (typeof fileValue !== "string" || !fileValue.trim()) {
1465
1521
  errors.push(`[${label}] Skipping ${rawName}: owned asset "${ownedName}" file role "${role}" must be a non-empty string path.`);
1466
1522
  ownedAssetHasError = true;
1467
1523
  continue;
@@ -1472,7 +1528,7 @@ function buildAppTasks(rootDir, catalogues, options) {
1472
1528
  const allowMissingGenerated = Boolean(generatedModel3DRoles?.generated.has(normalizedRole));
1473
1529
  if (!(0, node_fs_1.existsSync)(absoluteEmbeddedPath) || !(0, node_fs_1.statSync)(absoluteEmbeddedPath).isFile()) {
1474
1530
  if (allowMissingGenerated) {
1475
- files[role] = relativeEmbeddedPath.replace(/\\/g, '/');
1531
+ files[role] = relativeEmbeddedPath.replace(/\\/g, "/");
1476
1532
  filePaths[role] = absoluteEmbeddedPath;
1477
1533
  continue;
1478
1534
  }
@@ -1480,7 +1536,7 @@ function buildAppTasks(rootDir, catalogues, options) {
1480
1536
  ownedAssetHasError = true;
1481
1537
  continue;
1482
1538
  }
1483
- files[role] = relativeEmbeddedPath.replace(/\\/g, '/');
1539
+ files[role] = relativeEmbeddedPath.replace(/\\/g, "/");
1484
1540
  filePaths[role] = absoluteEmbeddedPath;
1485
1541
  }
1486
1542
  if (ownedAssetHasError) {
@@ -1490,26 +1546,26 @@ function buildAppTasks(rootDir, catalogues, options) {
1490
1546
  const normalizedRoles = new Set(Object.keys(files).map((role) => normalizeFileRole(role)));
1491
1547
  const missingRoles = generatedModel3DRoles.required.filter((role) => !normalizedRoles.has(role));
1492
1548
  if (missingRoles.length > 0) {
1493
- errors.push(`[${label}] Skipping ${rawName}: owned asset "${ownedName}" with subcategory=${subcategory} must define files.${missingRoles.join(' and files.')}.`);
1549
+ errors.push(`[${label}] Skipping ${rawName}: owned asset "${ownedName}" with subcategory=${subcategory} must define files.${missingRoles.join(" and files.")}.`);
1494
1550
  continue;
1495
1551
  }
1496
1552
  }
1497
- if (category === 'SPRITESHEET') {
1498
- const hasAtlas = Object.prototype.hasOwnProperty.call(files, 'atlas');
1499
- const hasManifest = Object.prototype.hasOwnProperty.call(files, 'manifest');
1553
+ if (category === "SPRITESHEET") {
1554
+ const hasAtlas = Object.prototype.hasOwnProperty.call(files, "atlas");
1555
+ const hasManifest = Object.prototype.hasOwnProperty.call(files, "manifest");
1500
1556
  if (!hasAtlas || !hasManifest) {
1501
1557
  errors.push(`[${label}] Skipping ${rawName}: owned asset "${ownedName}" with category=SPRITESHEET must define files.atlas and files.manifest.`);
1502
1558
  continue;
1503
1559
  }
1504
1560
  }
1505
- if (category === 'AUDIO') {
1506
- const hasPrimary = Object.prototype.hasOwnProperty.call(files, 'primary');
1561
+ if (category === "AUDIO") {
1562
+ const hasPrimary = Object.prototype.hasOwnProperty.call(files, "primary");
1507
1563
  if (!hasPrimary) {
1508
1564
  errors.push(`[${label}] Skipping ${rawName}: owned asset "${ownedName}" with category=AUDIO must define files.primary.`);
1509
1565
  continue;
1510
1566
  }
1511
1567
  }
1512
- if (category === 'MODEL_3D' && ownedFormat === 'GLB') {
1568
+ if (category === "MODEL_3D" && ownedFormat === "GLB") {
1513
1569
  const primaryPath = filePaths.primary;
1514
1570
  if (!primaryPath) {
1515
1571
  errors.push(`[${label}] Skipping ${rawName}: owned asset "${ownedName}" with format=GLB must define files.primary.`);
@@ -1519,8 +1575,8 @@ function buildAppTasks(rootDir, catalogues, options) {
1519
1575
  validateSelfContainedGlb(primaryPath);
1520
1576
  }
1521
1577
  catch (error) {
1522
- const message = typeof error?.message === 'string' ? error.message : 'invalid_glb_payload';
1523
- if (message === 'glb_external_dependency_unsupported') {
1578
+ const message = typeof error?.message === "string" ? error.message : "invalid_glb_payload";
1579
+ if (message === "glb_external_dependency_unsupported") {
1524
1580
  errors.push(`[${label}] Skipping ${rawName}: owned asset "${ownedName}" GLB must embed all buffers and textures instead of referencing external files.`);
1525
1581
  }
1526
1582
  else {
@@ -1541,12 +1597,14 @@ function buildAppTasks(rootDir, catalogues, options) {
1541
1597
  continue;
1542
1598
  }
1543
1599
  ownedAssets.push({
1544
- kind: 'owned-asset',
1600
+ kind: "owned-asset",
1545
1601
  appName: rawName,
1546
1602
  name: ownedName,
1547
1603
  runtimeKey,
1548
- displayName: typeof owned?.displayName === 'string' && owned.displayName.trim().length > 0 ? owned.displayName.trim() : undefined,
1549
- description: typeof owned?.description === 'string' ? owned.description : undefined,
1604
+ displayName: typeof owned?.displayName === "string" && owned.displayName.trim().length > 0
1605
+ ? owned.displayName.trim()
1606
+ : undefined,
1607
+ description: typeof owned?.description === "string" ? owned.description : undefined,
1550
1608
  cataloguePath: label,
1551
1609
  category,
1552
1610
  subcategory,
@@ -1555,7 +1613,7 @@ function buildAppTasks(rootDir, catalogues, options) {
1555
1613
  visibility: normalizeAssetVisibilityValue(owned?.visibility, errors, `${label} owned asset "${ownedName}"`),
1556
1614
  license: ownedLicense,
1557
1615
  tags: ownedTags,
1558
- shopListed: typeof owned?.shopListed === 'boolean' ? owned.shopListed : undefined,
1616
+ shopListed: typeof owned?.shopListed === "boolean" ? owned.shopListed : undefined,
1559
1617
  shopPriceCredits: Number.isInteger(owned?.shopPriceCredits) ? Number(owned.shopPriceCredits) : undefined,
1560
1618
  files,
1561
1619
  filePaths,
@@ -1591,13 +1649,13 @@ function buildAppTasks(rootDir, catalogues, options) {
1591
1649
  continue;
1592
1650
  }
1593
1651
  tasks.push({
1594
- kind: 'app',
1652
+ kind: "app",
1595
1653
  name: rawName,
1596
1654
  cataloguePath: label,
1597
1655
  catalogueAbsolutePath: file.absolutePath,
1598
1656
  catalogueDir: file.directory,
1599
1657
  filePath: absoluteFilePath,
1600
- relativeFile: fileField.replace(/\\/g, '/'),
1658
+ relativeFile: fileField.replace(/\\/g, "/"),
1601
1659
  projectDir: file.directory,
1602
1660
  packageJsonPath,
1603
1661
  isTypescriptProject,
@@ -1653,15 +1711,17 @@ function buildAssetSpecTasks(catalogues) {
1653
1711
  const errors = [];
1654
1712
  const seen = new Map();
1655
1713
  for (const file of catalogues) {
1656
- const label = file.relativePath || 'catalogue.json';
1657
- const rawAssetSpecs = Array.isArray(file.data.assetSpecs) ? file.data.assetSpecs : [];
1714
+ const label = file.relativePath || "catalogue.json";
1715
+ const rawAssetSpecs = Array.isArray(file.data.assetSpecs)
1716
+ ? file.data.assetSpecs
1717
+ : [];
1658
1718
  for (let index = 0; index < rawAssetSpecs.length; index += 1) {
1659
1719
  const entry = rawAssetSpecs[index];
1660
- if (!entry || typeof entry !== 'object') {
1720
+ if (!entry || typeof entry !== "object") {
1661
1721
  errors.push(`[${label}] Invalid assetSpecs[${index}] entry; expected object.`);
1662
1722
  continue;
1663
1723
  }
1664
- const name = typeof entry.name === 'string' ? entry.name.trim() : '';
1724
+ const name = typeof entry.name === "string" ? entry.name.trim() : "";
1665
1725
  if (!name) {
1666
1726
  errors.push(`[${label}] assetSpecs[${index}] is missing a name.`);
1667
1727
  continue;
@@ -1669,7 +1729,7 @@ function buildAssetSpecTasks(catalogues) {
1669
1729
  if (!(0, assetSpecs_1.validateAssetSpecName)(name, errors, `[${label}] Asset spec "${name}"`)) {
1670
1730
  continue;
1671
1731
  }
1672
- const version = typeof entry.version === 'string' ? entry.version.trim() : '';
1732
+ const version = typeof entry.version === "string" ? entry.version.trim() : "";
1673
1733
  if (!SEMVER_REGEX.test(version)) {
1674
1734
  errors.push(`[${label}] Asset spec "${name}" must define a semver version.`);
1675
1735
  continue;
@@ -1680,12 +1740,12 @@ function buildAssetSpecTasks(catalogues) {
1680
1740
  continue;
1681
1741
  }
1682
1742
  seen.set(dedupeKey, label);
1683
- const displayName = typeof entry.displayName === 'string' ? entry.displayName.trim() : '';
1743
+ const displayName = typeof entry.displayName === "string" ? entry.displayName.trim() : "";
1684
1744
  if (!displayName) {
1685
1745
  errors.push(`[${label}] Asset spec "${name}@${version}" must define displayName.`);
1686
1746
  continue;
1687
1747
  }
1688
- const symbol = typeof entry.symbol === 'string' ? entry.symbol.trim() : '';
1748
+ const symbol = typeof entry.symbol === "string" ? entry.symbol.trim() : "";
1689
1749
  if (!symbol) {
1690
1750
  errors.push(`[${label}] Asset spec "${name}@${version}" must define symbol.`);
1691
1751
  continue;
@@ -1695,7 +1755,7 @@ function buildAssetSpecTasks(catalogues) {
1695
1755
  errors.push(`[${label}] Asset spec "${name}@${version}" symbol file not found at ${symbolPath}.`);
1696
1756
  continue;
1697
1757
  }
1698
- const contractField = typeof entry.contract === 'string' ? entry.contract.trim() : '';
1758
+ const contractField = typeof entry.contract === "string" ? entry.contract.trim() : "";
1699
1759
  if (!contractField) {
1700
1760
  errors.push(`[${label}] Asset spec "${name}@${version}" must define contract.`);
1701
1761
  continue;
@@ -1705,10 +1765,10 @@ function buildAssetSpecTasks(catalogues) {
1705
1765
  continue;
1706
1766
  }
1707
1767
  let validationKind = loadedContract.validationKind;
1708
- if (typeof entry.validationKind === 'string' && entry.validationKind.trim().length > 0) {
1768
+ if (typeof entry.validationKind === "string" && entry.validationKind.trim().length > 0) {
1709
1769
  const normalizedValidationKind = entry.validationKind.trim().toUpperCase();
1710
1770
  if (!types_1.ASSET_SPEC_VALIDATION_KIND_VALUES.includes(normalizedValidationKind)) {
1711
- errors.push(`[${label}] Asset spec "${name}@${version}" validationKind must be one of ${types_1.ASSET_SPEC_VALIDATION_KIND_VALUES.join(', ')}.`);
1771
+ errors.push(`[${label}] Asset spec "${name}@${version}" validationKind must be one of ${types_1.ASSET_SPEC_VALIDATION_KIND_VALUES.join(", ")}.`);
1712
1772
  continue;
1713
1773
  }
1714
1774
  if (normalizedValidationKind !== loadedContract.validationKind) {
@@ -1717,24 +1777,24 @@ function buildAssetSpecTasks(catalogues) {
1717
1777
  }
1718
1778
  validationKind = normalizedValidationKind;
1719
1779
  }
1720
- let status = 'ACTIVE';
1721
- if (typeof entry.status === 'string' && entry.status.trim().length > 0) {
1780
+ let status = "ACTIVE";
1781
+ if (typeof entry.status === "string" && entry.status.trim().length > 0) {
1722
1782
  const normalizedStatus = entry.status.trim().toUpperCase();
1723
1783
  if (!types_1.ASSET_SPEC_STATUS_VALUES.includes(normalizedStatus)) {
1724
- errors.push(`[${label}] Asset spec "${name}@${version}" status must be one of ${types_1.ASSET_SPEC_STATUS_VALUES.join(', ')}.`);
1784
+ errors.push(`[${label}] Asset spec "${name}@${version}" status must be one of ${types_1.ASSET_SPEC_STATUS_VALUES.join(", ")}.`);
1725
1785
  continue;
1726
1786
  }
1727
1787
  status = normalizedStatus;
1728
1788
  }
1729
1789
  const visibility = normalizeAssetVisibilityValue(entry.visibility, errors, `${label} asset spec "${name}@${version}"`);
1730
- const successorVersion = typeof entry.successorVersion === 'string' && entry.successorVersion.trim().length > 0
1790
+ const successorVersion = typeof entry.successorVersion === "string" && entry.successorVersion.trim().length > 0
1731
1791
  ? entry.successorVersion.trim()
1732
1792
  : undefined;
1733
1793
  if (successorVersion && !SEMVER_REGEX.test(successorVersion)) {
1734
1794
  errors.push(`[${label}] Asset spec "${name}@${version}" successorVersion must be a semver string.`);
1735
1795
  continue;
1736
1796
  }
1737
- const documentationField = typeof entry.documentation === 'string' && entry.documentation.trim().length > 0
1797
+ const documentationField = typeof entry.documentation === "string" && entry.documentation.trim().length > 0
1738
1798
  ? entry.documentation.trim()
1739
1799
  : undefined;
1740
1800
  const documentationPath = documentationField ? (0, node_path_1.resolve)(file.directory, documentationField) : undefined;
@@ -1743,19 +1803,19 @@ function buildAssetSpecTasks(catalogues) {
1743
1803
  continue;
1744
1804
  }
1745
1805
  tasks.push({
1746
- kind: 'asset-spec',
1806
+ kind: "asset-spec",
1747
1807
  name,
1748
1808
  version,
1749
1809
  displayName,
1750
- description: typeof entry.description === 'string' ? entry.description.trim() || undefined : undefined,
1810
+ description: typeof entry.description === "string" ? entry.description.trim() || undefined : undefined,
1751
1811
  cataloguePath: label,
1752
1812
  catalogueAbsolutePath: file.absolutePath,
1753
1813
  catalogueDir: file.directory,
1754
- username: typeof entry.username === 'string' && entry.username.trim().length > 0 ? entry.username.trim() : undefined,
1814
+ username: typeof entry.username === "string" && entry.username.trim().length > 0 ? entry.username.trim() : undefined,
1755
1815
  visibility,
1756
1816
  validationKind,
1757
1817
  status,
1758
- releaseNotes: typeof entry.releaseNotes === 'string' ? entry.releaseNotes : undefined,
1818
+ releaseNotes: typeof entry.releaseNotes === "string" ? entry.releaseNotes : undefined,
1759
1819
  successorVersion,
1760
1820
  symbolPath,
1761
1821
  contractPath: loadedContract.contractPath,
@@ -1786,21 +1846,21 @@ function buildAssetTasks(catalogues, assetSpecTasks) {
1786
1846
  }
1787
1847
  }
1788
1848
  for (const file of catalogues) {
1789
- const label = file.relativePath || 'catalogue.json';
1849
+ const label = file.relativePath || "catalogue.json";
1790
1850
  const rawAssets = Array.isArray(file.data.assets) ? file.data.assets : [];
1791
1851
  for (let index = 0; index < rawAssets.length; index++) {
1792
1852
  const entry = rawAssets[index];
1793
- if (!entry || typeof entry !== 'object') {
1853
+ if (!entry || typeof entry !== "object") {
1794
1854
  errors.push(`[${label}] Invalid assets[${index}] entry; expected object.`);
1795
1855
  continue;
1796
1856
  }
1797
- const name = typeof entry.name === 'string' ? entry.name.trim() : '';
1857
+ const name = typeof entry.name === "string" ? entry.name.trim() : "";
1798
1858
  if (!name) {
1799
1859
  errors.push(`[${label}] assets[${index}] is missing a name.`);
1800
1860
  continue;
1801
1861
  }
1802
- const hasDisplayName = Object.prototype.hasOwnProperty.call(entry, 'displayName');
1803
- const displayName = typeof entry.displayName === 'string' ? entry.displayName.trim() : '';
1862
+ const hasDisplayName = Object.prototype.hasOwnProperty.call(entry, "displayName");
1863
+ const displayName = typeof entry.displayName === "string" ? entry.displayName.trim() : "";
1804
1864
  if (hasDisplayName && displayName.length === 0) {
1805
1865
  errors.push(`[${label}] Asset "${name}" displayName must be a non-empty string.`);
1806
1866
  continue;
@@ -1812,7 +1872,7 @@ function buildAssetTasks(catalogues, assetSpecTasks) {
1812
1872
  }
1813
1873
  seen.set(dedupeKey, label);
1814
1874
  const assetSpec = (0, assetSpecs_1.normalizeAssetSpecVersionRef)(entry.assetSpec, errors, `[${label}] Asset "${name}"`);
1815
- const isCustomAsset = typeof assetSpec === 'string';
1875
+ const isCustomAsset = typeof assetSpec === "string";
1816
1876
  let category;
1817
1877
  let subcategory;
1818
1878
  let format;
@@ -1832,15 +1892,20 @@ function buildAssetTasks(catalogues, assetSpecTasks) {
1832
1892
  if (!subcategory) {
1833
1893
  continue;
1834
1894
  }
1835
- format = typeof entry.format === 'string' && entry.format.trim().length > 0 ? entry.format.trim().toUpperCase() : undefined;
1895
+ format =
1896
+ typeof entry.format === "string" && entry.format.trim().length > 0
1897
+ ? entry.format.trim().toUpperCase()
1898
+ : undefined;
1836
1899
  }
1837
- const fileEntries = entry.files && typeof entry.files === 'object' && !Array.isArray(entry.files)
1900
+ const fileEntries = entry.files && typeof entry.files === "object" && !Array.isArray(entry.files)
1838
1901
  ? entry.files
1839
1902
  : null;
1840
1903
  const generatedModel3DFormat = !isCustomAsset && category && subcategory
1841
1904
  ? inferGeneratedModel3DFormat(category, subcategory, format, fileEntries)
1842
1905
  : null;
1843
- const generatedModel3DRoles = generatedModel3DFormat ? getGeneratedModel3DFileRoles(generatedModel3DFormat) : null;
1906
+ const generatedModel3DRoles = generatedModel3DFormat
1907
+ ? getGeneratedModel3DFileRoles(generatedModel3DFormat)
1908
+ : null;
1844
1909
  if (!fileEntries || Object.keys(fileEntries).length === 0) {
1845
1910
  errors.push(`[${label}] Asset "${name}" must define files.`);
1846
1911
  continue;
@@ -1849,7 +1914,7 @@ function buildAssetTasks(catalogues, assetSpecTasks) {
1849
1914
  const filePaths = {};
1850
1915
  let hasError = false;
1851
1916
  for (const [role, value] of Object.entries(fileEntries)) {
1852
- if (typeof value !== 'string' || !value.trim()) {
1917
+ if (typeof value !== "string" || !value.trim()) {
1853
1918
  errors.push(`[${label}] Asset "${name}" file role "${role}" must be a non-empty string path.`);
1854
1919
  hasError = true;
1855
1920
  continue;
@@ -1860,7 +1925,7 @@ function buildAssetTasks(catalogues, assetSpecTasks) {
1860
1925
  const allowMissingGenerated = Boolean(generatedModel3DRoles?.generated.has(normalizedRole));
1861
1926
  if (!(0, node_fs_1.existsSync)(absolutePath) || !(0, node_fs_1.statSync)(absolutePath).isFile()) {
1862
1927
  if (allowMissingGenerated) {
1863
- files[role] = relativePath.replace(/\\/g, '/');
1928
+ files[role] = relativePath.replace(/\\/g, "/");
1864
1929
  filePaths[role] = absolutePath;
1865
1930
  continue;
1866
1931
  }
@@ -1868,7 +1933,7 @@ function buildAssetTasks(catalogues, assetSpecTasks) {
1868
1933
  hasError = true;
1869
1934
  continue;
1870
1935
  }
1871
- files[role] = relativePath.replace(/\\/g, '/');
1936
+ files[role] = relativePath.replace(/\\/g, "/");
1872
1937
  filePaths[role] = absolutePath;
1873
1938
  }
1874
1939
  if (hasError) {
@@ -1878,35 +1943,33 @@ function buildAssetTasks(catalogues, assetSpecTasks) {
1878
1943
  const normalizedRoles = new Set(Object.keys(files).map((role) => normalizeFileRole(role)));
1879
1944
  const missingRoles = generatedModel3DRoles.required.filter((role) => !normalizedRoles.has(role));
1880
1945
  if (missingRoles.length > 0) {
1881
- errors.push(`[${label}] Asset "${name}" with subcategory=${subcategory} must define files.${missingRoles.join(' and files.')}.`);
1946
+ errors.push(`[${label}] Asset "${name}" with subcategory=${subcategory} must define files.${missingRoles.join(" and files.")}.`);
1882
1947
  continue;
1883
1948
  }
1884
1949
  }
1885
- if (!isCustomAsset && category === 'SPRITESHEET') {
1886
- const hasAtlas = Object.prototype.hasOwnProperty.call(files, 'atlas');
1887
- const hasManifest = Object.prototype.hasOwnProperty.call(files, 'manifest');
1950
+ if (!isCustomAsset && category === "SPRITESHEET") {
1951
+ const hasAtlas = Object.prototype.hasOwnProperty.call(files, "atlas");
1952
+ const hasManifest = Object.prototype.hasOwnProperty.call(files, "manifest");
1888
1953
  if (!hasAtlas || !hasManifest) {
1889
1954
  errors.push(`[${label}] Asset "${name}" with category=SPRITESHEET must define files.atlas and files.manifest.`);
1890
1955
  continue;
1891
1956
  }
1892
1957
  }
1893
- if (!isCustomAsset && category === 'AUDIO') {
1894
- const hasPrimary = Object.prototype.hasOwnProperty.call(files, 'primary');
1958
+ if (!isCustomAsset && category === "AUDIO") {
1959
+ const hasPrimary = Object.prototype.hasOwnProperty.call(files, "primary");
1895
1960
  if (!hasPrimary) {
1896
1961
  errors.push(`[${label}] Asset "${name}" with category=AUDIO must define files.primary.`);
1897
1962
  continue;
1898
1963
  }
1899
1964
  }
1900
1965
  const errorCountBeforeTagMetadata = errors.length;
1901
- const remix = normalizeCatalogueRemixRef(entry.remix, 'asset', errors, `[${label}] Asset "${name}"`);
1966
+ const remix = normalizeCatalogueRemixRef(entry.remix, "asset", errors, `[${label}] Asset "${name}"`);
1902
1967
  const tags = normalizeCatalogueTags(entry.tags, errors, `[${label}] Asset "${name}"`);
1903
1968
  const relations = normalizeCatalogueRelations(entry.relations, errors, `[${label}] Asset "${name}"`);
1904
1969
  if (errors.length > errorCountBeforeTagMetadata) {
1905
1970
  continue;
1906
1971
  }
1907
- const username = typeof entry.username === 'string' && entry.username.trim().length > 0
1908
- ? entry.username.trim()
1909
- : undefined;
1972
+ const username = typeof entry.username === "string" && entry.username.trim().length > 0 ? entry.username.trim() : undefined;
1910
1973
  if (rejectLegacyShopPriceCoinsField(entry, errors, `[${label}] Asset "${name}"`)) {
1911
1974
  continue;
1912
1975
  }
@@ -1931,7 +1994,7 @@ function buildAssetTasks(catalogues, assetSpecTasks) {
1931
1994
  continue;
1932
1995
  }
1933
1996
  tasks.push({
1934
- kind: 'asset',
1997
+ kind: "asset",
1935
1998
  name,
1936
1999
  displayName: displayName || undefined,
1937
2000
  cataloguePath: label,
@@ -1944,7 +2007,7 @@ function buildAssetTasks(catalogues, assetSpecTasks) {
1944
2007
  visibility: normalizeAssetVisibilityValue(entry.visibility, errors, `${label} asset "${name}"`),
1945
2008
  license,
1946
2009
  tags,
1947
- shopListed: typeof entry.shopListed === 'boolean' ? entry.shopListed : undefined,
2010
+ shopListed: typeof entry.shopListed === "boolean" ? entry.shopListed : undefined,
1948
2011
  shopPriceCredits: Number.isInteger(entry.shopPriceCredits) ? Number(entry.shopPriceCredits) : undefined,
1949
2012
  files,
1950
2013
  filePaths,
@@ -1961,21 +2024,23 @@ function buildAssetPackTasks(catalogues) {
1961
2024
  const errors = [];
1962
2025
  const seen = new Map();
1963
2026
  for (const file of catalogues) {
1964
- const label = file.relativePath || 'catalogue.json';
1965
- const rawPacks = Array.isArray(file.data.assetPacks) ? file.data.assetPacks : [];
2027
+ const label = file.relativePath || "catalogue.json";
2028
+ const rawPacks = Array.isArray(file.data.assetPacks)
2029
+ ? file.data.assetPacks
2030
+ : [];
1966
2031
  for (let index = 0; index < rawPacks.length; index++) {
1967
2032
  const entry = rawPacks[index];
1968
- if (!entry || typeof entry !== 'object') {
2033
+ if (!entry || typeof entry !== "object") {
1969
2034
  errors.push(`[${label}] Invalid assetPacks[${index}] entry; expected object.`);
1970
2035
  continue;
1971
2036
  }
1972
- const name = typeof entry.name === 'string' ? entry.name.trim() : '';
2037
+ const name = typeof entry.name === "string" ? entry.name.trim() : "";
1973
2038
  if (!name) {
1974
2039
  errors.push(`[${label}] assetPacks[${index}] is missing a name.`);
1975
2040
  continue;
1976
2041
  }
1977
- const displayName = typeof entry.displayName === 'string' ? entry.displayName.trim() : '';
1978
- const version = typeof entry.version === 'string' ? entry.version.trim() : '';
2042
+ const displayName = typeof entry.displayName === "string" ? entry.displayName.trim() : "";
2043
+ const version = typeof entry.version === "string" ? entry.version.trim() : "";
1979
2044
  if (!SEMVER_REGEX.test(version)) {
1980
2045
  errors.push(`[${label}] Asset pack "${name}" must define a semver version.`);
1981
2046
  continue;
@@ -1991,7 +2056,9 @@ function buildAssetPackTasks(catalogues) {
1991
2056
  continue;
1992
2057
  }
1993
2058
  const assets = Array.isArray(entry.assets)
1994
- ? entry.assets.filter((value) => typeof value === 'string' && value.trim().length > 0).map((value) => value.trim())
2059
+ ? entry.assets
2060
+ .filter((value) => typeof value === "string" && value.trim().length > 0)
2061
+ .map((value) => value.trim())
1995
2062
  : [];
1996
2063
  const ownedAssets = [];
1997
2064
  const rawOwnedAssets = Array.isArray(entry.ownedAssets)
@@ -1999,12 +2066,12 @@ function buildAssetPackTasks(catalogues) {
1999
2066
  : [];
2000
2067
  for (let ownedIndex = 0; ownedIndex < rawOwnedAssets.length; ownedIndex++) {
2001
2068
  const owned = rawOwnedAssets[ownedIndex];
2002
- const ownedName = typeof owned?.name === 'string' ? owned.name.trim() : '';
2069
+ const ownedName = typeof owned?.name === "string" ? owned.name.trim() : "";
2003
2070
  if (!ownedName) {
2004
2071
  errors.push(`[${label}] Skipping asset pack "${name}": ownedAssets[${ownedIndex}] is missing a name.`);
2005
2072
  continue;
2006
2073
  }
2007
- if (typeof owned?.runtimeKey === 'string' && owned.runtimeKey.trim().length > 0) {
2074
+ if (typeof owned?.runtimeKey === "string" && owned.runtimeKey.trim().length > 0) {
2008
2075
  errors.push(`[${label}] Skipping asset pack "${name}": owned asset "${ownedName}" must not define runtimeKey.`);
2009
2076
  continue;
2010
2077
  }
@@ -2013,20 +2080,22 @@ function buildAssetPackTasks(catalogues) {
2013
2080
  continue;
2014
2081
  }
2015
2082
  const subcategory = normalizeAssetSubcategoryValue(owned?.subcategory, category, errors, `[${label}] Skipping asset pack "${name}": owned asset "${ownedName}"`);
2016
- if (subcategory === null && category !== 'CUSTOM') {
2083
+ if (subcategory === null && category !== "CUSTOM") {
2017
2084
  continue;
2018
2085
  }
2019
- const fileEntries = owned?.files && typeof owned.files === 'object' && !Array.isArray(owned.files)
2086
+ const fileEntries = owned?.files && typeof owned.files === "object" && !Array.isArray(owned.files)
2020
2087
  ? owned.files
2021
2088
  : null;
2022
2089
  const assetSpec = (0, assetSpecs_1.normalizeAssetSpecVersionRef)(owned?.assetSpec, errors, `[${label}] Asset pack "${name}" owned asset "${ownedName}"`);
2023
- const ownedFormat = typeof owned?.format === 'string' && owned.format.trim().length > 0
2090
+ const ownedFormat = typeof owned?.format === "string" && owned.format.trim().length > 0
2024
2091
  ? owned.format.trim().toUpperCase()
2025
2092
  : undefined;
2026
2093
  const generatedModel3DFormat = subcategory
2027
2094
  ? inferGeneratedModel3DFormat(category, subcategory, ownedFormat, fileEntries)
2028
2095
  : null;
2029
- const generatedModel3DRoles = generatedModel3DFormat ? getGeneratedModel3DFileRoles(generatedModel3DFormat) : null;
2096
+ const generatedModel3DRoles = generatedModel3DFormat
2097
+ ? getGeneratedModel3DFileRoles(generatedModel3DFormat)
2098
+ : null;
2030
2099
  if (!fileEntries || Object.keys(fileEntries).length === 0) {
2031
2100
  errors.push(`[${label}] Skipping asset pack "${name}": owned asset "${ownedName}" must define files.`);
2032
2101
  continue;
@@ -2035,7 +2104,7 @@ function buildAssetPackTasks(catalogues) {
2035
2104
  const filePaths = {};
2036
2105
  let ownedAssetHasError = false;
2037
2106
  for (const [role, fileValue] of Object.entries(fileEntries)) {
2038
- if (typeof fileValue !== 'string' || !fileValue.trim()) {
2107
+ if (typeof fileValue !== "string" || !fileValue.trim()) {
2039
2108
  errors.push(`[${label}] Skipping asset pack "${name}": owned asset "${ownedName}" file role "${role}" must be a non-empty string path.`);
2040
2109
  ownedAssetHasError = true;
2041
2110
  continue;
@@ -2046,7 +2115,7 @@ function buildAssetPackTasks(catalogues) {
2046
2115
  const allowMissingGenerated = Boolean(generatedModel3DRoles?.generated.has(normalizedRole));
2047
2116
  if (!(0, node_fs_1.existsSync)(absoluteOwnedPath) || !(0, node_fs_1.statSync)(absoluteOwnedPath).isFile()) {
2048
2117
  if (allowMissingGenerated) {
2049
- files[role] = relativeOwnedPath.replace(/\\/g, '/');
2118
+ files[role] = relativeOwnedPath.replace(/\\/g, "/");
2050
2119
  filePaths[role] = absoluteOwnedPath;
2051
2120
  continue;
2052
2121
  }
@@ -2054,7 +2123,7 @@ function buildAssetPackTasks(catalogues) {
2054
2123
  ownedAssetHasError = true;
2055
2124
  continue;
2056
2125
  }
2057
- files[role] = relativeOwnedPath.replace(/\\/g, '/');
2126
+ files[role] = relativeOwnedPath.replace(/\\/g, "/");
2058
2127
  filePaths[role] = absoluteOwnedPath;
2059
2128
  }
2060
2129
  if (ownedAssetHasError) {
@@ -2064,19 +2133,19 @@ function buildAssetPackTasks(catalogues) {
2064
2133
  const normalizedRoles = new Set(Object.keys(files).map((role) => normalizeFileRole(role)));
2065
2134
  const missingRoles = generatedModel3DRoles.required.filter((role) => !normalizedRoles.has(role));
2066
2135
  if (missingRoles.length > 0) {
2067
- errors.push(`[${label}] Skipping asset pack "${name}": owned asset "${ownedName}" with subcategory=${subcategory} must define files.${missingRoles.join(' and files.')}.`);
2136
+ errors.push(`[${label}] Skipping asset pack "${name}": owned asset "${ownedName}" with subcategory=${subcategory} must define files.${missingRoles.join(" and files.")}.`);
2068
2137
  continue;
2069
2138
  }
2070
2139
  }
2071
- if (category === 'SPRITESHEET') {
2072
- const hasAtlas = Object.prototype.hasOwnProperty.call(files, 'atlas');
2073
- const hasManifest = Object.prototype.hasOwnProperty.call(files, 'manifest');
2140
+ if (category === "SPRITESHEET") {
2141
+ const hasAtlas = Object.prototype.hasOwnProperty.call(files, "atlas");
2142
+ const hasManifest = Object.prototype.hasOwnProperty.call(files, "manifest");
2074
2143
  if (!hasAtlas || !hasManifest) {
2075
2144
  errors.push(`[${label}] Skipping asset pack "${name}": owned asset "${ownedName}" with category=SPRITESHEET must define files.atlas and files.manifest.`);
2076
2145
  continue;
2077
2146
  }
2078
2147
  }
2079
- if (category === 'AUDIO' && !Object.prototype.hasOwnProperty.call(files, 'primary')) {
2148
+ if (category === "AUDIO" && !Object.prototype.hasOwnProperty.call(files, "primary")) {
2080
2149
  errors.push(`[${label}] Skipping asset pack "${name}": owned asset "${ownedName}" with category=AUDIO must define files.primary.`);
2081
2150
  continue;
2082
2151
  }
@@ -2094,8 +2163,10 @@ function buildAssetPackTasks(catalogues) {
2094
2163
  ownedAssets.push({
2095
2164
  name: ownedName,
2096
2165
  cataloguePath: label,
2097
- displayName: typeof owned?.displayName === 'string' && owned.displayName.trim().length > 0 ? owned.displayName.trim() : undefined,
2098
- description: typeof owned?.description === 'string' ? owned.description : undefined,
2166
+ displayName: typeof owned?.displayName === "string" && owned.displayName.trim().length > 0
2167
+ ? owned.displayName.trim()
2168
+ : undefined,
2169
+ description: typeof owned?.description === "string" ? owned.description : undefined,
2099
2170
  category,
2100
2171
  subcategory,
2101
2172
  assetSpec,
@@ -2103,23 +2174,23 @@ function buildAssetPackTasks(catalogues) {
2103
2174
  visibility: normalizeAssetVisibilityValue(owned?.visibility, errors, `${label} asset pack "${name}" owned asset "${ownedName}"`),
2104
2175
  license: ownedLicense,
2105
2176
  tags: ownedTags,
2106
- shopListed: typeof owned?.shopListed === 'boolean' ? owned.shopListed : undefined,
2177
+ shopListed: typeof owned?.shopListed === "boolean" ? owned.shopListed : undefined,
2107
2178
  shopPriceCredits: Number.isInteger(owned?.shopPriceCredits) ? Number(owned.shopPriceCredits) : undefined,
2108
2179
  files,
2109
2180
  filePaths,
2110
2181
  });
2111
2182
  }
2112
2183
  let hostingMode;
2113
- const rawHostingMode = typeof entry.hostingMode === 'string' ? entry.hostingMode.trim().toUpperCase() : '';
2184
+ const rawHostingMode = typeof entry.hostingMode === "string" ? entry.hostingMode.trim().toUpperCase() : "";
2114
2185
  if (rawHostingMode) {
2115
2186
  if (!types_1.APP_HOSTING_MODE_VALUES.includes(rawHostingMode)) {
2116
- errors.push(`[${label}] Asset pack "${name}" hostingMode must be one of ${types_1.APP_HOSTING_MODE_VALUES.join(', ')}.`);
2187
+ errors.push(`[${label}] Asset pack "${name}" hostingMode must be one of ${types_1.APP_HOSTING_MODE_VALUES.join(", ")}.`);
2117
2188
  continue;
2118
2189
  }
2119
2190
  hostingMode = rawHostingMode;
2120
2191
  }
2121
2192
  let externalUrl;
2122
- const rawExternalUrl = typeof entry.externalUrl === 'string' ? entry.externalUrl.trim() : '';
2193
+ const rawExternalUrl = typeof entry.externalUrl === "string" ? entry.externalUrl.trim() : "";
2123
2194
  if (rawExternalUrl) {
2124
2195
  if (!/^https:\/\//i.test(rawExternalUrl)) {
2125
2196
  errors.push(`[${label}] Asset pack "${name}" externalUrl must be an HTTPS URL.`);
@@ -2128,7 +2199,7 @@ function buildAssetPackTasks(catalogues) {
2128
2199
  externalUrl = rawExternalUrl;
2129
2200
  }
2130
2201
  let downloadUrl;
2131
- const rawDownloadUrl = typeof entry.downloadUrl === 'string' ? entry.downloadUrl.trim() : '';
2202
+ const rawDownloadUrl = typeof entry.downloadUrl === "string" ? entry.downloadUrl.trim() : "";
2132
2203
  if (rawDownloadUrl) {
2133
2204
  if (!/^https:\/\//i.test(rawDownloadUrl)) {
2134
2205
  errors.push(`[${label}] Asset pack "${name}" downloadUrl must be an HTTPS URL.`);
@@ -2136,38 +2207,36 @@ function buildAssetPackTasks(catalogues) {
2136
2207
  }
2137
2208
  downloadUrl = rawDownloadUrl;
2138
2209
  }
2139
- if (hostingMode === 'EXTERNAL' && !externalUrl) {
2210
+ if (hostingMode === "EXTERNAL" && !externalUrl) {
2140
2211
  errors.push(`[${label}] Asset pack "${name}" externalUrl is required when hostingMode is EXTERNAL.`);
2141
2212
  continue;
2142
2213
  }
2143
- if (externalUrl && hostingMode !== 'EXTERNAL') {
2144
- hostingMode = 'EXTERNAL';
2214
+ if (externalUrl && hostingMode !== "EXTERNAL") {
2215
+ hostingMode = "EXTERNAL";
2145
2216
  }
2146
- if (assets.length === 0 && ownedAssets.length === 0 && hostingMode !== 'EXTERNAL') {
2217
+ if (assets.length === 0 && ownedAssets.length === 0 && hostingMode !== "EXTERNAL") {
2147
2218
  errors.push(`[${label}] Asset pack "${name}" must reference at least one asset.`);
2148
2219
  continue;
2149
2220
  }
2150
2221
  const errorCountBeforeTagMetadata = errors.length;
2151
- const remix = normalizeCatalogueRemixRef(entry.remix, 'pack', errors, `[${label}] Asset pack "${name}@${version}"`);
2222
+ const remix = normalizeCatalogueRemixRef(entry.remix, "pack", errors, `[${label}] Asset pack "${name}@${version}"`);
2152
2223
  const tags = normalizeCatalogueTags(entry.tags, errors, `[${label}] Asset pack "${name}@${version}"`);
2153
2224
  const relations = normalizeCatalogueRelations(entry.relations, errors, `[${label}] Asset pack "${name}@${version}"`);
2154
2225
  if (errors.length > errorCountBeforeTagMetadata) {
2155
2226
  continue;
2156
2227
  }
2157
- const username = typeof entry.username === 'string' && entry.username.trim().length > 0
2158
- ? entry.username.trim()
2159
- : undefined;
2160
- const visibility = typeof entry.visibility === 'string' && entry.visibility.trim().length > 0
2228
+ const username = typeof entry.username === "string" && entry.username.trim().length > 0 ? entry.username.trim() : undefined;
2229
+ const visibility = typeof entry.visibility === "string" && entry.visibility.trim().length > 0
2161
2230
  ? entry.visibility.trim().toUpperCase()
2162
2231
  : undefined;
2163
- if (visibility && visibility !== 'PUBLIC' && visibility !== 'PRIVATE') {
2232
+ if (visibility && visibility !== "PUBLIC" && visibility !== "PRIVATE") {
2164
2233
  errors.push(`[${label}] Asset pack "${name}" visibility must be PUBLIC or PRIVATE.`);
2165
2234
  continue;
2166
2235
  }
2167
2236
  let listing;
2168
2237
  const rawListing = entry.listing;
2169
2238
  let listingValidationFailed = false;
2170
- if (rawListing && typeof rawListing === 'object') {
2239
+ if (rawListing && typeof rawListing === "object") {
2171
2240
  const resolvedListing = {
2172
2241
  screenshotPortraitPaths: [],
2173
2242
  screenshotLandscapePaths: [],
@@ -2175,15 +2244,15 @@ function buildAssetPackTasks(catalogues) {
2175
2244
  videoLandscapePaths: [],
2176
2245
  mediaMetadata: [],
2177
2246
  };
2178
- if (typeof rawListing.icon === 'string' && rawListing.icon.trim()) {
2247
+ if (typeof rawListing.icon === "string" && rawListing.icon.trim()) {
2179
2248
  const iconPath = rawListing.icon.trim();
2180
- const extensionError = validateListingAssetExtension(iconPath, 'assetPacks.listing.icon', 'image');
2249
+ const extensionError = validateListingAssetExtension(iconPath, "assetPacks.listing.icon", "image");
2181
2250
  if (extensionError) {
2182
2251
  errors.push(`[${label}] Skipping asset pack "${name}": ${extensionError}`);
2183
2252
  listingValidationFailed = true;
2184
2253
  }
2185
2254
  else {
2186
- const validationError = validateListingAssetPath(file.directory, iconPath, 'assetPacks.listing.icon');
2255
+ const validationError = validateListingAssetPath(file.directory, iconPath, "assetPacks.listing.icon");
2187
2256
  if (validationError) {
2188
2257
  errors.push(`[${label}] Skipping asset pack "${name}": ${validationError}`);
2189
2258
  listingValidationFailed = true;
@@ -2193,15 +2262,15 @@ function buildAssetPackTasks(catalogues) {
2193
2262
  }
2194
2263
  }
2195
2264
  }
2196
- if (!listingValidationFailed && typeof rawListing.heroPortrait === 'string' && rawListing.heroPortrait.trim()) {
2265
+ if (!listingValidationFailed && typeof rawListing.heroPortrait === "string" && rawListing.heroPortrait.trim()) {
2197
2266
  const heroPortraitPath = rawListing.heroPortrait.trim();
2198
- const extensionError = validateListingAssetExtension(heroPortraitPath, 'assetPacks.listing.heroPortrait', 'image');
2267
+ const extensionError = validateListingAssetExtension(heroPortraitPath, "assetPacks.listing.heroPortrait", "image");
2199
2268
  if (extensionError) {
2200
2269
  errors.push(`[${label}] Skipping asset pack "${name}": ${extensionError}`);
2201
2270
  listingValidationFailed = true;
2202
2271
  }
2203
2272
  else {
2204
- const validationError = validateListingAssetPath(file.directory, heroPortraitPath, 'assetPacks.listing.heroPortrait');
2273
+ const validationError = validateListingAssetPath(file.directory, heroPortraitPath, "assetPacks.listing.heroPortrait");
2205
2274
  if (validationError) {
2206
2275
  errors.push(`[${label}] Skipping asset pack "${name}": ${validationError}`);
2207
2276
  listingValidationFailed = true;
@@ -2211,15 +2280,17 @@ function buildAssetPackTasks(catalogues) {
2211
2280
  }
2212
2281
  }
2213
2282
  }
2214
- if (!listingValidationFailed && typeof rawListing.heroLandscape === 'string' && rawListing.heroLandscape.trim()) {
2283
+ if (!listingValidationFailed &&
2284
+ typeof rawListing.heroLandscape === "string" &&
2285
+ rawListing.heroLandscape.trim()) {
2215
2286
  const heroLandscapePath = rawListing.heroLandscape.trim();
2216
- const extensionError = validateListingAssetExtension(heroLandscapePath, 'assetPacks.listing.heroLandscape', 'image');
2287
+ const extensionError = validateListingAssetExtension(heroLandscapePath, "assetPacks.listing.heroLandscape", "image");
2217
2288
  if (extensionError) {
2218
2289
  errors.push(`[${label}] Skipping asset pack "${name}": ${extensionError}`);
2219
2290
  listingValidationFailed = true;
2220
2291
  }
2221
2292
  else {
2222
- const validationError = validateListingAssetPath(file.directory, heroLandscapePath, 'assetPacks.listing.heroLandscape');
2293
+ const validationError = validateListingAssetPath(file.directory, heroLandscapePath, "assetPacks.listing.heroLandscape");
2223
2294
  if (validationError) {
2224
2295
  errors.push(`[${label}] Skipping asset pack "${name}": ${validationError}`);
2225
2296
  listingValidationFailed = true;
@@ -2230,10 +2301,34 @@ function buildAssetPackTasks(catalogues) {
2230
2301
  }
2231
2302
  }
2232
2303
  for (const mediaField of [
2233
- { value: rawListing.screenshotsPortrait, fieldName: 'assetPacks.listing.screenshotsPortrait', kind: 'image', fileKeyPrefix: 'screenshotsPortrait', target: resolvedListing.screenshotPortraitPaths },
2234
- { value: rawListing.screenshotsLandscape, fieldName: 'assetPacks.listing.screenshotsLandscape', kind: 'image', fileKeyPrefix: 'screenshotsLandscape', target: resolvedListing.screenshotLandscapePaths },
2235
- { value: rawListing.videosPortrait, fieldName: 'assetPacks.listing.videosPortrait', kind: 'video', fileKeyPrefix: 'videosPortrait', target: resolvedListing.videoPortraitPaths },
2236
- { value: rawListing.videosLandscape, fieldName: 'assetPacks.listing.videosLandscape', kind: 'video', fileKeyPrefix: 'videosLandscape', target: resolvedListing.videoLandscapePaths },
2304
+ {
2305
+ value: rawListing.screenshotsPortrait,
2306
+ fieldName: "assetPacks.listing.screenshotsPortrait",
2307
+ kind: "image",
2308
+ fileKeyPrefix: "screenshotsPortrait",
2309
+ target: resolvedListing.screenshotPortraitPaths,
2310
+ },
2311
+ {
2312
+ value: rawListing.screenshotsLandscape,
2313
+ fieldName: "assetPacks.listing.screenshotsLandscape",
2314
+ kind: "image",
2315
+ fileKeyPrefix: "screenshotsLandscape",
2316
+ target: resolvedListing.screenshotLandscapePaths,
2317
+ },
2318
+ {
2319
+ value: rawListing.videosPortrait,
2320
+ fieldName: "assetPacks.listing.videosPortrait",
2321
+ kind: "video",
2322
+ fileKeyPrefix: "videosPortrait",
2323
+ target: resolvedListing.videoPortraitPaths,
2324
+ },
2325
+ {
2326
+ value: rawListing.videosLandscape,
2327
+ fieldName: "assetPacks.listing.videosLandscape",
2328
+ kind: "video",
2329
+ fileKeyPrefix: "videosLandscape",
2330
+ target: resolvedListing.videoLandscapePaths,
2331
+ },
2237
2332
  ]) {
2238
2333
  if (listingValidationFailed)
2239
2334
  break;
@@ -2257,15 +2352,15 @@ function buildAssetPackTasks(catalogues) {
2257
2352
  }
2258
2353
  listing = resolvedListing;
2259
2354
  }
2260
- const previewApp = typeof entry.previewApp === 'string'
2355
+ const previewApp = typeof entry.previewApp === "string"
2261
2356
  ? String(entry.previewApp).trim()
2262
- : '';
2357
+ : "";
2263
2358
  if (previewApp.length > 0) {
2264
2359
  errors.push(`[${label}] Skipping asset pack "${name}": previewApp is no longer supported during publish. Upload the pack first, then set previewAppVersionId explicitly with creations asset-packs update.`);
2265
2360
  continue;
2266
2361
  }
2267
2362
  tasks.push({
2268
- kind: 'asset-pack',
2363
+ kind: "asset-pack",
2269
2364
  name,
2270
2365
  displayName: displayName || undefined,
2271
2366
  version,
@@ -2279,7 +2374,7 @@ function buildAssetPackTasks(catalogues) {
2279
2374
  downloadUrl,
2280
2375
  visibility,
2281
2376
  license,
2282
- releaseNotes: typeof entry.releaseNotes === 'string' ? entry.releaseNotes : undefined,
2377
+ releaseNotes: typeof entry.releaseNotes === "string" ? entry.releaseNotes : undefined,
2283
2378
  tags,
2284
2379
  listing,
2285
2380
  relations,
@@ -2296,13 +2391,13 @@ function resolveCatalogueTagGroups(rootDir) {
2296
2391
  errors: discoveryErrors,
2297
2392
  };
2298
2393
  }
2299
- const rootCatalogue = files.find((file) => file.relativePath === 'catalogue.json');
2300
- const hasNestedCatalogues = files.some((file) => file.relativePath !== 'catalogue.json');
2394
+ const rootCatalogue = files.find((file) => file.relativePath === "catalogue.json");
2395
+ const hasNestedCatalogues = files.some((file) => file.relativePath !== "catalogue.json");
2301
2396
  if (rootCatalogue && hasNestedCatalogues && !isCatalogueMarker(rootCatalogue)) {
2302
2397
  return {
2303
2398
  groups: [],
2304
2399
  errors: [
2305
- '[catalogue.json] Workspace root catalogue.json must be {} when nested catalogue.json files exist below it.',
2400
+ "[catalogue.json] Workspace root catalogue.json must be {} when nested catalogue.json files exist below it.",
2306
2401
  ],
2307
2402
  };
2308
2403
  }
@@ -2310,15 +2405,15 @@ function resolveCatalogueTagGroups(rootDir) {
2310
2405
  const errors = [];
2311
2406
  const seenGroupSlugs = new Set();
2312
2407
  for (const file of files) {
2313
- const label = file.relativePath || 'catalogue.json';
2408
+ const label = file.relativePath || "catalogue.json";
2314
2409
  const rawGroups = Array.isArray(file.data.tagGroups) ? file.data.tagGroups : [];
2315
2410
  for (let index = 0; index < rawGroups.length; index += 1) {
2316
2411
  const entry = rawGroups[index];
2317
- if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
2412
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
2318
2413
  errors.push(`[${label}] tagGroups[${index}] must be an object.`);
2319
2414
  continue;
2320
2415
  }
2321
- const slug = typeof entry.slug === 'string' ? entry.slug.trim().toLowerCase() : '';
2416
+ const slug = typeof entry.slug === "string" ? entry.slug.trim().toLowerCase() : "";
2322
2417
  if (!slug || !(0, types_1.isValidTagSlug)(slug)) {
2323
2418
  errors.push(`[${label}] tagGroups[${index}] slug must use lowercase slug format.`);
2324
2419
  continue;
@@ -2327,7 +2422,7 @@ function resolveCatalogueTagGroups(rootDir) {
2327
2422
  errors.push(`[${label}] Duplicate tag group slug "${slug}".`);
2328
2423
  continue;
2329
2424
  }
2330
- const displayName = typeof entry.displayName === 'string' ? entry.displayName.trim() : '';
2425
+ const displayName = typeof entry.displayName === "string" ? entry.displayName.trim() : "";
2331
2426
  if (!displayName) {
2332
2427
  errors.push(`[${label}] tagGroups[${index}] displayName must be a non-empty string.`);
2333
2428
  continue;
@@ -2342,11 +2437,11 @@ function resolveCatalogueTagGroups(rootDir) {
2342
2437
  const seenTagSlugs = new Set();
2343
2438
  for (let tagIndex = 0; tagIndex < entry.tags.length; tagIndex += 1) {
2344
2439
  const rawTag = entry.tags[tagIndex];
2345
- if (!rawTag || typeof rawTag !== 'object' || Array.isArray(rawTag)) {
2440
+ if (!rawTag || typeof rawTag !== "object" || Array.isArray(rawTag)) {
2346
2441
  errors.push(`[${label}] Tag group "${slug}" tags[${tagIndex}] must be an object.`);
2347
2442
  continue;
2348
2443
  }
2349
- const tagSlug = typeof rawTag.slug === 'string' ? rawTag.slug.trim().toLowerCase() : '';
2444
+ const tagSlug = typeof rawTag.slug === "string" ? rawTag.slug.trim().toLowerCase() : "";
2350
2445
  if (!tagSlug || !(0, types_1.isValidTagSlug)(tagSlug)) {
2351
2446
  errors.push(`[${label}] Tag group "${slug}" tags[${tagIndex}] slug must use lowercase slug format.`);
2352
2447
  continue;
@@ -2355,7 +2450,7 @@ function resolveCatalogueTagGroups(rootDir) {
2355
2450
  errors.push(`[${label}] Tag group "${slug}" contains duplicate tag slug "${tagSlug}".`);
2356
2451
  continue;
2357
2452
  }
2358
- const tagDisplayName = typeof rawTag.displayName === 'string' ? rawTag.displayName.trim() : '';
2453
+ const tagDisplayName = typeof rawTag.displayName === "string" ? rawTag.displayName.trim() : "";
2359
2454
  if (!tagDisplayName) {
2360
2455
  errors.push(`[${label}] Tag group "${slug}" tag "${tagSlug}" displayName must be a non-empty string.`);
2361
2456
  continue;
@@ -2400,8 +2495,8 @@ function resolveCatalogueEntries(rootDir, options = {}) {
2400
2495
  errors: discoveryErrors,
2401
2496
  };
2402
2497
  }
2403
- const rootCatalogue = files.find((file) => file.relativePath === 'catalogue.json');
2404
- const hasNestedCatalogues = files.some((file) => file.relativePath !== 'catalogue.json');
2498
+ const rootCatalogue = files.find((file) => file.relativePath === "catalogue.json");
2499
+ const hasNestedCatalogues = files.some((file) => file.relativePath !== "catalogue.json");
2405
2500
  if (rootCatalogue && hasNestedCatalogues && !isCatalogueMarker(rootCatalogue)) {
2406
2501
  return {
2407
2502
  apps: [],
@@ -2411,7 +2506,7 @@ function resolveCatalogueEntries(rootDir, options = {}) {
2411
2506
  assetPacks: [],
2412
2507
  warnings: [],
2413
2508
  errors: [
2414
- '[catalogue.json] Workspace root catalogue.json must be {} when nested catalogue.json files exist below it.',
2509
+ "[catalogue.json] Workspace root catalogue.json must be {} when nested catalogue.json files exist below it.",
2415
2510
  ],
2416
2511
  };
2417
2512
  }
@@ -2437,7 +2532,7 @@ function findNearestCatalogue(startDir) {
2437
2532
  const visited = new Set();
2438
2533
  while (!visited.has(current)) {
2439
2534
  visited.add(current);
2440
- const candidate = (0, node_path_1.join)(current, 'catalogue.json');
2535
+ const candidate = (0, node_path_1.join)(current, "catalogue.json");
2441
2536
  if ((0, node_fs_1.existsSync)(candidate) && (0, node_fs_1.statSync)(candidate).isFile()) {
2442
2537
  return candidate;
2443
2538
  }
@@ -2455,70 +2550,70 @@ function resolveWorkspaceApp(options) {
2455
2550
  if ((0, node_fs_1.existsSync)(absolute)) {
2456
2551
  const stats = (0, node_fs_1.statSync)(absolute);
2457
2552
  if (stats.isDirectory()) {
2458
- const cataloguePath = (0, node_path_1.join)(absolute, 'catalogue.json');
2553
+ const cataloguePath = (0, node_path_1.join)(absolute, "catalogue.json");
2459
2554
  if (!(0, node_fs_1.existsSync)(cataloguePath)) {
2460
2555
  return {
2461
- kind: 'not_found',
2462
- reason: 'catalogue_missing',
2556
+ kind: "not_found",
2557
+ reason: "catalogue_missing",
2463
2558
  message: `Directory ${absolute} does not contain catalogue.json.`,
2464
2559
  };
2465
2560
  }
2466
- return { kind: 'directory', cataloguePath };
2561
+ return { kind: "directory", cataloguePath };
2467
2562
  }
2468
- if (!stats.isFile() || (0, node_path_1.extname)(absolute).toLowerCase() !== '.html') {
2563
+ if (!stats.isFile() || (0, node_path_1.extname)(absolute).toLowerCase() !== ".html") {
2469
2564
  return {
2470
- kind: 'not_found',
2471
- reason: 'app_missing',
2565
+ kind: "not_found",
2566
+ reason: "app_missing",
2472
2567
  message: `Target ${absolute} is not an app HTML file.`,
2473
2568
  };
2474
2569
  }
2475
- return { kind: 'file', htmlPath: absolute };
2570
+ return { kind: "file", htmlPath: absolute };
2476
2571
  }
2477
2572
  const lookup = resolveCatalogueEntries(cwd, { filterName: target });
2478
2573
  if (lookup.errors.length > 0) {
2479
2574
  return {
2480
- kind: 'not_found',
2481
- reason: 'app_missing',
2575
+ kind: "not_found",
2576
+ reason: "app_missing",
2482
2577
  message: lookup.errors[0],
2483
2578
  };
2484
2579
  }
2485
2580
  if (lookup.apps.length > 1) {
2486
2581
  return {
2487
- kind: 'not_found',
2488
- reason: 'multiple_apps',
2582
+ kind: "not_found",
2583
+ reason: "multiple_apps",
2489
2584
  message: `Multiple catalogue entries share the app name "${target}".`,
2490
2585
  };
2491
2586
  }
2492
2587
  if (lookup.apps.length === 1) {
2493
- return { kind: 'app', task: lookup.apps[0], warnings: lookup.warnings };
2588
+ return { kind: "app", task: lookup.apps[0], warnings: lookup.warnings };
2494
2589
  }
2495
2590
  return {
2496
- kind: 'not_found',
2497
- reason: 'app_missing',
2591
+ kind: "not_found",
2592
+ reason: "app_missing",
2498
2593
  message: `App "${target}" was not found in any catalogue.json under ${cwd}.`,
2499
2594
  };
2500
2595
  }
2501
2596
  const nearestCatalogue = findNearestCatalogue(cwd);
2502
2597
  if (!nearestCatalogue) {
2503
2598
  return {
2504
- kind: 'not_found',
2505
- reason: 'catalogue_missing',
2506
- message: 'Run this command inside a workspace with catalogue.json or provide a path.',
2599
+ kind: "not_found",
2600
+ reason: "catalogue_missing",
2601
+ message: "Run this command inside a workspace with catalogue.json or provide a path.",
2507
2602
  };
2508
2603
  }
2509
2604
  const catalogueDir = (0, node_path_1.dirname)(nearestCatalogue);
2510
2605
  const lookup = resolveCatalogueEntries(catalogueDir);
2511
2606
  if (lookup.errors.length > 0) {
2512
2607
  return {
2513
- kind: 'not_found',
2514
- reason: 'app_missing',
2608
+ kind: "not_found",
2609
+ reason: "app_missing",
2515
2610
  message: lookup.errors[0],
2516
2611
  };
2517
2612
  }
2518
2613
  if (lookup.apps.length === 0) {
2519
2614
  return {
2520
- kind: 'not_found',
2521
- reason: 'no_apps',
2615
+ kind: "not_found",
2616
+ reason: "no_apps",
2522
2617
  message: `No apps found in ${nearestCatalogue}.`,
2523
2618
  };
2524
2619
  }
@@ -2526,21 +2621,21 @@ function resolveWorkspaceApp(options) {
2526
2621
  const matched = lookup.apps.find((task) => task.name === appName);
2527
2622
  if (!matched) {
2528
2623
  return {
2529
- kind: 'not_found',
2530
- reason: 'app_missing',
2624
+ kind: "not_found",
2625
+ reason: "app_missing",
2531
2626
  message: `App "${appName}" was not found in ${nearestCatalogue}.`,
2532
2627
  };
2533
2628
  }
2534
- return { kind: 'app', task: matched, warnings: lookup.warnings };
2629
+ return { kind: "app", task: matched, warnings: lookup.warnings };
2535
2630
  }
2536
2631
  if (lookup.apps.length > 1) {
2537
2632
  return {
2538
- kind: 'not_found',
2539
- reason: 'multiple_apps',
2633
+ kind: "not_found",
2634
+ reason: "multiple_apps",
2540
2635
  message: `Multiple apps found in ${nearestCatalogue}. Specify the app name to continue.`,
2541
2636
  };
2542
2637
  }
2543
- return { kind: 'app', task: lookup.apps[0], warnings: lookup.warnings };
2638
+ return { kind: "app", task: lookup.apps[0], warnings: lookup.warnings };
2544
2639
  }
2545
2640
  function formatMetadataWarnings(warnings) {
2546
2641
  return new Set(warnings);
@@ -2551,7 +2646,7 @@ function formatMissingMetadataWarnings(task) {
2551
2646
  }
2552
2647
  return `${task.cataloguePath} (${formatMissingMetadata(task.missingMetadata)})`;
2553
2648
  }
2554
- function findAppTaskByFile(htmlPath) {
2649
+ function findAppTaskByFile(htmlPath, options = {}) {
2555
2650
  const normalizedFilePath = normalizePathForComparison(htmlPath);
2556
2651
  const visited = new Set();
2557
2652
  let currentDir = (0, node_path_1.dirname)(normalizedFilePath);
@@ -2559,6 +2654,7 @@ function findAppTaskByFile(htmlPath) {
2559
2654
  visited.add(currentDir);
2560
2655
  const result = resolveCatalogueEntries(currentDir, {
2561
2656
  filterFile: normalizedFilePath,
2657
+ listingAssets: options.listingAssets,
2562
2658
  });
2563
2659
  if (result.errors.length > 0) {
2564
2660
  return { errors: result.errors, warnings: [], task: undefined };