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