@supanadit/morse-web 0.12.0 → 0.13.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/dist/server.mjs +306 -80
- package/dist/server.mjs.map +4 -4
- package/dist/ui/{chunk-BJlUk5wu.js → chunk-CVTlMVVu.js} +1 -1
- package/dist/ui/{chunk-C_meY2xd.js → chunk-D1etuzzZ.js} +1 -1
- package/dist/ui/{chunk-DxDqTq2a.js → chunk-ourdPesa.js} +1 -1
- package/dist/ui/index.html +1 -1
- package/dist/ui/main-TLDRX4Y6.js +120 -0
- package/dist/ui/webview.manifest.json +3 -3
- package/package.json +1 -1
- package/dist/ui/main-33GJA27U.js +0 -120
package/dist/server.mjs
CHANGED
|
@@ -79,7 +79,7 @@ function hasKnownType(value, allowed) {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
// ../protocol/dist/version.js
|
|
82
|
-
var PROTOCOL_VERSION =
|
|
82
|
+
var PROTOCOL_VERSION = 21;
|
|
83
83
|
var FRONTEND_MANIFEST_FILE = "webview.manifest.json";
|
|
84
84
|
var DEFAULT_WS_PATH = "/ws";
|
|
85
85
|
|
|
@@ -2145,6 +2145,17 @@ function createPiRpcAdapter(config, logger) {
|
|
|
2145
2145
|
};
|
|
2146
2146
|
}
|
|
2147
2147
|
|
|
2148
|
+
// ../host-runtime/dist/terminal-buffer.js
|
|
2149
|
+
var TERMINAL_BUFFER_CHARS = 256 * 1024;
|
|
2150
|
+
function trimTail(text, maxChars = TERMINAL_BUFFER_CHARS) {
|
|
2151
|
+
if (text.length <= maxChars) {
|
|
2152
|
+
return text;
|
|
2153
|
+
}
|
|
2154
|
+
const tail = text.slice(-maxChars);
|
|
2155
|
+
const newline = tail.indexOf("\n");
|
|
2156
|
+
return newline === -1 ? tail : tail.slice(newline + 1);
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2148
2159
|
// ../host-runtime/dist/transcript-projector.js
|
|
2149
2160
|
var TranscriptProjector = class {
|
|
2150
2161
|
options;
|
|
@@ -2671,8 +2682,15 @@ var HostSessionController = class {
|
|
|
2671
2682
|
loadingOlderHistory = false;
|
|
2672
2683
|
/** Discriminates history item ids across pages (see `historyItems`). */
|
|
2673
2684
|
historyPageSeq = 0;
|
|
2674
|
-
/**
|
|
2685
|
+
/** This client's live attachments to shells, keyed by the terminal id it named. */
|
|
2675
2686
|
terminals = /* @__PURE__ */ new Map();
|
|
2687
|
+
/**
|
|
2688
|
+
* The ids this connection has already opened. It separates a *first* open — a
|
|
2689
|
+
* page reattaching to a shell the host still runs — from a repeat open (the
|
|
2690
|
+
* Restart button), which must replace the shell and clear its scrollback
|
|
2691
|
+
* instead of replaying it into the emulator that already shows it.
|
|
2692
|
+
*/
|
|
2693
|
+
openedTerminals = /* @__PURE__ */ new Set();
|
|
2676
2694
|
/**
|
|
2677
2695
|
* Bumped whenever a terminal id is opened or closed. A shell's late `exit`
|
|
2678
2696
|
* (a replaced one, a killed one) compares its generation and stays silent, so
|
|
@@ -2721,11 +2739,12 @@ var HostSessionController = class {
|
|
|
2721
2739
|
return;
|
|
2722
2740
|
}
|
|
2723
2741
|
this.disposed = true;
|
|
2724
|
-
for (const
|
|
2725
|
-
|
|
2742
|
+
for (const session of this.terminals.values()) {
|
|
2743
|
+
session.detach();
|
|
2726
2744
|
}
|
|
2727
2745
|
this.terminals.clear();
|
|
2728
2746
|
this.terminalGenerations.clear();
|
|
2747
|
+
this.openedTerminals.clear();
|
|
2729
2748
|
this.detachRegistry?.();
|
|
2730
2749
|
this.detachRegistry = void 0;
|
|
2731
2750
|
this.detachTranscripts?.();
|
|
@@ -2830,23 +2849,38 @@ var HostSessionController = class {
|
|
|
2830
2849
|
}
|
|
2831
2850
|
}
|
|
2832
2851
|
/**
|
|
2833
|
-
*
|
|
2834
|
-
* (or the draft's), never an arbitrary path the client
|
|
2835
|
-
* still re-checks it against the host policy before
|
|
2852
|
+
* Attaches this client to a shell for the bottom panel. The directory is the
|
|
2853
|
+
* viewing session's (or the draft's), never an arbitrary path the client
|
|
2854
|
+
* names — the backend still re-checks it against the host policy before
|
|
2855
|
+
* spawning.
|
|
2856
|
+
*
|
|
2857
|
+
* A *first* open on this connection may land on a shell that is already running
|
|
2858
|
+
* (a reloaded page reattaching); a repeat open (the Restart button) replaces
|
|
2859
|
+
* it, scrollback and all, so the fresh shell does not replay the old one.
|
|
2836
2860
|
*/
|
|
2837
2861
|
async openTerminal(payload) {
|
|
2838
|
-
|
|
2862
|
+
const backend = this.options.terminal;
|
|
2863
|
+
if (backend === void 0) {
|
|
2839
2864
|
this.emitTerminalExit(payload.terminalId, void 0, "This host has no terminal.");
|
|
2840
2865
|
return;
|
|
2841
2866
|
}
|
|
2842
|
-
this.
|
|
2843
|
-
|
|
2867
|
+
const previous = this.terminals.get(payload.terminalId);
|
|
2868
|
+
if (previous !== void 0) {
|
|
2869
|
+
previous.detach();
|
|
2870
|
+
this.terminals.delete(payload.terminalId);
|
|
2871
|
+
backend.close(payload.terminalId);
|
|
2872
|
+
} else if (this.openedTerminals.has(payload.terminalId)) {
|
|
2873
|
+
backend.close(payload.terminalId);
|
|
2874
|
+
}
|
|
2875
|
+
this.openedTerminals.add(payload.terminalId);
|
|
2876
|
+
const generation = (this.terminalGenerations.get(payload.terminalId) ?? 0) + 1;
|
|
2877
|
+
this.terminalGenerations.set(payload.terminalId, generation);
|
|
2844
2878
|
const cwd = payload.cwd ?? this.currentState().workspace.cwd;
|
|
2845
2879
|
const cols = clampGeometry(payload.cols, 80);
|
|
2846
2880
|
const rows = clampGeometry(payload.rows, 24);
|
|
2847
|
-
let
|
|
2881
|
+
let session;
|
|
2848
2882
|
try {
|
|
2849
|
-
|
|
2883
|
+
session = await backend.attach(payload.terminalId, { cwd, cols, rows }, {
|
|
2850
2884
|
output: (data) => {
|
|
2851
2885
|
if (this.terminalGenerations.get(payload.terminalId) === generation) {
|
|
2852
2886
|
this.emitTerminalOutput(payload.terminalId, data);
|
|
@@ -2866,19 +2900,25 @@ var HostSessionController = class {
|
|
|
2866
2900
|
return;
|
|
2867
2901
|
}
|
|
2868
2902
|
if (!this.disposed && this.terminalGenerations.get(payload.terminalId) === generation) {
|
|
2869
|
-
this.terminals.set(payload.terminalId,
|
|
2903
|
+
this.terminals.set(payload.terminalId, session);
|
|
2904
|
+
if (session.replay.length > 0) {
|
|
2905
|
+
this.emitTerminalOutput(payload.terminalId, session.replay);
|
|
2906
|
+
}
|
|
2870
2907
|
} else {
|
|
2871
|
-
|
|
2908
|
+
session.detach();
|
|
2872
2909
|
}
|
|
2873
2910
|
}
|
|
2911
|
+
/**
|
|
2912
|
+
* Ends a shell for good: the reader closed the pane. Bumps the generation first,
|
|
2913
|
+
* so a late `exit` for the shell being replaced is not reported as news.
|
|
2914
|
+
*/
|
|
2874
2915
|
closeTerminal(terminalId) {
|
|
2875
2916
|
this.terminalGenerations.set(terminalId, (this.terminalGenerations.get(terminalId) ?? 0) + 1);
|
|
2876
|
-
const
|
|
2877
|
-
if (process2 === void 0) {
|
|
2878
|
-
return;
|
|
2879
|
-
}
|
|
2917
|
+
const session = this.terminals.get(terminalId);
|
|
2880
2918
|
this.terminals.delete(terminalId);
|
|
2881
|
-
|
|
2919
|
+
this.openedTerminals.delete(terminalId);
|
|
2920
|
+
session?.detach();
|
|
2921
|
+
this.options.terminal?.close(terminalId);
|
|
2882
2922
|
}
|
|
2883
2923
|
emitTerminalOutput(terminalId, data) {
|
|
2884
2924
|
if (data.length > 0 && !this.disposed) {
|
|
@@ -3769,7 +3809,50 @@ var ServerProjectPolicy = class {
|
|
|
3769
3809
|
// ../server/src/internal/terminal/terminal.service.ts
|
|
3770
3810
|
import { spawn as spawnPty } from "node-pty";
|
|
3771
3811
|
import { Inject as Inject2, Injectable } from "@nestjs/common";
|
|
3812
|
+
|
|
3813
|
+
// ../server/src/internal/terminal/terminal-log.ts
|
|
3814
|
+
import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
3815
|
+
import { join as join5 } from "node:path";
|
|
3816
|
+
var LOG_DIR = "terminals";
|
|
3817
|
+
var LOG_MAX_CHARS = TERMINAL_BUFFER_CHARS * 4;
|
|
3818
|
+
function readTerminalLog(dataDir, terminalId) {
|
|
3819
|
+
try {
|
|
3820
|
+
const raw = readFileSync(logPath(dataDir, terminalId), "utf8");
|
|
3821
|
+
return trimTail(raw, LOG_MAX_CHARS);
|
|
3822
|
+
} catch {
|
|
3823
|
+
return "";
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3826
|
+
function writeTerminalLog(dataDir, terminalId, text) {
|
|
3827
|
+
const trimmed = trimTail(text, LOG_MAX_CHARS);
|
|
3828
|
+
const target = logPath(dataDir, terminalId);
|
|
3829
|
+
const temp = `${target}.${process.pid}.tmp`;
|
|
3830
|
+
try {
|
|
3831
|
+
mkdirSync(join5(dataDir, LOG_DIR), { recursive: true });
|
|
3832
|
+
writeFileSync(temp, trimmed);
|
|
3833
|
+
renameSync(temp, target);
|
|
3834
|
+
} catch {
|
|
3835
|
+
try {
|
|
3836
|
+
writeFileSync(target, trimmed);
|
|
3837
|
+
} catch {
|
|
3838
|
+
rmSync(temp, { force: true });
|
|
3839
|
+
}
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
3842
|
+
function removeTerminalLog(dataDir, terminalId) {
|
|
3843
|
+
rmSync(logPath(dataDir, terminalId), { force: true });
|
|
3844
|
+
}
|
|
3845
|
+
function safeTerminalName(terminalId) {
|
|
3846
|
+
const safe = terminalId.replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 100);
|
|
3847
|
+
return safe.length > 0 ? safe : "terminal";
|
|
3848
|
+
}
|
|
3849
|
+
function logPath(dataDir, terminalId) {
|
|
3850
|
+
return join5(dataDir, LOG_DIR, `${safeTerminalName(terminalId)}.log`);
|
|
3851
|
+
}
|
|
3852
|
+
|
|
3853
|
+
// ../server/src/internal/terminal/terminal.service.ts
|
|
3772
3854
|
var SIGKILL_AFTER_MS = 1500;
|
|
3855
|
+
var FLUSH_DEBOUNCE_MS = 2e3;
|
|
3773
3856
|
function defaultShell() {
|
|
3774
3857
|
const configured = process.env.SHELL?.trim();
|
|
3775
3858
|
if (configured !== void 0 && configured.length > 0) {
|
|
@@ -3778,17 +3861,95 @@ function defaultShell() {
|
|
|
3778
3861
|
return process.platform === "win32" ? "powershell.exe" : "/bin/bash";
|
|
3779
3862
|
}
|
|
3780
3863
|
var ServerTerminalBackend = class {
|
|
3781
|
-
constructor(policy, logger) {
|
|
3864
|
+
constructor(policy, logger, config) {
|
|
3782
3865
|
this.policy = policy;
|
|
3783
3866
|
this.logger = logger;
|
|
3867
|
+
this.config = config;
|
|
3868
|
+
this.entries = /* @__PURE__ */ new Map();
|
|
3869
|
+
}
|
|
3870
|
+
attach(terminalId, options, sink) {
|
|
3871
|
+
const entry = this.entryFor(terminalId);
|
|
3872
|
+
if (entry.pty === void 0) {
|
|
3873
|
+
this.spawn(terminalId, entry, options);
|
|
3874
|
+
} else {
|
|
3875
|
+
this.resize(entry, options.cols, options.rows);
|
|
3876
|
+
}
|
|
3877
|
+
entry.sinks.add(sink);
|
|
3878
|
+
this.cancelIdle(entry);
|
|
3879
|
+
return {
|
|
3880
|
+
replay: entry.output,
|
|
3881
|
+
write: (data) => entry.pty?.write(data),
|
|
3882
|
+
resize: (cols, rows) => this.resize(entry, cols, rows),
|
|
3883
|
+
detach: () => this.detach(terminalId, entry, sink)
|
|
3884
|
+
};
|
|
3784
3885
|
}
|
|
3785
|
-
|
|
3886
|
+
/** Ends a shell for good, drops its scrollback, and tells any other viewer. */
|
|
3887
|
+
close(terminalId) {
|
|
3888
|
+
const entry = this.entries.get(terminalId);
|
|
3889
|
+
this.entries.delete(terminalId);
|
|
3890
|
+
if (entry !== void 0) {
|
|
3891
|
+
this.stopFlush(entry);
|
|
3892
|
+
this.cancelIdle(entry);
|
|
3893
|
+
entry.generation += 1;
|
|
3894
|
+
const sinks = [...entry.sinks];
|
|
3895
|
+
entry.sinks.clear();
|
|
3896
|
+
this.killPty(entry.pty);
|
|
3897
|
+
entry.pty = void 0;
|
|
3898
|
+
for (const sink of sinks) {
|
|
3899
|
+
sink.exit();
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3902
|
+
removeTerminalLog(this.config.dataDir, terminalId);
|
|
3903
|
+
}
|
|
3904
|
+
onModuleDestroy() {
|
|
3905
|
+
for (const [terminalId, entry] of this.entries) {
|
|
3906
|
+
entry.generation += 1;
|
|
3907
|
+
this.persist(terminalId, entry);
|
|
3908
|
+
this.killPty(entry.pty);
|
|
3909
|
+
entry.pty = void 0;
|
|
3910
|
+
this.stopFlush(entry);
|
|
3911
|
+
this.cancelIdle(entry);
|
|
3912
|
+
}
|
|
3913
|
+
this.entries.clear();
|
|
3914
|
+
}
|
|
3915
|
+
/** SIGTERM, then SIGKILL for a shell that ignores it (a hung process group). */
|
|
3916
|
+
killPty(pty) {
|
|
3917
|
+
if (pty === void 0) {
|
|
3918
|
+
return;
|
|
3919
|
+
}
|
|
3920
|
+
try {
|
|
3921
|
+
pty.kill();
|
|
3922
|
+
} catch {
|
|
3923
|
+
return;
|
|
3924
|
+
}
|
|
3925
|
+
const timer = setTimeout(() => {
|
|
3926
|
+
try {
|
|
3927
|
+
pty.kill("SIGKILL");
|
|
3928
|
+
} catch {
|
|
3929
|
+
}
|
|
3930
|
+
}, SIGKILL_AFTER_MS);
|
|
3931
|
+
timer.unref?.();
|
|
3932
|
+
}
|
|
3933
|
+
entryFor(terminalId) {
|
|
3934
|
+
const existing = this.entries.get(terminalId);
|
|
3935
|
+
if (existing !== void 0) {
|
|
3936
|
+
return existing;
|
|
3937
|
+
}
|
|
3938
|
+
const entry = {
|
|
3939
|
+
sinks: /* @__PURE__ */ new Set(),
|
|
3940
|
+
output: readTerminalLog(this.config.dataDir, terminalId),
|
|
3941
|
+
dirty: false,
|
|
3942
|
+
generation: 0
|
|
3943
|
+
};
|
|
3944
|
+
this.entries.set(terminalId, entry);
|
|
3945
|
+
return entry;
|
|
3946
|
+
}
|
|
3947
|
+
spawn(terminalId, entry, options) {
|
|
3786
3948
|
if (!this.policy.canOpen(options.cwd)) {
|
|
3787
|
-
throw new UnsupportedByHostError(
|
|
3788
|
-
`Morse is not allowed to run a terminal in ${options.cwd}.`
|
|
3789
|
-
);
|
|
3949
|
+
throw new UnsupportedByHostError(`Morse is not allowed to run a terminal in ${options.cwd}.`);
|
|
3790
3950
|
}
|
|
3791
3951
|
const shell = defaultShell();
|
|
3952
|
+
const generation = ++entry.generation;
|
|
3792
3953
|
const pty = spawnPty(shell, [], {
|
|
3793
3954
|
name: "xterm-256color",
|
|
3794
3955
|
cols: options.cols,
|
|
@@ -3796,41 +3957,105 @@ var ServerTerminalBackend = class {
|
|
|
3796
3957
|
cwd: options.cwd,
|
|
3797
3958
|
env: { ...process.env, TERM: "xterm-256color", MORSE_TERMINAL: "1" }
|
|
3798
3959
|
});
|
|
3960
|
+
entry.pty = pty;
|
|
3799
3961
|
this.logger.info(`Terminal opened in ${options.cwd} (${shell}, pid ${pty.pid})`);
|
|
3800
|
-
|
|
3801
|
-
|
|
3962
|
+
pty.onData((chunk) => {
|
|
3963
|
+
if (entry.generation !== generation) {
|
|
3964
|
+
return;
|
|
3965
|
+
}
|
|
3966
|
+
entry.output += chunk;
|
|
3967
|
+
if (entry.output.length > TERMINAL_BUFFER_CHARS * 1.5) {
|
|
3968
|
+
entry.output = trimTail(entry.output, TERMINAL_BUFFER_CHARS);
|
|
3969
|
+
}
|
|
3970
|
+
entry.dirty = true;
|
|
3971
|
+
this.scheduleFlush(terminalId, entry);
|
|
3972
|
+
for (const sink of entry.sinks) {
|
|
3973
|
+
sink.output(chunk);
|
|
3974
|
+
}
|
|
3975
|
+
});
|
|
3802
3976
|
pty.onExit(({ exitCode }) => {
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3977
|
+
if (entry.generation !== generation) {
|
|
3978
|
+
return;
|
|
3979
|
+
}
|
|
3980
|
+
entry.pty = void 0;
|
|
3981
|
+
this.persist(terminalId, entry);
|
|
3982
|
+
for (const sink of [...entry.sinks]) {
|
|
3983
|
+
sink.exit(exitCode);
|
|
3984
|
+
}
|
|
3806
3985
|
});
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3986
|
+
}
|
|
3987
|
+
resize(entry, cols, rows) {
|
|
3988
|
+
try {
|
|
3989
|
+
entry.pty?.resize(cols, rows);
|
|
3990
|
+
} catch {
|
|
3991
|
+
}
|
|
3992
|
+
}
|
|
3993
|
+
detach(terminalId, entry, sink) {
|
|
3994
|
+
entry.sinks.delete(sink);
|
|
3995
|
+
if (entry.sinks.size > 0) {
|
|
3996
|
+
return;
|
|
3997
|
+
}
|
|
3998
|
+
this.persist(terminalId, entry);
|
|
3999
|
+
this.scheduleIdle(terminalId, entry);
|
|
4000
|
+
}
|
|
4001
|
+
/**
|
|
4002
|
+
* Reclaims a shell nobody has reattached to. The log stays (the output should
|
|
4003
|
+
* still be there next time); only the process goes.
|
|
4004
|
+
*/
|
|
4005
|
+
scheduleIdle(terminalId, entry) {
|
|
4006
|
+
if (this.config.terminalIdleMs <= 0) {
|
|
4007
|
+
return;
|
|
4008
|
+
}
|
|
4009
|
+
this.cancelIdle(entry);
|
|
4010
|
+
entry.idleTimer = setTimeout(() => {
|
|
4011
|
+
entry.idleTimer = void 0;
|
|
4012
|
+
if (entry.sinks.size > 0) {
|
|
4013
|
+
return;
|
|
3826
4014
|
}
|
|
3827
|
-
|
|
4015
|
+
entry.generation += 1;
|
|
4016
|
+
this.killPty(entry.pty);
|
|
4017
|
+
entry.pty = void 0;
|
|
4018
|
+
this.persist(terminalId, entry);
|
|
4019
|
+
this.entries.delete(terminalId);
|
|
4020
|
+
}, this.config.terminalIdleMs);
|
|
4021
|
+
entry.idleTimer.unref?.();
|
|
4022
|
+
}
|
|
4023
|
+
cancelIdle(entry) {
|
|
4024
|
+
if (entry.idleTimer !== void 0) {
|
|
4025
|
+
clearTimeout(entry.idleTimer);
|
|
4026
|
+
entry.idleTimer = void 0;
|
|
4027
|
+
}
|
|
4028
|
+
}
|
|
4029
|
+
scheduleFlush(terminalId, entry) {
|
|
4030
|
+
if (entry.flushTimer !== void 0) {
|
|
4031
|
+
return;
|
|
4032
|
+
}
|
|
4033
|
+
entry.flushTimer = setTimeout(() => {
|
|
4034
|
+
entry.flushTimer = void 0;
|
|
4035
|
+
this.persist(terminalId, entry);
|
|
4036
|
+
}, FLUSH_DEBOUNCE_MS);
|
|
4037
|
+
entry.flushTimer.unref?.();
|
|
4038
|
+
}
|
|
4039
|
+
stopFlush(entry) {
|
|
4040
|
+
if (entry.flushTimer !== void 0) {
|
|
4041
|
+
clearTimeout(entry.flushTimer);
|
|
4042
|
+
entry.flushTimer = void 0;
|
|
4043
|
+
}
|
|
4044
|
+
}
|
|
4045
|
+
persist(terminalId, entry) {
|
|
4046
|
+
this.stopFlush(entry);
|
|
4047
|
+
if (!entry.dirty) {
|
|
4048
|
+
return;
|
|
4049
|
+
}
|
|
4050
|
+
entry.dirty = false;
|
|
4051
|
+
writeTerminalLog(this.config.dataDir, terminalId, entry.output);
|
|
3828
4052
|
}
|
|
3829
4053
|
};
|
|
3830
4054
|
ServerTerminalBackend = __decorateClass([
|
|
3831
4055
|
Injectable(),
|
|
3832
4056
|
__decorateParam(0, Inject2(MORSE_PROJECT_POLICY)),
|
|
3833
|
-
__decorateParam(1, Inject2(MORSE_LOGGER))
|
|
4057
|
+
__decorateParam(1, Inject2(MORSE_LOGGER)),
|
|
4058
|
+
__decorateParam(2, Inject2(MORSE_CONFIG))
|
|
3834
4059
|
], ServerTerminalBackend);
|
|
3835
4060
|
|
|
3836
4061
|
// ../server/src/internal/registry.ts
|
|
@@ -3872,7 +4097,7 @@ import { Inject as Inject4, Injectable as Injectable3 } from "@nestjs/common";
|
|
|
3872
4097
|
// ../server/src/internal/uploads/upload-store.ts
|
|
3873
4098
|
import { existsSync } from "node:fs";
|
|
3874
4099
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
3875
|
-
import { basename as basename2, isAbsolute as isAbsolute3, join as
|
|
4100
|
+
import { basename as basename2, isAbsolute as isAbsolute3, join as join6, relative, sep as sep3 } from "node:path";
|
|
3876
4101
|
var MAX_UPLOAD_BYTES = 10 * 1024 * 1024;
|
|
3877
4102
|
var DEFAULT_UPLOAD_DIR = ".morse/uploads";
|
|
3878
4103
|
var UploadError = class extends Error {
|
|
@@ -3892,7 +4117,7 @@ async function saveUpload(cwd, request, uploadDir = DEFAULT_UPLOAD_DIR) {
|
|
|
3892
4117
|
`That file is ${formatMb(bytes.length)} MB; the limit is ${formatMb(MAX_UPLOAD_BYTES)} MB.`
|
|
3893
4118
|
);
|
|
3894
4119
|
}
|
|
3895
|
-
const directory = isAbsolute3(uploadDir) ? uploadDir :
|
|
4120
|
+
const directory = isAbsolute3(uploadDir) ? uploadDir : join6(cwd, uploadDir);
|
|
3896
4121
|
await mkdir(directory, { recursive: true });
|
|
3897
4122
|
const target = uniquePath(directory, name);
|
|
3898
4123
|
await writeFile(target, bytes);
|
|
@@ -3911,10 +4136,10 @@ function uniquePath(directory, name) {
|
|
|
3911
4136
|
const dot = name.lastIndexOf(".");
|
|
3912
4137
|
const stem = dot > 0 ? name.slice(0, dot) : name;
|
|
3913
4138
|
const extension = dot > 0 ? name.slice(dot) : "";
|
|
3914
|
-
let candidate =
|
|
4139
|
+
let candidate = join6(directory, name);
|
|
3915
4140
|
let suffix = 1;
|
|
3916
4141
|
while (existsSync(candidate)) {
|
|
3917
|
-
candidate =
|
|
4142
|
+
candidate = join6(directory, `${stem}-${suffix}${extension}`);
|
|
3918
4143
|
suffix += 1;
|
|
3919
4144
|
}
|
|
3920
4145
|
return candidate;
|
|
@@ -3931,7 +4156,7 @@ function formatMb(bytes) {
|
|
|
3931
4156
|
// ../server/src/internal/workspace/directory-browser.ts
|
|
3932
4157
|
import { readdir as readdir3, stat as stat3 } from "node:fs/promises";
|
|
3933
4158
|
import { homedir as homedir3 } from "node:os";
|
|
3934
|
-
import { basename as basename3, dirname, isAbsolute as isAbsolute4, join as
|
|
4159
|
+
import { basename as basename3, dirname, isAbsolute as isAbsolute4, join as join7, resolve as resolve4 } from "node:path";
|
|
3935
4160
|
var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
3936
4161
|
"node_modules",
|
|
3937
4162
|
"dist",
|
|
@@ -3955,7 +4180,7 @@ async function browseDirectory(path, options) {
|
|
|
3955
4180
|
throw new Error(`A folder path must be absolute: ${requested}`);
|
|
3956
4181
|
}
|
|
3957
4182
|
const entries = await readdir3(start, { withFileTypes: true });
|
|
3958
|
-
const directories = entries.filter((entry) => entry.isDirectory()).filter((entry) => !shouldSkip(entry.name)).map((entry) => ({ name: entry.name, path:
|
|
4183
|
+
const directories = entries.filter((entry) => entry.isDirectory()).filter((entry) => !shouldSkip(entry.name)).map((entry) => ({ name: entry.name, path: join7(start, entry.name) })).sort((left, right) => left.name.localeCompare(right.name));
|
|
3959
4184
|
const parent = dirname(start);
|
|
3960
4185
|
return {
|
|
3961
4186
|
path: start,
|
|
@@ -3970,7 +4195,7 @@ function shouldSkip(name) {
|
|
|
3970
4195
|
}
|
|
3971
4196
|
async function isGitRepo(path) {
|
|
3972
4197
|
try {
|
|
3973
|
-
await stat3(
|
|
4198
|
+
await stat3(join7(path, ".git"));
|
|
3974
4199
|
return true;
|
|
3975
4200
|
} catch {
|
|
3976
4201
|
return false;
|
|
@@ -4384,7 +4609,7 @@ function gitExec(args, cwd, timeout = 5e3) {
|
|
|
4384
4609
|
// ../server/src/internal/workspace/workspace-index.ts
|
|
4385
4610
|
import { execFile as execFile2 } from "node:child_process";
|
|
4386
4611
|
import { readdir as readdir4 } from "node:fs/promises";
|
|
4387
|
-
import { join as
|
|
4612
|
+
import { join as join8, relative as relative4 } from "node:path";
|
|
4388
4613
|
var SKIPPED_DIRECTORIES2 = /* @__PURE__ */ new Set([
|
|
4389
4614
|
"node_modules",
|
|
4390
4615
|
"dist",
|
|
@@ -4495,7 +4720,7 @@ async function walk(root, directory, files) {
|
|
|
4495
4720
|
if (files.length >= MAX_INDEXED_FILES) {
|
|
4496
4721
|
return;
|
|
4497
4722
|
}
|
|
4498
|
-
const full =
|
|
4723
|
+
const full = join8(directory, entry.name);
|
|
4499
4724
|
if (entry.isDirectory()) {
|
|
4500
4725
|
if (SKIPPED_DIRECTORIES2.has(entry.name) || entry.name.startsWith(".")) {
|
|
4501
4726
|
continue;
|
|
@@ -4513,20 +4738,20 @@ function toPosix3(path) {
|
|
|
4513
4738
|
}
|
|
4514
4739
|
|
|
4515
4740
|
// ../server/src/internal/workspace/workbench-store.ts
|
|
4516
|
-
import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
4517
|
-
import { join as
|
|
4741
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
4742
|
+
import { join as join9 } from "node:path";
|
|
4518
4743
|
var LAYOUT_FILE = "workbench.json";
|
|
4519
4744
|
var DRAFTS_FILE = "drafts.json";
|
|
4520
4745
|
var LAYOUT_MAX_BYTES = 256 * 1024;
|
|
4521
4746
|
var DRAFTS_MAX_BYTES = 32 * 1024 * 1024;
|
|
4522
4747
|
function readWorkbench(dataDir) {
|
|
4523
|
-
return readSlot(
|
|
4748
|
+
return readSlot(join9(dataDir, LAYOUT_FILE), LAYOUT_MAX_BYTES);
|
|
4524
4749
|
}
|
|
4525
4750
|
function saveWorkbench(dataDir, snapshot) {
|
|
4526
4751
|
return saveSlot(dataDir, LAYOUT_FILE, snapshot, LAYOUT_MAX_BYTES);
|
|
4527
4752
|
}
|
|
4528
4753
|
function readDrafts(dataDir) {
|
|
4529
|
-
return readSlot(
|
|
4754
|
+
return readSlot(join9(dataDir, DRAFTS_FILE), DRAFTS_MAX_BYTES);
|
|
4530
4755
|
}
|
|
4531
4756
|
function saveDrafts(dataDir, snapshot) {
|
|
4532
4757
|
return saveSlot(dataDir, DRAFTS_FILE, snapshot, DRAFTS_MAX_BYTES);
|
|
@@ -4534,7 +4759,7 @@ function saveDrafts(dataDir, snapshot) {
|
|
|
4534
4759
|
function readSlot(path, maxBytes) {
|
|
4535
4760
|
let raw;
|
|
4536
4761
|
try {
|
|
4537
|
-
raw =
|
|
4762
|
+
raw = readFileSync2(path, "utf8");
|
|
4538
4763
|
} catch {
|
|
4539
4764
|
return void 0;
|
|
4540
4765
|
}
|
|
@@ -4558,19 +4783,19 @@ function saveSlot(dataDir, file, snapshot, maxBytes) {
|
|
|
4558
4783
|
if (Buffer.byteLength(serialized, "utf8") > maxBytes) {
|
|
4559
4784
|
throw new UnsupportedByHostError(`That ${file} snapshot is too large to save.`);
|
|
4560
4785
|
}
|
|
4561
|
-
const target =
|
|
4786
|
+
const target = join9(dataDir, file);
|
|
4562
4787
|
const temp = `${target}.${process.pid}.tmp`;
|
|
4563
4788
|
try {
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4789
|
+
mkdirSync2(dataDir, { recursive: true });
|
|
4790
|
+
writeFileSync2(temp, serialized);
|
|
4791
|
+
renameSync2(temp, target);
|
|
4567
4792
|
return true;
|
|
4568
4793
|
} catch {
|
|
4569
4794
|
try {
|
|
4570
|
-
|
|
4795
|
+
writeFileSync2(target, serialized);
|
|
4571
4796
|
return true;
|
|
4572
4797
|
} catch {
|
|
4573
|
-
|
|
4798
|
+
rmSync2(temp, { force: true });
|
|
4574
4799
|
return false;
|
|
4575
4800
|
}
|
|
4576
4801
|
}
|
|
@@ -4892,19 +5117,19 @@ MorseGateway = __decorateClass([
|
|
|
4892
5117
|
], MorseGateway);
|
|
4893
5118
|
|
|
4894
5119
|
// ../server/src/app/config.ts
|
|
4895
|
-
import { basename as basename4, dirname as dirname2, isAbsolute as isAbsolute6, join as
|
|
4896
|
-
import { existsSync as existsSync2, readFileSync as
|
|
5120
|
+
import { basename as basename4, dirname as dirname2, isAbsolute as isAbsolute6, join as join10 } from "node:path";
|
|
5121
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
|
|
4897
5122
|
import { homedir as homedir4 } from "node:os";
|
|
4898
5123
|
import { fileURLToPath } from "node:url";
|
|
4899
5124
|
function loadConfig(env = process.env) {
|
|
4900
5125
|
const workspacePath = resolveWorkspacePath(env);
|
|
4901
5126
|
const configuredUiDir = env.MORSE_UI_DIR;
|
|
4902
5127
|
const moduleDir = dirname2(fileURLToPath(import.meta.url));
|
|
4903
|
-
const bundledUiDir =
|
|
4904
|
-
const monorepoUiDir =
|
|
4905
|
-
const defaultUiDir = existsSync2(
|
|
5128
|
+
const bundledUiDir = join10(moduleDir, "ui");
|
|
5129
|
+
const monorepoUiDir = join10(moduleDir, "..", "..", "..", "ui-angular", "dist");
|
|
5130
|
+
const defaultUiDir = existsSync2(join10(bundledUiDir, "index.html")) ? bundledUiDir : monorepoUiDir;
|
|
4906
5131
|
const defaultHost = "127.0.0.1";
|
|
4907
|
-
const uiDir = configuredUiDir ? isAbsolute6(configuredUiDir) ? configuredUiDir :
|
|
5132
|
+
const uiDir = configuredUiDir ? isAbsolute6(configuredUiDir) ? configuredUiDir : join10(process.cwd(), configuredUiDir) : defaultUiDir;
|
|
4908
5133
|
return {
|
|
4909
5134
|
host: env.MORSE_HOST ?? defaultHost,
|
|
4910
5135
|
port: parsePositiveInt(env.MORSE_PORT, 4399),
|
|
@@ -4920,6 +5145,7 @@ function loadConfig(env = process.env) {
|
|
|
4920
5145
|
projects: parseList(env.MORSE_PROJECTS),
|
|
4921
5146
|
uploadDir: env.MORSE_UPLOAD_DIR?.trim() || DEFAULT_UPLOAD_DIR,
|
|
4922
5147
|
dataDir: resolveDataDir(env),
|
|
5148
|
+
terminalIdleMs: parsePositiveInt(env.MORSE_TERMINAL_IDLE_MS, 30 * 6e4),
|
|
4923
5149
|
updateCheck: !isOff(env.MORSE_UPDATE_CHECK),
|
|
4924
5150
|
instance: env.MORSE_INSTANCE?.trim() || void 0
|
|
4925
5151
|
};
|
|
@@ -4927,7 +5153,7 @@ function loadConfig(env = process.env) {
|
|
|
4927
5153
|
function readFrontendIdentity(uiDir) {
|
|
4928
5154
|
try {
|
|
4929
5155
|
return frontendIdentity(
|
|
4930
|
-
parseFrontendManifest(
|
|
5156
|
+
parseFrontendManifest(readFileSync3(join10(uiDir, FRONTEND_MANIFEST_FILE), "utf8"))
|
|
4931
5157
|
);
|
|
4932
5158
|
} catch {
|
|
4933
5159
|
return void 0;
|
|
@@ -4935,7 +5161,7 @@ function readFrontendIdentity(uiDir) {
|
|
|
4935
5161
|
}
|
|
4936
5162
|
function resolveDataDir(env) {
|
|
4937
5163
|
const explicit = env.MORSE_HOME?.trim();
|
|
4938
|
-
return explicit && explicit.length > 0 ? explicit :
|
|
5164
|
+
return explicit && explicit.length > 0 ? explicit : join10(homedir4(), ".morse");
|
|
4939
5165
|
}
|
|
4940
5166
|
function parsePositiveInt(value, fallback) {
|
|
4941
5167
|
if (!value) {
|
|
@@ -4962,7 +5188,7 @@ function resolveWorkspacePath(env) {
|
|
|
4962
5188
|
const start = process.cwd();
|
|
4963
5189
|
let directory = start;
|
|
4964
5190
|
for (; ; ) {
|
|
4965
|
-
const manifest =
|
|
5191
|
+
const manifest = join10(directory, "package.json");
|
|
4966
5192
|
if (existsSync2(manifest) && declaresWorkspaces(manifest)) {
|
|
4967
5193
|
return directory;
|
|
4968
5194
|
}
|
|
@@ -4975,7 +5201,7 @@ function resolveWorkspacePath(env) {
|
|
|
4975
5201
|
}
|
|
4976
5202
|
function declaresWorkspaces(manifestPath) {
|
|
4977
5203
|
try {
|
|
4978
|
-
const manifest = JSON.parse(
|
|
5204
|
+
const manifest = JSON.parse(readFileSync3(manifestPath, "utf8"));
|
|
4979
5205
|
return manifest.workspaces !== void 0;
|
|
4980
5206
|
} catch {
|
|
4981
5207
|
return false;
|