@quenty/studio-bridge 0.21.1 → 0.22.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (482) hide show
  1. package/CHANGELOG.md +10 -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
@@ -1,439 +1,337 @@
1
+ --!strict
1
2
  --[[
2
- StudioBridge unified plugin entry point — Layer 2 Roblox glue.
3
+ Entry point for the studio-bridge plugin. Wires the modules under Shared/ to
4
+ Roblox services and boots in one of two modes:
3
5
 
4
- Supports two boot modes:
5
- - Ephemeral: build constants substituted by the CLI, connects directly.
6
- - Persistent: template strings intact, uses DiscoveryStateMachine to
7
- scan ports and auto-connect to a running studio-bridge server.
6
+ - Ephemeral: the CLI substituted the build constants below, so it connects
7
+ straight to that port and session.
8
+ - Persistent: the constants are left empty, so it scans for a
9
+ running bridge with DiscoveryStateMachine and reconnects whenever it drops.
8
10
 
9
- All protocol logic, state machine logic, action routing, and message
10
- buffering live in Layer 1 modules under Shared/. This file is thin
11
- glue that wires those modules to Roblox services.
11
+ The plugin cannot use the Nevermore loader: rojo builds this template on its
12
+ own from the published CLI package, without any node_modules.
12
13
  ]]
13
14
 
14
15
  local HttpService = game:GetService("HttpService")
15
16
  local LogService = game:GetService("LogService")
17
+ local Players = game:GetService("Players")
16
18
  local RunService = game:GetService("RunService")
17
19
 
18
- -- Layer 1 modules (pure logic, no Roblox deps)
19
20
  local ActionRouter = require(script.Parent.Shared.ActionRouter)
21
+ local BuiltInActions = require(script.Parent.Shared.BuiltInActions)
20
22
  local DiscoveryStateMachine = require(script.Parent.Shared.DiscoveryStateMachine)
21
23
  local MessageBuffer = require(script.Parent.Shared.MessageBuffer)
24
+ local StudioBridgeConnection = require(script.Parent.Shared.StudioBridgeConnection)
25
+ local StudioBridgeLogUtils = require(script.Parent.Shared.StudioBridgeLogUtils)
26
+ local StudioBridgeSessionUtils = require(script.Parent.Shared.StudioBridgeSessionUtils)
22
27
 
23
- -- Actions are pushed dynamically over the wire via registerAction.
24
- -- No static action requires needed.
25
-
26
- -- Build constants (Handlebars templates substituted at build time)
27
28
  local PORT = "{{PORT}}"
28
29
  local SESSION_ID = "{{SESSION_ID}}"
29
- local IS_EPHEMERAL = ("{{EPHEMERAL}}" == "true")
30
+ local IS_EPHEMERAL = ("{{EPHEMERAL}}" :: string) == "true"
30
31
 
31
- -- Only run inside Studio edit context. Plugin instances spawned by play
32
- -- mode (client/server) cannot make HTTP requests, so they cannot discover
33
- -- the bridge. The edit-context instance stays alive during play mode.
34
- if not RunService:IsStudio() or RunService:IsRunning() then
35
- return
36
- end
32
+ local LOG_BUFFER_CAPACITY = 1000
33
+ local POLL_INTERVAL_SEC = 2
37
34
 
38
- -- ---------------------------------------------------------------------------
39
- -- Context detection
40
- -- ---------------------------------------------------------------------------
35
+ local LOG_LEVELS: { [Enum.MessageType]: string } = {
36
+ [Enum.MessageType.MessageOutput] = "Print",
37
+ [Enum.MessageType.MessageInfo] = "Info",
38
+ [Enum.MessageType.MessageWarning] = "Warning",
39
+ [Enum.MessageType.MessageError] = "Error",
40
+ }
41
41
 
