@quenty/studio-bridge 0.21.1 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (482) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +109 -17
  3. package/build-scripts/transform-add-session-attribute.lua +1 -0
  4. package/deploy.nevermore.json +10 -0
  5. package/dist/src/bridge/bridge-connection.d.ts +26 -2
  6. package/dist/src/bridge/bridge-connection.d.ts.map +1 -1
  7. package/dist/src/bridge/bridge-connection.js +159 -14
  8. package/dist/src/bridge/bridge-connection.js.map +1 -1
  9. package/dist/src/bridge/bridge-connection.test.js +81 -0
  10. package/dist/src/bridge/bridge-connection.test.js.map +1 -1
  11. package/dist/src/bridge/bridge-session.d.ts +35 -2
  12. package/dist/src/bridge/bridge-session.d.ts.map +1 -1
  13. package/dist/src/bridge/bridge-session.js +185 -28
  14. package/dist/src/bridge/bridge-session.js.map +1 -1
  15. package/dist/src/bridge/bridge-session.test.js +6 -4
  16. package/dist/src/bridge/bridge-session.test.js.map +1 -1
  17. package/dist/src/bridge/index.d.ts +1 -1
  18. package/dist/src/bridge/index.d.ts.map +1 -1
  19. package/dist/src/bridge/index.js.map +1 -1
  20. package/dist/src/bridge/internal/bridge-client.d.ts +4 -0
  21. package/dist/src/bridge/internal/bridge-client.d.ts.map +1 -1
  22. package/dist/src/bridge/internal/bridge-client.js +20 -7
  23. package/dist/src/bridge/internal/bridge-client.js.map +1 -1
  24. package/dist/src/bridge/internal/bridge-client.test.js +18 -0
  25. package/dist/src/bridge/internal/bridge-client.test.js.map +1 -1
  26. package/dist/src/bridge/internal/bridge-host.d.ts +13 -1
  27. package/dist/src/bridge/internal/bridge-host.d.ts.map +1 -1
  28. package/dist/src/bridge/internal/bridge-host.js +35 -0
  29. package/dist/src/bridge/internal/bridge-host.js.map +1 -1
  30. package/dist/src/bridge/internal/bridge-host.test.js +31 -0
  31. package/dist/src/bridge/internal/bridge-host.test.js.map +1 -1
  32. package/dist/src/bridge/internal/hand-off.d.ts.map +1 -1
  33. package/dist/src/bridge/internal/hand-off.js +4 -1
  34. package/dist/src/bridge/internal/hand-off.js.map +1 -1
  35. package/dist/src/bridge/internal/host-protocol.d.ts +8 -0
  36. package/dist/src/bridge/internal/host-protocol.d.ts.map +1 -1
  37. package/dist/src/bridge/internal/host-protocol.js +120 -13
  38. package/dist/src/bridge/internal/host-protocol.js.map +1 -1
  39. package/dist/src/bridge/internal/host-protocol.test.js +13 -0
  40. package/dist/src/bridge/internal/host-protocol.test.js.map +1 -1
  41. package/dist/src/bridge/internal/instance-state.d.ts +9 -0
  42. package/dist/src/bridge/internal/instance-state.d.ts.map +1 -0
  43. package/dist/src/bridge/internal/instance-state.js +28 -0
  44. package/dist/src/bridge/internal/instance-state.js.map +1 -0
  45. package/dist/src/bridge/internal/transport-client.d.ts.map +1 -1
  46. package/dist/src/bridge/internal/transport-client.js +2 -1
  47. package/dist/src/bridge/internal/transport-client.js.map +1 -1
  48. package/dist/src/bridge/internal/transport-server.d.ts.map +1 -1
  49. package/dist/src/bridge/internal/transport-server.js +5 -1
  50. package/dist/src/bridge/internal/transport-server.js.map +1 -1
  51. package/dist/src/bridge/types.d.ts +43 -4
  52. package/dist/src/bridge/types.d.ts.map +1 -1
  53. package/dist/src/bridge/types.js.map +1 -1
  54. package/dist/src/cli/adapters/cli-command-adapter-handler.test.js +24 -0
  55. package/dist/src/cli/adapters/cli-command-adapter-handler.test.js.map +1 -1
  56. package/dist/src/cli/adapters/cli-command-adapter.d.ts +6 -0
  57. package/dist/src/cli/adapters/cli-command-adapter.d.ts.map +1 -1
  58. package/dist/src/cli/adapters/cli-command-adapter.js +107 -11
  59. package/dist/src/cli/adapters/cli-command-adapter.js.map +1 -1
  60. package/dist/src/cli/adapters/group-builder.d.ts +3 -0
  61. package/dist/src/cli/adapters/group-builder.d.ts.map +1 -1
  62. package/dist/src/cli/adapters/group-builder.js +34 -15
  63. package/dist/src/cli/adapters/group-builder.js.map +1 -1
  64. package/dist/src/cli/adapters/group-builder.test.js +26 -2
  65. package/dist/src/cli/adapters/group-builder.test.js.map +1 -1
  66. package/dist/src/cli/adapters/help-formatter.d.ts.map +1 -1
  67. package/dist/src/cli/adapters/help-formatter.js +4 -3
  68. package/dist/src/cli/adapters/help-formatter.js.map +1 -1
  69. package/dist/src/cli/adapters/help-formatter.test.js +1 -1
  70. package/dist/src/cli/adapters/help-formatter.test.js.map +1 -1
  71. package/dist/src/cli/adapters/playtest-edit-guard.test.d.ts +2 -0
  72. package/dist/src/cli/adapters/playtest-edit-guard.test.d.ts.map +1 -0
  73. package/dist/src/cli/adapters/playtest-edit-guard.test.js +28 -0
  74. package/dist/src/cli/adapters/playtest-edit-guard.test.js.map +1 -0
  75. package/dist/src/cli/args/global-args.d.ts +1 -1
  76. package/dist/src/cli/args/global-args.d.ts.map +1 -1
  77. package/dist/src/cli/cli-output-context.d.ts +14 -0
  78. package/dist/src/cli/cli-output-context.d.ts.map +1 -0
  79. package/dist/src/cli/cli-output-context.js +39 -0
  80. package/dist/src/cli/cli-output-context.js.map +1 -0
  81. package/dist/src/cli/cli-output-context.test.d.ts +2 -0
  82. package/dist/src/cli/cli-output-context.test.d.ts.map +1 -0
  83. package/dist/src/cli/cli-output-context.test.js +38 -0
  84. package/dist/src/cli/cli-output-context.test.js.map +1 -0
  85. package/dist/src/cli/cli.js +40 -17
  86. package/dist/src/cli/cli.js.map +1 -1
  87. package/dist/src/cli/managed-studio-run.d.ts +21 -0
  88. package/dist/src/cli/managed-studio-run.d.ts.map +1 -0
  89. package/dist/src/cli/managed-studio-run.js +75 -0
  90. package/dist/src/cli/managed-studio-run.js.map +1 -0
  91. package/dist/src/cli/managed-studio-run.test.d.ts +2 -0
  92. package/dist/src/cli/managed-studio-run.test.d.ts.map +1 -0
  93. package/dist/src/cli/managed-studio-run.test.js +52 -0
  94. package/dist/src/cli/managed-studio-run.test.js.map +1 -0
  95. package/dist/src/cli/resolve-session.js +1 -1
  96. package/dist/src/cli/resolve-session.js.map +1 -1
  97. package/dist/src/cli/resolve-session.test.js +1 -1
  98. package/dist/src/cli/resolve-session.test.js.map +1 -1
  99. package/dist/src/cli/script-executor.d.ts +13 -1
  100. package/dist/src/cli/script-executor.d.ts.map +1 -1
  101. package/dist/src/cli/script-executor.js +77 -47
  102. package/dist/src/cli/script-executor.js.map +1 -1
  103. package/dist/src/commands/console/exec/exec.d.ts +17 -0
  104. package/dist/src/commands/console/exec/exec.d.ts.map +1 -1
  105. package/dist/src/commands/console/exec/exec.js +39 -7
  106. package/dist/src/commands/console/exec/exec.js.map +1 -1
  107. package/dist/src/commands/console/exec/exec.test.js +62 -3
  108. package/dist/src/commands/console/exec/exec.test.js.map +1 -1
  109. package/dist/src/commands/console/logs/logs.d.ts +30 -2
  110. package/dist/src/commands/console/logs/logs.d.ts.map +1 -1
  111. package/dist/src/commands/console/logs/logs.js +82 -7
  112. package/dist/src/commands/console/logs/logs.js.map +1 -1
  113. package/dist/src/commands/console/logs/logs.test.js +63 -4
  114. package/dist/src/commands/console/logs/logs.test.js.map +1 -1
  115. package/dist/src/commands/explorer/export/export.d.ts +19 -0
  116. package/dist/src/commands/explorer/export/export.d.ts.map +1 -0
  117. package/dist/src/commands/explorer/export/export.js +37 -0
  118. package/dist/src/commands/explorer/export/export.js.map +1 -0
  119. package/dist/src/commands/explorer/export/export.test.d.ts +2 -0
  120. package/dist/src/commands/explorer/export/export.test.d.ts.map +1 -0
  121. package/dist/src/commands/explorer/export/export.test.js +25 -0
  122. package/dist/src/commands/explorer/export/export.test.js.map +1 -0
  123. package/dist/src/commands/explorer/find/find.d.ts +28 -0
  124. package/dist/src/commands/explorer/find/find.d.ts.map +1 -0
  125. package/dist/src/commands/explorer/find/find.js +75 -0
  126. package/dist/src/commands/explorer/find/find.js.map +1 -0
  127. package/dist/src/commands/explorer/find/find.test.d.ts +2 -0
  128. package/dist/src/commands/explorer/find/find.test.d.ts.map +1 -0
  129. package/dist/src/commands/explorer/find/find.test.js +44 -0
  130. package/dist/src/commands/explorer/find/find.test.js.map +1 -0
  131. package/dist/src/commands/explorer/grep/grep.d.ts +24 -0
  132. package/dist/src/commands/explorer/grep/grep.d.ts.map +1 -0
  133. package/dist/src/commands/explorer/grep/grep.js +63 -0
  134. package/dist/src/commands/explorer/grep/grep.js.map +1 -0
  135. package/dist/src/commands/explorer/grep/grep.test.d.ts +2 -0
  136. package/dist/src/commands/explorer/grep/grep.test.d.ts.map +1 -0
  137. package/dist/src/commands/explorer/grep/grep.test.js +42 -0
  138. package/dist/src/commands/explorer/grep/grep.test.js.map +1 -0
  139. package/dist/src/commands/explorer/import/import.d.ts +21 -0
  140. package/dist/src/commands/explorer/import/import.d.ts.map +1 -0
  141. package/dist/src/commands/explorer/import/import.js +37 -0
  142. package/dist/src/commands/explorer/import/import.js.map +1 -0
  143. package/dist/src/commands/explorer/import/import.test.d.ts +2 -0
  144. package/dist/src/commands/explorer/import/import.test.d.ts.map +1 -0
  145. package/dist/src/commands/explorer/import/import.test.js +45 -0
  146. package/dist/src/commands/explorer/import/import.test.js.map +1 -0
  147. package/dist/src/commands/formatters.test.js +7 -1
  148. package/dist/src/commands/formatters.test.js.map +1 -1
  149. package/dist/src/commands/framework/action-loader.d.ts +5 -1
  150. package/dist/src/commands/framework/action-loader.d.ts.map +1 -1
  151. package/dist/src/commands/framework/action-loader.js +9 -2
  152. package/dist/src/commands/framework/action-loader.js.map +1 -1
  153. package/dist/src/commands/framework/action-loader.test.js +23 -0
  154. package/dist/src/commands/framework/action-loader.test.js.map +1 -1
  155. package/dist/src/commands/framework/arg-builder.d.ts +15 -2
  156. package/dist/src/commands/framework/arg-builder.d.ts.map +1 -1
  157. package/dist/src/commands/framework/arg-builder.js +22 -2
  158. package/dist/src/commands/framework/arg-builder.js.map +1 -1
  159. package/dist/src/commands/framework/define-command.d.ts +19 -3
  160. package/dist/src/commands/framework/define-command.d.ts.map +1 -1
  161. package/dist/src/commands/framework/define-command.js.map +1 -1
  162. package/dist/src/commands/framework/index.d.ts +1 -1
  163. package/dist/src/commands/framework/index.d.ts.map +1 -1
  164. package/dist/src/commands/framework/index.js.map +1 -1
  165. package/dist/src/commands/index.d.ts +11 -0
  166. package/dist/src/commands/index.d.ts.map +1 -1
  167. package/dist/src/commands/index.js +11 -0
  168. package/dist/src/commands/index.js.map +1 -1
  169. package/dist/src/commands/linux/inject-credentials/inject-credentials.d.ts.map +1 -1
  170. package/dist/src/commands/linux/inject-credentials/inject-credentials.js +1 -3
  171. package/dist/src/commands/linux/inject-credentials/inject-credentials.js.map +1 -1
  172. package/dist/src/commands/linux/setup/setup.d.ts.map +1 -1
  173. package/dist/src/commands/linux/setup/setup.js +1 -3
  174. package/dist/src/commands/linux/setup/setup.js.map +1 -1
  175. package/dist/src/commands/linux/status/status.d.ts +9 -1
  176. package/dist/src/commands/linux/status/status.d.ts.map +1 -1
  177. package/dist/src/commands/linux/status/status.js +66 -86
  178. package/dist/src/commands/linux/status/status.js.map +1 -1
  179. package/dist/src/commands/playtest/player/add/add.d.ts +17 -0
  180. package/dist/src/commands/playtest/player/add/add.d.ts.map +1 -0
  181. package/dist/src/commands/playtest/player/add/add.js +45 -0
  182. package/dist/src/commands/playtest/player/add/add.js.map +1 -0
  183. package/dist/src/commands/playtest/player/list/list.d.ts +13 -0
  184. package/dist/src/commands/playtest/player/list/list.d.ts.map +1 -0
  185. package/dist/src/commands/playtest/player/list/list.js +33 -0
  186. package/dist/src/commands/playtest/player/list/list.js.map +1 -0
  187. package/dist/src/commands/playtest/player/playtest-players.d.ts +36 -0
  188. package/dist/src/commands/playtest/player/playtest-players.d.ts.map +1 -0
  189. package/dist/src/commands/playtest/player/playtest-players.js +187 -0
  190. package/dist/src/commands/playtest/player/playtest-players.js.map +1 -0
  191. package/dist/src/commands/playtest/player/playtest-players.test.d.ts +2 -0
  192. package/dist/src/commands/playtest/player/playtest-players.test.d.ts.map +1 -0
  193. package/dist/src/commands/playtest/player/playtest-players.test.js +89 -0
  194. package/dist/src/commands/playtest/player/playtest-players.test.js.map +1 -0
  195. package/dist/src/commands/playtest/player/remove/remove.d.ts +17 -0
  196. package/dist/src/commands/playtest/player/remove/remove.d.ts.map +1 -0
  197. package/dist/src/commands/playtest/player/remove/remove.js +42 -0
  198. package/dist/src/commands/playtest/player/remove/remove.js.map +1 -0
  199. package/dist/src/commands/playtest/playtest-sessions.d.ts +49 -0
  200. package/dist/src/commands/playtest/playtest-sessions.d.ts.map +1 -0
  201. package/dist/src/commands/playtest/playtest-sessions.js +283 -0
  202. package/dist/src/commands/playtest/playtest-sessions.js.map +1 -0
  203. package/dist/src/commands/playtest/playtest-sessions.test.d.ts +2 -0
  204. package/dist/src/commands/playtest/playtest-sessions.test.d.ts.map +1 -0
  205. package/dist/src/commands/playtest/playtest-sessions.test.js +87 -0
  206. package/dist/src/commands/playtest/playtest-sessions.test.js.map +1 -0
  207. package/dist/src/commands/playtest/restart/restart.d.ts +23 -0
  208. package/dist/src/commands/playtest/restart/restart.d.ts.map +1 -0
  209. package/dist/src/commands/playtest/restart/restart.js +105 -0
  210. package/dist/src/commands/playtest/restart/restart.js.map +1 -0
  211. package/dist/src/commands/playtest/run/run.d.ts +47 -0
  212. package/dist/src/commands/playtest/run/run.d.ts.map +1 -0
  213. package/dist/src/commands/playtest/run/run.js +137 -0
  214. package/dist/src/commands/playtest/run/run.js.map +1 -0
  215. package/dist/src/commands/playtest/start/start.d.ts +42 -0
  216. package/dist/src/commands/playtest/start/start.d.ts.map +1 -0
  217. package/dist/src/commands/playtest/start/start.js +70 -0
  218. package/dist/src/commands/playtest/start/start.js.map +1 -0
  219. package/dist/src/commands/playtest/stop/stop.d.ts +15 -0
  220. package/dist/src/commands/playtest/stop/stop.d.ts.map +1 -0
  221. package/dist/src/commands/playtest/stop/stop.js +24 -0
  222. package/dist/src/commands/playtest/stop/stop.js.map +1 -0
  223. package/dist/src/commands/process/close/close.d.ts +19 -10
  224. package/dist/src/commands/process/close/close.d.ts.map +1 -1
  225. package/dist/src/commands/process/close/close.js +118 -15
  226. package/dist/src/commands/process/close/close.js.map +1 -1
  227. package/dist/src/commands/process/close/close.test.d.ts +2 -0
  228. package/dist/src/commands/process/close/close.test.d.ts.map +1 -0
  229. package/dist/src/commands/process/close/close.test.js +190 -0
  230. package/dist/src/commands/process/close/close.test.js.map +1 -0
  231. package/dist/src/commands/process/info/info.d.ts +45 -5
  232. package/dist/src/commands/process/info/info.d.ts.map +1 -1
  233. package/dist/src/commands/process/info/info.js +117 -13
  234. package/dist/src/commands/process/info/info.js.map +1 -1
  235. package/dist/src/commands/process/info/info.test.js +140 -38
  236. package/dist/src/commands/process/info/info.test.js.map +1 -1
  237. package/dist/src/commands/process/launch/launch.d.ts +11 -1
  238. package/dist/src/commands/process/launch/launch.d.ts.map +1 -1
  239. package/dist/src/commands/process/launch/launch.js +37 -7
  240. package/dist/src/commands/process/launch/launch.js.map +1 -1
  241. package/dist/src/commands/process/launch/launch.test.js +99 -20
  242. package/dist/src/commands/process/launch/launch.test.js.map +1 -1
  243. package/dist/src/commands/process/list/list.d.ts +2 -1
  244. package/dist/src/commands/process/list/list.d.ts.map +1 -1
  245. package/dist/src/commands/process/list/list.js +9 -6
  246. package/dist/src/commands/process/list/list.js.map +1 -1
  247. package/dist/src/commands/process/list/list.test.js +1 -0
  248. package/dist/src/commands/process/list/list.test.js.map +1 -1
  249. package/dist/src/commands/process/run/run.d.ts +9 -0
  250. package/dist/src/commands/process/run/run.d.ts.map +1 -1
  251. package/dist/src/commands/process/run/run.js +15 -5
  252. package/dist/src/commands/process/run/run.js.map +1 -1
  253. package/dist/src/commands/serve/serve.d.ts +2 -0
  254. package/dist/src/commands/serve/serve.d.ts.map +1 -1
  255. package/dist/src/commands/serve/serve.js +17 -4
  256. package/dist/src/commands/serve/serve.js.map +1 -1
  257. package/dist/src/commands/serve/serve.test.js +9 -2
  258. package/dist/src/commands/serve/serve.test.js.map +1 -1
  259. package/dist/src/commands/viewport/screenshot/screenshot.d.ts.map +1 -1
  260. package/dist/src/commands/viewport/screenshot/screenshot.js +4 -1
  261. package/dist/src/commands/viewport/screenshot/screenshot.js.map +1 -1
  262. package/dist/src/commands/viewport/screenshot/screenshot.test.js +5 -9
  263. package/dist/src/commands/viewport/screenshot/screenshot.test.js.map +1 -1
  264. package/dist/src/deploy/deploy-places.d.ts +39 -0
  265. package/dist/src/deploy/deploy-places.d.ts.map +1 -0
  266. package/dist/src/deploy/deploy-places.js +162 -0
  267. package/dist/src/deploy/deploy-places.js.map +1 -0
  268. package/dist/src/deploy/deploy-places.test.d.ts +2 -0
  269. package/dist/src/deploy/deploy-places.test.d.ts.map +1 -0
  270. package/dist/src/deploy/deploy-places.test.js +96 -0
  271. package/dist/src/deploy/deploy-places.test.js.map +1 -0
  272. package/dist/src/index.d.ts +2 -2
  273. package/dist/src/index.d.ts.map +1 -1
  274. package/dist/src/index.js +1 -1
  275. package/dist/src/index.js.map +1 -1
  276. package/dist/src/plugin/persistent-plugin-installer.d.ts.map +1 -1
  277. package/dist/src/plugin/persistent-plugin-installer.js +2 -1
  278. package/dist/src/plugin/persistent-plugin-installer.js.map +1 -1
  279. package/dist/src/plugin/persistent-plugin-installer.test.js +3 -0
  280. package/dist/src/plugin/persistent-plugin-installer.test.js.map +1 -1
  281. package/dist/src/plugin/plugin-build-version.d.ts +7 -0
  282. package/dist/src/plugin/plugin-build-version.d.ts.map +1 -0
  283. package/dist/src/plugin/plugin-build-version.js +42 -0
  284. package/dist/src/plugin/plugin-build-version.js.map +1 -0
  285. package/dist/src/plugin/plugin-build-version.test.d.ts +2 -0
  286. package/dist/src/plugin/plugin-build-version.test.d.ts.map +1 -0
  287. package/dist/src/plugin/plugin-build-version.test.js +41 -0
  288. package/dist/src/plugin/plugin-build-version.test.js.map +1 -0
  289. package/dist/src/plugin/plugin-injector.d.ts.map +1 -1
  290. package/dist/src/plugin/plugin-injector.js +7 -1
  291. package/dist/src/plugin/plugin-injector.js.map +1 -1
  292. package/dist/src/process/studio-process-closer.d.ts +35 -0
  293. package/dist/src/process/studio-process-closer.d.ts.map +1 -0
  294. package/dist/src/process/studio-process-closer.js +129 -0
  295. package/dist/src/process/studio-process-closer.js.map +1 -0
  296. package/dist/src/process/studio-process-closer.test.d.ts +2 -0
  297. package/dist/src/process/studio-process-closer.test.d.ts.map +1 -0
  298. package/dist/src/process/studio-process-closer.test.js +80 -0
  299. package/dist/src/process/studio-process-closer.test.js.map +1 -0
  300. package/dist/src/process/studio-settings.d.ts +14 -0
  301. package/dist/src/process/studio-settings.d.ts.map +1 -0
  302. package/dist/src/process/studio-settings.js +50 -0
  303. package/dist/src/process/studio-settings.js.map +1 -0
  304. package/dist/src/process/studio-settings.test.d.ts +2 -0
  305. package/dist/src/process/studio-settings.test.d.ts.map +1 -0
  306. package/dist/src/process/studio-settings.test.js +16 -0
  307. package/dist/src/process/studio-settings.test.js.map +1 -0
  308. package/dist/src/process/studio-window-state.d.ts +13 -0
  309. package/dist/src/process/studio-window-state.d.ts.map +1 -0
  310. package/dist/src/process/studio-window-state.js +48 -0
  311. package/dist/src/process/studio-window-state.js.map +1 -0
  312. package/dist/src/server/studio-bridge-server.d.ts +3 -1
  313. package/dist/src/server/studio-bridge-server.d.ts.map +1 -1
  314. package/dist/src/server/studio-bridge-server.js +12 -4
  315. package/dist/src/server/studio-bridge-server.js.map +1 -1
  316. package/dist/src/server/web-socket-protocol.d.ts +173 -6
  317. package/dist/src/server/web-socket-protocol.d.ts.map +1 -1
  318. package/dist/src/server/web-socket-protocol.js +244 -1
  319. package/dist/src/server/web-socket-protocol.js.map +1 -1
  320. package/dist/src/server/web-socket-protocol.test.js +48 -0
  321. package/dist/src/server/web-socket-protocol.test.js.map +1 -1
  322. package/dist/src/test/e2e/persistent-session.test.js +121 -0
  323. package/dist/src/test/e2e/persistent-session.test.js.map +1 -1
  324. package/dist/src/test/e2e/split-server.test.d.ts +3 -5
  325. package/dist/src/test/e2e/split-server.test.d.ts.map +1 -1
  326. package/dist/src/test/e2e/split-server.test.js +3 -5
  327. package/dist/src/test/e2e/split-server.test.js.map +1 -1
  328. package/dist/tsconfig.tsbuildinfo +1 -1
  329. package/docker/Dockerfile +3 -0
  330. package/package.json +8 -6
  331. package/src/bridge/bridge-connection.test.ts +126 -0
  332. package/src/bridge/bridge-connection.ts +217 -22
  333. package/src/bridge/bridge-session.test.ts +6 -4
  334. package/src/bridge/bridge-session.ts +273 -28
  335. package/src/bridge/index.ts +3 -0
  336. package/src/bridge/internal/bridge-client.test.ts +23 -0
  337. package/src/bridge/internal/bridge-client.ts +25 -7
  338. package/src/bridge/internal/bridge-host.test.ts +38 -0
  339. package/src/bridge/internal/bridge-host.ts +47 -0
  340. package/src/bridge/internal/hand-off.ts +4 -1
  341. package/src/bridge/internal/host-protocol.test.ts +15 -0
  342. package/src/bridge/internal/host-protocol.ts +148 -37
  343. package/src/bridge/internal/instance-state.ts +34 -0
  344. package/src/bridge/internal/transport-client.ts +2 -1
  345. package/src/bridge/internal/transport-server.ts +5 -1
  346. package/src/bridge/types.ts +47 -2
  347. package/src/cli/adapters/cli-command-adapter-handler.test.ts +28 -0
  348. package/src/cli/adapters/cli-command-adapter.ts +150 -18
  349. package/src/cli/adapters/group-builder.test.ts +34 -2
  350. package/src/cli/adapters/group-builder.ts +52 -20
  351. package/src/cli/adapters/help-formatter.test.ts +1 -1
  352. package/src/cli/adapters/help-formatter.ts +4 -3
  353. package/src/cli/adapters/playtest-edit-guard.test.ts +54 -0
  354. package/src/cli/args/global-args.ts +1 -1
  355. package/src/cli/cli-output-context.test.ts +55 -0
  356. package/src/cli/cli-output-context.ts +50 -0
  357. package/src/cli/cli.ts +49 -17
  358. package/src/cli/managed-studio-run.test.ts +71 -0
  359. package/src/cli/managed-studio-run.ts +135 -0
  360. package/src/cli/resolve-session.test.ts +1 -1
  361. package/src/cli/resolve-session.ts +1 -1
  362. package/src/cli/script-executor.ts +99 -51
  363. package/src/commands/console/exec/exec.test.ts +96 -3
  364. package/src/commands/console/exec/exec.ts +59 -6
  365. package/src/commands/console/exec/execute.lua +356 -240
  366. package/src/commands/console/exec/execute.spec.lua +286 -0
  367. package/src/commands/console/logs/logs.test.ts +76 -4
  368. package/src/commands/console/logs/logs.ts +132 -17
  369. package/src/commands/console/logs/query-logs.lua +85 -52
  370. package/src/commands/console/logs/query-logs.spec.lua +132 -0
  371. package/src/commands/explorer/export/export-instances.lua +108 -0
  372. package/src/commands/explorer/export/export-instances.spec.lua +87 -0
  373. package/src/commands/explorer/export/export.test.ts +27 -0
  374. package/src/commands/explorer/export/export.ts +60 -0
  375. package/src/commands/explorer/find/find-instances.lua +174 -0
  376. package/src/commands/explorer/find/find-instances.spec.lua +122 -0
  377. package/src/commands/explorer/find/find.test.ts +53 -0
  378. package/src/commands/explorer/find/find.ts +113 -0
  379. package/src/commands/explorer/grep/grep-scripts.lua +214 -0
  380. package/src/commands/explorer/grep/grep-scripts.spec.lua +130 -0
  381. package/src/commands/explorer/grep/grep.test.ts +45 -0
  382. package/src/commands/explorer/grep/grep.ts +93 -0
  383. package/src/commands/explorer/import/import-instances.lua +106 -0
  384. package/src/commands/explorer/import/import-instances.spec.lua +84 -0
  385. package/src/commands/explorer/import/import.test.ts +60 -0
  386. package/src/commands/explorer/import/import.ts +66 -0
  387. package/src/commands/explorer/query/query-data-model.lua +124 -152
  388. package/src/commands/explorer/query/query-data-model.spec.lua +150 -0
  389. package/src/commands/formatters.test.ts +7 -1
  390. package/src/commands/framework/action-loader.test.ts +28 -0
  391. package/src/commands/framework/action-loader.ts +10 -2
  392. package/src/commands/framework/arg-builder.ts +26 -3
  393. package/src/commands/framework/define-command.ts +32 -3
  394. package/src/commands/framework/index.ts +1 -0
  395. package/src/commands/index.ts +50 -0
  396. package/src/commands/linux/inject-credentials/inject-credentials.ts +1 -3
  397. package/src/commands/linux/setup/setup.ts +1 -3
  398. package/src/commands/linux/status/status.ts +105 -98
  399. package/src/commands/playtest/player/add/add.ts +85 -0
  400. package/src/commands/playtest/player/list/list.ts +57 -0
  401. package/src/commands/playtest/player/playtest-players.test.ts +146 -0
  402. package/src/commands/playtest/player/playtest-players.ts +270 -0
  403. package/src/commands/playtest/player/remove/remove.ts +78 -0
  404. package/src/commands/playtest/playtest-sessions.test.ts +143 -0
  405. package/src/commands/playtest/playtest-sessions.ts +417 -0
  406. package/src/commands/playtest/restart/restart.ts +199 -0
  407. package/src/commands/playtest/run/playtest-runner.luau +264 -0
  408. package/src/commands/playtest/run/playtest-runner.spec.lua +71 -0
  409. package/src/commands/playtest/run/run-playtest-script.lua +139 -0
  410. package/src/commands/playtest/run/run-playtest-script.spec.lua +43 -0
  411. package/src/commands/playtest/run/run.ts +239 -0
  412. package/src/commands/playtest/start/start-playtest.lua +108 -0
  413. package/src/commands/playtest/start/start.ts +138 -0
  414. package/src/commands/playtest/stop/end-playtest.lua +47 -0
  415. package/src/commands/playtest/stop/stop.ts +51 -0
  416. package/src/commands/process/close/close.test.ts +250 -0
  417. package/src/commands/process/close/close.ts +183 -17
  418. package/src/commands/process/info/info.test.ts +198 -44
  419. package/src/commands/process/info/info.ts +204 -15
  420. package/src/commands/process/info/query-state.lua +42 -29
  421. package/src/commands/process/info/query-state.spec.lua +39 -0
  422. package/src/commands/process/launch/launch.test.ts +138 -21
  423. package/src/commands/process/launch/launch.ts +69 -9
  424. package/src/commands/process/list/list.test.ts +1 -0
  425. package/src/commands/process/list/list.ts +14 -6
  426. package/src/commands/process/run/run.ts +26 -5
  427. package/src/commands/serve/serve.test.ts +17 -2
  428. package/src/commands/serve/serve.ts +25 -6
  429. package/src/commands/viewport/screenshot/capture-screenshot.lua +175 -182
  430. package/src/commands/viewport/screenshot/capture-screenshot.spec.lua +129 -0
  431. package/src/commands/viewport/screenshot/screenshot.test.ts +7 -10
  432. package/src/commands/viewport/screenshot/screenshot.ts +6 -1
  433. package/src/deploy/deploy-places.test.ts +127 -0
  434. package/src/deploy/deploy-places.ts +229 -0
  435. package/src/index.ts +11 -0
  436. package/src/plugin/persistent-plugin-installer.test.ts +4 -0
  437. package/src/plugin/persistent-plugin-installer.ts +2 -1
  438. package/src/plugin/plugin-build-version.test.ts +55 -0
  439. package/src/plugin/plugin-build-version.ts +53 -0
  440. package/src/plugin/plugin-injector.ts +7 -1
  441. package/src/process/studio-process-closer.test.ts +107 -0
  442. package/src/process/studio-process-closer.ts +187 -0
  443. package/src/process/studio-settings.test.ts +18 -0
  444. package/src/process/studio-settings.ts +61 -0
  445. package/src/process/studio-window-state.ts +65 -0
  446. package/src/server/studio-bridge-server.ts +15 -4
  447. package/src/server/web-socket-protocol.test.ts +55 -0
  448. package/src/server/web-socket-protocol.ts +468 -8
  449. package/src/test/e2e/persistent-session.test.ts +137 -0
  450. package/src/test/e2e/split-server.test.ts +3 -5
  451. package/templates/studio-bridge-plugin/default.project.json +3 -0
  452. package/templates/studio-bridge-plugin/src/Shared/ActionRouter.lua +208 -159
  453. package/templates/studio-bridge-plugin/src/Shared/ActionRouter.spec.lua +418 -0
  454. package/templates/studio-bridge-plugin/src/Shared/BuiltInActions.lua +94 -0
  455. package/templates/studio-bridge-plugin/src/Shared/BuiltInActions.spec.lua +166 -0
  456. package/templates/studio-bridge-plugin/src/Shared/DiscoveryStateMachine.lua +118 -137
  457. package/templates/studio-bridge-plugin/src/Shared/DiscoveryStateMachine.spec.lua +228 -0
  458. package/templates/studio-bridge-plugin/src/Shared/MessageBuffer.lua +80 -109
  459. package/templates/studio-bridge-plugin/src/Shared/MessageBuffer.spec.lua +116 -0
  460. package/templates/studio-bridge-plugin/src/Shared/StudioBridgeConnection.lua +233 -0
  461. package/templates/studio-bridge-plugin/src/Shared/StudioBridgeConnection.spec.lua +239 -0
  462. package/templates/studio-bridge-plugin/src/Shared/StudioBridgeLogUtils.lua +53 -0
  463. package/templates/studio-bridge-plugin/src/Shared/StudioBridgeLogUtils.spec.lua +27 -0
  464. package/templates/studio-bridge-plugin/src/Shared/StudioBridgeSessionUtils.lua +164 -0
  465. package/templates/studio-bridge-plugin/src/Shared/StudioBridgeSessionUtils.spec.lua +159 -0
  466. package/templates/studio-bridge-plugin/src/StudioBridgePlugin.server.lua +266 -368
  467. package/templates/studio-bridge-plugin/src/Vendor/png.lua +8 -2
  468. package/test/default.project.json +41 -0
  469. package/test/jest.config.lua +3 -0
  470. package/test/scripts/Server/ServerMain.server.lua +10 -0
  471. package/tsconfig.json +1 -0
  472. package/templates/studio-bridge-plugin/src/Shared/Protocol.lua +0 -132
  473. package/templates/studio-bridge-plugin-test/test/actions.test.lua +0 -580
  474. package/templates/studio-bridge-plugin-test/test/discovery.test.lua +0 -622
  475. package/templates/studio-bridge-plugin-test/test/execute-handler.test.lua +0 -517
  476. package/templates/studio-bridge-plugin-test/test/hash-lifecycle.test.lua +0 -814
  477. package/templates/studio-bridge-plugin-test/test/integration.test.lua +0 -1020
  478. package/templates/studio-bridge-plugin-test/test/new-actions.test.lua +0 -339
  479. package/templates/studio-bridge-plugin-test/test/plugin-entry.test.lua +0 -493
  480. package/templates/studio-bridge-plugin-test/test/protocol.test.lua +0 -506
  481. package/templates/studio-bridge-plugin-test/test/roblox-mocks.lua +0 -108
  482. package/templates/studio-bridge-plugin-test/test/test-runner.lua +0 -112
