@volter/supercode-terminal 0.2.39 → 0.2.40
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/index.mjs +51 -3
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { execFile, spawn, spawnSync } from 'node:child_process';
|
|
2
2
|
import { randomBytes, randomUUID } from 'node:crypto';
|
|
3
|
-
import { existsSync } from 'node:fs';
|
|
3
|
+
import { cpSync, existsSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
4
5
|
import { connect as netConnect } from 'node:net';
|
|
5
6
|
import { fileURLToPath } from 'node:url';
|
|
6
7
|
import { createServer as defaultCreateServer } from 'node:http';
|
|
@@ -904,6 +905,20 @@ export class PtyTerminalHost {
|
|
|
904
905
|
* connection; the panes and their programs go on, and the next daemon finds them there. The server starts on first
|
|
905
906
|
* use, detached, and leaves a minute after it holds no pane and no client.
|
|
906
907
|
*/
|
|
908
|
+
/** What the pane server loads beyond this package (PtyTerminalHost's dynamic imports). */
|
|
909
|
+
const PANE_RUNTIME_PACKAGES = ['@homebridge/node-pty-prebuilt-multiarch', '@xterm/headless', '@xterm/addon-serialize'];
|
|
910
|
+
|
|
911
|
+
/** A dependency's package directory: the nearest one above its entry named for it (ESM resolution, as it is loaded). */
|
|
912
|
+
function packageDir(name) {
|
|
913
|
+
let dir = dirname(fileURLToPath(import.meta.resolve(name)));
|
|
914
|
+
for (;;) {
|
|
915
|
+
try { if (JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')).name === name) return dir; } catch { /* not a package root */ }
|
|
916
|
+
const up = dirname(dir);
|
|
917
|
+
if (up === dir) throw new Error(`no package directory for ${name}`);
|
|
918
|
+
dir = up;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
907
922
|
export class PaneServerHost {
|
|
908
923
|
#connection = null;
|
|
909
924
|
#connecting = null;
|
|
@@ -912,11 +927,44 @@ export class PaneServerHost {
|
|
|
912
927
|
#streams = new Map();
|
|
913
928
|
#pipe;
|
|
914
929
|
#serverScript;
|
|
930
|
+
#runtimeDir;
|
|
915
931
|
|
|
932
|
+
/**
|
|
933
|
+
* `runtimeDir`: where the pane server runs from, a copy of this package and its runtime dependencies per version
|
|
934
|
+
* (`<runtimeDir>/<version>`). A running pane server holds node-pty's native module open, and Windows cannot replace
|
|
935
|
+
* a file in use, so a server started from the installed package would stop the next install of it; started from its
|
|
936
|
+
* own copy, it leaves the installed package free to change under it. Without one, it runs from the package.
|
|
937
|
+
*/
|
|
916
938
|
constructor(options = {}) {
|
|
917
939
|
if (!options.pipe) throw new TypeError('PaneServerHost requires a pipe name');
|
|
918
940
|
this.#pipe = options.pipe;
|
|
919
|
-
this.#serverScript = options.serverScript ??
|
|
941
|
+
this.#serverScript = options.serverScript ?? null;
|
|
942
|
+
this.#runtimeDir = options.runtimeDir ?? null;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
/** The pane server's script: its own versioned copy under runtimeDir, made once, else the package's. */
|
|
946
|
+
#script() {
|
|
947
|
+
if (this.#serverScript) return this.#serverScript;
|
|
948
|
+
const installed = fileURLToPath(new URL('./substrate/pane-server.mjs', import.meta.url));
|
|
949
|
+
if (!this.#runtimeDir) return installed;
|
|
950
|
+
const root = fileURLToPath(new URL('.', import.meta.url));
|
|
951
|
+
const version = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')).version;
|
|
952
|
+
const target = join(this.#runtimeDir, version);
|
|
953
|
+
const script = join(target, 'node_modules', '@volter', 'supercode-terminal', 'substrate', 'pane-server.mjs');
|
|
954
|
+
if (existsSync(join(target, '.complete'))) return script;
|
|
955
|
+
try {
|
|
956
|
+
const staging = `${target}.${process.pid}.staging`;
|
|
957
|
+
rmSync(staging, { recursive: true, force: true });
|
|
958
|
+
cpSync(root, join(staging, 'node_modules', '@volter', 'supercode-terminal'), { recursive: true, filter: (path) => !path.slice(root.length).split(/[\\/]/).includes('node_modules') });
|
|
959
|
+
for (const name of PANE_RUNTIME_PACKAGES) cpSync(packageDir(name), join(staging, 'node_modules', ...name.split('/')), { recursive: true });
|
|
960
|
+
writeFileSync(join(staging, '.complete'), `${version}\n`);
|
|
961
|
+
rmSync(target, { recursive: true, force: true });
|
|
962
|
+
renameSync(staging, target);
|
|
963
|
+
return script;
|
|
964
|
+
} catch {
|
|
965
|
+
// A copy that cannot be made (a full disk, a racing daemon) leaves the package's own script, as before.
|
|
966
|
+
return existsSync(join(target, '.complete')) ? script : installed;
|
|
967
|
+
}
|
|
920
968
|
}
|
|
921
969
|
|
|
922
970
|
listSessions() { return this.#request('listSessions', []); }
|
|
@@ -998,7 +1046,7 @@ export class PaneServerHost {
|
|
|
998
1046
|
} catch (error) {
|
|
999
1047
|
// No server on the pipe yet: start one, detached from this daemon, and try again as it comes up.
|
|
1000
1048
|
if (attempt === 0 && (error.code === 'ENOENT' || error.code === 'ECONNREFUSED')) {
|
|
1001
|
-
const child = spawn(process.execPath, [this.#
|
|
1049
|
+
const child = spawn(process.execPath, [this.#script(), this.#pipe], { detached: true, stdio: 'ignore', windowsHide: true, env: process.env });
|
|
1002
1050
|
child.on('error', () => {});
|
|
1003
1051
|
child.unref();
|
|
1004
1052
|
}
|