@devframes/hub 0.5.4 → 0.6.0-beta.2
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/dist/client/index.d.mts +68 -4
- package/dist/client/index.mjs +282 -3
- package/dist/constants.d.mts +1 -1
- package/dist/context-CXN9eDXS.d.mts +343 -0
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +1 -1
- package/dist/node/index.d.mts +139 -4
- package/dist/node/index.mjs +358 -51
- package/dist/settings-DJs3n2p1.d.mts +373 -0
- package/dist/types/index.d.mts +4 -4
- package/package.json +10 -6
- package/dist/context-Dox-G9vt.d.mts +0 -433
- package/dist/settings-Bp5Ax6Os.d.mts +0 -131
- /package/dist/{define-tHP6fX6A.mjs → define-Dp-x6d09.mjs} +0 -0
- /package/dist/{index-BWTfvdqa.d.mts → index-Dic9q4eN.d.mts} +0 -0
package/dist/node/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { r as defineHubRpcFunction } from "../define-
|
|
1
|
+
import { r as defineHubRpcFunction } from "../define-Dp-x6d09.mjs";
|
|
2
2
|
import { DEFAULT_STATE_USER_SETTINGS } from "../constants.mjs";
|
|
3
3
|
import { REMOTE_CONNECTION_KEY } from "devframe/constants";
|
|
4
|
+
import { createEventEmitter } from "devframe/utils/events";
|
|
4
5
|
import { createHostContext, createStorage } from "devframe/node";
|
|
5
6
|
import { debounce } from "perfect-debounce";
|
|
6
|
-
import { createEventEmitter } from "devframe/utils/events";
|
|
7
7
|
import { defineDiagnostics } from "nostics";
|
|
8
8
|
import { colors } from "devframe/utils/colors";
|
|
9
9
|
import { ansiFormatter } from "nostics/formatters/ansi";
|
|
@@ -33,8 +33,14 @@ const diagnostics = defineDiagnostics({
|
|
|
33
33
|
why: (p) => `Dock with id "${p.id}" is already registered`,
|
|
34
34
|
fix: "Use the `force` parameter to overwrite an existing registration."
|
|
35
35
|
},
|
|
36
|
-
DF8101: {
|
|
37
|
-
|
|
36
|
+
DF8101: {
|
|
37
|
+
why: (p) => `Cannot change the id of dock "${p.id}" to "${p.attempted}". Dock ids are immutable once registered`,
|
|
38
|
+
fix: (p) => `Remove \`id\` from the patch to keep updating "${p.id}", or call register() with the full entry to add "${p.attempted}" as a new dock.`
|
|
39
|
+
},
|
|
40
|
+
DF8102: {
|
|
41
|
+
why: (p) => `Dock with id "${p.id}" is not registered and cannot be updated`,
|
|
42
|
+
fix: (p) => `Call register() to add "${p.id}" as a new dock, or check the id for typos.`
|
|
43
|
+
},
|
|
38
44
|
DF8103: {
|
|
39
45
|
why: (p) => `Dock entry "${p.id}" cannot set groupId to its own id`,
|
|
40
46
|
fix: "Point groupId at a different group entry, or omit it."
|
|
@@ -43,10 +49,19 @@ const diagnostics = defineDiagnostics({
|
|
|
43
49
|
why: (p) => `Dock group "${p.id}" cannot itself belong to a group (nested groups are unsupported)`,
|
|
44
50
|
fix: "Remove groupId from the group entry; nest members one level only."
|
|
45
51
|
},
|
|
52
|
+
DF8105: {
|
|
53
|
+
why: (p) => `Devframe "${p.name}" (id "${p.id}") is already mounted on this hub`,
|
|
54
|
+
fix: "Each devframe is deduplicated by id. Set `duplicationStrategy: \"duplicate\"` on the definition to let instances coexist, `\"silent\"` to drop duplicates quietly, or `\"throw\"` to surface them as errors."
|
|
55
|
+
},
|
|
46
56
|
DF8200: { why: (p) => `Terminal session with id "${p.id}" already registered` },
|
|
47
57
|
DF8201: { why: (p) => `Terminal session with id "${p.id}" not registered` },
|
|
58
|
+
DF8202: {
|
|
59
|
+
why: (p) => `Terminal session "${p.id}" does not accept input`,
|
|
60
|
+
fix: "Spawn it via ctx.terminals.startPtySession() to get an interactive, writable session."
|
|
61
|
+
},
|
|
62
|
+
DF8203: { why: (p) => `Failed to spawn PTY session for "${p.command}": ${p.reason}` },
|
|
48
63
|
DF8400: { why: (p) => `Command "${p.id}" is already registered` },
|
|
49
|
-
DF8401: { why: "Cannot change the id of a command. Use register() to add new commands
|
|
64
|
+
DF8401: { why: "Cannot change the id of a command. Use register() to add new commands" },
|
|
50
65
|
DF8402: { why: (p) => `Command "${p.id}" is not registered` },
|
|
51
66
|
DF8403: {
|
|
52
67
|
why: (p) => `Command id "${p.id}" is already used by another command or child command`,
|
|
@@ -192,13 +207,15 @@ function buildRemoteUrl(baseUrl, payload, transport) {
|
|
|
192
207
|
}
|
|
193
208
|
var DevframeDocksHost = class {
|
|
194
209
|
context;
|
|
210
|
+
builtinDocks;
|
|
195
211
|
views = /* @__PURE__ */ new Map();
|
|
196
212
|
events = createEventEmitter();
|
|
197
213
|
userSettings = void 0;
|
|
198
214
|
/** Dock-id → allocated remote token + resolved options. */
|
|
199
215
|
remoteDocks = /* @__PURE__ */ new Map();
|
|
200
|
-
constructor(context) {
|
|
216
|
+
constructor(context, builtinDocks = {}) {
|
|
201
217
|
this.context = context;
|
|
218
|
+
this.builtinDocks = builtinDocks;
|
|
202
219
|
}
|
|
203
220
|
async init() {
|
|
204
221
|
this.userSettings = await this.context.rpc.sharedState.get("devframe:user-settings", { sharedState: createStorage({
|
|
@@ -208,36 +225,36 @@ var DevframeDocksHost = class {
|
|
|
208
225
|
}
|
|
209
226
|
values({ includeBuiltin = true } = {}) {
|
|
210
227
|
const context = this.context;
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
},
|
|
233
|
-
{
|
|
234
|
-
type: "~builtin",
|
|
235
|
-
id: "~settings",
|
|
236
|
-
title: "Settings",
|
|
237
|
-
category: "~builtin",
|
|
238
|
-
icon: "ph:gear-duotone"
|
|
228
|
+
const { terminals = true, messages = true, settings = true } = this.builtinDocks;
|
|
229
|
+
const builtinDocksEntries = [];
|
|
230
|
+
if (terminals) builtinDocksEntries.push({
|
|
231
|
+
type: "~builtin",
|
|
232
|
+
id: "~terminals",
|
|
233
|
+
title: "Terminals",
|
|
234
|
+
icon: "ph:terminal-duotone",
|
|
235
|
+
category: "~builtin",
|
|
236
|
+
get when() {
|
|
237
|
+
return context.terminals.sessions.size === 0 ? "false" : void 0;
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
if (messages) builtinDocksEntries.push({
|
|
241
|
+
type: "~builtin",
|
|
242
|
+
id: "~messages",
|
|
243
|
+
title: "Messages & Notifications",
|
|
244
|
+
icon: "ph:notification-duotone",
|
|
245
|
+
category: "~builtin",
|
|
246
|
+
get badge() {
|
|
247
|
+
const size = context.messages.entries.size;
|
|
248
|
+
return size > 0 ? String(size) : void 0;
|
|
239
249
|
}
|
|
240
|
-
|
|
250
|
+
});
|
|
251
|
+
if (settings) builtinDocksEntries.push({
|
|
252
|
+
type: "~builtin",
|
|
253
|
+
id: "~settings",
|
|
254
|
+
title: "Settings",
|
|
255
|
+
category: "~builtin",
|
|
256
|
+
icon: "ph:gear-duotone"
|
|
257
|
+
});
|
|
241
258
|
return [...Array.from(this.views.values(), (view) => this.projectView(view)), ...includeBuiltin ? builtinDocksEntries : []];
|
|
242
259
|
}
|
|
243
260
|
projectView(view) {
|
|
@@ -267,7 +284,10 @@ var DevframeDocksHost = class {
|
|
|
267
284
|
this.views.set(view.id, view);
|
|
268
285
|
this.events.emit("dock:entry:updated", view);
|
|
269
286
|
return { update: (patch) => {
|
|
270
|
-
if (patch.id && patch.id !== view.id) throw diagnostics.DF8101(
|
|
287
|
+
if (patch.id && patch.id !== view.id) throw diagnostics.DF8101({
|
|
288
|
+
id: view.id,
|
|
289
|
+
attempted: patch.id
|
|
290
|
+
});
|
|
271
291
|
this.update(Object.assign(this.views.get(view.id), patch));
|
|
272
292
|
} };
|
|
273
293
|
}
|
|
@@ -306,13 +326,6 @@ var DevframeDocksHost = class {
|
|
|
306
326
|
//#region src/node/host-messages.ts
|
|
307
327
|
const MAX_ENTRIES = 1e3;
|
|
308
328
|
const MAX_REMOVALS = 1e3;
|
|
309
|
-
function recordRemoval(removals, id, time) {
|
|
310
|
-
removals.push({
|
|
311
|
-
id,
|
|
312
|
-
time
|
|
313
|
-
});
|
|
314
|
-
if (removals.length > MAX_REMOVALS) removals.splice(0, removals.length - MAX_REMOVALS);
|
|
315
|
-
}
|
|
316
329
|
var DevframeMessagesHost = class {
|
|
317
330
|
context;
|
|
318
331
|
entries = /* @__PURE__ */ new Map();
|
|
@@ -323,9 +336,25 @@ var DevframeMessagesHost = class {
|
|
|
323
336
|
removals = [];
|
|
324
337
|
_autoDeleteTimers = /* @__PURE__ */ new Map();
|
|
325
338
|
_clock = 0;
|
|
339
|
+
/**
|
|
340
|
+
* The tick of the newest removal record dropped from the capped
|
|
341
|
+
* `removals` log — cursors older than this can't get a reliable delta
|
|
342
|
+
* and fall back to a full snapshot in {@link listSince}.
|
|
343
|
+
*/
|
|
344
|
+
_removalsTrimmedAt = 0;
|
|
326
345
|
_tick() {
|
|
327
346
|
return ++this._clock;
|
|
328
347
|
}
|
|
348
|
+
_recordRemoval(id, time) {
|
|
349
|
+
this.removals.push({
|
|
350
|
+
id,
|
|
351
|
+
time
|
|
352
|
+
});
|
|
353
|
+
if (this.removals.length > MAX_REMOVALS) {
|
|
354
|
+
const dropped = this.removals.splice(0, this.removals.length - MAX_REMOVALS);
|
|
355
|
+
this._removalsTrimmedAt = dropped[dropped.length - 1].time;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
329
358
|
constructor(context) {
|
|
330
359
|
this.context = context;
|
|
331
360
|
}
|
|
@@ -385,18 +414,75 @@ var DevframeMessagesHost = class {
|
|
|
385
414
|
}
|
|
386
415
|
this.entries.delete(id);
|
|
387
416
|
this.lastModified.delete(id);
|
|
388
|
-
|
|
417
|
+
this._recordRemoval(id, this._tick());
|
|
389
418
|
this.events.emit("message:removed", id);
|
|
390
419
|
}
|
|
420
|
+
info(message, extra) {
|
|
421
|
+
return this.add({
|
|
422
|
+
...extra,
|
|
423
|
+
message,
|
|
424
|
+
level: "info"
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
warn(message, extra) {
|
|
428
|
+
return this.add({
|
|
429
|
+
...extra,
|
|
430
|
+
message,
|
|
431
|
+
level: "warn"
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
error(message, extra) {
|
|
435
|
+
return this.add({
|
|
436
|
+
...extra,
|
|
437
|
+
message,
|
|
438
|
+
level: "error"
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
success(message, extra) {
|
|
442
|
+
return this.add({
|
|
443
|
+
...extra,
|
|
444
|
+
message,
|
|
445
|
+
level: "success"
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
debug(message, extra) {
|
|
449
|
+
return this.add({
|
|
450
|
+
...extra,
|
|
451
|
+
message,
|
|
452
|
+
level: "debug"
|
|
453
|
+
});
|
|
454
|
+
}
|
|
391
455
|
async clear() {
|
|
392
456
|
for (const timer of this._autoDeleteTimers.values()) clearTimeout(timer);
|
|
393
457
|
this._autoDeleteTimers.clear();
|
|
394
458
|
const tick = this._tick();
|
|
395
|
-
for (const id of this.entries.keys())
|
|
459
|
+
for (const id of this.entries.keys()) this._recordRemoval(id, tick);
|
|
396
460
|
this.entries.clear();
|
|
397
461
|
this.lastModified.clear();
|
|
398
462
|
this.events.emit("message:cleared");
|
|
399
463
|
}
|
|
464
|
+
listSince(since) {
|
|
465
|
+
const version = this._clock;
|
|
466
|
+
if (since == null || since < this._removalsTrimmedAt || since > version) return {
|
|
467
|
+
entries: Array.from(this.entries.values()),
|
|
468
|
+
removedIds: [],
|
|
469
|
+
version,
|
|
470
|
+
full: true
|
|
471
|
+
};
|
|
472
|
+
const entries = [];
|
|
473
|
+
for (const [id, entry] of this.entries) {
|
|
474
|
+
const mod = this.lastModified.get(id);
|
|
475
|
+
if (mod != null && mod > since) entries.push(entry);
|
|
476
|
+
}
|
|
477
|
+
const removedIds = [];
|
|
478
|
+
for (const removal of this.removals) if (removal.time > since) removedIds.push(removal.id);
|
|
479
|
+
return {
|
|
480
|
+
entries,
|
|
481
|
+
removedIds,
|
|
482
|
+
version,
|
|
483
|
+
full: false
|
|
484
|
+
};
|
|
485
|
+
}
|
|
400
486
|
_createHandle(id) {
|
|
401
487
|
const host = this;
|
|
402
488
|
return {
|
|
@@ -419,6 +505,8 @@ var DevframeMessagesHost = class {
|
|
|
419
505
|
*/
|
|
420
506
|
const TERMINAL_STREAM_CHANNEL = "devframe:terminals";
|
|
421
507
|
const TERMINAL_REPLAY_WINDOW = 1e3;
|
|
508
|
+
/** TERM handed to spawned PTYs; also used to reject fallback process labels. */
|
|
509
|
+
const PTY_TERM_NAME = "xterm-256color";
|
|
422
510
|
var DevframeTerminalsHost = class {
|
|
423
511
|
context;
|
|
424
512
|
sessions = /* @__PURE__ */ new Map();
|
|
@@ -576,10 +664,123 @@ var DevframeTerminalsHost = class {
|
|
|
576
664
|
this.register(session);
|
|
577
665
|
return Promise.resolve(session);
|
|
578
666
|
}
|
|
667
|
+
async startPtySession(executeOptions, terminal) {
|
|
668
|
+
if (this.sessions.has(terminal.id)) throw diagnostics.DF8200({ id: terminal.id });
|
|
669
|
+
const { spawn } = await import("zigpty");
|
|
670
|
+
const cols = executeOptions.cols ?? 80;
|
|
671
|
+
const rows = executeOptions.rows ?? 24;
|
|
672
|
+
let controller;
|
|
673
|
+
let pty;
|
|
674
|
+
let runId = 0;
|
|
675
|
+
let streamClosed = false;
|
|
676
|
+
const closeStream = () => {
|
|
677
|
+
if (streamClosed) return;
|
|
678
|
+
streamClosed = true;
|
|
679
|
+
try {
|
|
680
|
+
controller?.close();
|
|
681
|
+
} catch {}
|
|
682
|
+
};
|
|
683
|
+
const errorStream = (error) => {
|
|
684
|
+
if (streamClosed) return;
|
|
685
|
+
streamClosed = true;
|
|
686
|
+
try {
|
|
687
|
+
controller?.error(error);
|
|
688
|
+
} catch {}
|
|
689
|
+
};
|
|
690
|
+
const stream = new ReadableStream({
|
|
691
|
+
start(_controller) {
|
|
692
|
+
controller = _controller;
|
|
693
|
+
},
|
|
694
|
+
cancel() {
|
|
695
|
+
pty?.kill();
|
|
696
|
+
pty = void 0;
|
|
697
|
+
closeStream();
|
|
698
|
+
}
|
|
699
|
+
});
|
|
700
|
+
const spawnPty = () => {
|
|
701
|
+
const currentRun = ++runId;
|
|
702
|
+
const proc = spawn(executeOptions.command, executeOptions.args ?? [], {
|
|
703
|
+
name: PTY_TERM_NAME,
|
|
704
|
+
cols,
|
|
705
|
+
rows,
|
|
706
|
+
cwd: executeOptions.cwd ?? process$1.cwd(),
|
|
707
|
+
env: {
|
|
708
|
+
TERM: PTY_TERM_NAME,
|
|
709
|
+
COLORTERM: "truecolor",
|
|
710
|
+
FORCE_COLOR: "1",
|
|
711
|
+
...executeOptions.env ?? {}
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
proc.onData((data) => {
|
|
715
|
+
if (streamClosed || currentRun !== runId) return;
|
|
716
|
+
controller?.enqueue(typeof data === "string" ? data : data.toString("utf8"));
|
|
717
|
+
});
|
|
718
|
+
proc.onExit(() => {
|
|
719
|
+
if (currentRun === runId) closeStream();
|
|
720
|
+
});
|
|
721
|
+
return proc;
|
|
722
|
+
};
|
|
723
|
+
try {
|
|
724
|
+
pty = spawnPty();
|
|
725
|
+
} catch (error) {
|
|
726
|
+
errorStream(error);
|
|
727
|
+
throw diagnostics.DF8203({
|
|
728
|
+
command: executeOptions.command,
|
|
729
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
const session = {
|
|
733
|
+
...terminal,
|
|
734
|
+
status: "running",
|
|
735
|
+
interactive: true,
|
|
736
|
+
stream,
|
|
737
|
+
type: "pty",
|
|
738
|
+
executeOptions,
|
|
739
|
+
write: (data) => {
|
|
740
|
+
try {
|
|
741
|
+
pty?.write(data);
|
|
742
|
+
} catch {}
|
|
743
|
+
},
|
|
744
|
+
resize: (nextCols, nextRows) => {
|
|
745
|
+
try {
|
|
746
|
+
pty?.resize(Math.max(1, nextCols), Math.max(1, nextRows));
|
|
747
|
+
} catch {}
|
|
748
|
+
},
|
|
749
|
+
getProcessName: () => {
|
|
750
|
+
try {
|
|
751
|
+
const name = pty?.process;
|
|
752
|
+
return name && name !== PTY_TERM_NAME ? name : void 0;
|
|
753
|
+
} catch {
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
},
|
|
757
|
+
terminate: async () => {
|
|
758
|
+
pty?.kill();
|
|
759
|
+
pty = void 0;
|
|
760
|
+
closeStream();
|
|
761
|
+
},
|
|
762
|
+
restart: async () => {
|
|
763
|
+
pty?.kill();
|
|
764
|
+
pty = spawnPty();
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
this.register(session);
|
|
768
|
+
return session;
|
|
769
|
+
}
|
|
579
770
|
};
|
|
580
771
|
//#endregion
|
|
581
772
|
//#region src/node/rpc-builtins.ts
|
|
582
773
|
/**
|
|
774
|
+
* Resolve an interactive (PTY) terminal session by id, or throw. Sessions
|
|
775
|
+
* spawned via `startChildProcess` are output-only and are rejected here.
|
|
776
|
+
*/
|
|
777
|
+
function resolveInteractiveSession(sessions, id) {
|
|
778
|
+
const session = sessions.get(id);
|
|
779
|
+
if (!session) throw diagnostics.DF8201({ id });
|
|
780
|
+
if (typeof session.write !== "function") throw diagnostics.DF8202({ id });
|
|
781
|
+
return session;
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
583
784
|
* `hub:commands:execute` — Invoke a registered server command by id. The
|
|
584
785
|
* arguments after `id` are forwarded to the command's `handler(...)`.
|
|
585
786
|
* Returns whatever the handler returns.
|
|
@@ -595,12 +796,85 @@ const hubCommandsExecute = defineHubRpcFunction({
|
|
|
595
796
|
} })
|
|
596
797
|
});
|
|
597
798
|
/**
|
|
799
|
+
* `hub:messages:add` — Add a message from a browser client into the hub's
|
|
800
|
+
* messages subsystem. Marked `from: 'browser'`. Returns the serializable
|
|
801
|
+
* entry (the mutation handle stays server-side).
|
|
802
|
+
*
|
|
803
|
+
* Pairs with the client-side {@link import('../client').createDevframeClientHost}
|
|
804
|
+
* context, whose `messages` client dispatches through these built-ins so a
|
|
805
|
+
* dock client script can report into the same feed the server writes to.
|
|
806
|
+
*/
|
|
807
|
+
const hubMessagesAdd = defineHubRpcFunction({
|
|
808
|
+
name: "hub:messages:add",
|
|
809
|
+
type: "action",
|
|
810
|
+
jsonSerializable: true,
|
|
811
|
+
setup: (context) => ({ async handler(input) {
|
|
812
|
+
return (await context.messages.add({
|
|
813
|
+
...input,
|
|
814
|
+
from: "browser"
|
|
815
|
+
})).entry;
|
|
816
|
+
} })
|
|
817
|
+
});
|
|
818
|
+
/** `hub:messages:update` — Patch a message by id; returns the updated entry (or `undefined`). */
|
|
819
|
+
const hubMessagesUpdate = defineHubRpcFunction({
|
|
820
|
+
name: "hub:messages:update",
|
|
821
|
+
type: "action",
|
|
822
|
+
jsonSerializable: true,
|
|
823
|
+
setup: (context) => ({ async handler(id, patch) {
|
|
824
|
+
return context.messages.update(id, patch);
|
|
825
|
+
} })
|
|
826
|
+
});
|
|
827
|
+
/** `hub:messages:remove` — Remove a message by id. */
|
|
828
|
+
const hubMessagesRemove = defineHubRpcFunction({
|
|
829
|
+
name: "hub:messages:remove",
|
|
830
|
+
type: "action",
|
|
831
|
+
setup: (context) => ({ async handler(id) {
|
|
832
|
+
await context.messages.remove(id);
|
|
833
|
+
} })
|
|
834
|
+
});
|
|
835
|
+
/** `hub:messages:clear` — Remove every message. */
|
|
836
|
+
const hubMessagesClear = defineHubRpcFunction({
|
|
837
|
+
name: "hub:messages:clear",
|
|
838
|
+
type: "action",
|
|
839
|
+
setup: (context) => ({ async handler() {
|
|
840
|
+
await context.messages.clear();
|
|
841
|
+
} })
|
|
842
|
+
});
|
|
843
|
+
/**
|
|
844
|
+
* `hub:terminals:write` — Send input to an interactive PTY session spawned
|
|
845
|
+
* via `ctx.terminals.startPtySession`. Lets a hub-aware terminal UI (e.g. the
|
|
846
|
+
* terminals plugin) drive a session owned by another plugin.
|
|
847
|
+
*/
|
|
848
|
+
const hubTerminalsWrite = defineHubRpcFunction({
|
|
849
|
+
name: "hub:terminals:write",
|
|
850
|
+
type: "action",
|
|
851
|
+
setup: (context) => ({ async handler(id, data) {
|
|
852
|
+
resolveInteractiveSession(context.terminals.sessions, id).write(data);
|
|
853
|
+
} })
|
|
854
|
+
});
|
|
855
|
+
/** `hub:terminals:resize` — Resize an interactive PTY session by id. */
|
|
856
|
+
const hubTerminalsResize = defineHubRpcFunction({
|
|
857
|
+
name: "hub:terminals:resize",
|
|
858
|
+
type: "action",
|
|
859
|
+
setup: (context) => ({ async handler(id, cols, rows) {
|
|
860
|
+
resolveInteractiveSession(context.terminals.sessions, id).resize(cols, rows);
|
|
861
|
+
} })
|
|
862
|
+
});
|
|
863
|
+
/**
|
|
598
864
|
* Framework-neutral RPC declarations auto-registered by
|
|
599
865
|
* {@link createHubContext}. Provide additional RPCs by passing your own
|
|
600
866
|
* array via `CreateHubContextOptions.builtinRpcDeclarations`; the hub's
|
|
601
867
|
* list is prepended automatically.
|
|
602
868
|
*/
|
|
603
|
-
const builtinHubRpcDeclarations = [
|
|
869
|
+
const builtinHubRpcDeclarations = [
|
|
870
|
+
hubCommandsExecute,
|
|
871
|
+
hubMessagesAdd,
|
|
872
|
+
hubMessagesUpdate,
|
|
873
|
+
hubMessagesRemove,
|
|
874
|
+
hubMessagesClear,
|
|
875
|
+
hubTerminalsWrite,
|
|
876
|
+
hubTerminalsResize
|
|
877
|
+
];
|
|
604
878
|
//#endregion
|
|
605
879
|
//#region src/node/context.ts
|
|
606
880
|
/**
|
|
@@ -614,7 +888,7 @@ async function createHubContext(options) {
|
|
|
614
888
|
...options,
|
|
615
889
|
builtinRpcDeclarations: [...builtinHubRpcDeclarations, ...options.builtinRpcDeclarations ?? []]
|
|
616
890
|
});
|
|
617
|
-
const docks = new DevframeDocksHost(context);
|
|
891
|
+
const docks = new DevframeDocksHost(context, options.builtinDocks);
|
|
618
892
|
const terminals = new DevframeTerminalsHost(context);
|
|
619
893
|
const messages = new DevframeMessagesHost(context);
|
|
620
894
|
const commands = new DevframeCommandsHost(context);
|
|
@@ -680,6 +954,18 @@ async function createHubContext(options) {
|
|
|
680
954
|
//#endregion
|
|
681
955
|
//#region src/node/mount-devframe.ts
|
|
682
956
|
/**
|
|
957
|
+
* Find the next free dock id derived from `baseId`. Returns `baseId`
|
|
958
|
+
* when it is unused, otherwise appends `-2`, `-3`, … until a free slot
|
|
959
|
+
* is found. Used by the `'duplicate'` strategy so co-existing instances
|
|
960
|
+
* never collide in the dock registry.
|
|
961
|
+
*/
|
|
962
|
+
function nextAvailableDockId(views, baseId) {
|
|
963
|
+
if (!views.has(baseId)) return baseId;
|
|
964
|
+
let n = 2;
|
|
965
|
+
while (views.has(`${baseId}-${n}`)) n++;
|
|
966
|
+
return `${baseId}-${n}`;
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
683
969
|
* Framework-neutral primitive — mounts a {@link DevframeDefinition} as a
|
|
684
970
|
* dock inside a hub-aware context: serves the devframe's SPA at the
|
|
685
971
|
* resolved base path, synthesizes an iframe dock entry from the
|
|
@@ -690,10 +976,31 @@ async function createHubContext(options) {
|
|
|
690
976
|
* Vite `Plugin` whose `devtools.setup` ultimately delegates here.
|
|
691
977
|
*/
|
|
692
978
|
async function mountDevframe(ctx, d, options = {}) {
|
|
693
|
-
const
|
|
694
|
-
|
|
979
|
+
const strategy = d.duplicationStrategy ?? "warn";
|
|
980
|
+
const isDuplicate = ctx.docks.views.has(d.id);
|
|
981
|
+
if (isDuplicate && strategy !== "duplicate") {
|
|
982
|
+
if (strategy === "throw") throw diagnostics.DF8105({
|
|
983
|
+
id: d.id,
|
|
984
|
+
name: d.name
|
|
985
|
+
});
|
|
986
|
+
if (strategy === "warn") diagnostics.DF8105({
|
|
987
|
+
id: d.id,
|
|
988
|
+
name: d.name
|
|
989
|
+
});
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
const id = isDuplicate ? nextAvailableDockId(ctx.docks.views, d.id) : d.id;
|
|
993
|
+
const base = options.base ?? (id === d.id ? resolveBasePath(d, "hosted") : resolveBasePath({
|
|
994
|
+
...d,
|
|
995
|
+
id,
|
|
996
|
+
basePath: void 0
|
|
997
|
+
}, "hosted"));
|
|
998
|
+
if (d.cli?.distDir) {
|
|
999
|
+
ctx.views.hostStatic(base, resolve(d.cli.distDir));
|
|
1000
|
+
await ctx.host.mountConnectionMeta?.(base);
|
|
1001
|
+
}
|
|
695
1002
|
ctx.docks.register({
|
|
696
|
-
id
|
|
1003
|
+
id,
|
|
697
1004
|
title: d.name,
|
|
698
1005
|
icon: d.icon ?? "ph:plug-duotone",
|
|
699
1006
|
...options.dock,
|
|
@@ -6142,4 +6449,4 @@ function createSimpleClientScript(fn) {
|
|
|
6142
6449
|
};
|
|
6143
6450
|
}
|
|
6144
6451
|
//#endregion
|
|
6145
|
-
export { DevframeCommandsHost, DevframeDocksHost, DevframeMessagesHost, DevframeTerminalsHost, builtinHubRpcDeclarations, createHubContext, createSimpleClientScript, hubCommandsExecute, mountDevframe };
|
|
6452
|
+
export { DevframeCommandsHost, DevframeDocksHost, DevframeMessagesHost, DevframeTerminalsHost, builtinHubRpcDeclarations, createHubContext, createSimpleClientScript, hubCommandsExecute, hubMessagesAdd, hubMessagesClear, hubMessagesRemove, hubMessagesUpdate, hubTerminalsResize, hubTerminalsWrite, mountDevframe };
|