@quenty/studio-bridge 0.15.0 → 0.17.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 +10 -0
- package/build-scripts/.luaurc +5 -0
- package/dist/src/commands/process/launch/launch.d.ts +4 -0
- package/dist/src/commands/process/launch/launch.d.ts.map +1 -1
- package/dist/src/commands/process/launch/launch.js +18 -3
- package/dist/src/commands/process/launch/launch.js.map +1 -1
- package/dist/src/commands/process/launch/launch.test.js +20 -3
- package/dist/src/commands/process/launch/launch.test.js.map +1 -1
- package/dist/src/process/studio-process-manager.d.ts +21 -2
- package/dist/src/process/studio-process-manager.d.ts.map +1 -1
- package/dist/src/process/studio-process-manager.js +32 -5
- package/dist/src/process/studio-process-manager.js.map +1 -1
- package/dist/src/process/studio-process-manager.test.js +18 -1
- package/dist/src/process/studio-process-manager.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/commands/process/launch/launch.test.ts +26 -3
- package/src/commands/process/launch/launch.ts +28 -5
- package/src/process/studio-process-manager.test.ts +36 -1
- package/src/process/studio-process-manager.ts +44 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quenty/studio-bridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "WebSocket-based bridge for running Luau scripts in Roblox Studio",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -39,5 +39,5 @@
|
|
|
39
39
|
"test:plugin": "lune run templates/studio-bridge-plugin-test/test/test-runner",
|
|
40
40
|
"test:integration": "node dist/test/integration/smoke-test.js"
|
|
41
41
|
},
|
|
42
|
-
"gitHead": "
|
|
42
|
+
"gitHead": "13f0162f4971a77378e55e9b7236aea94f0dd3a8"
|
|
43
43
|
}
|
|
@@ -29,15 +29,38 @@ describe('launchHandlerAsync', () => {
|
|
|
29
29
|
placePath: '/tmp/game.rbxl',
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
-
expect(launchStudioAsync).toHaveBeenCalledWith(
|
|
32
|
+
expect(launchStudioAsync).toHaveBeenCalledWith({
|
|
33
|
+
placePath: '/tmp/game.rbxl',
|
|
34
|
+
});
|
|
33
35
|
expect(result.launched).toBe(true);
|
|
34
36
|
expect(result.summary).toBe('Studio launched with /tmp/game.rbxl');
|
|
35
37
|
});
|
|
36
38
|
|
|
37
|
-
it('passes
|
|
39
|
+
it('passes cloud place + universe ids to launchStudioAsync', async () => {
|
|
40
|
+
const result = await launchHandlerAsync({
|
|
41
|
+
placeId: 84760113521251,
|
|
42
|
+
universeId: 9893751595,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expect(launchStudioAsync).toHaveBeenCalledWith({
|
|
46
|
+
placeId: 84760113521251,
|
|
47
|
+
universeId: 9893751595,
|
|
48
|
+
});
|
|
49
|
+
expect(result.summary).toBe(
|
|
50
|
+
'Studio launched with place 84760113521251 (universe 9893751595)'
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('summarizes a cloud place id without a universe id', async () => {
|
|
55
|
+
const result = await launchHandlerAsync({ placeId: 123 });
|
|
56
|
+
|
|
57
|
+
expect(result.summary).toBe('Studio launched with place 123');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('passes an empty options object when nothing is specified', async () => {
|
|
38
61
|
await launchHandlerAsync({});
|
|
39
62
|
|
|
40
|
-
expect(launchStudioAsync).toHaveBeenCalledWith(
|
|
63
|
+
expect(launchStudioAsync).toHaveBeenCalledWith({});
|
|
41
64
|
});
|
|
42
65
|
|
|
43
66
|
it('returns correct summary without place path', async () => {
|
|
@@ -8,6 +8,8 @@ import { launchStudioAsync } from '../../../process/studio-process-manager.js';
|
|
|
8
8
|
|
|
9
9
|
export interface LaunchOptions {
|
|
10
10
|
placePath?: string;
|
|
11
|
+
placeId?: number;
|
|
12
|
+
universeId?: number;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
export interface LaunchResult {
|
|
@@ -18,17 +20,23 @@ export interface LaunchResult {
|
|
|
18
20
|
export async function launchHandlerAsync(
|
|
19
21
|
options: LaunchOptions = {}
|
|
20
22
|
): Promise<LaunchResult> {
|
|
21
|
-
await launchStudioAsync(options
|
|
23
|
+
await launchStudioAsync(options);
|
|
24
|
+
const target =
|
|
25
|
+
options.placeId != null
|
|
26
|
+
? `place ${options.placeId}${
|
|
27
|
+
options.universeId != null ? ` (universe ${options.universeId})` : ''
|
|
28
|
+
}`
|
|
29
|
+
: options.placePath;
|
|
22
30
|
return {
|
|
23
31
|
launched: true,
|
|
24
|
-
summary: `Studio launched${
|
|
25
|
-
options.placePath ? ` with ${options.placePath}` : ''
|
|
26
|
-
}`,
|
|
32
|
+
summary: `Studio launched${target ? ` with ${target}` : ''}`,
|
|
27
33
|
};
|
|
28
34
|
}
|
|
29
35
|
|
|
30
36
|
interface LaunchArgs {
|
|
31
37
|
place?: string;
|
|
38
|
+
placeId?: number;
|
|
39
|
+
universeId?: number;
|
|
32
40
|
}
|
|
33
41
|
|
|
34
42
|
export const launchCommand = defineCommand<LaunchArgs, LaunchResult>({
|
|
@@ -40,6 +48,21 @@ export const launchCommand = defineCommand<LaunchArgs, LaunchResult>({
|
|
|
40
48
|
scope: 'standalone',
|
|
41
49
|
args: {
|
|
42
50
|
place: arg.option({ description: 'Path to a .rbxl place file' }),
|
|
51
|
+
placeId: arg.option({
|
|
52
|
+
description:
|
|
53
|
+
'Cloud place id to open in Studio (edit mode, via the EditPlace deep-link)',
|
|
54
|
+
type: 'number',
|
|
55
|
+
}),
|
|
56
|
+
universeId: arg.option({
|
|
57
|
+
description:
|
|
58
|
+
'Cloud universe id for the place (recommended with --place-id)',
|
|
59
|
+
type: 'number',
|
|
60
|
+
}),
|
|
43
61
|
},
|
|
44
|
-
handler: async (args) =>
|
|
62
|
+
handler: async (args) =>
|
|
63
|
+
launchHandlerAsync({
|
|
64
|
+
placePath: args.place,
|
|
65
|
+
placeId: args.placeId,
|
|
66
|
+
universeId: args.universeId,
|
|
67
|
+
}),
|
|
45
68
|
});
|
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
findPluginsFolder,
|
|
4
|
+
buildStudioLaunchArg,
|
|
5
|
+
} from './studio-process-manager.js';
|
|
6
|
+
|
|
7
|
+
describe('buildStudioLaunchArg', () => {
|
|
8
|
+
it('builds an EditPlace deep-link from place + universe ids', () => {
|
|
9
|
+
expect(
|
|
10
|
+
buildStudioLaunchArg({ placeId: 84760113521251, universeId: 9893751595 })
|
|
11
|
+
).toBe(
|
|
12
|
+
'roblox-studio:1+launchmode:edit+task:EditPlace+placeId:84760113521251+universeId:9893751595'
|
|
13
|
+
);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('omits universeId from the deep-link when not provided', () => {
|
|
17
|
+
expect(buildStudioLaunchArg({ placeId: 123 })).toBe(
|
|
18
|
+
'roblox-studio:1+launchmode:edit+task:EditPlace+placeId:123'
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('prefers the cloud place id over a local place path', () => {
|
|
23
|
+
expect(
|
|
24
|
+
buildStudioLaunchArg({ placeId: 5, placePath: '/tmp/game.rbxl' })
|
|
25
|
+
).toContain('placeId:5');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('returns the local place path when no place id is given', () => {
|
|
29
|
+
expect(buildStudioLaunchArg({ placePath: '/tmp/game.rbxl' })).toBe(
|
|
30
|
+
'/tmp/game.rbxl'
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('returns an empty string for no place at all', () => {
|
|
35
|
+
expect(buildStudioLaunchArg({})).toBe('');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
3
38
|
|
|
4
39
|
describe('findPluginsFolder', () => {
|
|
5
40
|
const originalPlatform = process.platform;
|
|
@@ -125,24 +125,63 @@ export interface StudioProcess {
|
|
|
125
125
|
killAsync: () => Promise<void>;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
export interface LaunchStudioOptions {
|
|
129
|
+
/** Path to a local `.rbxl` place file to open. */
|
|
130
|
+
placePath?: string;
|
|
131
|
+
/** Cloud place id to open in Studio (via the `EditPlace` deep-link). */
|
|
132
|
+
placeId?: number;
|
|
133
|
+
/** Cloud universe id for the place (recommended alongside `placeId`). */
|
|
134
|
+
universeId?: number;
|
|
135
|
+
}
|
|
136
|
+
|
|
128
137
|
/**
|
|
129
|
-
*
|
|
138
|
+
* Resolve the single argument handed to `RobloxStudioBeta`: a cloud
|
|
139
|
+
* `roblox-studio:` EditPlace deep-link when a `placeId` is given, otherwise
|
|
140
|
+
* the local `.rbxl` path (or `''` to open Studio with no place). The deep-link
|
|
141
|
+
* is exactly what the OS protocol handler passes to Studio, so Studio opens the
|
|
142
|
+
* cloud place in edit mode using the current logged-in session.
|
|
143
|
+
*/
|
|
144
|
+
export function buildStudioLaunchArg(options: LaunchStudioOptions): string {
|
|
145
|
+
if (options.placeId != null) {
|
|
146
|
+
const parts = [
|
|
147
|
+
'roblox-studio:1',
|
|
148
|
+
'launchmode:edit',
|
|
149
|
+
'task:EditPlace',
|
|
150
|
+
`placeId:${options.placeId}`,
|
|
151
|
+
];
|
|
152
|
+
if (options.universeId != null) {
|
|
153
|
+
parts.push(`universeId:${options.universeId}`);
|
|
154
|
+
}
|
|
155
|
+
return parts.join('+');
|
|
156
|
+
}
|
|
157
|
+
return options.placePath ?? '';
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Launch Roblox Studio with a local place file or a cloud place.
|
|
162
|
+
*
|
|
163
|
+
* Accepts a `LaunchStudioOptions` object (`{ placePath | placeId, universeId }`)
|
|
164
|
+
* or, for backward compatibility, a bare `.rbxl` path string.
|
|
130
165
|
*
|
|
131
166
|
* Uses Node's built-in `spawn` with `detached: true` + `unref()` so that
|
|
132
167
|
* Studio survives after the CLI process exits. execa's internal Job Object
|
|
133
168
|
* on Windows kills children on parent exit, so we avoid it here.
|
|
134
169
|
*/
|
|
135
170
|
export async function launchStudioAsync(
|
|
136
|
-
|
|
171
|
+
options: LaunchStudioOptions | string = {}
|
|
137
172
|
): Promise<StudioProcess> {
|
|
173
|
+
const resolved: LaunchStudioOptions =
|
|
174
|
+
typeof options === 'string' ? { placePath: options } : options;
|
|
175
|
+
const launchArg = buildStudioLaunchArg(resolved);
|
|
176
|
+
|
|
138
177
|
if (process.platform === 'linux') {
|
|
139
|
-
return launchStudioLinuxAsync(
|
|
178
|
+
return launchStudioLinuxAsync(launchArg);
|
|
140
179
|
}
|
|
141
180
|
|
|
142
181
|
const studioExe = await findStudioPathAsync();
|
|
143
|
-
OutputHelper.verbose(`[StudioBridge] ${studioExe} "${
|
|
182
|
+
OutputHelper.verbose(`[StudioBridge] ${studioExe} "${launchArg}"`);
|
|
144
183
|
|
|
145
|
-
const proc = spawn(studioExe,
|
|
184
|
+
const proc = spawn(studioExe, launchArg ? [launchArg] : [], {
|
|
146
185
|
detached: true,
|
|
147
186
|
stdio: 'ignore',
|
|
148
187
|
});
|