@@ -0,0 +1,127 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as os from 'os';
3
+ import * as path from 'path';
4
+ import { afterEach, beforeEach, describe, it, expect } from 'vitest';
5
+ import {
6
+ findInstancesShowingPlaceAsync,
7
+ tryFindFolderInstanceAsync,
8
+ } from './deploy-places.js';
9
+
10
+ let tempDir: string;
11
+
12
+ beforeEach(async () => {
13
+ tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'deploy-places-test-'));
14
+ await fs.writeFile(
15
+ path.join(tempDir, 'deploy.nevermore.json'),
16
+ JSON.stringify({
17
+ targets: {
18
+ test: { universeId: 9, placeId: 111, project: 'test.project.json' },
19
+ integration: {
20
+ places: [
21
+ {
22
+ name: 'hub',
23
+ universeId: 9,
24
+ placeId: 222,
25
+ project: 'hub.project.json',
26
+ },
27
+ ],
28
+ },
29
+ },
30
+ })
31
+ );
32
+ });
33
+
34
+ afterEach(async () => {
35
+ await fs.rm(tempDir, { recursive: true, force: true });
36
+ });
37
+
38
+ /** Studios by instance id, with the place id each registered and the one each has open now. */
39
+ function createConnection(
40
+ studios: Record<string, { registered: number; live: number }>
41
+ ) {
42
+ const ids = Object.keys(studios);
43
+ return {
44
+ listInstances: () =>
45
+ ids.map((instanceId) => ({
46
+ instanceId,
47
+ placeId: studios[instanceId].registered,
48
+ })),
49
+ listSessions: () =>
50
+ ids.map((instanceId) => ({
51
+ instanceId,
52
+ sessionId: `${instanceId}-edit`,
53
+ context: 'edit',
54
+ })),
55
+ getSession: (sessionId: string) => ({
56
+ queryStateAsync: async () => ({
57
+ placeId: studios[sessionId.replace(/-edit$/, '')].live,
58
+ }),
59
+ }),
60
+ } as any;
61
+ }
62
+
63
+ describe('tryFindFolderInstanceAsync', () => {
64
+ it("picks the one Studio that has a place of the folder's config open", async () => {
65
+ const connection = createConnection({
66
+ 'local-1a2b': { registered: 0, live: 0 },
67
+ 'place1-3c4d': { registered: 0, live: 222 },
68
+ });
69
+
70
+ const found = await tryFindFolderInstanceAsync(connection, tempDir);
71
+
72
+ expect(found?.instanceId).toBe('place1-3c4d');
73
+ expect(found?.deployPlace.selector).toBe('integration');
74
+ });
75
+
76
+ it('leaves the choice alone when two Studios have the config open', async () => {
77
+ const connection = createConnection({
78
+ 'place1-1a2b': { registered: 111, live: 111 },
79
+ 'place1-3c4d': { registered: 222, live: 222 },
80
+ });
81
+
82
+ expect(
83
+ await tryFindFolderInstanceAsync(connection, tempDir)
84
+ ).toBeUndefined();
85
+ });
86
+
87
+ it('leaves a single Studio to the usual resolution', async () => {
88
+ const connection = createConnection({
89
+ 'local-1a2b': { registered: 0, live: 0 },
90
+ });
91
+
92
+ expect(
93
+ await tryFindFolderInstanceAsync(connection, tempDir)
94
+ ).toBeUndefined();
95
+ });
96
+
97
+ it('does nothing outside a deploy.nevermore.json', async () => {
98
+ const empty = await fs.mkdtemp(
99
+ path.join(os.tmpdir(), 'deploy-places-empty-')
100
+ );
101
+ const connection = createConnection({
102
+ 'place1-1a2b': { registered: 111, live: 111 },
103
+ 'local-3c4d': { registered: 0, live: 0 },
104
+ });
105
+
106
+ try {
107
+ expect(
108
+ await tryFindFolderInstanceAsync(connection, empty)
109
+ ).toBeUndefined();
110
+ } finally {
111
+ await fs.rm(empty, { recursive: true, force: true });
112
+ }
113
+ });
114
+ });
115
+
116
+ describe('findInstancesShowingPlaceAsync', () => {
117
+ it('reads a cloud place id live when it registered as 0', async () => {
118
+ const connection = createConnection({
119
+ 'place1-1a2b': { registered: 0, live: 111 },
120
+ 'place1-3c4d': { registered: 222, live: 222 },
121
+ });
122
+
123
+ expect(await findInstancesShowingPlaceAsync(connection, 111)).toEqual([
124
+ 'place1-1a2b',
125
+ ]);
126
+ });
127
+ });
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Finds the places of the nearest `deploy.nevermore.json`, so commands run in
3
+ * a package or game folder can open or pick that project's Studio.
4
+ */
5
+
6
+ import * as fs from 'fs/promises';
7
+ import * as path from 'path';
8
+ import {
9
+ formatTargetSelector,
10
+ loadDeployConfigAsync,
11
+ parseTargetSelector,
12
+ resolveDefaultTargetName,
13
+ resolveDeployTargetPlaces,
14
+ type DeployConfig,
15
+ type DeployTarget,
16
+ } from '@quenty/nevermore-deploy';
17
+ import type { BridgeConnection, InstanceInfo } from '../bridge/index.js';
18
+
19
+ export const DEPLOY_CONFIG_FILE_NAME = 'deploy.nevermore.json';
20
+
21
+ const PLACE_ID_QUERY_TIMEOUT_MS = 3_000;
22
+
23
+ export interface DeployPlace {
24
+ /** The target selector naming this place, e.g. `test` or `integration.places.hub`. */
25
+ selector: string;
26
+ place: DeployTarget;
27
+ configPath: string;
28
+ }
29
+
30
+ /**
31
+ * The nearest `deploy.nevermore.json` at or above `startDir` that has targets.
32
+ * A repo root's config can hold only a shared `universeId`, so it is skipped.
33
+ */
34
+ export async function findDeployConfigPathAsync(
35
+ startDir: string
36
+ ): Promise<string | undefined> {
37
+ let current = path.resolve(startDir);
38
+ while (true) {
39
+ const candidate = path.join(current, DEPLOY_CONFIG_FILE_NAME);
40
+ if (await hasTargetsAsync(candidate)) {
41
+ return candidate;
42
+ }
43
+
44
+ const parent = path.dirname(current);
45
+ if (parent === current) {
46
+ return undefined;
47
+ }
48
+ current = parent;
49
+ }
50
+ }
51
+
52
+ async function hasTargetsAsync(configPath: string): Promise<boolean> {
53
+ let content: string;
54
+ try {
55
+ content = await fs.readFile(configPath, 'utf-8');
56
+ } catch {
57
+ return false;
58
+ }
59
+ try {
60
+ return 'targets' in (JSON.parse(content) as object);
61
+ } catch {
62
+ // Loading it reports the parse error
63
+ return true;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * The place a target names, from the config nearest `cwd`. Without `target`,
69
+ * uses the config's default target. Undefined when no config is found and no
70
+ * target was asked for.
71
+ */
72
+ export async function resolveDeployPlaceAsync(
73
+ cwd: string,
74
+ target?: string
75
+ ): Promise<DeployPlace | undefined> {
76
+ const configPath = await findDeployConfigPathAsync(cwd);
77
+ if (!configPath) {
78
+ if (target !== undefined) {
79
+ throw new Error(
80
+ `No ${DEPLOY_CONFIG_FILE_NAME} in ${cwd} or its parents to find target '${target}' in`
81
+ );
82
+ }
83
+ return undefined;
84
+ }
85
+
86
+ const config = await loadDeployConfigAsync(configPath);
87
+ let selector = target;
88
+ if (selector === undefined) {
89
+ try {
90
+ selector = resolveDefaultTargetName(config);
91
+ } catch {
92
+ throw new Error(
93
+ `${configPath} has several targets and no default. Name one: ${Object.keys(
94
+ config.targets
95
+ ).join(', ')}`
96
+ );
97
+ }
98
+ }
99
+ const places = resolveDeployTargetPlaces(config, selector);
100
+ if (places.length > 1) {
101
+ const { targetName } = parseTargetSelector(selector);
102
+ const choices = places
103
+ .map((place) =>
104
+ place.name
105
+ ? ` - ${formatTargetSelector({ targetName, placeName: place.name })}`
106
+ : ` - place ${place.placeId}`
107
+ )
108
+ .join('\n');
109
+ throw new Error(
110
+ `Target '${targetName}' has ${places.length} places. Pick one:\n${choices}`
111
+ );
112
+ }
113
+
114
+ return { selector, place: places[0], configPath };
115
+ }
116
+
117
+ /** Every place in `config`, each under the selector that names it. */
118
+ export function listDeployPlaces(
119
+ config: DeployConfig,
120
+ configPath: string
121
+ ): DeployPlace[] {
122
+ return Object.keys(config.targets).flatMap((targetName) => {
123
+ const places = resolveDeployTargetPlaces(config, targetName);
124
+ return places.map((place) => ({
125
+ selector:
126
+ places.length > 1 && place.name
127
+ ? formatTargetSelector({ targetName, placeName: place.name })
128
+ : targetName,
129
+ place,
130
+ configPath,
131
+ }));
132
+ });
133
+ }
134
+
135
+ export interface FolderInstance {
136
+ instanceId: string;
137
+ deployPlace: DeployPlace;
138
+ }
139
+
140
+ /**
141
+ * The one connected Studio that has a place of the nearest config open, when
142
+ * several Studios are connected and exactly one does. Undefined otherwise,
143
+ * including when the config can't be read.
144
+ */
145
+ export async function tryFindFolderInstanceAsync(
146
+ connection: BridgeConnection,
147
+ cwd: string
148
+ ): Promise<FolderInstance | undefined> {
149
+ const instances = connection.listInstances();
150
+ if (instances.length < 2) {
151
+ return undefined;
152
+ }
153
+
154
+ const configPath = await findDeployConfigPathAsync(cwd);
155
+ if (!configPath) {
156
+ return undefined;
157
+ }
158
+ let places: DeployPlace[];
159
+ try {
160
+ places = listDeployPlaces(
161
+ await loadDeployConfigAsync(configPath),
162
+ configPath
163
+ );
164
+ } catch {
165
+ return undefined;
166
+ }
167
+
168
+ const placeIds = await readInstancePlaceIdsAsync(connection);
169
+ const matches = placeIds.flatMap(({ instanceId, placeId }) => {
170
+ const deployPlace = places.find((p) => p.place.placeId === placeId);
171
+ return deployPlace ? [{ instanceId, deployPlace }] : [];
172
+ });
173
+ return matches.length === 1 ? matches[0] : undefined;
174
+ }
175
+
176
+ /** The connected Studios whose open place is `placeId`. */
177
+ export async function findInstancesShowingPlaceAsync(
178
+ connection: BridgeConnection,
179
+ placeId: number
180
+ ): Promise<string[]> {
181
+ const placeIds = await readInstancePlaceIdsAsync(connection);
182
+ return placeIds
183
+ .filter((instance) => instance.placeId === placeId)
184
+ .map((instance) => instance.instanceId);
185
+ }
186
+
187
+ async function readInstancePlaceIdsAsync(
188
+ connection: BridgeConnection
189
+ ): Promise<Array<{ instanceId: string; placeId: number | undefined }>> {
190
+ return Promise.all(
191
+ connection.listInstances().map(async (instance) => ({
192
+ instanceId: instance.instanceId,
193
+ placeId: await tryGetPlaceIdAsync(connection, instance),
194
+ }))
195
+ );
196
+ }
197
+
198
+ /** A cloud place loads after the plugin registers, so a registered 0 is read again live. */
199
+ async function tryGetPlaceIdAsync(
200
+ connection: BridgeConnection,
201
+ instance: InstanceInfo
202
+ ): Promise<number | undefined> {
203
+ if (instance.placeId) {
204
+ return instance.placeId;
205
+ }
206
+
207
+ const editSession = connection
208
+ .listSessions()
209
+ .find((s) => s.instanceId === instance.instanceId && s.context === 'edit');
210
+ const session = editSession && connection.getSession(editSession.sessionId);
211
+ if (!session) {
212
+ return undefined;
213
+ }
214
+
215
+ let timer: ReturnType<typeof setTimeout> | undefined;
216
+ try {
217
+ const state = await Promise.race([
218
+ session.queryStateAsync(),
219
+ new Promise<undefined>((resolve) => {
220
+ timer = setTimeout(() => resolve(undefined), PLACE_ID_QUERY_TIMEOUT_MS);
221
+ }),
222
+ ]);
223
+ return state?.placeId;
224
+ } catch {
225
+ return undefined;
226
+ } finally {
227
+ clearTimeout(timer);
228
+ }
229
+ }
package/src/index.ts CHANGED
@@ -107,11 +107,16 @@ export {
107
107
  queryLogsHandlerAsync,
108
108
  captureScreenshotHandlerAsync,
109
109
  queryDataModelHandlerAsync,
110
+ exportInstancesHandlerAsync,
111
+ importInstancesHandlerAsync,
110
112
  execHandlerAsync,
111
113
  runHandlerAsync,
112
114
  launchHandlerAsync,
113
115
  connectHandlerAsync,
114
116
  disconnectHandler,
117
+ playtestStartHandlerAsync,
118
+ playtestStopHandlerAsync,
119
+ playtestRunHandlerAsync,
115
120
  } from './commands/index.js';
116
121
 
117
122
  export type {
@@ -130,6 +135,12 @@ export type {
130
135
  ConnectOptions,
131
136
  ConnectResult,
132
137
  DisconnectResult,
138
+ PlaytestStartOptions,
139
+ PlaytestStartResult,
140
+ PlaytestStopOptions,
141
+ PlaytestStopResult,
142
+ PlaytestRunOptions,
143
+ PlaytestRunResult,
133
144
  } from './commands/index.js';
134
145
 
135
146
  // Command option/result types that conflict with bridge types are aliased
@@ -43,6 +43,10 @@ vi.mock('../process/studio-process-manager.js', () => ({
43
43
  findPluginsFolder: vi.fn(() => '/mock/plugins/folder'),
44
44
  }));
45
45
 
46
+ vi.mock('./plugin-build-version.js', () => ({
47
+ getPluginBuildVersionAsync: vi.fn().mockResolvedValue('abc123'),
48
+ }));
49
+
46
50
  vi.mock('./plugin-discovery.js', () => ({
47
51
  getPersistentPluginPath: mockGetPersistentPluginPath,
48
52
  }));
@@ -12,6 +12,7 @@ import {
12
12
  } from '@quenty/nevermore-template-helpers';
13
13
  import { findPluginsFolder } from '../process/studio-process-manager.js';
14
14
  import { getPersistentPluginPath } from './plugin-discovery.js';
15
+ import { getPluginBuildVersionAsync } from './plugin-build-version.js';
15
16
 
16
17
  const templateDir = resolveTemplatePath(
17
18
  import.meta.url,
@@ -39,7 +40,7 @@ export async function installPersistentPluginAsync(): Promise<string> {
39
40
  await TemplateHelper.createDirectoryContentsAsync(
40
41
  templateDir,
41
42
  buildContext.buildDir,
42
- {},
43
+ { PLUGIN_VERSION: await getPluginBuildVersionAsync() },
43
44
  false
44
45
  );
45
46
 
@@ -0,0 +1,55 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import * as fs from 'fs/promises';
3
+ import * as os from 'os';
4
+ import * as path from 'path';
5
+ import { hashTemplateDirAsync } from './plugin-build-version.js';
6
+
7
+ describe('hashTemplateDirAsync', () => {
8
+ const dirs: string[] = [];
9
+
10
+ afterEach(async () => {
11
+ for (const dir of dirs) {
12
+ await fs.rm(dir, { recursive: true, force: true });
13
+ }
14
+ dirs.length = 0;
15
+ });
16
+
17
+ async function makeTemplateAsync(
18
+ files: Record<string, string>
19
+ ): Promise<string> {
20
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'sb-template-'));
21
+ dirs.push(dir);
22
+ for (const [name, contents] of Object.entries(files)) {
23
+ await fs.mkdir(path.dirname(path.join(dir, name)), { recursive: true });
24
+ await fs.writeFile(path.join(dir, name), contents);
25
+ }
26
+ return dir;
27
+ }
28
+
29
+ it('changes when plugin source changes', async () => {
30
+ const before = await makeTemplateAsync({ 'src/Plugin.lua': 'print(1)' });
31
+ const after = await makeTemplateAsync({ 'src/Plugin.lua': 'print(2)' });
32
+
33
+ expect(await hashTemplateDirAsync(before)).not.toBe(
34
+ await hashTemplateDirAsync(after)
35
+ );
36
+ });
37
+
38
+ it('ignores specs and line endings', async () => {
39
+ const plain = await makeTemplateAsync({ 'src/Plugin.lua': 'a\nb\n' });
40
+ const withSpec = await makeTemplateAsync({
41
+ 'src/Plugin.lua': 'a\r\nb\r\n',
42
+ 'src/Plugin.spec.lua': 'test',
43
+ });
44
+
45
+ expect(await hashTemplateDirAsync(plain)).toBe(
46
+ await hashTemplateDirAsync(withSpec)
47
+ );
48
+ });
49
+
50
+ it('is a short hex string', async () => {
51
+ const dir = await makeTemplateAsync({ 'src/Plugin.lua': 'print(1)' });
52
+
53
+ expect(await hashTemplateDirAsync(dir)).toMatch(/^[0-9a-f]{12}$/);
54
+ });
55
+ });
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The plugin reports a hash of the template it was built from as its
3
+ * `pluginVersion`, so the CLI can tell when the installed plugin is stale.
4
+ */
5
+
6
+ import { createHash } from 'crypto';
7
+ import * as fs from 'fs/promises';
8
+ import * as path from 'path';
9
+ import { resolveTemplatePath } from '@quenty/nevermore-template-helpers';
10
+
11
+ const templateDir = resolveTemplatePath(
12
+ import.meta.url,
13
+ 'studio-bridge-plugin'
14
+ );
15
+
16
+ let cachedVersion: Promise<string> | undefined;
17
+
18
+ export function getPluginBuildVersionAsync(): Promise<string> {
19
+ cachedVersion ??= hashTemplateDirAsync(templateDir);
20
+ return cachedVersion;
21
+ }
22
+
23
+ export async function hashTemplateDirAsync(dir: string): Promise<string> {
24
+ const files = await listTemplateFilesAsync(dir, dir);
25
+ files.sort();
26
+
27
+ const hash = createHash('sha256');
28
+ for (const relativePath of files) {
29
+ const contents = await fs.readFile(path.join(dir, relativePath), 'utf-8');
30
+ hash.update(relativePath.replace(/\\/g, '/'));
31
+ hash.update('\0');
32
+ hash.update(contents.replace(/\r\n/g, '\n'));
33
+ hash.update('\0');
34
+ }
35
+ return hash.digest('hex').slice(0, 12);
36
+ }
37
+
38
+ async function listTemplateFilesAsync(
39
+ baseDir: string,
40
+ currentDir: string
41
+ ): Promise<string[]> {
42
+ const entries = await fs.readdir(currentDir, { withFileTypes: true });
43
+ const files: string[] = [];
44
+ for (const entry of entries) {
45
+ const fullPath = path.join(currentDir, entry.name);
46
+ if (entry.isDirectory()) {
47
+ files.push(...(await listTemplateFilesAsync(baseDir, fullPath)));
48
+ } else if (!entry.name.endsWith('.spec.lua')) {
49
+ files.push(path.relative(baseDir, fullPath));
50
+ }
51
+ }
52
+ return files;
53
+ }
@@ -9,6 +9,7 @@ import {
9
9
  resolveTemplatePath,
10
10
  } from '@quenty/nevermore-template-helpers';
11
11
  import { findPluginsFolder } from '../process/studio-process-manager.js';
12
+ import { getPluginBuildVersionAsync } from './plugin-build-version.js';
12
13
 
13
14
  const templateDir = resolveTemplatePath(
14
15
  import.meta.url,
@@ -43,7 +44,12 @@ export async function injectPluginAsync(
43
44
  await TemplateHelper.createDirectoryContentsAsync(
44
45
  templateDir,
45
46
  buildContext.buildDir,
46
- { PORT: String(port), SESSION_ID: sessionId, EPHEMERAL: 'true' },
47
+ {
48
+ PORT: String(port),
49
+ SESSION_ID: sessionId,
50
+ EPHEMERAL: 'true',
51
+ PLUGIN_VERSION: await getPluginBuildVersionAsync(),
52
+ },
47
53
  false
48
54
  );
49
55
 
@@ -0,0 +1,107 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ matchStudioProcesses,
4
+ parseMacProcessList,
5
+ parseWindowsProcessList,
6
+ } from './studio-process-closer.js';
7
+
8
+ describe('parseWindowsProcessList', () => {
9
+ it('parses a single process object', () => {
10
+ expect(
11
+ parseWindowsProcessList(
12
+ '{"Id":9792,"MainWindowTitle":"C:\\\\places\\\\test-place.rbxl - Roblox Studio"}'
13
+ )
14
+ ).toEqual([
15
+ { pid: 9792, windowTitle: 'C:\\places\\test-place.rbxl - Roblox Studio' },
16
+ ]);
17
+ });
18
+
19
+ it('parses an array and treats a null title as empty', () => {
20
+ expect(
21
+ parseWindowsProcessList(
22
+ '[{"Id":1,"MainWindowTitle":"Map - Roblox Studio"},{"Id":2,"MainWindowTitle":null}]'
23
+ )
24
+ ).toEqual([
25
+ { pid: 1, windowTitle: 'Map - Roblox Studio' },
26
+ { pid: 2, windowTitle: '' },
27
+ ]);
28
+ });
29
+
30
+ it('returns nothing when no Studio is running', () => {
31
+ expect(parseWindowsProcessList('')).toEqual([]);
32
+ });
33
+ });
34
+
35
+ describe('parseMacProcessList', () => {
36
+ it('keeps only Studio processes', () => {
37
+ const stdout = [
38
+ ' 101 /Applications/RobloxStudio.app/Contents/MacOS/RobloxStudio /Users/me/test-place.rbxl',
39
+ ' 102 /usr/bin/zsh',
40
+ ].join('\n');
41
+
42
+ expect(parseMacProcessList(stdout)).toEqual([
43
+ {
44
+ pid: 101,
45
+ windowTitle:
46
+ '/Applications/RobloxStudio.app/Contents/MacOS/RobloxStudio /Users/me/test-place.rbxl',
47
+ commandLine:
48
+ '/Applications/RobloxStudio.app/Contents/MacOS/RobloxStudio /Users/me/test-place.rbxl',
49
+ },
50
+ ]);
51
+ });
52
+ });
53
+
54
+ describe('matchStudioProcesses', () => {
55
+ const processes = [
56
+ { pid: 1, windowTitle: 'C:\\places\\test-place.rbxl - Roblox Studio' },
57
+ { pid: 2, windowTitle: 'Map - Roblox Studio' },
58
+ { pid: 3, windowTitle: 'Other Map - Roblox Studio' },
59
+ { pid: 4, windowTitle: '' },
60
+ ];
61
+
62
+ it('matches a local file by its path', () => {
63
+ expect(matchStudioProcesses(processes, 'test-place.rbxl')).toEqual([
64
+ processes[0],
65
+ ]);
66
+ });
67
+
68
+ it('matches a cloud place by the place id it was launched with', () => {
69
+ const cloud = [
70
+ {
71
+ pid: 5,
72
+ windowTitle: 'My Game (abc123) - Roblox Studio',
73
+ commandLine:
74
+ 'RobloxStudioBeta.exe roblox-studio:1+launchmode:edit+task:EditPlace+placeId:123+universeId:9',
75
+ },
76
+ {
77
+ pid: 6,
78
+ windowTitle: 'Other - Roblox Studio',
79
+ commandLine: 'RobloxStudioBeta.exe roblox-studio:1+placeId:1234',
80
+ },
81
+ ];
82
+
83
+ expect(matchStudioProcesses(cloud, 'Place1', 123)).toEqual([cloud[0]]);
84
+ expect(matchStudioProcesses(cloud, 'Place1')).toEqual([]);
85
+ });
86
+
87
+ it('prefers a title match over a launch place id', () => {
88
+ const cloud = [
89
+ {
90
+ pid: 5,
91
+ windowTitle: 'Other - Roblox Studio',
92
+ commandLine: 'RobloxStudioBeta.exe roblox-studio:1+placeId:123',
93
+ },
94
+ { pid: 6, windowTitle: 'Map - Roblox Studio' },
95
+ ];
96
+
97
+ expect(matchStudioProcesses(cloud, 'Map', 123)).toEqual([cloud[1]]);
98
+ });
99
+
100
+ it('matches a cloud place by its exact name', () => {
101
+ expect(matchStudioProcesses(processes, 'Map')).toEqual([processes[1]]);
102
+ });
103
+
104
+ it('matches nothing for an unknown place', () => {
105
+ expect(matchStudioProcesses(processes, 'Missing')).toEqual([]);
106
+ });
107
+ });