42
- local function detectContext()
43
- if RunService:IsRunning() then
44
- if RunService:IsClient() then
45
- return "client"
46
- else
47
- return "server"
48
- end
49
- end
50
- return "edit"
42
+ -- Playtests load a plugin instance into their server and client, but only when
43
+ -- Studio's "load user plugins on playtest" setting is on. Those instances cannot
44
+ -- make the HTTP requests port discovery needs, so they only connect in a
45
+ -- playtest studio-bridge started, which hands them the port and the instance id
46
+ -- of the edit session that started it.
47
+ local playtestArgs: StudioBridgeSessionUtils.PlaytestArgs? = nil
48
+ if RunService:IsRunning() then
49
+ local ok, testArgs = pcall(function()
50
+ return (game:GetService("StudioTestService") :: any):GetTestArgs()
51
+ end)
52
+ playtestArgs = if ok then StudioBridgeSessionUtils.getPlaytestArgs(testArgs) else nil
53
+ end
54
+ if not RunService:IsStudio() or (RunService:IsRunning() and not playtestArgs) then
55
+ return
51
56
  end
52
57
 
53
- -- ---------------------------------------------------------------------------
54
- -- Instance / session ID helpers
55
- -- ---------------------------------------------------------------------------
58
+ local function createNonce(): string
59
+ return string.lower(string.sub(HttpService:GenerateGUID(false), 1, 4))
60
+ end
56
61
 
57
- -- Short unique nonce for disambiguating unpublished places with the same name.
58
- -- Generated once per plugin lifecycle so the session ID stays stable across reconnects.
59
- local _nonce: string = string.sub(HttpService:GenerateGUID(false), 1, 8)
62
+ local context: StudioBridgeSessionUtils.PluginContext = StudioBridgeSessionUtils.getContext()
63
+ local nonceOk, nonce = pcall(StudioBridgeSessionUtils.getOrCreateNonce, game:GetService("CoreGui"), createNonce)
64
+ if not nonceOk then
65
+ nonce = createNonce()
66
+ end
60
67
 
61
- local function getInstanceId()
62
- if game.GameId ~= 0 or game.PlaceId ~= 0 then
63
- return `{game.GameId}-{game.PlaceId}`
68
+ -- Read on every connect: Studio can load plugins before it names the DataModel after the place
69
+ local function getInstanceId(): string
70
+ if playtestArgs then
71
+ return playtestArgs.instanceId
64
72
  end
65
- -- Unpublished place: use sanitized place name + nonce for uniqueness
66
- local name = string.lower(game.Name or "untitled")
67
- name = string.gsub(name, "%s+", "-")
68
- name = string.gsub(name, "[^%w%-]", "")
69
- if name == "" then
70
- name = "untitled"
71
- end
72
- return `local-{name}-{_nonce}`
73
+ return StudioBridgeSessionUtils.getInstanceId(game.Name, nonce)
73
74
  end
75
+ local playtestPort = if playtestArgs then playtestArgs.port else nil
74
76
 
75
- local function getSessionId()
76
- return `{getInstanceId()}-{detectContext()}`
77
+ local function getPlayerName(): string?
78
+ local player = if context == "client" then Players.LocalPlayer else nil
79
+ return if player then player.Name else nil
77
80
  end
78
81
 
79
- -- ---------------------------------------------------------------------------
80
- -- JSON helpers (HttpService wrappers for Roblox environment)
81
- -- ---------------------------------------------------------------------------
82
+ -- The clients of a multiplayer test share an instance id, so their session ids name their player
83
+ local function getSessionId(): string
84
+ local isMultiplayerClient = playtestArgs and playtestArgs.multiplayer and context == "client"
85
+ return StudioBridgeSessionUtils.getSessionId(
86
+ getInstanceId(),
87
+ context,
88
+ if isMultiplayerClient then getPlayerName() else nil
89
+ )
90
+ end
82
91
 
83
- local function jsonEncode(tbl)
84
- return HttpService:JSONEncode(tbl)
92
+ local router = ActionRouter.new()
93
+ local logBuffer = MessageBuffer.new(LOG_BUFFER_CAPACITY)
94
+ local currentConnection: StudioBridgeConnection.StudioBridgeConnection? = nil
95
+ local currentWebSocket: any = nil
96
+ local discovery: DiscoveryStateMachine.DiscoveryStateMachine? = nil
97
+ local pollThread: thread? = nil
98
+ local nameConnection: RBXScriptConnection? = nil
99
+ local isUnloaded = false
100
+
101
+ local pngOk, png = pcall(function()
102
+ return (require :: any)(script.Parent.Vendor.png)
103
+ end)
104
+ if pngOk then
105
+ router:SetVendorPng(png)
85
106
  end
