@quenty/studio-bridge 0.21.1 → 0.22.0

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 (482) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +109 -17
  3. package/build-scripts/transform-add-session-attribute.lua +1 -0
  4. package/deploy.nevermore.json +10 -0
  5. package/dist/src/bridge/bridge-connection.d.ts +26 -2
  6. package/dist/src/bridge/bridge-connection.d.ts.map +1 -1
  7. package/dist/src/bridge/bridge-connection.js +159 -14
  8. package/dist/src/bridge/bridge-connection.js.map +1 -1
  9. package/dist/src/bridge/bridge-connection.test.js +81 -0
  10. package/dist/src/bridge/bridge-connection.test.js.map +1 -1
  11. package/dist/src/bridge/bridge-session.d.ts +35 -2
  12. package/dist/src/bridge/bridge-session.d.ts.map +1 -1
  13. package/dist/src/bridge/bridge-session.js +185 -28
  14. package/dist/src/bridge/bridge-session.js.map +1 -1
  15. package/dist/src/bridge/bridge-session.test.js +6 -4
  16. package/dist/src/bridge/bridge-session.test.js.map +1 -1
  17. package/dist/src/bridge/index.d.ts +1 -1
  18. package/dist/src/bridge/index.d.ts.map +1 -1
  19. package/dist/src/bridge/index.js.map +1 -1
  20. package/dist/src/bridge/internal/bridge-client.d.ts +4 -0
  21. package/dist/src/bridge/internal/bridge-client.d.ts.map +1 -1
  22. package/dist/src/bridge/internal/bridge-client.js +20 -7
  23. package/dist/src/bridge/internal/bridge-client.js.map +1 -1
  24. package/dist/src/bridge/internal/bridge-client.test.js +18 -0
  25. package/dist/src/bridge/internal/bridge-client.test.js.map +1 -1
  26. package/dist/src/bridge/internal/bridge-host.d.ts +13 -1
  27. package/dist/src/bridge/internal/bridge-host.d.ts.map +1 -1
  28. package/dist/src/bridge/internal/bridge-host.js +35 -0
  29. package/dist/src/bridge/internal/bridge-host.js.map +1 -1
  30. package/dist/src/bridge/internal/bridge-host.test.js +31 -0
  31. package/dist/src/bridge/internal/bridge-host.test.js.map +1 -1
  32. package/dist/src/bridge/internal/hand-off.d.ts.map +1 -1
  33. package/dist/src/bridge/internal/hand-off.js +4 -1
  34. package/dist/src/bridge/internal/hand-off.js.map +1 -1
  35. package/dist/src/bridge/internal/host-protocol.d.ts +8 -0
  36. package/dist/src/bridge/internal/host-protocol.d.ts.map +1 -1
  37. package/dist/src/bridge/internal/host-protocol.js +120 -13
  38. package/dist/src/bridge/internal/host-protocol.js.map +1 -1
  39. package/dist/src/bridge/internal/host-protocol.test.js +13 -0
  40. package/dist/src/bridge/internal/host-protocol.test.js.map +1 -1
  41. package/dist/src/bridge/internal/instance-state.d.ts +9 -0
  42. package/dist/src/bridge/internal/instance-state.d.ts.map +1 -0
  43. package/dist/src/bridge/internal/instance-state.js +28 -0
  44. package/dist/src/bridge/internal/instance-state.js.map +1 -0
  45. package/dist/src/bridge/internal/transport-client.d.ts.map +1 -1
  46. package/dist/src/bridge/internal/transport-client.js +2 -1
  47. package/dist/src/bridge/internal/transport-client.js.map +1 -1
  48. package/dist/src/bridge/internal/transport-server.d.ts.map +1 -1
  49. package/dist/src/bridge/internal/transport-server.js +5 -1
  50. package/dist/src/bridge/internal/transport-server.js.map +1 -1
  51. package/dist/src/bridge/types.d.ts +43 -4
  52. package/dist/src/bridge/types.d.ts.map +1 -1
  53. package/dist/src/bridge/types.js.map +1 -1
  54. package/dist/src/cli/adapters/cli-command-adapter-handler.test.js +24 -0
  55. package/dist/src/cli/adapters/cli-command-adapter-handler.test.js.map +1 -1
  56. package/dist/src/cli/adapters/cli-command-adapter.d.ts +6 -0
  57. package/dist/src/cli/adapters/cli-command-adapter.d.ts.map +1 -1
  58. package/dist/src/cli/adapters/cli-command-adapter.js +107 -11
  59. package/dist/src/cli/adapters/cli-command-adapter.js.map +1 -1
  60. package/dist/src/cli/adapters/group-builder.d.ts +3 -0
  61. package/dist/src/cli/adapters/group-builder.d.ts.map +1 -1
  62. package/dist/src/cli/adapters/group-builder.js +34 -15
  63. package/dist/src/cli/adapters/group-builder.js.map +1 -1
  64. package/dist/src/cli/adapters/group-builder.test.js +26 -2
  65. package/dist/src/cli/adapters/group-builder.test.js.map +1 -1
  66. package/dist/src/cli/adapters/help-formatter.d.ts.map +1 -1
  67. package/dist/src/cli/adapters/help-formatter.js +4 -3
  68. package/dist/src/cli/adapters/help-formatter.js.map +1 -1
  69. package/dist/src/cli/adapters/help-formatter.test.js +1 -1
  70. package/dist/src/cli/adapters/help-formatter.test.js.map +1 -1
  71. package/dist/src/cli/adapters/playtest-edit-guard.test.d.ts +2 -0
  72. package/dist/src/cli/adapters/playtest-edit-guard.test.d.ts.map +1 -0
  73. package/dist/src/cli/adapters/playtest-edit-guard.test.js +28 -0
  74. package/dist/src/cli/adapters/playtest-edit-guard.test.js.map +1 -0
  75. package/dist/src/cli/args/global-args.d.ts +1 -1
  76. package/dist/src/cli/args/global-args.d.ts.map +1 -1
  77. package/dist/src/cli/cli-output-context.d.ts +14 -0
  78. package/dist/src/cli/cli-output-context.d.ts.map +1 -0
  79. package/dist/src/cli/cli-output-context.js +39 -0
  80. package/dist/src/cli/cli-output-context.js.map +1 -0
  81. package/dist/src/cli/cli-output-context.test.d.ts +2 -0
  82. package/dist/src/cli/cli-output-context.test.d.ts.map +1 -0
  83. package/dist/src/cli/cli-output-context.test.js +38 -0
  84. package/dist/src/cli/cli-output-context.test.js.map +1 -0
  85. package/dist/src/cli/cli.js +40 -17
  86. package/dist/src/cli/cli.js.map +1 -1
  87. package/dist/src/cli/managed-studio-run.d.ts +21 -0
  88. package/dist/src/cli/managed-studio-run.d.ts.map +1 -0
  89. package/dist/src/cli/managed-studio-run.js +75 -0
  90. package/dist/src/cli/managed-studio-run.js.map +1 -0
  91. package/dist/src/cli/managed-studio-run.test.d.ts +2 -0
  92. package/dist/src/cli/managed-studio-run.test.d.ts.map +1 -0
  93. package/dist/src/cli/managed-studio-run.test.js +52 -0
  94. package/dist/src/cli/managed-studio-run.test.js.map +1 -0
  95. package/dist/src/cli/resolve-session.js +1 -1
  96. package/dist/src/cli/resolve-session.js.map +1 -1
  97. package/dist/src/cli/resolve-session.test.js +1 -1
  98. package/dist/src/cli/resolve-session.test.js.map +1 -1
  99. package/dist/src/cli/script-executor.d.ts +13 -1
  100. package/dist/src/cli/script-executor.d.ts.map +1 -1
  101. package/dist/src/cli/script-executor.js +77 -47
  102. package/dist/src/cli/script-executor.js.map +1 -1
  103. package/dist/src/commands/console/exec/exec.d.ts +17 -0
  104. package/dist/src/commands/console/exec/exec.d.ts.map +1 -1
  105. package/dist/src/commands/console/exec/exec.js +39 -7
  106. package/dist/src/commands/console/exec/exec.js.map +1 -1
  107. package/dist/src/commands/console/exec/exec.test.js +62 -3
  108. package/dist/src/commands/console/exec/exec.test.js.map +1 -1
  109. package/dist/src/commands/console/logs/logs.d.ts +30 -2
  110. package/dist/src/commands/console/logs/logs.d.ts.map +1 -1
  111. package/dist/src/commands/console/logs/logs.js +82 -7
  112. package/dist/src/commands/console/logs/logs.js.map +1 -1
  113. package/dist/src/commands/console/logs/logs.test.js +63 -4
  114. package/dist/src/commands/console/logs/logs.test.js.map +1 -1
  115. package/dist/src/commands/explorer/export/export.d.ts +19 -0
  116. package/dist/src/commands/explorer/export/export.d.ts.map +1 -0
  117. package/dist/src/commands/explorer/export/export.js +37 -0
  118. package/dist/src/commands/explorer/export/export.js.map +1 -0
  119. package/dist/src/commands/explorer/export/export.test.d.ts +2 -0
  120. package/dist/src/commands/explorer/export/export.test.d.ts.map +1 -0
  121. package/dist/src/commands/explorer/export/export.test.js +25 -0
  122. package/dist/src/commands/explorer/export/export.test.js.map +1 -0
  123. package/dist/src/commands/explorer/find/find.d.ts +28 -0
  124. package/dist/src/commands/explorer/find/find.d.ts.map +1 -0
  125. package/dist/src/commands/explorer/find/find.js +75 -0
  126. package/dist/src/commands/explorer/find/find.js.map +1 -0
  127. package/dist/src/commands/explorer/find/find.test.d.ts +2 -0
  128. package/dist/src/commands/explorer/find/find.test.d.ts.map +1 -0
  129. package/dist/src/commands/explorer/find/find.test.js +44 -0
  130. package/dist/src/commands/explorer/find/find.test.js.map +1 -0
  131. package/dist/src/commands/explorer/grep/grep.d.ts +24 -0
  132. package/dist/src/commands/explorer/grep/grep.d.ts.map +1 -0
  133. package/dist/src/commands/explorer/grep/grep.js +63 -0
  134. package/dist/src/commands/explorer/grep/grep.js.map +1 -0
  135. package/dist/src/commands/explorer/grep/grep.test.d.ts +2 -0
  136. package/dist/src/commands/explorer/grep/grep.test.d.ts.map +1 -0
  137. package/dist/src/commands/explorer/grep/grep.test.js +42 -0
  138. package/dist/src/commands/explorer/grep/grep.test.js.map +1 -0
  139. package/dist/src/commands/explorer/import/import.d.ts +21 -0
  140. package/dist/src/commands/explorer/import/import.d.ts.map +1 -0
  141. package/dist/src/commands/explorer/import/import.js +37 -0
  142. package/dist/src/commands/explorer/import/import.js.map +1 -0
  143. package/dist/src/commands/explorer/import/import.test.d.ts +2 -0
  144. package/dist/src/commands/explorer/import/import.test.d.ts.map +1 -0
  145. package/dist/src/commands/explorer/import/import.test.js +45 -0
  146. package/dist/src/commands/explorer/import/import.test.js.map +1 -0
  147. package/dist/src/commands/formatters.test.js +7 -1
  148. package/dist/src/commands/formatters.test.js.map +1 -1
  149. package/dist/src/commands/framework/action-loader.d.ts +5 -1
  150. package/dist/src/commands/framework/action-loader.d.ts.map +1 -1
  151. package/dist/src/commands/framework/action-loader.js +9 -2
  152. package/dist/src/commands/framework/action-loader.js.map +1 -1
  153. package/dist/src/commands/framework/action-loader.test.js +23 -0
  154. package/dist/src/commands/framework/action-loader.test.js.map +1 -1
  155. package/dist/src/commands/framework/arg-builder.d.ts +15 -2
  156. package/dist/src/commands/framework/arg-builder.d.ts.map +1 -1
  157. package/dist/src/commands/framework/arg-builder.js +22 -2
  158. package/dist/src/commands/framework/arg-builder.js.map +1 -1
  159. package/dist/src/commands/framework/define-command.d.ts +19 -3
  160. package/dist/src/commands/framework/define-command.d.ts.map +1 -1
  161. package/dist/src/commands/framework/define-command.js.map +1 -1
  162. package/dist/src/commands/framework/index.d.ts +1 -1
  163. package/dist/src/commands/framework/index.d.ts.map +1 -1
  164. package/dist/src/commands/framework/index.js.map +1 -1
  165. package/dist/src/commands/index.d.ts +11 -0
  166. package/dist/src/commands/index.d.ts.map +1 -1
  167. package/dist/src/commands/index.js +11 -0
  168. package/dist/src/commands/index.js.map +1 -1
  169. package/dist/src/commands/linux/inject-credentials/inject-credentials.d.ts.map +1 -1
  170. package/dist/src/commands/linux/inject-credentials/inject-credentials.js +1 -3
  171. package/dist/src/commands/linux/inject-credentials/inject-credentials.js.map +1 -1
  172. package/dist/src/commands/linux/setup/setup.d.ts.map +1 -1
  173. package/dist/src/commands/linux/setup/setup.js +1 -3
  174. package/dist/src/commands/linux/setup/setup.js.map +1 -1
  175. package/dist/src/commands/linux/status/status.d.ts +9 -1
  176. package/dist/src/commands/linux/status/status.d.ts.map +1 -1
  177. package/dist/src/commands/linux/status/status.js +66 -86
  178. package/dist/src/commands/linux/status/status.js.map +1 -1
  179. package/dist/src/commands/playtest/player/add/add.d.ts +17 -0
  180. package/dist/src/commands/playtest/player/add/add.d.ts.map +1 -0
  181. package/dist/src/commands/playtest/player/add/add.js +45 -0
  182. package/dist/src/commands/playtest/player/add/add.js.map +1 -0
  183. package/dist/src/commands/playtest/player/list/list.d.ts +13 -0
  184. package/dist/src/commands/playtest/player/list/list.d.ts.map +1 -0
  185. package/dist/src/commands/playtest/player/list/list.js +33 -0
  186. package/dist/src/commands/playtest/player/list/list.js.map +1 -0
  187. package/dist/src/commands/playtest/player/playtest-players.d.ts +36 -0
  188. package/dist/src/commands/playtest/player/playtest-players.d.ts.map +1 -0
  189. package/dist/src/commands/playtest/player/playtest-players.js +187 -0
  190. package/dist/src/commands/playtest/player/playtest-players.js.map +1 -0
  191. package/dist/src/commands/playtest/player/playtest-players.test.d.ts +2 -0
  192. package/dist/src/commands/playtest/player/playtest-players.test.d.ts.map +1 -0
  193. package/dist/src/commands/playtest/player/playtest-players.test.js +89 -0
  194. package/dist/src/commands/playtest/player/playtest-players.test.js.map +1 -0
  195. package/dist/src/commands/playtest/player/remove/remove.d.ts +17 -0
  196. package/dist/src/commands/playtest/player/remove/remove.d.ts.map +1 -0
  197. package/dist/src/commands/playtest/player/remove/remove.js +42 -0
  198. package/dist/src/commands/playtest/player/remove/remove.js.map +1 -0
  199. package/dist/src/commands/playtest/playtest-sessions.d.ts +49 -0
  200. package/dist/src/commands/playtest/playtest-sessions.d.ts.map +1 -0
  201. package/dist/src/commands/playtest/playtest-sessions.js +283 -0
  202. package/dist/src/commands/playtest/playtest-sessions.js.map +1 -0
  203. package/dist/src/commands/playtest/playtest-sessions.test.d.ts +2 -0
  204. package/dist/src/commands/playtest/playtest-sessions.test.d.ts.map +1 -0
  205. package/dist/src/commands/playtest/playtest-sessions.test.js +87 -0
  206. package/dist/src/commands/playtest/playtest-sessions.test.js.map +1 -0
  207. package/dist/src/commands/playtest/restart/restart.d.ts +23 -0
  208. package/dist/src/commands/playtest/restart/restart.d.ts.map +1 -0
  209. package/dist/src/commands/playtest/restart/restart.js +105 -0
  210. package/dist/src/commands/playtest/restart/restart.js.map +1 -0
  211. package/dist/src/commands/playtest/run/run.d.ts +47 -0
  212. package/dist/src/commands/playtest/run/run.d.ts.map +1 -0
  213. package/dist/src/commands/playtest/run/run.js +137 -0
  214. package/dist/src/commands/playtest/run/run.js.map +1 -0
  215. package/dist/src/commands/playtest/start/start.d.ts +42 -0
  216. package/dist/src/commands/playtest/start/start.d.ts.map +1 -0
  217. package/dist/src/commands/playtest/start/start.js +70 -0
  218. package/dist/src/commands/playtest/start/start.js.map +1 -0
  219. package/dist/src/commands/playtest/stop/stop.d.ts +15 -0
  220. package/dist/src/commands/playtest/stop/stop.d.ts.map +1 -0
  221. package/dist/src/commands/playtest/stop/stop.js +24 -0
  222. package/dist/src/commands/playtest/stop/stop.js.map +1 -0
  223. package/dist/src/commands/process/close/close.d.ts +19 -10
  224. package/dist/src/commands/process/close/close.d.ts.map +1 -1
  225. package/dist/src/commands/process/close/close.js +118 -15
  226. package/dist/src/commands/process/close/close.js.map +1 -1
  227. package/dist/src/commands/process/close/close.test.d.ts +2 -0
  228. package/dist/src/commands/process/close/close.test.d.ts.map +1 -0
  229. package/dist/src/commands/process/close/close.test.js +190 -0
  230. package/dist/src/commands/process/close/close.test.js.map +1 -0
  231. package/dist/src/commands/process/info/info.d.ts +45 -5
  232. package/dist/src/commands/process/info/info.d.ts.map +1 -1
  233. package/dist/src/commands/process/info/info.js +117 -13
  234. package/dist/src/commands/process/info/info.js.map +1 -1
  235. package/dist/src/commands/process/info/info.test.js +140 -38
  236. package/dist/src/commands/process/info/info.test.js.map +1 -1
  237. package/dist/src/commands/process/launch/launch.d.ts +11 -1
  238. package/dist/src/commands/process/launch/launch.d.ts.map +1 -1
  239. package/dist/src/commands/process/launch/launch.js +37 -7
  240. package/dist/src/commands/process/launch/launch.js.map +1 -1
  241. package/dist/src/commands/process/launch/launch.test.js +99 -20
  242. package/dist/src/commands/process/launch/launch.test.js.map +1 -1
  243. package/dist/src/commands/process/list/list.d.ts +2 -1
  244. package/dist/src/commands/process/list/list.d.ts.map +1 -1
  245. package/dist/src/commands/process/list/list.js +9 -6
  246. package/dist/src/commands/process/list/list.js.map +1 -1
  247. package/dist/src/commands/process/list/list.test.js +1 -0
  248. package/dist/src/commands/process/list/list.test.js.map +1 -1
  249. package/dist/src/commands/process/run/run.d.ts +9 -0
  250. package/dist/src/commands/process/run/run.d.ts.map +1 -1
  251. package/dist/src/commands/process/run/run.js +15 -5
  252. package/dist/src/commands/process/run/run.js.map +1 -1
  253. package/dist/src/commands/serve/serve.d.ts +2 -0
  254. package/dist/src/commands/serve/serve.d.ts.map +1 -1
  255. package/dist/src/commands/serve/serve.js +17 -4
  256. package/dist/src/commands/serve/serve.js.map +1 -1
  257. package/dist/src/commands/serve/serve.test.js +9 -2
  258. package/dist/src/commands/serve/serve.test.js.map +1 -1
  259. package/dist/src/commands/viewport/screenshot/screenshot.d.ts.map +1 -1
  260. package/dist/src/commands/viewport/screenshot/screenshot.js +4 -1
  261. package/dist/src/commands/viewport/screenshot/screenshot.js.map +1 -1
  262. package/dist/src/commands/viewport/screenshot/screenshot.test.js +5 -9
  263. package/dist/src/commands/viewport/screenshot/screenshot.test.js.map +1 -1
  264. package/dist/src/deploy/deploy-places.d.ts +39 -0
  265. package/dist/src/deploy/deploy-places.d.ts.map +1 -0
  266. package/dist/src/deploy/deploy-places.js +162 -0
  267. package/dist/src/deploy/deploy-places.js.map +1 -0
  268. package/dist/src/deploy/deploy-places.test.d.ts +2 -0
  269. package/dist/src/deploy/deploy-places.test.d.ts.map +1 -0
  270. package/dist/src/deploy/deploy-places.test.js +96 -0
  271. package/dist/src/deploy/deploy-places.test.js.map +1 -0
  272. package/dist/src/index.d.ts +2 -2
  273. package/dist/src/index.d.ts.map +1 -1
  274. package/dist/src/index.js +1 -1
  275. package/dist/src/index.js.map +1 -1
  276. package/dist/src/plugin/persistent-plugin-installer.d.ts.map +1 -1
  277. package/dist/src/plugin/persistent-plugin-installer.js +2 -1
  278. package/dist/src/plugin/persistent-plugin-installer.js.map +1 -1
  279. package/dist/src/plugin/persistent-plugin-installer.test.js +3 -0
  280. package/dist/src/plugin/persistent-plugin-installer.test.js.map +1 -1
  281. package/dist/src/plugin/plugin-build-version.d.ts +7 -0
  282. package/dist/src/plugin/plugin-build-version.d.ts.map +1 -0
  283. package/dist/src/plugin/plugin-build-version.js +42 -0
  284. package/dist/src/plugin/plugin-build-version.js.map +1 -0
  285. package/dist/src/plugin/plugin-build-version.test.d.ts +2 -0
  286. package/dist/src/plugin/plugin-build-version.test.d.ts.map +1 -0
  287. package/dist/src/plugin/plugin-build-version.test.js +41 -0
  288. package/dist/src/plugin/plugin-build-version.test.js.map +1 -0
  289. package/dist/src/plugin/plugin-injector.d.ts.map +1 -1
  290. package/dist/src/plugin/plugin-injector.js +7 -1
  291. package/dist/src/plugin/plugin-injector.js.map +1 -1
  292. package/dist/src/process/studio-process-closer.d.ts +35 -0
  293. package/dist/src/process/studio-process-closer.d.ts.map +1 -0
  294. package/dist/src/process/studio-process-closer.js +129 -0
  295. package/dist/src/process/studio-process-closer.js.map +1 -0
  296. package/dist/src/process/studio-process-closer.test.d.ts +2 -0
  297. package/dist/src/process/studio-process-closer.test.d.ts.map +1 -0
  298. package/dist/src/process/studio-process-closer.test.js +80 -0
  299. package/dist/src/process/studio-process-closer.test.js.map +1 -0
  300. package/dist/src/process/studio-settings.d.ts +14 -0
  301. package/dist/src/process/studio-settings.d.ts.map +1 -0
  302. package/dist/src/process/studio-settings.js +50 -0
  303. package/dist/src/process/studio-settings.js.map +1 -0
  304. package/dist/src/process/studio-settings.test.d.ts +2 -0
  305. package/dist/src/process/studio-settings.test.d.ts.map +1 -0
  306. package/dist/src/process/studio-settings.test.js +16 -0
  307. package/dist/src/process/studio-settings.test.js.map +1 -0
  308. package/dist/src/process/studio-window-state.d.ts +13 -0
  309. package/dist/src/process/studio-window-state.d.ts.map +1 -0
  310. package/dist/src/process/studio-window-state.js +48 -0
  311. package/dist/src/process/studio-window-state.js.map +1 -0
  312. package/dist/src/server/studio-bridge-server.d.ts +3 -1
  313. package/dist/src/server/studio-bridge-server.d.ts.map +1 -1
  314. package/dist/src/server/studio-bridge-server.js +12 -4
  315. package/dist/src/server/studio-bridge-server.js.map +1 -1
  316. package/dist/src/server/web-socket-protocol.d.ts +173 -6
  317. package/dist/src/server/web-socket-protocol.d.ts.map +1 -1
  318. package/dist/src/server/web-socket-protocol.js +244 -1
  319. package/dist/src/server/web-socket-protocol.js.map +1 -1
  320. package/dist/src/server/web-socket-protocol.test.js +48 -0
  321. package/dist/src/server/web-socket-protocol.test.js.map +1 -1
  322. package/dist/src/test/e2e/persistent-session.test.js +121 -0
  323. package/dist/src/test/e2e/persistent-session.test.js.map +1 -1
  324. package/dist/src/test/e2e/split-server.test.d.ts +3 -5
  325. package/dist/src/test/e2e/split-server.test.d.ts.map +1 -1
  326. package/dist/src/test/e2e/split-server.test.js +3 -5
  327. package/dist/src/test/e2e/split-server.test.js.map +1 -1
  328. package/dist/tsconfig.tsbuildinfo +1 -1
  329. package/docker/Dockerfile +3 -0
  330. package/package.json +8 -6
  331. package/src/bridge/bridge-connection.test.ts +126 -0
  332. package/src/bridge/bridge-connection.ts +217 -22
  333. package/src/bridge/bridge-session.test.ts +6 -4
  334. package/src/bridge/bridge-session.ts +273 -28
  335. package/src/bridge/index.ts +3 -0
  336. package/src/bridge/internal/bridge-client.test.ts +23 -0
  337. package/src/bridge/internal/bridge-client.ts +25 -7
  338. package/src/bridge/internal/bridge-host.test.ts +38 -0
  339. package/src/bridge/internal/bridge-host.ts +47 -0
  340. package/src/bridge/internal/hand-off.ts +4 -1
  341. package/src/bridge/internal/host-protocol.test.ts +15 -0
  342. package/src/bridge/internal/host-protocol.ts +148 -37
  343. package/src/bridge/internal/instance-state.ts +34 -0
  344. package/src/bridge/internal/transport-client.ts +2 -1
  345. package/src/bridge/internal/transport-server.ts +5 -1
  346. package/src/bridge/types.ts +47 -2
  347. package/src/cli/adapters/cli-command-adapter-handler.test.ts +28 -0
  348. package/src/cli/adapters/cli-command-adapter.ts +150 -18
  349. package/src/cli/adapters/group-builder.test.ts +34 -2
  350. package/src/cli/adapters/group-builder.ts +52 -20
  351. package/src/cli/adapters/help-formatter.test.ts +1 -1
  352. package/src/cli/adapters/help-formatter.ts +4 -3
  353. package/src/cli/adapters/playtest-edit-guard.test.ts +54 -0
  354. package/src/cli/args/global-args.ts +1 -1
  355. package/src/cli/cli-output-context.test.ts +55 -0
  356. package/src/cli/cli-output-context.ts +50 -0
  357. package/src/cli/cli.ts +49 -17
  358. package/src/cli/managed-studio-run.test.ts +71 -0
  359. package/src/cli/managed-studio-run.ts +135 -0
  360. package/src/cli/resolve-session.test.ts +1 -1
  361. package/src/cli/resolve-session.ts +1 -1
  362. package/src/cli/script-executor.ts +99 -51
  363. package/src/commands/console/exec/exec.test.ts +96 -3
  364. package/src/commands/console/exec/exec.ts +59 -6
  365. package/src/commands/console/exec/execute.lua +356 -240
  366. package/src/commands/console/exec/execute.spec.lua +286 -0
  367. package/src/commands/console/logs/logs.test.ts +76 -4
  368. package/src/commands/console/logs/logs.ts +132 -17
  369. package/src/commands/console/logs/query-logs.lua +85 -52
  370. package/src/commands/console/logs/query-logs.spec.lua +132 -0
  371. package/src/commands/explorer/export/export-instances.lua +108 -0
  372. package/src/commands/explorer/export/export-instances.spec.lua +87 -0
  373. package/src/commands/explorer/export/export.test.ts +27 -0
  374. package/src/commands/explorer/export/export.ts +60 -0
  375. package/src/commands/explorer/find/find-instances.lua +174 -0
  376. package/src/commands/explorer/find/find-instances.spec.lua +122 -0
  377. package/src/commands/explorer/find/find.test.ts +53 -0
  378. package/src/commands/explorer/find/find.ts +113 -0
  379. package/src/commands/explorer/grep/grep-scripts.lua +214 -0
  380. package/src/commands/explorer/grep/grep-scripts.spec.lua +130 -0
  381. package/src/commands/explorer/grep/grep.test.ts +45 -0
  382. package/src/commands/explorer/grep/grep.ts +93 -0
  383. package/src/commands/explorer/import/import-instances.lua +106 -0
  384. package/src/commands/explorer/import/import-instances.spec.lua +84 -0
  385. package/src/commands/explorer/import/import.test.ts +60 -0
  386. package/src/commands/explorer/import/import.ts +66 -0
  387. package/src/commands/explorer/query/query-data-model.lua +124 -152
  388. package/src/commands/explorer/query/query-data-model.spec.lua +150 -0
  389. package/src/commands/formatters.test.ts +7 -1
  390. package/src/commands/framework/action-loader.test.ts +28 -0
  391. package/src/commands/framework/action-loader.ts +10 -2
  392. package/src/commands/framework/arg-builder.ts +26 -3
  393. package/src/commands/framework/define-command.ts +32 -3
  394. package/src/commands/framework/index.ts +1 -0
  395. package/src/commands/index.ts +50 -0
  396. package/src/commands/linux/inject-credentials/inject-credentials.ts +1 -3
  397. package/src/commands/linux/setup/setup.ts +1 -3
  398. package/src/commands/linux/status/status.ts +105 -98
  399. package/src/commands/playtest/player/add/add.ts +85 -0
  400. package/src/commands/playtest/player/list/list.ts +57 -0
  401. package/src/commands/playtest/player/playtest-players.test.ts +146 -0
  402. package/src/commands/playtest/player/playtest-players.ts +270 -0
  403. package/src/commands/playtest/player/remove/remove.ts +78 -0
  404. package/src/commands/playtest/playtest-sessions.test.ts +143 -0
  405. package/src/commands/playtest/playtest-sessions.ts +417 -0
  406. package/src/commands/playtest/restart/restart.ts +199 -0
  407. package/src/commands/playtest/run/playtest-runner.luau +264 -0
  408. package/src/commands/playtest/run/playtest-runner.spec.lua +71 -0
  409. package/src/commands/playtest/run/run-playtest-script.lua +139 -0
  410. package/src/commands/playtest/run/run-playtest-script.spec.lua +43 -0
  411. package/src/commands/playtest/run/run.ts +239 -0
  412. package/src/commands/playtest/start/start-playtest.lua +108 -0
  413. package/src/commands/playtest/start/start.ts +138 -0
  414. package/src/commands/playtest/stop/end-playtest.lua +47 -0
  415. package/src/commands/playtest/stop/stop.ts +51 -0
  416. package/src/commands/process/close/close.test.ts +250 -0
  417. package/src/commands/process/close/close.ts +183 -17
  418. package/src/commands/process/info/info.test.ts +198 -44
  419. package/src/commands/process/info/info.ts +204 -15
  420. package/src/commands/process/info/query-state.lua +42 -29
  421. package/src/commands/process/info/query-state.spec.lua +39 -0
  422. package/src/commands/process/launch/launch.test.ts +138 -21
  423. package/src/commands/process/launch/launch.ts +69 -9
  424. package/src/commands/process/list/list.test.ts +1 -0
  425. package/src/commands/process/list/list.ts +14 -6
  426. package/src/commands/process/run/run.ts +26 -5
  427. package/src/commands/serve/serve.test.ts +17 -2
  428. package/src/commands/serve/serve.ts +25 -6
  429. package/src/commands/viewport/screenshot/capture-screenshot.lua +175 -182
  430. package/src/commands/viewport/screenshot/capture-screenshot.spec.lua +129 -0
  431. package/src/commands/viewport/screenshot/screenshot.test.ts +7 -10
  432. package/src/commands/viewport/screenshot/screenshot.ts +6 -1
  433. package/src/deploy/deploy-places.test.ts +127 -0
  434. package/src/deploy/deploy-places.ts +229 -0
  435. package/src/index.ts +11 -0
  436. package/src/plugin/persistent-plugin-installer.test.ts +4 -0
  437. package/src/plugin/persistent-plugin-installer.ts +2 -1
  438. package/src/plugin/plugin-build-version.test.ts +55 -0
  439. package/src/plugin/plugin-build-version.ts +53 -0
  440. package/src/plugin/plugin-injector.ts +7 -1
  441. package/src/process/studio-process-closer.test.ts +107 -0
  442. package/src/process/studio-process-closer.ts +187 -0
  443. package/src/process/studio-settings.test.ts +18 -0
  444. package/src/process/studio-settings.ts +61 -0
  445. package/src/process/studio-window-state.ts +65 -0
  446. package/src/server/studio-bridge-server.ts +15 -4
  447. package/src/server/web-socket-protocol.test.ts +55 -0
  448. package/src/server/web-socket-protocol.ts +468 -8
  449. package/src/test/e2e/persistent-session.test.ts +137 -0
  450. package/src/test/e2e/split-server.test.ts +3 -5
  451. package/templates/studio-bridge-plugin/default.project.json +3 -0
  452. package/templates/studio-bridge-plugin/src/Shared/ActionRouter.lua +208 -159
  453. package/templates/studio-bridge-plugin/src/Shared/ActionRouter.spec.lua +418 -0
  454. package/templates/studio-bridge-plugin/src/Shared/BuiltInActions.lua +94 -0
  455. package/templates/studio-bridge-plugin/src/Shared/BuiltInActions.spec.lua +166 -0
  456. package/templates/studio-bridge-plugin/src/Shared/DiscoveryStateMachine.lua +118 -137
  457. package/templates/studio-bridge-plugin/src/Shared/DiscoveryStateMachine.spec.lua +228 -0
  458. package/templates/studio-bridge-plugin/src/Shared/MessageBuffer.lua +80 -109
  459. package/templates/studio-bridge-plugin/src/Shared/MessageBuffer.spec.lua +116 -0
  460. package/templates/studio-bridge-plugin/src/Shared/StudioBridgeConnection.lua +233 -0
  461. package/templates/studio-bridge-plugin/src/Shared/StudioBridgeConnection.spec.lua +239 -0
  462. package/templates/studio-bridge-plugin/src/Shared/StudioBridgeLogUtils.lua +53 -0
  463. package/templates/studio-bridge-plugin/src/Shared/StudioBridgeLogUtils.spec.lua +27 -0
  464. package/templates/studio-bridge-plugin/src/Shared/StudioBridgeSessionUtils.lua +164 -0
  465. package/templates/studio-bridge-plugin/src/Shared/StudioBridgeSessionUtils.spec.lua +159 -0
  466. package/templates/studio-bridge-plugin/src/StudioBridgePlugin.server.lua +266 -368
  467. package/templates/studio-bridge-plugin/src/Vendor/png.lua +8 -2
  468. package/test/default.project.json +41 -0
  469. package/test/jest.config.lua +3 -0
  470. package/test/scripts/Server/ServerMain.server.lua +10 -0
  471. package/tsconfig.json +1 -0
  472. package/templates/studio-bridge-plugin/src/Shared/Protocol.lua +0 -132
  473. package/templates/studio-bridge-plugin-test/test/actions.test.lua +0 -580
  474. package/templates/studio-bridge-plugin-test/test/discovery.test.lua +0 -622
  475. package/templates/studio-bridge-plugin-test/test/execute-handler.test.lua +0 -517
  476. package/templates/studio-bridge-plugin-test/test/hash-lifecycle.test.lua +0 -814
  477. package/templates/studio-bridge-plugin-test/test/integration.test.lua +0 -1020
  478. package/templates/studio-bridge-plugin-test/test/new-actions.test.lua +0 -339
  479. package/templates/studio-bridge-plugin-test/test/plugin-entry.test.lua +0 -493
  480. package/templates/studio-bridge-plugin-test/test/protocol.test.lua +0 -506
  481. package/templates/studio-bridge-plugin-test/test/roblox-mocks.lua +0 -108
  482. package/templates/studio-bridge-plugin-test/test/test-runner.lua +0 -112
