pixifact-cli 0.4.0 → 0.6.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.
@@ -1,9 +1,14 @@
1
1
  #!/usr/bin/env bun
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
2
4
  import { createPixifactAutomation } from './automation';
3
5
  import { hintForCommandError } from 'pixifact';
4
6
  import { CompileSceneError, compileScenes } from 'pixifact/compiler-node';
5
7
  import type { SceneValidationDiagnostic } from 'pixifact/compiler';
6
- import { createLiveBridgeServer } from './liveBridgeServer';
8
+ import { startEditorServer } from './editorServer';
9
+ import { captureEditorScreenshot, queryEditorContext } from './editorSession';
10
+ import { queryRuntime, queryRuntimeList } from './runtimeSession';
11
+ import type { RuntimeLogLevel, RuntimeRequest } from 'pixifact/runtime-dev';
7
12
  import {
8
13
  buildWechatTarget,
9
14
  devWechatTarget,
@@ -13,14 +18,15 @@ import {
13
18
  } from './wechatTarget';
14
19
 
15
20
  type Automation = ReturnType<typeof createPixifactAutomation>;
16
- type LiveBridge = Pick<ReturnType<typeof createLiveBridgeServer>, 'connected' | 'callAction' | 'stop'> & {
17
- waitForConnection?: (timeoutMs?: number) => Promise<void>;
18
- };
19
21
 
20
22
  interface CliOptions {
21
23
  automation?: Automation;
22
- liveBridge?: LiveBridge;
24
+ captureEditorScreenshot?: typeof captureEditorScreenshot;
23
25
  onWechatDevEvent?: (event: WechatDevEvent) => void;
26
+ readEditorContext?: typeof queryEditorContext;
27
+ listRuntimes?: typeof queryRuntimeList;
28
+ queryRuntime?: typeof queryRuntime;
29
+ startEditor?: typeof startEditorServer;
24
30
  }
25
31
 
26
32
  interface CliResult {
@@ -83,6 +89,23 @@ function projectRootFlag(flags: Record<string, string | true>) {
83
89
  return requireFlag(flags, 'project-root');
84
90
  }
85
91
 
92
+ function optionalFlag(flags: Record<string, string | true>, name: string) {
93
+ const value = flags[name];
94
+ return value === undefined ? undefined : requireFlag(flags, name);
95
+ }
96
+
97
+ function finiteNumberFlag(flags: Record<string, string | true>, name: string) {
98
+ const value = Number(requireFlag(flags, name));
99
+ if (!Number.isFinite(value)) throw new Error(`--${name} must be a finite number.`);
100
+ return value;
101
+ }
102
+
103
+ function nonNegativeInteger(value: string | undefined, error: string) {
104
+ const number = Number(value);
105
+ if (!Number.isInteger(number) || number < 0) throw new Error(error);
106
+ return number;
107
+ }
108
+
86
109
  function jsonLine(value: unknown) {
87
110
  return `${JSON.stringify(value, null, 2)}\n`;
88
111
  }
@@ -93,9 +116,25 @@ function isFailedResult(value: unknown): value is CliJsonResult {
93
116
  && (value as CliJsonResult).ok === false;
94
117
  }
95
118
 
119
+ function isRuntimeTreeResult(value: unknown): value is { runtimeId: string; root: unknown } {
120
+ return typeof value === 'object'
121
+ && value !== null
122
+ && typeof (value as { runtimeId?: unknown }).runtimeId === 'string'
123
+ && 'root' in value;
124
+ }
125
+
96
126
  function compileScenesFailure(error: unknown): CliJsonResult {
97
127
  const message = error instanceof Error ? error.message : String(error);
98
128
  if (error instanceof CompileSceneError) {
129
+ if (error.diagnostics) {
130
+ return {
131
+ ok: false,
132
+ scene: error.scene,
133
+ error: 'Scene compile failed.',
134
+ diagnostics: error.diagnostics,
135
+ hint: 'Fix the listed diagnostics, then run compile-scenes again.',
136
+ };
137
+ }
99
138
  const sourceDiagnostic = sourceDiagnosticFromMessage(error.source ?? '', message);
100
139
  if (sourceDiagnostic) {
101
140
  return {
@@ -218,10 +257,119 @@ async function executeFileCommand(
218
257
  flags: Record<string, string | true>,
219
258
  automation: Automation,
220
259
  onWechatDevEvent?: (event: WechatDevEvent) => void,
260
+ startEditor: typeof startEditorServer = startEditorServer,
261
+ readEditorContext: typeof queryEditorContext = queryEditorContext,
262
+ captureScreenshot: typeof captureEditorScreenshot = captureEditorScreenshot,
263
+ listRuntimes: typeof queryRuntimeList = queryRuntimeList,
264
+ sendRuntimeRequest: typeof queryRuntime = queryRuntime,
221
265
  ) {
222
266
  const [area, action] = positionals;
223
267
  const projectRoot = projectRootFlag(flags);
224
268
 
269
+ if (area === 'runtime' && action === 'list') {
270
+ return listRuntimes({ projectRoot });
271
+ }
272
+
273
+ if (area === 'runtime' && action !== undefined) {
274
+ const runtimeId = optionalFlag(flags, 'runtime');
275
+ let request: RuntimeRequest;
276
+ if (action === 'tree') {
277
+ request = { type: 'tree' };
278
+ } else if (action === 'node') {
279
+ request = {
280
+ type: 'node',
281
+ uid: nonNegativeInteger(
282
+ positionals[2],
283
+ 'Runtime node uid must be a non-negative integer.',
284
+ ),
285
+ };
286
+ } else if (action === 'state') {
287
+ request = { type: 'state' };
288
+ } else if (action === 'logs') {
289
+ const after = flags.after === undefined
290
+ ? undefined
291
+ : nonNegativeInteger(requireFlag(flags, 'after'), '--after must be a non-negative integer.');
292
+ const level = optionalFlag(flags, 'level');
293
+ if (level && !['debug', 'log', 'info', 'warn', 'error'].includes(level)) {
294
+ throw new Error('--level must be debug, log, info, warn, or error.');
295
+ }
296
+ request = {
297
+ type: 'logs',
298
+ ...(after === undefined ? {} : { after }),
299
+ ...(level === undefined ? {} : { level: level as RuntimeLogLevel }),
300
+ };
301
+ } else if (action === 'input') {
302
+ const inputAction = positionals[2];
303
+ if (inputAction === 'click' || inputAction === 'move') {
304
+ request = {
305
+ type: 'input',
306
+ action: inputAction,
307
+ x: finiteNumberFlag(flags, 'x'),
308
+ y: finiteNumberFlag(flags, 'y'),
309
+ };
310
+ } else if (inputAction === 'key' || inputAction === 'keydown' || inputAction === 'keyup') {
311
+ const key = positionals[3];
312
+ if (!key) throw new Error(`runtime input ${inputAction} requires a key.`);
313
+ request = { type: 'input', action: inputAction, key };
314
+ } else {
315
+ throw new Error(`Unknown Pixifact Runtime input "${inputAction ?? ''}".`);
316
+ }
317
+ } else {
318
+ throw new Error(`Unknown Pixifact CLI command "${positionals.join(' ')}".`);
319
+ }
320
+ const result = await sendRuntimeRequest({ projectRoot, runtimeId, request });
321
+ if (action !== 'tree' || flags.output === undefined || isFailedResult(result)) {
322
+ return result;
323
+ }
324
+ if (!isRuntimeTreeResult(result)) {
325
+ throw new Error('Pixifact Runtime returned an invalid tree response.');
326
+ }
327
+ const output = path.resolve(requireFlag(flags, 'output'));
328
+ const capturedAt = new Date().toISOString();
329
+ fs.mkdirSync(path.dirname(output), { recursive: true });
330
+ fs.writeFileSync(output, jsonLine({
331
+ schemaVersion: 1,
332
+ capturedAt,
333
+ runtimeId: result.runtimeId,
334
+ root: result.root,
335
+ }), 'utf8');
336
+ return {
337
+ ok: true,
338
+ output,
339
+ runtimeId: result.runtimeId,
340
+ capturedAt,
341
+ };
342
+ }
343
+
344
+ if (area === 'editor' && action === undefined) {
345
+ const session = await startEditor({ projectRoot });
346
+ return {
347
+ ok: true,
348
+ projectRoot,
349
+ url: session.url,
350
+ };
351
+ }
352
+
353
+ if (area === 'editor' && action === 'context') {
354
+ return readEditorContext({ projectRoot });
355
+ }
356
+
357
+ if (area === 'editor' && action === 'screenshot') {
358
+ const output = path.resolve(requireFlag(flags, 'output'));
359
+ const screenshot = await captureScreenshot({ projectRoot });
360
+ if (!screenshot.ok) return screenshot;
361
+ fs.writeFileSync(output, screenshot.data);
362
+ return {
363
+ ok: true,
364
+ scene: screenshot.scene,
365
+ revision: screenshot.revision,
366
+ width: screenshot.width,
367
+ height: screenshot.height,
368
+ bytes: screenshot.data.byteLength,
369
+ output,
370
+ };
371
+ }
372
+
225
373
  if ((area === 'build' || area === 'dev' || area === 'validate') && action === undefined) {
226
374
  const target = requireFlag(flags, 'target');
227
375
  if (target !== 'wechat') {
@@ -350,32 +498,7 @@ async function executeFileCommand(
350
498
  throw new Error(`Unknown Pixifact CLI command "${positionals.join(' ')}".`);
351
499
  }
352
500
 
353
- async function executeLiveCommand(positionals: string[], flags: Record<string, string | true>, bridge: LiveBridge) {
354
- if (!bridge.connected && bridge.waitForConnection) {
355
- await bridge.waitForConnection();
356
- }
357
- if (!bridge.connected) {
358
- throw new Error('No live Pixifact editor is connected.');
359
- }
360
-
361
- const [area, action] = positionals;
362
- if (area === 'summary' && action === undefined) {
363
- return bridge.callAction('summary', {});
364
- }
365
- if (area === 'scene' && action === 'get') {
366
- return bridge.callAction('scene.get', {});
367
- }
368
- if (area === 'node' && action === 'inspect') {
369
- return bridge.callAction('node.inspect', {
370
- node: requireFlag(flags, 'node'),
371
- });
372
- }
373
-
374
- throw new Error(`Unknown Pixifact live command "${positionals.join(' ')}".`);
375
- }
376
-
377
501
  export async function executePixifactCli(argv: string[], options: CliOptions = {}): Promise<CliResult> {
378
- let ownedBridge: ReturnType<typeof createLiveBridgeServer> | undefined;
379
502
  try {
380
503
  const parsed = parseArgs(argv);
381
504
  if (parsed.flags.help === true) {
@@ -394,15 +517,26 @@ export async function executePixifactCli(argv: string[], options: CliOptions = {
394
517
  aiValidationAlternatives: [
395
518
  'scene validate --all',
396
519
  ],
520
+ runtimeCommands: [
521
+ 'runtime list',
522
+ 'runtime tree [--output <json-path>] [--runtime <runtime-id>]',
523
+ 'runtime node <pixi-uid> [--runtime <runtime-id>]',
524
+ 'runtime state [--runtime <runtime-id>]',
525
+ 'runtime logs [--after <seq>] [--level <level>] [--runtime <runtime-id>]',
526
+ 'runtime input click --x <x> --y <y> [--runtime <runtime-id>]',
527
+ 'runtime input move --x <x> --y <y> [--runtime <runtime-id>]',
528
+ 'runtime input key <key> [--runtime <runtime-id>]',
529
+ 'runtime input keydown <key> [--runtime <runtime-id>]',
530
+ 'runtime input keyup <key> [--runtime <runtime-id>]',
531
+ ],
397
532
  auxiliaryCommands: [
533
+ 'editor',
534
+ 'editor context',
535
+ 'editor screenshot --output <png-path>',
398
536
  'scene create --scene <scene-path> --name <SceneName>',
399
537
  'node inspect --scene <scene-path> --node <locator>',
400
538
  ],
401
- liveContextCommands: [
402
- 'live summary',
403
- 'live scene get',
404
- 'live node inspect --node <locator>',
405
- ],
539
+ nodeLocatorSource: 'scene inspect --scene <scene-path> returns node locator values',
406
540
  defaults: {
407
541
  projectRoot: 'current working directory',
408
542
  },
@@ -412,19 +546,17 @@ export async function executePixifactCli(argv: string[], options: CliOptions = {
412
546
  }
413
547
 
414
548
  const automation = options.automation ?? createPixifactAutomation();
415
- const isLive = parsed.positionals[0] === 'live';
416
- const result = isLive
417
- ? await executeLiveCommand(
418
- parsed.positionals.slice(1),
419
- parsed.flags,
420
- options.liveBridge ?? (ownedBridge = createLiveBridgeServer()),
421
- )
422
- : await executeFileCommand(
423
- parsed.positionals,
424
- parsed.flags,
425
- automation,
426
- options.onWechatDevEvent,
427
- );
549
+ const result = await executeFileCommand(
550
+ parsed.positionals,
551
+ parsed.flags,
552
+ automation,
553
+ options.onWechatDevEvent,
554
+ options.startEditor,
555
+ options.readEditorContext,
556
+ options.captureEditorScreenshot,
557
+ options.listRuntimes,
558
+ options.queryRuntime,
559
+ );
428
560
  if (isFailedResult(result)) {
429
561
  return {
430
562
  exitCode: 1,
@@ -447,8 +579,6 @@ export async function executePixifactCli(argv: string[], options: CliOptions = {
447
579
  hint: hintForCommandError(error instanceof Error ? error.message : String(error)),
448
580
  }),
449
581
  };
450
- } finally {
451
- ownedBridge?.stop();
452
582
  }
453
583
  }
454
584
 
@@ -0,0 +1,133 @@
1
+ import {
2
+ findActiveRuntimeSession,
3
+ type RuntimeSessionDescriptor,
4
+ } from 'pixifact/compiler-node';
5
+ import type { RuntimePageDescriptor, RuntimeRequest } from 'pixifact/runtime-dev';
6
+
7
+ interface RuntimeSessionLookupOptions {
8
+ projectRoot: string;
9
+ sessionsRoot?: string;
10
+ fetch?: typeof fetch;
11
+ }
12
+
13
+ interface RuntimeQueryOptions extends RuntimeSessionLookupOptions {
14
+ runtimeId?: string;
15
+ request: RuntimeRequest;
16
+ }
17
+
18
+ type RuntimeListResult =
19
+ | { ok: true; runtimes: Array<RuntimePageDescriptor & { connectedAt: string }> }
20
+ | { ok: false; error: string; [key: string]: unknown };
21
+
22
+ async function activeDescriptor(options: RuntimeSessionLookupOptions) {
23
+ const descriptor = await findActiveRuntimeSession(options);
24
+ if (!descriptor) {
25
+ return {
26
+ ok: false as const,
27
+ error: 'No Pixifact Runtime Vite server is running for this project.',
28
+ };
29
+ }
30
+ return { ok: true as const, descriptor };
31
+ }
32
+
33
+ async function runtimeHostFetch(
34
+ descriptor: RuntimeSessionDescriptor,
35
+ pathname: string,
36
+ fetcher: typeof fetch,
37
+ init: RequestInit = {},
38
+ ) {
39
+ try {
40
+ const response = await fetcher(`${descriptor.origin}/__pixifact_runtime__/${pathname}`, {
41
+ ...init,
42
+ headers: {
43
+ authorization: `Bearer ${descriptor.token}`,
44
+ ...init.headers,
45
+ },
46
+ });
47
+ return await response.json() as Record<string, unknown>;
48
+ } catch {
49
+ return {
50
+ ok: false,
51
+ error: 'Pixifact Runtime Vite server is not reachable for this project.',
52
+ };
53
+ }
54
+ }
55
+
56
+ export async function queryRuntimeList(
57
+ options: RuntimeSessionLookupOptions,
58
+ ): Promise<RuntimeListResult> {
59
+ const active = await activeDescriptor(options);
60
+ if (!active.ok) return active;
61
+ const result = await runtimeHostFetch(active.descriptor, 'list', options.fetch ?? fetch);
62
+ if (
63
+ result.ok !== true
64
+ || !Array.isArray(result.runtimes)
65
+ || !result.runtimes.every((runtime) => (
66
+ typeof runtime === 'object'
67
+ && runtime !== null
68
+ && typeof runtime.runtimeId === 'string'
69
+ && typeof runtime.url === 'string'
70
+ && typeof runtime.title === 'string'
71
+ && typeof runtime.ready === 'boolean'
72
+ && typeof runtime.connectedAt === 'string'
73
+ ))
74
+ ) {
75
+ return result.ok === false && typeof result.error === 'string'
76
+ ? result as RuntimeListResult
77
+ : {
78
+ ok: false,
79
+ error: 'Pixifact Runtime Vite server returned an invalid runtime list.',
80
+ };
81
+ }
82
+ return result as RuntimeListResult;
83
+ }
84
+
85
+ function selectRuntime(
86
+ runtimes: Array<RuntimePageDescriptor & { connectedAt: string }>,
87
+ runtimeId: string | undefined,
88
+ ) {
89
+ if (runtimeId) {
90
+ const selected = runtimes.find((runtime) => runtime.runtimeId === runtimeId);
91
+ return selected
92
+ ? { ok: true as const, runtime: selected }
93
+ : {
94
+ ok: false as const,
95
+ error: `Pixifact Runtime "${runtimeId}" is not connected.`,
96
+ runtimes,
97
+ };
98
+ }
99
+ if (runtimes.length === 0) {
100
+ return {
101
+ ok: false as const,
102
+ error: 'No Pixifact Runtime game page is connected for this project.',
103
+ };
104
+ }
105
+ if (runtimes.length > 1) {
106
+ return {
107
+ ok: false as const,
108
+ error: 'Multiple Pixifact Runtime game pages are connected. Use --runtime <runtime-id>.',
109
+ runtimes,
110
+ };
111
+ }
112
+ return { ok: true as const, runtime: runtimes[0] };
113
+ }
114
+
115
+ export async function queryRuntime(options: RuntimeQueryOptions) {
116
+ const active = await activeDescriptor(options);
117
+ if (!active.ok) return active;
118
+ const listed = await runtimeHostFetch(active.descriptor, 'list', options.fetch ?? fetch);
119
+ if (listed.ok !== true || !Array.isArray(listed.runtimes)) return listed;
120
+ const selected = selectRuntime(
121
+ listed.runtimes as Array<RuntimePageDescriptor & { connectedAt: string }>,
122
+ options.runtimeId,
123
+ );
124
+ if (!selected.ok) return selected;
125
+ return runtimeHostFetch(active.descriptor, 'request', options.fetch ?? fetch, {
126
+ method: 'POST',
127
+ headers: { 'content-type': 'application/json' },
128
+ body: JSON.stringify({
129
+ runtimeId: selected.runtime.runtimeId,
130
+ request: options.request,
131
+ }),
132
+ });
133
+ }
package/src/liveBridge.ts DELETED
@@ -1,28 +0,0 @@
1
- export const pixifactAgentBridgePort = 8791;
2
- export const pixifactAgentBridgePath = '/pixifact-agent';
3
- export const pixifactAgentBridgeUrl = `ws://127.0.0.1:${pixifactAgentBridgePort}${pixifactAgentBridgePath}`;
4
-
5
- export type LiveBridgeRole = 'editor';
6
-
7
- export interface LiveBridgeHelloMessage {
8
- type: 'hello';
9
- role: LiveBridgeRole;
10
- }
11
-
12
- export interface LiveBridgeRequestMessage {
13
- type: 'request';
14
- id: string;
15
- action: string;
16
- arguments: unknown;
17
- }
18
-
19
- export interface LiveBridgeResponseMessage {
20
- type: 'response';
21
- id: string;
22
- ok: boolean;
23
- result?: unknown;
24
- error?: string;
25
- }
26
-
27
- export type LiveBridgeClientMessage = LiveBridgeHelloMessage | LiveBridgeResponseMessage;
28
- export type LiveBridgeServerMessage = LiveBridgeRequestMessage;
@@ -1,163 +0,0 @@
1
- import {
2
- pixifactAgentBridgePath,
3
- pixifactAgentBridgePort,
4
- type LiveBridgeClientMessage,
5
- type LiveBridgeRequestMessage,
6
- type LiveBridgeResponseMessage,
7
- } from './liveBridge';
8
-
9
- interface BunServerSocket {
10
- send(data: string): void;
11
- }
12
-
13
- interface BunServer {
14
- upgrade(request: Request): boolean;
15
- stop(force?: boolean): void;
16
- }
17
-
18
- interface BunServeOptions {
19
- hostname: string;
20
- port: number;
21
- fetch(request: Request, server: BunServer): Response | undefined;
22
- websocket: {
23
- message(socket: BunServerSocket, data: string | Uint8Array): void;
24
- close(socket: BunServerSocket): void;
25
- };
26
- }
27
-
28
- declare const Bun: {
29
- serve(options: BunServeOptions): BunServer;
30
- };
31
-
32
- interface PendingRequest {
33
- resolve(result: unknown): void;
34
- reject(error: Error): void;
35
- timer: ReturnType<typeof setTimeout>;
36
- }
37
-
38
- interface LiveBridgeServerOptions {
39
- port?: number;
40
- requestTimeoutMs?: number;
41
- }
42
-
43
- function responseError(message: LiveBridgeResponseMessage) {
44
- return new Error(message.error ?? 'Live editor request failed.');
45
- }
46
-
47
- export function createLiveBridgeServer(options: LiveBridgeServerOptions = {}) {
48
- const port = options.port ?? pixifactAgentBridgePort;
49
- const requestTimeoutMs = options.requestTimeoutMs ?? 30000;
50
- let editor: BunServerSocket | undefined;
51
- const pending = new Map<string, PendingRequest>();
52
- const connectionWaiters = new Set<() => void>();
53
- let nextRequestId = 0;
54
-
55
- function rejectPending(error: Error) {
56
- for (const pendingRequest of pending.values()) {
57
- clearTimeout(pendingRequest.timer);
58
- pendingRequest.reject(error);
59
- }
60
- pending.clear();
61
- }
62
-
63
- const server = Bun.serve({
64
- hostname: '127.0.0.1',
65
- port,
66
- fetch(request, server) {
67
- const url = new URL(request.url);
68
- if (url.pathname !== pixifactAgentBridgePath) {
69
- return new Response('Not found.', { status: 404 });
70
- }
71
- if (server.upgrade(request)) {
72
- return undefined;
73
- }
74
- return new Response('WebSocket upgrade failed.', { status: 400 });
75
- },
76
- websocket: {
77
- message(socket, data) {
78
- const message = JSON.parse(String(data)) as LiveBridgeClientMessage;
79
- if (message.type === 'hello' && message.role === 'editor') {
80
- editor = socket;
81
- for (const resolve of connectionWaiters) {
82
- resolve();
83
- }
84
- connectionWaiters.clear();
85
- return;
86
- }
87
- if (message.type !== 'response') {
88
- return;
89
- }
90
- const pendingRequest = pending.get(message.id);
91
- if (!pendingRequest) {
92
- return;
93
- }
94
- pending.delete(message.id);
95
- clearTimeout(pendingRequest.timer);
96
- if (message.ok) {
97
- pendingRequest.resolve(message.result);
98
- } else {
99
- pendingRequest.reject(responseError(message));
100
- }
101
- },
102
- close(socket) {
103
- if (socket === editor) {
104
- editor = undefined;
105
- rejectPending(new Error('Live editor disconnected.'));
106
- }
107
- },
108
- },
109
- });
110
-
111
- return {
112
- get connected() {
113
- return editor !== undefined;
114
- },
115
-
116
- async waitForConnection(timeoutMs = 3000) {
117
- if (editor) {
118
- return;
119
- }
120
- return new Promise<void>((resolve, reject) => {
121
- const timer = setTimeout(() => {
122
- connectionWaiters.delete(done);
123
- reject(new Error('No live Pixifact editor is connected.'));
124
- }, timeoutMs);
125
- function done() {
126
- clearTimeout(timer);
127
- resolve();
128
- }
129
- connectionWaiters.add(done);
130
- });
131
- },
132
-
133
- async callAction(action: string, args: unknown) {
134
- if (!editor) {
135
- throw new Error('No live Pixifact editor is connected.');
136
- }
137
- const id = `live-${Date.now()}-${nextRequestId++}`;
138
- const message: LiveBridgeRequestMessage = {
139
- type: 'request',
140
- id,
141
- action,
142
- arguments: args,
143
- };
144
- return new Promise((resolve, reject) => {
145
- const timer = setTimeout(() => {
146
- pending.delete(id);
147
- reject(new Error(`Live editor action "${action}" timed out.`));
148
- }, requestTimeoutMs);
149
- pending.set(id, {
150
- resolve,
151
- reject,
152
- timer,
153
- });
154
- editor!.send(JSON.stringify(message));
155
- });
156
- },
157
-
158
- stop() {
159
- rejectPending(new Error('Live bridge stopped.'));
160
- server.stop(true);
161
- },
162
- };
163
- }