86
107
 
87
- local function jsonDecode(raw)
88
- local ok, result = pcall(HttpService.JSONDecode, HttpService, raw)
89
- if ok then
90
- return result
108
+ BuiltInActions.register(router, function(message)
109
+ if currentConnection then
110
+ currentConnection:Send(message)
91
111
  end
92
- return nil
93
- end
112
+ end, logBuffer)
94
113
 
95
- -- ---------------------------------------------------------------------------
96
- -- Shared state
97
- -- ---------------------------------------------------------------------------
114
+ -- Hybrid clock: the wall clock at load, then os.clock() deltas, which are cheaper per entry
115
+ local clockBase = os.clock()
116
+ local timeBase = DateTime.now().UnixTimestampMillis / 1000
98
117
 
99
- local router = ActionRouter.new()
100
- local logBuffer = MessageBuffer.new(1000)
118
+ local logConnection = LogService.MessageOut:Connect(function(message, messageType)
119
+ if string.sub(message, 1, #StudioBridgeLogUtils.PREFIX) == StudioBridgeLogUtils.PREFIX then
120
+ return
121
+ end
101
122
 
102
- -- Pre-load the PNG encoder and expose it to dynamically loaded actions
103
- -- via router._vendorPng. The screenshot action reads this in its register().
104
- local _pngOk, _pngModule = pcall(function()
105
- return require(script.Parent.Vendor.png)
123
+ logBuffer:Push({
124
+ level = LOG_LEVELS[messageType] or "Print",
125
+ body = message,
126
+ timestamp = timeBase + (os.clock() - clockBase),
127
+ })
106
128
  end)
107
- if _pngOk and _pngModule then
108
- router._vendorPng = _pngModule
109
- end
110
129
 
111
- -- Wire outgoing messages (set after WebSocket is connected)
112
- local sendMessageFn = nil
130
+ local function openConnection(webSocket: any, sessionId: string, label: string)
131
+ if isUnloaded then
132
+ return
133
+ end
113
134
 
114
- -- Callback that forwards messages through the active WebSocket
115
- local function sendMessage(msg)
116
- if sendMessageFn then
117
- sendMessageFn(msg)
135
+ if currentConnection then
136
+ currentConnection:Destroy()
118
137
  end
138
+
139
+ currentConnection = StudioBridgeConnection.new({
140
+ webSocket = webSocket,
141
+ router = router,
142
+ sessionId = sessionId,
143
+ registerPayload = StudioBridgeSessionUtils.createRegisterPayload(
144
+ getInstanceId(),
145
+ context,
146
+ StudioBridgeSessionUtils.getLaunchId(workspace),
147
+ getPlayerName()
148
+ ),
149
+ })
150
+
151
+ StudioBridgeLogUtils.logVerbose(`Connected to {label} as {sessionId}`)
119
152
  end
120
153
 
121
- -- Register the built-in registerAction handler. This allows the bridge
122
- -- server to push Luau action modules over the wire after a plugin connects.
123
- router:register("registerAction", function(payload, requestId, sessionId)
124
- local name = payload.name
125
- local source = payload.source
126
- local responseType = payload.responseType
127
- local hash = payload.hash -- optional content hash for skip-on-same-hash
128
-
129
- if type(name) ~= "string" or type(source) ~= "string" then
130
- return {
131
- name = name or "unknown",
132
- success = false,
133
- error = "Invalid registerAction payload: name and source are required strings",
134
- }
154
+ local function createWebSocket(url: string): (boolean, any)
155
+ local ok, webSocket = pcall(function()
156
+ return HttpService:CreateWebStreamClient(Enum.WebStreamClientType.WebSocket, { Url = url })
157
+ end)
158
+ if ok then
159
+ currentWebSocket = webSocket
135
160
  end
161
+ return ok, webSocket
162
+ end
136
163
 
137
- -- Check for hash-based skip before full registration
138
- if type(hash) == "string" and router._actions[name] and router._actions[name].hash == hash then
139
- return {
140
- name = name,
141
- success = true,
142
- skipped = true,
143
- hash = hash,
144
- handlers = router._actions[name].handlerNames,
145
- }
164
+ -- Reloading the plugin destroys this script, but the socket callbacks and the
165
+ -- poll loop outlive it, leaving the old instance connected beside the new one
166
+ plugin.Unloading:Connect(function()
167
+ isUnloaded = true
168
+ logConnection:Disconnect()
169
+ if nameConnection then
170
+ nameConnection:Disconnect()
171
+ nameConnection = nil
146
172
  end
147
173
 
148
- local success, err = router:registerAction(name, source, sendMessage, logBuffer, responseType, hash)
149
-
150
- -- Build response with handler list from _actions if available
151
- local handlers = {}
152
- if success and router._actions[name] then
153
- handlers = router._actions[name].handlerNames
174
+ if pollThread then
175
+ task.cancel(pollThread)
176
+ pollThread = nil
177
+ end
178
+ if discovery then
179
+ discovery:Stop()
180
+ end
181
+ if currentConnection then
182
+ currentConnection:Destroy()
183
+ currentConnection = nil
184
+ end
185
+ if currentWebSocket then
186
+ pcall(function()
187
+ currentWebSocket:Close()
188
+ end)
189
+ currentWebSocket = nil
154
190
  end
155
191
 
156
- return {
157
- name = name,
158
- success = success,
159
- skipped = false,
160
- hash = hash,
161
- handlers = handlers,
162
- error = err,
163
- }
192
+ router:Destroy()
164
193
  end)
165
- router:setResponseType("registerAction", "registerActionResult")
166
-
167
- -- Register the built-in syncActions handler. This allows the bridge server
168
- -- to query which actions need updating by comparing content hashes.
169
- router:register("syncActions", function(payload, requestId, sessionId)
170
- local clientActions = payload.actions or {}
171
- local installed = router:getInstalledActions()
172
- local needed = {}
173
-
174
- for name, clientHash in clientActions do
175
- if installed[name] ~= clientHash then
176
- table.insert(needed, name)
194
+
195
+ local function warnOnError(webSocket: any)
196
+ webSocket.Error:Connect(function(status, err)
197
+ StudioBridgeLogUtils.logVerbose(`WebSocket error ({status}): {err}`)
198
+ end)
199
+ end
200
+
201
+ --[[
202
+ Probes every port's /health endpoint in parallel and returns the first port
203
+ to answer, or nil once all fail or the timeout passes. The caller is resumed
204
+ with task.defer so it has reached coroutine.yield() first.
205
+ ]]
206
+ local function scanPortsAsync(ports: { number }, timeoutSec: number): (number?, string?)
207
+ local callerThread = coroutine.running()
208
+ local foundPort: number? = nil
209
+ local foundBody: string? = nil
210
+ local remaining = #ports
211
+ local settled = false
212
+
213
+ local function settle()
214
+ if not settled then
215
+ settled = true
216
+ task.defer(callerThread)
177
217
  end
178
218
  end
179
219
 
180
- return { needed = needed, installed = installed }
181
- end)
182
- router:setResponseType("syncActions", "syncActionsResult")
183
-
184
- local connected = false
220
+ local threads: { thread } = {}
221
+ for _, port in ports do
222
+ table.insert(
223
+ threads,
224
+ task.spawn(function()
225
+ local ok, body = pcall(HttpService.GetAsync, HttpService, `http://localhost:{port}/health`)
226
+ if ok and not settled then
227
+ foundPort = port
228
+ foundBody = body
229
+ settle()
230
+ return
231
+ end
185
232
 
186
- local LEVEL_MAP = {
187
- [Enum.MessageType.MessageOutput] = "Print",
188
- [Enum.MessageType.MessageInfo] = "Info",
189
- [Enum.MessageType.MessageWarning] = "Warning",
190
- [Enum.MessageType.MessageError] = "Error",
191
- }
233
+ remaining -= 1
234
+ if remaining <= 0 then
235
+ settle()
236
+ end
237
+ end)
238
+ )
239
+ end
240
+ table.insert(threads, task.delay(timeoutSec, settle))
192
241
 
193
- -- Hybrid clock: os.time() for absolute wall-clock, os.clock() deltas for sub-second precision
194
- local clockBase = os.clock()
195
- local timeBase = os.time()
242
+ coroutine.yield()
196
243
 
197
- -- Capture logs from the moment the plugin loads, regardless of WebSocket state
198
- LogService.MessageOut:Connect(function(message, messageType)
199
- if string.sub(message, 1, 14) == "[StudioBridge]" then
200
- return
244
+ for _, thread in threads do
245
+ pcall(task.cancel, thread)
201
246
  end
202
- logBuffer:push({
203
- level = LEVEL_MAP[messageType] or "Print",
204
- body = message,
205
- timestamp = timeBase + (os.clock() - clockBase),
206
- })
207
- end)
208
247
 
209
- -- ---------------------------------------------------------------------------
210
- -- Wire a WebSocket connection
211
- -- ---------------------------------------------------------------------------
248
+ return foundPort, foundBody
249
+ end
212
250
 
213
- local function wireConnection(ws, sessionId, connectLabel)
214
- connected = true
251
+ if IS_EPHEMERAL then
252
+ -- Every open Studio loads this plugin, but only the one launched for this run
253
+ -- has the session id stamped on its place, which may load after the plugin
254
+ local function connectEphemeral()
255
+ if StudioBridgeSessionUtils.getLaunchId(workspace) ~= SESSION_ID or currentWebSocket then
256
+ return
257
+ end
215
258
 
216
- -- Wire the sendMessage callback for action handlers
217
- sendMessageFn = function(msg)
218
- pcall(function()
219
- ws:Send(jsonEncode(msg))
259
+ local ok, webSocket = createWebSocket(`ws://localhost:{PORT}/{SESSION_ID}`)
260
+ if not (ok and webSocket) then
261
+ StudioBridgeLogUtils.warn("Failed to create WebSocket client")
262
+ return
263
+ end
264
+
265
+ webSocket.Opened:Connect(function()
266
+ openConnection(webSocket, SESSION_ID, `localhost:{PORT} (ephemeral)`)
220
267
  end)
268
+ warnOnError(webSocket)
221
269
  end
222
270
 
223
- -- Send register message. The plugin only runs in edit context (the
224
- -- early-return at top of file rejects play-mode instances), so state
225
- -- is always "Edit". Capabilities include the dynamically-dispatchable
226
- -- actions — the bridge pushes their source via registerAction after
227
- -- this register completes, but the plugin is capable of handling any
228
- -- of them.
229
- ws:Send(jsonEncode({
230
- type = "register",
231
- sessionId = sessionId,
232
- payload = {
233
- pluginVersion = "0.7.0",
234
- instanceId = getInstanceId(),
235
- context = detectContext(),
236
- placeName = game.Name or "Unknown",
237
- placeId = game.PlaceId,
238
- gameId = game.GameId,
239
- state = "Edit",
240
- capabilities = {
241
- "registerAction",
242
- "syncActions",
243
- "heartbeat",
244
- "execute",
245
- "queryState",
246
- "captureScreenshot",
247
- "queryDataModel",
248
- "queryLogs",
249
- },
250
- },
251
- }))
252
-
253
- -- Incoming messages -> ActionRouter dispatch
254
- ws.MessageReceived:Connect(function(rawData)
255
- local ok, err = pcall(function()
256
- local msg = jsonDecode(rawData)
257
- if not msg or type(msg.type) ~= "string" then
258
- print(`[StudioBridge] Failed to decode message: {tostring(rawData):sub(1, 200)}`)
259
- return
260
- end
261
-
262
- print(`[StudioBridge] Received: {msg.type} (requestId={tostring(msg.requestId):sub(1, 8)}...)`)
263
-
264
- if msg.type == "shutdown" then
265
- connected = false
266
- pcall(function()
267
- ws:Close()
268
- end)
269
- return
270
- end
271
-
272
- local response = router:dispatch(msg)
273
- if response then
274
- local encoded = jsonEncode(response)
275
- print(`[StudioBridge] Sending: {response.type} ({#encoded} bytes)`)
276
- ws:Send(encoded)
277
- else
278
- print(`[StudioBridge] No response for: {msg.type}`)
279
- end
280
- end)
281
- if not ok then
282
- warn(`[StudioBridge] MessageReceived handler error: {tostring(err)}`)
283
- end
284
- end)
271
+ workspace:GetAttributeChangedSignal("StudioBridgeSessionId"):Connect(connectEphemeral)
272
+ connectEphemeral()
273
+ return
274
+ end
285
275
 
286
- ws.Closed:Connect(function()
287
- connected = false
288
- end)
276
+ -- A multiplayer client's session id names its player, so wait for one
277
+ if playtestArgs and playtestArgs.multiplayer and context == "client" then
278
+ while not Players.LocalPlayer do
279
+ Players:GetPropertyChangedSignal("LocalPlayer"):Wait()
280
+ end
281
+ end
289
282
 
290
- -- Heartbeat coroutine
291
- local heartbeatStart = os.clock()
292
- task.spawn(function()
293
- while connected do
294
- pcall(function()
295
- ws:Send(jsonEncode({
296
- type = "heartbeat",
297
- sessionId = sessionId,
298
- payload = {
299
- uptimeMs = math.floor((os.clock() - heartbeatStart) * 1000),
300
- state = detectContext() == "edit" and "Edit" or detectContext(),
301
- pendingRequests = 0,
302
- },
303
- }))
304
- end)
305
- task.wait(15)
283
+ local stateMachine: DiscoveryStateMachine.DiscoveryStateMachine
284
+ stateMachine = DiscoveryStateMachine.new(nil, {
285
+ scanPortsAsync = if playtestPort
286
+ then function()
287
+ return playtestPort, nil
306
288
  end
307
- end)
289
+ else scanPortsAsync,
290
+ connectWebSocket = createWebSocket,
291
+ onStateChange = function(oldState, newState)
292
+ if oldState == "idle" and newState == "searching" then
293
+ StudioBridgeLogUtils.logVerbose("Searching for host on ports 38741-38744...")
294
+ end
295
+ end,
296
+ onConnected = function(webSocket, port)
297
+ webSocket.Opened:Connect(function()
298
+ openConnection(webSocket, getSessionId(), `localhost:{port}`)
299
+ end)
300
+ webSocket.Closed:Connect(function()
301
+ stateMachine:HandleDisconnect("closed")
302
+ end)
303
+ warnOnError(webSocket)
304
+ end,
305
+ onDisconnected = function(reason)
306
+ StudioBridgeLogUtils.logVerbose(`Disconnected ({reason}), searching...`)
307
+ end,
308
+ })
308
309
 
309
- print(`[StudioBridge] Connected to {connectLabel} as {sessionId}`)
310
- end
310
+ discovery = stateMachine
311
311
 
312
- -- ---------------------------------------------------------------------------
313
- -- Boot
314
- -- ---------------------------------------------------------------------------
312
+ stateMachine:Start()
315
313
 
316
- if IS_EPHEMERAL then
317
- -- Ephemeral mode: CLI substituted PORT and SESSION_ID, connect directly
318
- local wsUrl = `ws://localhost:{PORT}/{SESSION_ID}`
319
- local ok, ws = pcall(function()
320
- return HttpService:CreateWebStreamClient(Enum.WebStreamClientType.WebSocket, { Url = wsUrl })
321
- end)
322
- if ok and ws then
323
- ws.Opened:Connect(function()
324
- wireConnection(ws, SESSION_ID, `localhost:{PORT} (ephemeral)`)
325
- end)
326
- ws.Error:Connect(function(status, err)
327
- warn(`[StudioBridge] WebSocket error ({status}): {err}`)
314
+ -- Reconnecting re-registers under the session id for the new name. A
315
+ -- playtest's id comes from its test args, so a rename leaves it unchanged.
316
+ nameConnection = game:GetPropertyChangedSignal("Name"):Connect(function()
317
+ if currentWebSocket and not playtestArgs then
318
+ pcall(function()
319
+ currentWebSocket:Close()
328
320
  end)
329
- else
330
- warn("[StudioBridge] Failed to create WebSocket client")
331
321
  end
332
- else
333
- -- Persistent mode: discover server via port scanning
334
- local POLL_INTERVAL_SEC = 2
335
-
336
- -- Forward-declare so closures inside the callback table can reference it.
337
- local discovery
338
- discovery = DiscoveryStateMachine.new(nil, {
339
- scanPortsAsync = function(ports, timeoutSec)
340
- -- Scan all ports in parallel using task.spawn. The calling thread
341
- -- yields and is resumed as soon as any port succeeds, all fail,
342
- -- or the timeout expires. Use task.defer (not task.spawn) to resume
343
- -- the caller so it has time to reach coroutine.yield() first.
344
- local callerThread = coroutine.running()
345
- local foundPort = nil
346
- local foundBody = nil
347
- local remaining = #ports
348
- local settled = false
349
- local threads = {}
350
-
351
- for _, port in ports do
352
- local thread = task.spawn(function()
353
- local url = `http://localhost:{port}/health`
354
- local ok2, body = pcall(HttpService.GetAsync, HttpService, url)
355
- if ok2 and not settled then
356
- foundPort = port
357
- foundBody = body
358
- settled = true
359
- task.defer(callerThread)
360
- return
361
- end
362
- remaining -= 1
363
- if remaining <= 0 and not settled then
364
- settled = true
365
- task.defer(callerThread)
366
- end
367
- end)
368
- table.insert(threads, thread)
369
- end
370
-
371
- -- Timeout: use the deadline from the poll loop
372
- local timeoutThread = task.delay(timeoutSec, function()
373
- if not settled then
374
- settled = true
375
- task.defer(callerThread)
376
- end
377
- end)
378
-
379
- coroutine.yield()
322
+ end)
380
323
 
381
- -- Cancel remaining HTTP threads and the timeout
382
- pcall(task.cancel, timeoutThread)
383
- for _, thread in threads do
384
- pcall(task.cancel, thread)
385
- end
324
+ -- Each cycle gets a fixed time budget, which PollAsync threads through to the port scan
325
+ pollThread = task.spawn(function()
326
+ while true do
327
+ local startTime = os.clock()
328
+ stateMachine:PollAsync(POLL_INTERVAL_SEC)
386
329
 
387
- return foundPort, foundBody
388
- end,
389
- connectWebSocket = function(url)
390
- local ok2, ws = pcall(function()
391
- return HttpService:CreateWebStreamClient(Enum.WebStreamClientType.WebSocket, { Url = url })
392
- end)
393
- return ok2, ws
394
- end,
395
- onStateChange = function(oldState, newState)
396
- if newState == "searching" and oldState == "idle" then
397
- print("[StudioBridge] Searching for host on ports 38741-38744...")
398
- end
399
- end,
400
- onConnected = function(ws, port)
401
- local sessionId = getSessionId()
402
- ws.Opened:Connect(function()
403
- wireConnection(ws, sessionId, `localhost:{port}`)
404
- end)
405
- ws.Error:Connect(function(status, err)
406
- warn(`[StudioBridge] WebSocket error ({status}): {err}`)
407
- end)
408
- ws.Closed:Connect(function()
409
- connected = false
410
- discovery:onDisconnect("closed")
411
- end)
412
- end,
413
- onDisconnected = function(reason)
414
- connected = false
415
- print(`[StudioBridge] Disconnected ({reason}), searching...`)
416
- end,
417
- })
418
- print(`[StudioBridge] Session ID: {getSessionId()}`)
419
- discovery:start()
420
-
421
- -- Drive the state machine with a simple polling loop.
422
- -- Each iteration has a fixed time budget (POLL_INTERVAL_SEC). The
423
- -- remaining time threads through pollAsync → scanPortsAsync so all
424
- -- async operations complete within the budget.
425
- task.spawn(function()
426
- while true do
427
- local startTime = os.clock()
428
- discovery:pollAsync(POLL_INTERVAL_SEC)
429
- -- Sleep for the remainder of the cycle
430
- local elapsed = os.clock() - startTime
431
- local remaining = POLL_INTERVAL_SEC - elapsed
432
- if remaining > 0 then
433
- task.wait(remaining)
434
- else
435
- task.wait() -- yield at least one frame
436
- end
330
+ local remaining = POLL_INTERVAL_SEC - (os.clock() - startTime)
331
+ if remaining > 0 then
332
+ task.wait(remaining)
333
+ else
334
+ task.wait()
437
335
  end
438
- end)
439
- end
336
+ end
337
+ end)