@@ -0,0 +1,417 @@
1
+ /**
2
+ * Starting, finding, and ending StudioTestService playtests.
3
+ *
4
+ * A playtest opens fresh server (and, in play mode, client) DataModels, and
5
+ * each connects to the bridge as its own session. Those sessions can't be
6
+ * matched to the Studio that started them by instance id, since unpublished
7
+ * places give every DataModel its own. Instead, `startPlaytestAsync` passes a
8
+ * playtest id as the StudioTestService test args, and each new session is
9
+ * asked for it back through `GetTestArgs`.
10
+ */
11
+
12
+ import { randomUUID } from 'crypto';
13
+ import type {
14
+ BridgeConnection,
15
+ BridgeSession,
16
+ EndPlaytestResult,
17
+ PlaytestEndResult,
18
+ PlaytestMode,
19
+ SessionContext,
20
+ } from '../../bridge/index.js';
21
+ import { ContextNotFoundError } from '../../bridge/index.js';
22
+ import { getPluginBuildVersionAsync } from '../../plugin/plugin-build-version.js';
23
+ import { tryReadLoadUserPluginsOnPlaytestAsync } from '../../process/studio-settings.js';
24
+
25
+ /** How long a started playtest may run before its pending start request is dropped. */
26
+ const PLAYTEST_MAX_DURATION_MS = 24 * 60 * 60 * 1000;
27
+
28
+ const SESSION_WAIT_TIMEOUT_MS = 30_000;
29
+ /** Each player of a multiplayer test boots its own Studio process. */
30
+ const MULTIPLAYER_SESSION_WAIT_TIMEOUT_MS = 90_000;
31
+ const SESSION_CLOSE_TIMEOUT_MS = 10_000;
32
+ const READ_RETRY_DELAY_MS = 1_000;
33
+
34
+ const PLAYTEST_PLUGINS_OFF_MESSAGE = `Studio's "Load user plugins on playtest" setting is off, so the studio-bridge plugin doesn't load into playtests and their sessions can't connect. Turn it on in Studio Settings, or run a script in a playtest's server with 'studio-bridge playtest run', which works without it.`;
35
+
36
+ const READ_PLAYTEST_ID_SCRIPT = [
37
+ 'local ok, args = pcall(function()',
38
+ '\treturn game:GetService("StudioTestService"):GetTestArgs()',
39
+ 'end)',
40
+ 'return if ok and type(args) == "table" then args.studioBridgePlaytestId else nil',
41
+ ].join('\n');
42
+
43
+ export interface StartedPlaytest {
44
+ playtestId: string;
45
+ mode: PlaytestMode;
46
+ /** Set for a multiplayer test. */
47
+ players?: number;
48
+ /** The playtest's sessions: always `server`, plus `client` in play mode. */
49
+ sessions: Partial<Record<SessionContext, BridgeSession>>;
50
+ /** Every client session, one per player. */
51
+ clients: BridgeSession[];
52
+ /** Settles when the playtest ends. Never rejects. */
53
+ ended: Promise<PlaytestEndResult>;
54
+ }
55
+
56
+ /** The sessions a playtest opens, by mode. Run mode has no player, so no client. */
57
+ export function getPlaytestContexts(mode: PlaytestMode): SessionContext[] {
58
+ return mode === 'play' ? ['server', 'client'] : ['server'];
59
+ }
60
+
61
+ /**
62
+ * Start a playtest from `editSession` and resolve once every session it opens
63
+ * has connected. With `players`, it is a multiplayer test with a client per
64
+ * player. Rejects if Studio refuses to start it, e.g. because a playtest is
65
+ * already running.
66
+ */
67
+ export async function startPlaytestAsync(
68
+ connection: BridgeConnection,
69
+ editSession: BridgeSession,
70
+ mode: PlaytestMode,
71
+ players?: number
72
+ ): Promise<StartedPlaytest> {
73
+ if (editSession.context !== 'edit') {
74
+ throw new Error(
75
+ `Playtests start from an edit session, but '${editSession.info.sessionId}' is a ${editSession.context} session`
76
+ );
77
+ }
78
+ if (await isPlaytestPluginsOffAsync(connection)) {
79
+ throw new Error(PLAYTEST_PLUGINS_OFF_MESSAGE);
80
+ }
81
+ if (players !== undefined) {
82
+ await assertCanStartMultiplayerAsync(editSession, mode, players);
83
+ }
84
+
85
+ const playtestId = randomUUID();
86
+ const ended = editSession
87
+ .runPlaytestAsync(
88
+ mode,
89
+ playtestId,
90
+ connection.port,
91
+ PLAYTEST_MAX_DURATION_MS,
92
+ players
93
+ )
94
+ .catch(
95
+ (err): PlaytestEndResult => ({
96
+ playtestId,
97
+ success: false,
98
+ error: err instanceof Error ? err.message : String(err),
99
+ })
100
+ );
101
+
102
+ const clientCount = players ?? (mode === 'play' ? 1 : 0);
103
+ const waitTimeoutMs =
104
+ players !== undefined
105
+ ? MULTIPLAYER_SESSION_WAIT_TIMEOUT_MS
106
+ : SESSION_WAIT_TIMEOUT_MS;
107
+ const abort = new AbortController();
108
+ let outcome;
109
+ try {
110
+ outcome = await Promise.race([
111
+ waitForPlaytestSessionsAsync(
112
+ connection,
113
+ playtestId,
114
+ clientCount,
115
+ waitTimeoutMs,
116
+ abort.signal
117
+ ).then((sessions) => ({ sessions })),
118
+ ended.then((result) => ({ result })),
119
+ ]);
120
+ } finally {
121
+ abort.abort();
122
+ }
123
+
124
+ if ('result' in outcome) {
125
+ throw new Error(
126
+ outcome.result.success
127
+ ? 'Playtest ended before its sessions connected'
128
+ : `Playtest failed to start: ${outcome.result.error}`
129
+ );
130
+ }
131
+
132
+ const { server, clients } = outcome.sessions;
133
+ const missing = [
134
+ ...(server ? [] : ['server']),
135
+ ...(clients.length < clientCount
136
+ ? [`${clientCount - clients.length} client`]
137
+ : []),
138
+ ];
139
+ if (server && missing.length === 0) {
140
+ return {
141
+ playtestId,
142
+ mode,
143
+ players,
144
+ sessions: { server, client: clients[0] },
145
+ clients,
146
+ ended,
147
+ };
148
+ }
149
+
150
+ const waited = `its ${missing.join(' and ')} session${
151
+ missing.length > 1 || clientCount - clients.length > 1 ? 's' : ''
152
+ } did not connect within ${waitTimeoutMs / 1000}s`;
153
+
154
+ // Without its server session the playtest can't be ended from here
155
+ if (!server?.isConnected) {
156
+ throw new Error(
157
+ `Playtest started, but ${waited}, so stop it in Studio. Play sessions only connect with Studio's "Load user plugins on playtest" setting on.`
158
+ );
159
+ }
160
+
161
+ await endPlaytestAsync(connection, server);
162
+ throw new Error(`Playtest started, but ${waited}, so it was stopped.`);
163
+ }
164
+
165
+ async function assertCanStartMultiplayerAsync(
166
+ editSession: BridgeSession,
167
+ mode: PlaytestMode,
168
+ players: number
169
+ ): Promise<void> {
170
+ if (!Number.isInteger(players) || players < 1) {
171
+ throw new Error(
172
+ `--players must be a whole number of at least 1, not ${players}`
173
+ );
174
+ }
175
+ if (mode !== 'play') {
176
+ throw new Error('A multiplayer test runs in play mode');
177
+ }
178
+
179
+ // Older plugins give every client the same session id, so all but one drop off
180
+ const expected = await getPluginBuildVersionAsync();
181
+ if (editSession.info.pluginVersion !== expected) {
182
+ throw new Error(
183
+ `Multiplayer tests need the current studio-bridge plugin, but '${editSession.info.placeName}' has an older one. Run 'studio-bridge plugin install' first.`
184
+ );
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Resolve the server session of a running playtest. `target` names it or
190
+ * any session of its Studio; otherwise there must be exactly one connected.
191
+ */
192
+ export async function resolvePlaytestServerSessionAsync(
193
+ connection: BridgeConnection,
194
+ target?: string
195
+ ): Promise<BridgeSession> {
196
+ if (target) {
197
+ try {
198
+ return await connection.resolveSessionAsync(target, 'server');
199
+ } catch (err) {
200
+ throw await explainMissingPlaytestContextAsync(connection, err);
201
+ }
202
+ }
203
+
204
+ const servers = connection
205
+ .listSessions()
206
+ .filter((info) => info.context === 'server');
207
+ if (servers.length === 0) {
208
+ throw new Error(await explainNoPlaytestAsync(connection));
209
+ }
210
+ if (servers.length > 1) {
211
+ const list = servers
212
+ .map((info) => ` - ${info.sessionId}: ${info.placeName}`)
213
+ .join('\n');
214
+ throw new Error(
215
+ `Multiple playtests are running. Use --target <id> to pick a server session.\n${list}`
216
+ );
217
+ }
218
+
219
+ const session = connection.getSession(servers[0].sessionId);
220
+ if (!session) {
221
+ throw new Error(`Session '${servers[0].sessionId}' disconnected`);
222
+ }
223
+ return session;
224
+ }
225
+
226
+ /**
227
+ * Explains a missing `server` or `client` session: no playtest is running, a
228
+ * run-mode playtest has no client, or the playtest's sessions can't connect.
229
+ * Other errors pass through unchanged.
230
+ */
231
+ export async function explainMissingPlaytestContextAsync(
232
+ connection: BridgeConnection,
233
+ err: unknown
234
+ ): Promise<unknown> {
235
+ if (
236
+ !(err instanceof ContextNotFoundError) ||
237
+ (err.context !== 'server' && err.context !== 'client')
238
+ ) {
239
+ return err;
240
+ }
241
+
242
+ const placeName = connection
243
+ .listSessions()
244
+ .find((info) => info.instanceId === err.instanceId)?.placeName;
245
+ const studio = placeName ? `'${placeName}'` : `instance '${err.instanceId}'`;
246
+
247
+ if (err.context === 'client' && err.availableContexts.includes('server')) {
248
+ return new Error(
249
+ `The playtest of ${studio} runs in run mode, which has no client. Restart it in play mode with 'studio-bridge playtest restart --mode play'.`
250
+ );
251
+ }
252
+ return new Error(
253
+ await explainNoPlaytestAsync(connection, studio, err.context)
254
+ );
255
+ }
256
+
257
+ /**
258
+ * Whether this machine's Studio has "Load user plugins on playtest" off. A
259
+ * remote Studio's settings aren't on this machine, so they count as unknown.
260
+ */
261
+ async function isPlaytestPluginsOffAsync(
262
+ connection: BridgeConnection
263
+ ): Promise<boolean> {
264
+ return (
265
+ !connection.isRemote &&
266
+ (await tryReadLoadUserPluginsOnPlaytestAsync()) === false
267
+ );
268
+ }
269
+
270
+ async function explainNoPlaytestAsync(
271
+ connection: BridgeConnection,
272
+ studio?: string,
273
+ context?: SessionContext
274
+ ): Promise<string> {
275
+ const of = studio ? ` of ${studio}` : '';
276
+ if (await isPlaytestPluginsOffAsync(connection)) {
277
+ return `No playtest${of} is connected. ${PLAYTEST_PLUGINS_OFF_MESSAGE}`;
278
+ }
279
+ const missing = context ? `, so there is no ${context} session` : '';
280
+ return `No playtest${of} is running${missing}. Start one with 'studio-bridge playtest start'.`;
281
+ }
282
+
283
+ /**
284
+ * End the playtest `serverSession` belongs to, resolving once the session has
285
+ * closed with what the playtest reported about itself.
286
+ */
287
+ export async function endPlaytestAsync(
288
+ connection: BridgeConnection,
289
+ serverSession: BridgeSession
290
+ ): Promise<EndPlaytestResult> {
291
+ const ended = await serverSession.endPlaytestAsync();
292
+ await waitForSessionCloseAsync(connection, serverSession.info.sessionId);
293
+ return ended;
294
+ }
295
+
296
+ interface PlaytestSessions {
297
+ server?: BridgeSession;
298
+ clients: BridgeSession[];
299
+ }
300
+
301
+ async function waitForPlaytestSessionsAsync(
302
+ connection: BridgeConnection,
303
+ playtestId: string,
304
+ clientCount: number,
305
+ timeoutMs: number,
306
+ signal: AbortSignal
307
+ ): Promise<PlaytestSessions> {
308
+ return new Promise((resolve) => {
309
+ const found: PlaytestSessions = { clients: [] };
310
+ const checked = new Set<string>();
311
+
312
+ const cleanup = () => {
313
+ clearTimeout(timer);
314
+ connection.off('session-connected', onSession);
315
+ signal.removeEventListener('abort', cleanup);
316
+ };
317
+
318
+ const onSession = (session: BridgeSession) => {
319
+ const { sessionId } = session.info;
320
+ if (session.context === 'edit' || checked.has(sessionId)) {
321
+ return;
322
+ }
323
+ checked.add(sessionId);
324
+
325
+ void readPlaytestIdAsync(connection, sessionId, signal).then((read) => {
326
+ if (signal.aborted || !read) return;
327
+ if (read.id !== playtestId) {
328
+ // The last playtest's session can linger under the same id; read its replacement too
329
+ checked.delete(sessionId);
330
+ return;
331
+ }
332
+
333
+ if (read.session.context === 'server') {
334
+ found.server = read.session;
335
+ } else {
336
+ found.clients.push(read.session);
337
+ }
338
+ if (found.server && found.clients.length >= clientCount) {
339
+ cleanup();
340
+ resolve(found);
341
+ }
342
+ });
343
+ };
344
+
345
+ // Resolves with whichever sessions connected; the caller reports the rest
346
+ const timer = setTimeout(() => {
347
+ cleanup();
348
+ resolve(found);
349
+ }, timeoutMs);
350
+
351
+ signal.addEventListener('abort', cleanup);
352
+ connection.on('session-connected', onSession);
353
+
354
+ for (const info of connection.listSessions()) {
355
+ const session = connection.getSession(info.sessionId);
356
+ if (session) onSession(session);
357
+ }
358
+ });
359
+ }
360
+
361
+ /**
362
+ * Reads the playtest id a session was started under, retrying while the read
363
+ * fails: a playtest's DataModels are slow to answer while their Studio boots,
364
+ * and a session that reconnects is read again under the same id.
365
+ */
366
+ async function readPlaytestIdAsync(
367
+ connection: BridgeConnection,
368
+ sessionId: string,
369
+ signal: AbortSignal
370
+ ): Promise<{ session: BridgeSession; id: string | undefined } | undefined> {
371
+ while (!signal.aborted) {
372
+ const session = connection.getSession(sessionId);
373
+ if (session) {
374
+ try {
375
+ const result = await session.execAsync(READ_PLAYTEST_ID_SCRIPT, 5_000);
376
+ if (result.success) {
377
+ const value = result.returnValues?.[0];
378
+ return { session, id: typeof value === 'string' ? value : undefined };
379
+ }
380
+ } catch {
381
+ // Retried below
382
+ }
383
+ }
384
+ await new Promise((resolve) => setTimeout(resolve, READ_RETRY_DELAY_MS));
385
+ }
386
+ return undefined;
387
+ }
388
+
389
+ async function waitForSessionCloseAsync(
390
+ connection: BridgeConnection,
391
+ sessionId: string
392
+ ): Promise<void> {
393
+ return new Promise((resolve, reject) => {
394
+ if (!connection.getSession(sessionId)) {
395
+ resolve();
396
+ return;
397
+ }
398
+
399
+ const onDisconnected = (disconnectedId: string) => {
400
+ if (disconnectedId !== sessionId) return;
401
+ clearTimeout(timer);
402
+ connection.off('session-disconnected', onDisconnected);
403
+ resolve();
404
+ };
405
+
406
+ const timer = setTimeout(() => {
407
+ connection.off('session-disconnected', onDisconnected);
408
+ reject(
409
+ new Error(
410
+ `Playtest did not close within ${SESSION_CLOSE_TIMEOUT_MS / 1000}s`
411
+ )
412
+ );
413
+ }, SESSION_CLOSE_TIMEOUT_MS);
414
+
415
+ connection.on('session-disconnected', onDisconnected);
416
+ });
417
+ }
@@ -0,0 +1,199 @@
1
+ /**
2
+ * `playtest restart` — stop the running playtest and start a new one in the
3
+ * same mode, so it picks up code changed since it started. Starts one when
4
+ * none is running.
5
+ */
6
+
7
+ import { defineCommand } from '../../framework/define-command.js';
8
+ import { arg } from '../../framework/arg-builder.js';
9
+ import type {
10
+ BridgeConnection,
11
+ BridgeSession,
12
+ PlaytestMode,
13
+ } from '../../../bridge/index.js';
14
+ import {
15
+ endPlaytestAsync,
16
+ startPlaytestAsync,
17
+ type StartedPlaytest,
18
+ } from '../playtest-sessions.js';
19
+ import { listPlaytestPlayersAsync } from '../player/playtest-players.js';
20
+ import {
21
+ describePlaytest,
22
+ formatPlaytestStartText,
23
+ toPlaytestStartResult,
24
+ type PlaytestStartResult,
25
+ } from '../start/start.js';
26
+
27
+ /** Studio refuses a new playtest until the old one has fully wound down. */
28
+ const START_ATTEMPTS = 5;
29
+ const START_RETRY_DELAY_MS = 1_000;
30
+
31
+ export interface PlaytestRestartOptions {
32
+ /** Defaults to the running playtest's mode, or play when none is running. */
33
+ mode?: PlaytestMode;
34
+ /** Defaults to the running multiplayer test's player count. */
35
+ players?: number;
36
+ /** Picks the Studio: any of its sessions. */
37
+ target?: string;
38
+ }
39
+
40
+ interface PlaytestRestartArgs {
41
+ mode?: PlaytestMode;
42
+ players?: number;
43
+ }
44
+
45
+ export async function playtestRestartHandlerAsync(
46
+ connection: BridgeConnection,
47
+ options: PlaytestRestartOptions = {}
48
+ ): Promise<PlaytestStartResult> {
49
+ const targetSession = options.target
50
+ ? await connection.resolveSessionAsync(options.target)
51
+ : undefined;
52
+ const serverSession = findServerSession(
53
+ connection,
54
+ targetSession?.info.instanceId
55
+ );
56
+
57
+ if (!serverSession) {
58
+ const editSession =
59
+ targetSession?.context === 'edit'
60
+ ? targetSession
61
+ : await connection.resolveSessionAsync(
62
+ undefined,
63
+ 'edit',
64
+ targetSession?.info.instanceId
65
+ );
66
+ const playtest = await startWithRetryAsync(
67
+ connection,
68
+ editSession,
69
+ options.mode ?? 'play',
70
+ options.players
71
+ );
72
+ return toPlaytestStartResult(
73
+ playtest,
74
+ `No playtest was running; started one (${describePlaytest(playtest)})`
75
+ );
76
+ }
77
+
78
+ const editSession = await connection.resolveSessionAsync(
79
+ undefined,
80
+ 'edit',
81
+ serverSession.info.instanceId
82
+ );
83
+
84
+ // Players added since the start count too, so read who is in the test now
85
+ const livePlayers =
86
+ options.players === undefined && options.mode === undefined
87
+ ? await tryCountMultiplayerPlayersAsync(connection, serverSession)
88
+ : undefined;
89
+ const ended = await endPlaytestAsync(connection, serverSession);
90
+ const mode = options.mode ?? ended.mode ?? 'play';
91
+ const players =
92
+ options.players ??
93
+ (options.mode === undefined && ended.players !== undefined
94
+ ? livePlayers || ended.players
95
+ : undefined);
96
+ const playtest = await startWithRetryAsync(
97
+ connection,
98
+ editSession,
99
+ mode,
100
+ players
101
+ );
102
+ return toPlaytestStartResult(
103
+ playtest,
104
+ `Playtest restarted (${describePlaytest(playtest)})`
105
+ );
106
+ }
107
+
108
+ /** How many players the running test has, or undefined when it can't tell. */
109
+ async function tryCountMultiplayerPlayersAsync(
110
+ connection: BridgeConnection,
111
+ serverSession: BridgeSession
112
+ ): Promise<number | undefined> {
113
+ try {
114
+ return (await listPlaytestPlayersAsync(connection, serverSession)).length;
115
+ } catch {
116
+ return undefined;
117
+ }
118
+ }
119
+
120
+ /** The running playtest's server session, within `instanceId` when given. */
121
+ function findServerSession(
122
+ connection: BridgeConnection,
123
+ instanceId: string | undefined
124
+ ): BridgeSession | undefined {
125
+ const servers = connection
126
+ .listSessions()
127
+ .filter(
128
+ (info) =>
129
+ info.context === 'server' &&
130
+ (instanceId === undefined || info.instanceId === instanceId)
131
+ );
132
+
133
+ if (servers.length > 1) {
134
+ const list = servers
135
+ .map((info) => ` - ${info.sessionId}: ${info.placeName}`)
136
+ .join('\n');
137
+ throw new Error(
138
+ `Multiple playtests are running. Use --target <id> to pick one.\n${list}`
139
+ );
140
+ }
141
+
142
+ return servers.length === 1
143
+ ? connection.getSession(servers[0].sessionId)
144
+ : undefined;
145
+ }
146
+
147
+ async function startWithRetryAsync(
148
+ connection: BridgeConnection,
149
+ editSession: BridgeSession,
150
+ mode: PlaytestMode,
151
+ players: number | undefined
152
+ ): Promise<StartedPlaytest> {
153
+ for (let attempt = 1; ; attempt++) {
154
+ try {
155
+ return await startPlaytestAsync(connection, editSession, mode, players);
156
+ } catch (err) {
157
+ const stillWindingDown =
158
+ err instanceof Error && err.message.includes('still in progress');
159
+ if (!stillWindingDown || attempt >= START_ATTEMPTS) {
160
+ throw err;
161
+ }
162
+ await new Promise((resolve) => setTimeout(resolve, START_RETRY_DELAY_MS));
163
+ }
164
+ }
165
+ }
166
+
167
+ export const playtestRestartCommand = defineCommand<
168
+ PlaytestRestartArgs,
169
+ PlaytestStartResult
170
+ >({
171
+ group: 'playtest',
172
+ name: 'restart',
173
+ description:
174
+ 'Stop the running playtest and start a new one in the same mode, to pick up code changes',
175
+ category: 'execution',
176
+ safety: 'mutate',
177
+ scope: 'connection',
178
+ args: {
179
+ mode: arg.option({
180
+ description:
181
+ "Start in this mode instead of the running playtest's (play or run)",
182
+ choices: ['play', 'run'],
183
+ }),
184
+ players: arg.option({
185
+ description:
186
+ "Start a multiplayer test with this many players instead of the running playtest's",
187
+ type: 'number',
188
+ }),
189
+ },
190
+ cli: {
191
+ format: formatPlaytestStartText,
192
+ },
193
+ handler: async (connection, args, targeting) =>
194
+ playtestRestartHandlerAsync(connection, {
195
+ mode: args.mode,
196
+ players: args.players,
197
+ target: targeting.target,
198
+ }),
199
+ });