@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
@@ -9,6 +9,7 @@
9
9
  * - Honors `--output` (file write), `--open`, `--watch`, `--interval`
10
10
  */
11
11
 
12
+ import * as path from 'path';
12
13
  import type { Argv, CommandModule } from 'yargs';
13
14
  import { OutputHelper } from '@quenty/cli-output-helpers';
14
15
  import {
@@ -16,10 +17,20 @@ import {
16
17
  type ResultReporter,
17
18
  formatJson,
18
19
  } from '@quenty/cli-output-helpers/reporting';
19
- import { BridgeConnection } from '../../bridge/index.js';
20
- import type { SessionContext } from '../../bridge/index.js';
21
- import type { CommandDefinition } from '../../commands/framework/define-command.js';
20
+ import { BridgeConnection, SessionNotFoundError } from '../../bridge/index.js';
21
+ import type { BridgeSession, SessionContext } from '../../bridge/index.js';
22
+ import type {
23
+ CommandDefinition,
24
+ SessionTargeting,
25
+ } from '../../commands/framework/define-command.js';
22
26
  import { toYargsOptions } from '../../commands/framework/arg-builder.js';
27
+ import { explainMissingPlaytestContextAsync } from '../../commands/playtest/playtest-sessions.js';
28
+ import { tryFindFolderInstanceAsync } from '../../deploy/deploy-places.js';
29
+ import {
30
+ addWarning,
31
+ getWarnings,
32
+ warnIfPluginStaleAsync,
33
+ } from '../cli-output-context.js';
23
34
 
24
35
  /** Args injected by the adapter (not defined on CommandDefinition). */
25
36
  export interface AdapterArgs {
@@ -51,8 +62,12 @@ function extractCommandArgs(
51
62
  def: CommandDefinition
52
63
  ): Record<string, unknown> {
53
64
  const commandArgs: Record<string, unknown> = {};
54
- for (const name of Object.keys(def.args)) {
55
- if (name in argv) {
65
+ for (const [name, argDef] of Object.entries(def.args)) {
66
+ if (argDef.kind === 'passthrough') {
67
+ commandArgs[name] = ((argv['--'] as unknown[] | undefined) ?? []).map(
68
+ String
69
+ );
70
+ } else if (name in argv) {
56
71
  commandArgs[name] = argv[name];
57
72
  }
58
73
  }
@@ -81,15 +96,33 @@ async function executeCommandAsync(
81
96
  waitForSessions: def.scope === 'connection',
82
97
  }));
83
98
 
99
+ const targeting: SessionTargeting = {
100
+ target: argv.target as string | undefined,
101
+ context: argv.context as SessionContext | undefined,
102
+ };
103
+
104
+ // Connection commands start once Studios have connected, so this can look now
105
+ if (def.scope === 'connection' && !def.coversAllSessions) {
106
+ await tryTargetFolderInstanceAsync(connection, targeting);
107
+ }
108
+
109
+ if (connection.isHostOutdated) {
110
+ addWarning(
111
+ `The bridge host on port ${connection.port} runs an older studio-bridge, which may drop newer options. Restart it (e.g. stop 'studio-bridge serve') to use this version.`
112
+ );
113
+ }
114
+
84
115
  try {
85
116
  if (def.scope === 'session') {
86
- const session = await connection.resolveSessionAsync(
87
- argv.target as string | undefined,
88
- argv.context as SessionContext | undefined
89
- );
90
- return await (def.handler as any)(session, commandArgs);
117
+ const session = await resolveTargetSessionAsync(connection, targeting);
118
+ if (def.changesDataModel && !argv.allowEdit) {
119
+ assertNotEditingDuringPlaytest(connection, session);
120
+ }
121
+ await warnIfPluginStaleAsync([session.info]);
122
+ return await (def.handler as any)(session, commandArgs, connection);
91
123
  }
92
- return await (def.handler as any)(connection, commandArgs);
124
+ await warnIfPluginStaleAsync(connection.listSessions());
125
+ return await (def.handler as any)(connection, commandArgs, targeting);
93
126
  } finally {
94
127
  if (!existingConnection) {
95
128
  await connection.disconnectAsync();
@@ -97,12 +130,85 @@ async function executeCommandAsync(
97
130
  }
98
131
  }
99
132
 
133
+ /**
134
+ * The session `targeting` names. When it names none and several Studios are
135
+ * connected, the one with the current folder's deploy.nevermore.json place open.
136
+ */
137
+ async function resolveTargetSessionAsync(
138
+ connection: BridgeConnection,
139
+ targeting: SessionTargeting
140
+ ): Promise<BridgeSession> {
141
+ try {
142
+ return await connection.resolveSessionAsync(
143
+ targeting.target,
144
+ targeting.context
145
+ );
146
+ } catch (err) {
147
+ if (
148
+ err instanceof SessionNotFoundError &&
149
+ (await tryTargetFolderInstanceAsync(connection, targeting))
150
+ ) {
151
+ return connection
152
+ .resolveSessionAsync(targeting.target, targeting.context)
153
+ .catch(async (retryErr: unknown) => {
154
+ throw await explainMissingPlaytestContextAsync(connection, retryErr);
155
+ });
156
+ }
157
+ throw await explainMissingPlaytestContextAsync(connection, err);
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Without a --target, targets the one connected Studio that has the current
163
+ * folder's deploy.nevermore.json place open, and says so.
164
+ */
165
+ async function tryTargetFolderInstanceAsync(
166
+ connection: BridgeConnection,
167
+ targeting: SessionTargeting
168
+ ): Promise<boolean> {
169
+ if (targeting.target) {
170
+ return false;
171
+ }
172
+ const folder = await tryFindFolderInstanceAsync(connection, process.cwd());
173
+ if (!folder) {
174
+ return false;
175
+ }
176
+
177
+ targeting.target = folder.instanceId;
178
+ OutputHelper.hint(
179
+ `Using ${folder.instanceId}, which has '${
180
+ folder.deployPlace.selector
181
+ }' of ${path.relative(process.cwd(), folder.deployPlace.configPath)} open`
182
+ );
183
+ return true;
184
+ }
185
+
186
+ /**
187
+ * Refuses to change an edit DataModel while a playtest of it runs: the
188
+ * playtest runs a copy, so the change would not show up there.
189
+ */
190
+ export function assertNotEditingDuringPlaytest(
191
+ connection: BridgeConnection,
192
+ session: BridgeSession
193
+ ): void {
194
+ if (session.context !== 'edit') return;
195
+
196
+ const playtest = connection
197
+ .listSessions()
198
+ .find(
199
+ (s) => s.instanceId === session.info.instanceId && s.context === 'server'
200
+ );
201
+ if (playtest) {
202
+ throw new Error(
203
+ `A playtest of '${session.info.placeName}' is running, so this would change Edit, not the playtest. Target the playtest with --context server (or client), or pass --allow-edit to change Edit anyway.`
204
+ );
205
+ }
206
+ }
207
+
100
208
  /** Inject version/diagnostic warnings into JSON result objects. */
101
209
  function injectWarnings(result: unknown): unknown {
102
- const warnings = (globalThis as any).__studioBridgeWarnings as
103
- | string[]
104
- | undefined;
105
- if (!warnings || warnings.length === 0) return result;
210
+ const warnings = getWarnings();
211
+ if (warnings.length === 0) return result;
106
212
  if (typeof result === 'object' && result !== null && !Array.isArray(result)) {
107
213
  return { ...result, _warnings: warnings };
108
214
  }
@@ -162,7 +268,10 @@ export function buildYargsCommand(
162
268
  const { positionals, options: yargsOptions } = toYargsOptions(def.args);
163
269
 
164
270
  const positionalSuffix = positionals
165
- .map((p) => (p.options.demandOption ? `<${p.name}>` : `[${p.name}]`))
271
+ .map((p) => {
272
+ const name = p.options.array ? `${p.name}..` : p.name;
273
+ return p.options.demandOption ? `<${name}>` : `[${name}]`;
274
+ })
166
275
  .join(' ');
167
276
 
168
277
  const command = positionalSuffix
@@ -177,6 +286,12 @@ export function buildYargsCommand(
177
286
  yargs.positional(pos.name, pos.options as any);
178
287
  }
179
288
 
289
+ for (const argDef of Object.values(def.args)) {
290
+ if (argDef.kind === 'passthrough') {
291
+ yargs.epilogue(`-- <args..> ${argDef.description}`);
292
+ }
293
+ }
294
+
180
295
  for (const [name, opt] of Object.entries(yargsOptions)) {
181
296
  yargs.option(name, opt as any);
182
297
  }
@@ -185,7 +300,7 @@ export function buildYargsCommand(
185
300
  yargs.option('target', {
186
301
  alias: 't',
187
302
  type: 'string',
188
- describe: 'Target session ID (or "all" for broadcast)',
303
+ describe: 'Target session or instance ID',
189
304
  });
190
305
  yargs.option('context', {
191
306
  type: 'string',
@@ -194,6 +309,14 @@ export function buildYargsCommand(
194
309
  });
195
310
  }
196
311
 
312
+ if (def.changesDataModel && def.scope === 'session') {
313
+ yargs.option('allow-edit', {
314
+ type: 'boolean',
315
+ default: false,
316
+ describe: 'Allow changing Edit while a playtest of it runs',
317
+ });
318
+ }
319
+
197
320
  yargs.option('format', {
198
321
  type: 'string',
199
322
  choices: ['text', 'json'],
@@ -248,7 +371,16 @@ export function buildYargsCommand(
248
371
  await runOnceAsync(def, commandArgs, argv, reporter, connect);
249
372
  }
250
373
  } catch (err) {
251
- OutputHelper.error(err instanceof Error ? err.message : String(err));
374
+ const message = err instanceof Error ? err.message : String(err);
375
+ if (explicitFormat === 'json') {
376
+ console.log(
377
+ formatJson(injectWarnings({ success: false, error: message }), {
378
+ pretty: process.stdout.isTTY,
379
+ })
380
+ );
381
+ } else {
382
+ OutputHelper.error(message);
383
+ }
252
384
  process.exit(1);
253
385
  }
254
386
  },
@@ -112,14 +112,14 @@ describe('buildGroupCommands', () => {
112
112
  expect(consoleMod!.command).toBe('console <command>');
113
113
  });
114
114
 
115
- it('group commands have descriptions', () => {
115
+ it('group commands have descriptions that name their subcommands', () => {
116
116
  const registry = makeRegistry();
117
117
  const result = buildGroupCommands(registry);
118
118
 
119
119
  const consoleMod = result.groups.find((g) =>
120
120
  (g.command as string).startsWith('console')
121
121
  );
122
- expect(consoleMod!.describe).toBe('Execute code and view logs');
122
+ expect(consoleMod!.describe).toBe('Execute code and view logs: exec, logs');
123
123
  });
124
124
 
125
125
  it('group builder registers subcommands', () => {
@@ -136,6 +136,38 @@ describe('buildGroupCommands', () => {
136
136
  expect(yargs.commands).toHaveLength(2);
137
137
  });
138
138
 
139
+ it('nests a group with a space under its first word', () => {
140
+ const registry = makeRegistry();
141
+ registry.register(
142
+ defineCommand({
143
+ group: 'console player',
144
+ name: 'list',
145
+ description: 'List players',
146
+ category: 'execution',
147
+ safety: 'read',
148
+ scope: 'connection',
149
+ args: {},
150
+ handler: async () => ({}),
151
+ })
152
+ );
153
+ const result = buildGroupCommands(registry);
154
+
155
+ const consoleMods = result.groups.filter((g) =>
156
+ (g.command as string).startsWith('console')
157
+ );
158
+ expect(consoleMods).toHaveLength(1);
159
+
160
+ const yargs = createMockYargs();
161
+ (consoleMods[0].builder as any)(yargs);
162
+ expect(yargs.commands).toHaveLength(3);
163
+
164
+ const playerMod = yargs.commands[2];
165
+ expect(playerMod.command).toBe('player <command>');
166
+ const playerYargs = createMockYargs();
167
+ playerMod.builder(playerYargs);
168
+ expect(playerYargs.commands.map((c: any) => c.command)).toEqual(['list']);
169
+ });
170
+
139
171
  it('returns empty arrays for empty registry', () => {
140
172
  const registry = new CommandRegistry();
141
173
  const result = buildGroupCommands(registry);
@@ -5,6 +5,9 @@
5
5
  * Grouped commands become parent commands with subcommands:
6
6
  * `console exec <code>` → `console` parent, `exec` subcommand
7
7
  *
8
+ * A group name with a space nests under its first word:
9
+ * `playtest player list` → `player` subcommand group of `playtest`
10
+ *
8
11
  * Top-level commands (group = null) are returned as standalone modules:
9
12
  * `serve`, `mcp`, `terminal`
10
13
  */
@@ -21,7 +24,8 @@ const GROUP_DESCRIPTIONS: Record<string, string> = {
21
24
  viewport: 'Screenshots and camera control',
22
25
  data: 'Load and save serialized data',
23
26
  playtest: 'Control play test sessions',
24
- process: 'Manage Studio processes',
27
+ 'playtest player': 'List, add and remove the players of a multiplayer test',
28
+ process: 'Open and manage Studio',
25
29
  plugin: 'Manage the bridge plugin',
26
30
  action: 'Invoke a Studio action',
27
31
  linux: 'Linux/Wine environment management',
@@ -50,26 +54,12 @@ export function buildGroupCommands(
50
54
  const groups: CommandModule[] = [];
51
55
  const topLevel: CommandModule[] = [];
52
56
 
53
- // Grouped commands → parent module with subcommands
54
- for (const groupName of registry.getGroups()) {
55
- const commands = registry.getByGroup(groupName);
56
- const description =
57
- GROUP_DESCRIPTIONS[groupName] ?? `${groupName} commands`;
57
+ const allGroups = registry.getGroups();
58
+ const topGroups = [...new Set(allGroups.map((name) => name.split(' ')[0]))];
58
59
 
59
- groups.push({
60
- command: `${groupName} <command>`,
61
- describe: description,
62
- builder: (yargs) => {
63
- for (const cmd of commands) {
64
- yargs.command(buildYargsCommand(cmd, options) as any);
65
- }
66
- return yargs.demandCommand(
67
- 1,
68
- `Run 'studio-bridge ${groupName} --help' for available commands`
69
- );
70
- },
71
- handler: () => {},
72
- });
60
+ // Grouped commands → parent module with subcommands
61
+ for (const groupName of topGroups) {
62
+ groups.push(buildGroupModule(registry, allGroups, groupName, options));
73
63
  }
74
64
 
75
65
  // Top-level commands (group = null) → standalone modules
@@ -79,3 +69,45 @@ export function buildGroupCommands(
79
69
 
80
70
  return { groups, topLevel };
81
71
  }
72
+
73
+ function buildGroupModule(
74
+ registry: CommandRegistry,
75
+ allGroups: string[],
76
+ groupName: string,
77
+ options: GroupBuilderOptions
78
+ ): CommandModule {
79
+ const commands = registry.getByGroup(groupName);
80
+ const subgroups = allGroups.filter(
81
+ (name) =>
82
+ name.startsWith(`${groupName} `) &&
83
+ !name.slice(groupName.length + 1).includes(' ')
84
+ );
85
+ // Naming the subcommands lets a reader find e.g. `process launch` from the top-level help
86
+ const names = [
87
+ ...commands.map((cmd) => cmd.name),
88
+ ...subgroups.map((subgroup) => subgroup.split(' ').pop()),
89
+ ];
90
+ const description = `${
91
+ GROUP_DESCRIPTIONS[groupName] ?? `${groupName} commands`
92
+ }: ${names.join(', ')}`;
93
+
94
+ return {
95
+ command: `${groupName.split(' ').pop()} <command>`,
96
+ describe: description,
97
+ builder: (yargs) => {
98
+ for (const cmd of commands) {
99
+ yargs.command(buildYargsCommand(cmd, options) as any);
100
+ }
101
+ for (const subgroup of subgroups) {
102
+ yargs.command(
103
+ buildGroupModule(registry, allGroups, subgroup, options) as any
104
+ );
105
+ }
106
+ return yargs.demandCommand(
107
+ 1,
108
+ `Run 'studio-bridge ${groupName} --help' for available commands`
109
+ );
110
+ },
111
+ handler: () => {},
112
+ };
113
+ }
@@ -85,7 +85,7 @@ describe('formatGroupedHelp', () => {
85
85
  it('includes group descriptions', () => {
86
86
  const help = formatGroupedHelp(makeRegistry());
87
87
  expect(help).toContain('Execute code and view logs');
88
- expect(help).toContain('Manage Studio processes');
88
+ expect(help).toContain('Open and manage Studio');
89
89
  });
90
90
 
91
91
  it('returns empty for empty registry', () => {
@@ -12,7 +12,7 @@ const GROUP_DESCRIPTIONS: Record<string, string> = {
12
12
  viewport: 'Screenshots and camera control',
13
13
  data: 'Load and save serialized data',
14
14
  playtest: 'Control play test sessions',
15
- process: 'Manage Studio processes',
15
+ process: 'Open and manage Studio',
16
16
  plugin: 'Manage the bridge plugin',
17
17
  action: 'Invoke a Studio action',
18
18
  };
@@ -36,10 +36,11 @@ export function formatGroupedHelp(registry: CommandRegistry): string {
36
36
 
37
37
  for (const cmd of registry.getAll()) {
38
38
  if (cmd.group !== null) {
39
+ const topGroup = cmd.group.split(' ')[0];
39
40
  if (cmd.category === 'execution') {
40
- executionGroups.add(cmd.group);
41
+ executionGroups.add(topGroup);
41
42
  } else {
42
- infrastructureGroups.add(cmd.group);
43
+ infrastructureGroups.add(topGroup);
43
44
  }
44
45
  } else {
45
46
  const entry = { name: cmd.name, description: cmd.description };
@@ -0,0 +1,54 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { assertNotEditingDuringPlaytest } from './cli-command-adapter.js';
3
+
4
+ function createSession(context: string, instanceId = 'test-place-3f9a') {
5
+ return {
6
+ context,
7
+ info: { instanceId, placeName: 'test-place.rbxl', context },
8
+ } as any;
9
+ }
10
+
11
+ function createConnection(contexts: string[]) {
12
+ return {
13
+ listSessions: () =>
14
+ contexts.map((context) => ({ instanceId: 'test-place-3f9a', context })),
15
+ } as any;
16
+ }
17
+
18
+ describe('assertNotEditingDuringPlaytest', () => {
19
+ it('refuses to change Edit while its playtest runs', () => {
20
+ expect(() =>
21
+ assertNotEditingDuringPlaytest(
22
+ createConnection(['edit', 'server', 'client']),
23
+ createSession('edit')
24
+ )
25
+ ).toThrow(/playtest of 'test-place.rbxl' is running/);
26
+ });
27
+
28
+ it('allows changing Edit with no playtest running', () => {
29
+ expect(() =>
30
+ assertNotEditingDuringPlaytest(
31
+ createConnection(['edit']),
32
+ createSession('edit')
33
+ )
34
+ ).not.toThrow();
35
+ });
36
+
37
+ it('allows changing the playtest itself', () => {
38
+ expect(() =>
39
+ assertNotEditingDuringPlaytest(
40
+ createConnection(['edit', 'server']),
41
+ createSession('server')
42
+ )
43
+ ).not.toThrow();
44
+ });
45
+
46
+ it('ignores playtests of other Studios', () => {
47
+ expect(() =>
48
+ assertNotEditingDuringPlaytest(
49
+ createConnection(['edit', 'server']),
50
+ createSession('edit', 'other-place-1111')
51
+ )
52
+ ).not.toThrow();
53
+ });
54
+ });
@@ -4,7 +4,7 @@
4
4
  export interface StudioBridgeGlobalArgs {
5
5
  verbose: boolean;
6
6
  place?: string;
7
- timeout: number;
7
+ timeout?: number;
8
8
  logs: boolean;
9
9
  remote?: string;
10
10
  local?: boolean;
@@ -0,0 +1,55 @@
1
+ import { describe, it, expect, afterEach, vi } from 'vitest';
2
+
3
+ vi.mock('../plugin/plugin-build-version.js', () => ({
4
+ getPluginBuildVersionAsync: async () => 'current',
5
+ }));
6
+
7
+ import { OutputHelper } from '@quenty/cli-output-helpers';
8
+ import {
9
+ addWarning,
10
+ clearWarnings,
11
+ getWarnings,
12
+ setJsonOutput,
13
+ warnIfPluginStaleAsync,
14
+ } from './cli-output-context.js';
15
+
16
+ describe('cli-output-context', () => {
17
+ afterEach(() => {
18
+ clearWarnings();
19
+ setJsonOutput(false);
20
+ vi.restoreAllMocks();
21
+ });
22
+
23
+ it('prints warnings right away in text mode', () => {
24
+ const warn = vi.spyOn(OutputHelper, 'warn').mockImplementation(() => {});
25
+
26
+ addWarning('careful');
27
+
28
+ expect(warn).toHaveBeenCalledWith('careful');
29
+ expect(getWarnings()).toEqual(['careful']);
30
+ });
31
+
32
+ it('only collects warnings in JSON mode', () => {
33
+ const warn = vi.spyOn(OutputHelper, 'warn').mockImplementation(() => {});
34
+ setJsonOutput(true);
35
+
36
+ addWarning('careful');
37
+ addWarning('careful');
38
+
39
+ expect(warn).not.toHaveBeenCalled();
40
+ expect(getWarnings()).toEqual(['careful']);
41
+ });
42
+
43
+ it('warns about plugins built from a different template', async () => {
44
+ setJsonOutput(true);
45
+
46
+ await warnIfPluginStaleAsync([
47
+ { sessionId: 'a-edit', placeName: 'A', pluginVersion: 'current' },
48
+ { sessionId: 'b-edit', placeName: 'B', pluginVersion: 'old' },
49
+ ] as any);
50
+
51
+ expect(getWarnings()).toEqual([
52
+ expect.stringContaining("'B' (b-edit) is out of date"),
53
+ ]);
54
+ });
55
+ });
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Process-wide output state: whether stdout carries JSON, and warnings about
3
+ * the CLI's own setup (outdated CLI, stale Studio plugin). Text output prints
4
+ * warnings to stderr as they come up. JSON output carries them in the
5
+ * result's `_warnings` field instead, so they are not lost.
6
+ */
7
+
8
+ import { OutputHelper } from '@quenty/cli-output-helpers';
9
+ import type { SessionInfo } from '../bridge/index.js';
10
+ import { getPluginBuildVersionAsync } from '../plugin/plugin-build-version.js';
11
+
12
+ let isJsonOutput = false;
13
+ const warnings: string[] = [];
14
+
15
+ export function setJsonOutput(json: boolean): void {
16
+ isJsonOutput = json;
17
+ }
18
+
19
+ export function isJsonOutputEnabled(): boolean {
20
+ return isJsonOutput;
21
+ }
22
+
23
+ export function addWarning(message: string): void {
24
+ if (warnings.includes(message)) return;
25
+ warnings.push(message);
26
+ if (!isJsonOutput) {
27
+ OutputHelper.warn(message);
28
+ }
29
+ }
30
+
31
+ export function getWarnings(): readonly string[] {
32
+ return warnings;
33
+ }
34
+
35
+ export function clearWarnings(): void {
36
+ warnings.length = 0;
37
+ }
38
+
39
+ export async function warnIfPluginStaleAsync(
40
+ sessions: SessionInfo[]
41
+ ): Promise<void> {
42
+ const expected = await getPluginBuildVersionAsync();
43
+ for (const session of sessions) {
44
+ if (session.pluginVersion !== expected) {
45
+ addWarning(
46
+ `The studio-bridge plugin in '${session.placeName}' (${session.sessionId}) is out of date. Run 'studio-bridge plugin install' to update it.`
47
+ );
48
+ }
49
+ }
50
+ }