@sharpe-jupyter/connect 0.4.3 → 0.4.4
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/index.js +116 -36
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.tsx
|
|
4
4
|
import { render } from "ink";
|
|
5
|
+
import { readFileSync, writeFileSync, mkdirSync as mkdirSync4, existsSync as existsSync5 } from "fs";
|
|
6
|
+
import { join as join5 } from "path";
|
|
5
7
|
|
|
6
8
|
// src/app.tsx
|
|
7
9
|
import { useState, useEffect, useRef, useCallback } from "react";
|
|
@@ -458,20 +460,7 @@ function App({ connectionCode: connectionCode2, port: port2 }) {
|
|
|
458
460
|
const proc = isUvProject() ? startJupyterWithUv(port2, getUvPath()) : startJupyter(port2);
|
|
459
461
|
jupyterProc.current = proc;
|
|
460
462
|
didWeSpawnJupyter.current = true;
|
|
461
|
-
const pipeStream = (
|
|
462
|
-
if (!stream) return;
|
|
463
|
-
let buf = "";
|
|
464
|
-
stream.on("data", (data) => {
|
|
465
|
-
buf += data.toString();
|
|
466
|
-
const lines = buf.split("\n");
|
|
467
|
-
buf = lines.pop() ?? "";
|
|
468
|
-
for (const line of lines) {
|
|
469
|
-
pushProcessLog(line);
|
|
470
|
-
const msg = parseJupyterLine(line);
|
|
471
|
-
if (msg) pushEvent(msg);
|
|
472
|
-
}
|
|
473
|
-
});
|
|
474
|
-
};
|
|
463
|
+
const pipeStream = createStreamPiper(pushProcessLog, pushEvent);
|
|
475
464
|
pipeStream(proc.stderr);
|
|
476
465
|
pipeStream(proc.stdout);
|
|
477
466
|
proc.on("error", () => {
|
|
@@ -566,7 +555,13 @@ function App({ connectionCode: connectionCode2, port: port2 }) {
|
|
|
566
555
|
const proc = startJupyterWithUv(port2, uvPath);
|
|
567
556
|
jupyterProc.current = proc;
|
|
568
557
|
didWeSpawnJupyter.current = true;
|
|
569
|
-
const spawnError = await waitForJupyterHealthy(
|
|
558
|
+
const spawnError = await waitForJupyterHealthy(
|
|
559
|
+
proc,
|
|
560
|
+
port2,
|
|
561
|
+
pushEvent,
|
|
562
|
+
pushProcessLog,
|
|
563
|
+
() => cancelled
|
|
564
|
+
);
|
|
570
565
|
if (cancelled) return;
|
|
571
566
|
const errorInfo = handleJupyterSpawnError(spawnError, port2, processLogsRef.current);
|
|
572
567
|
if (errorInfo) {
|
|
@@ -652,7 +647,13 @@ function App({ connectionCode: connectionCode2, port: port2 }) {
|
|
|
652
647
|
const proc = startJupyter(port2);
|
|
653
648
|
jupyterProc.current = proc;
|
|
654
649
|
didWeSpawnJupyter.current = true;
|
|
655
|
-
const spawnError = await waitForJupyterHealthy(
|
|
650
|
+
const spawnError = await waitForJupyterHealthy(
|
|
651
|
+
proc,
|
|
652
|
+
port2,
|
|
653
|
+
pushEvent,
|
|
654
|
+
pushProcessLog,
|
|
655
|
+
() => cancelled
|
|
656
|
+
);
|
|
656
657
|
if (cancelled) return;
|
|
657
658
|
const errorInfo = handleJupyterSpawnError(spawnError, port2, processLogsRef.current);
|
|
658
659
|
if (errorInfo) {
|
|
@@ -945,24 +946,12 @@ function waitForJupyterHealthy(proc, port2, pushEvent, pushLog, isCancelled) {
|
|
|
945
946
|
done(`signal-${signal}`);
|
|
946
947
|
}
|
|
947
948
|
});
|
|
948
|
-
const pipeOutput = (
|
|
949
|
-
if (
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
buf = lines.pop() ?? "";
|
|
955
|
-
for (const line of lines) {
|
|
956
|
-
pushLog(line);
|
|
957
|
-
if (line.includes("Address already in use")) {
|
|
958
|
-
done("port-conflict");
|
|
959
|
-
return;
|
|
960
|
-
}
|
|
961
|
-
const msg = parseJupyterLine(line);
|
|
962
|
-
if (msg) pushEvent(msg);
|
|
963
|
-
}
|
|
964
|
-
});
|
|
965
|
-
};
|
|
949
|
+
const pipeOutput = createStreamPiper(pushLog, pushEvent, (line) => {
|
|
950
|
+
if (line.includes("Address already in use")) {
|
|
951
|
+
done("port-conflict");
|
|
952
|
+
return true;
|
|
953
|
+
}
|
|
954
|
+
});
|
|
966
955
|
pipeOutput(proc.stderr);
|
|
967
956
|
pipeOutput(proc.stdout);
|
|
968
957
|
let attempts = 0;
|
|
@@ -981,6 +970,23 @@ function waitForJupyterHealthy(proc, port2, pushEvent, pushLog, isCancelled) {
|
|
|
981
970
|
}, 1e3);
|
|
982
971
|
});
|
|
983
972
|
}
|
|
973
|
+
function createStreamPiper(pushLog, pushEvent, onLine) {
|
|
974
|
+
return (stream) => {
|
|
975
|
+
if (!stream) return;
|
|
976
|
+
let buf = "";
|
|
977
|
+
stream.on("data", (data) => {
|
|
978
|
+
buf += data.toString();
|
|
979
|
+
const lines = buf.split("\n");
|
|
980
|
+
buf = lines.pop() ?? "";
|
|
981
|
+
for (const line of lines) {
|
|
982
|
+
pushLog(line);
|
|
983
|
+
if (onLine?.(line)) return;
|
|
984
|
+
const msg = parseJupyterLine(line);
|
|
985
|
+
if (msg) pushEvent(msg);
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
};
|
|
989
|
+
}
|
|
984
990
|
function handleJupyterSpawnError(spawnError, port2, lastLogs) {
|
|
985
991
|
const logTail = lastLogs.length > 0 ? `
|
|
986
992
|
|
|
@@ -1019,6 +1025,70 @@ Close whatever is using it and try again, or use a different port:
|
|
|
1019
1025
|
|
|
1020
1026
|
// src/index.tsx
|
|
1021
1027
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
1028
|
+
function getConfigDir() {
|
|
1029
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
1030
|
+
return join5(home, ".sharpe");
|
|
1031
|
+
}
|
|
1032
|
+
function getConfigPath() {
|
|
1033
|
+
return join5(getConfigDir(), "config.json");
|
|
1034
|
+
}
|
|
1035
|
+
function loadSavedToken() {
|
|
1036
|
+
try {
|
|
1037
|
+
const raw = readFileSync(getConfigPath(), "utf-8");
|
|
1038
|
+
const config = JSON.parse(raw);
|
|
1039
|
+
return config.connectionCode ?? null;
|
|
1040
|
+
} catch {
|
|
1041
|
+
return null;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
function saveToken(token) {
|
|
1045
|
+
try {
|
|
1046
|
+
mkdirSync4(getConfigDir(), { recursive: true });
|
|
1047
|
+
const config = { connectionCode: token };
|
|
1048
|
+
writeFileSync(getConfigPath(), JSON.stringify(config, null, 2) + "\n");
|
|
1049
|
+
} catch {
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
function ensureShellAlias() {
|
|
1053
|
+
const home = process.env.HOME;
|
|
1054
|
+
if (!home) return;
|
|
1055
|
+
const shell = process.env.SHELL ?? "";
|
|
1056
|
+
let rcFile;
|
|
1057
|
+
if (shell.endsWith("/zsh")) {
|
|
1058
|
+
rcFile = join5(home, ".zshrc");
|
|
1059
|
+
} else if (shell.endsWith("/bash")) {
|
|
1060
|
+
const bashrc = join5(home, ".bashrc");
|
|
1061
|
+
rcFile = existsSync5(bashrc) ? bashrc : join5(home, ".bash_profile");
|
|
1062
|
+
} else {
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
const aliasLine = 'alias sharpe="npx @sharpe-jupyter/connect@latest"';
|
|
1066
|
+
try {
|
|
1067
|
+
const contents = readFileSync(rcFile, "utf-8");
|
|
1068
|
+
if (contents.includes("alias sharpe=")) return;
|
|
1069
|
+
} catch {
|
|
1070
|
+
}
|
|
1071
|
+
try {
|
|
1072
|
+
writeFileSync(rcFile, readFileSync(rcFile, "utf-8").trimEnd() + `
|
|
1073
|
+
|
|
1074
|
+
# Sharpe CLI
|
|
1075
|
+
${aliasLine}
|
|
1076
|
+
`, "utf-8");
|
|
1077
|
+
console.log(`Added "sharpe" alias to ${rcFile}`);
|
|
1078
|
+
console.log(`Restart your terminal or run: source ${rcFile}
|
|
1079
|
+
`);
|
|
1080
|
+
} catch {
|
|
1081
|
+
try {
|
|
1082
|
+
writeFileSync(rcFile, `# Sharpe CLI
|
|
1083
|
+
${aliasLine}
|
|
1084
|
+
`, "utf-8");
|
|
1085
|
+
console.log(`Added "sharpe" alias to ${rcFile}`);
|
|
1086
|
+
console.log(`Restart your terminal or run: source ${rcFile}
|
|
1087
|
+
`);
|
|
1088
|
+
} catch {
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1022
1092
|
function parseArgs(argv) {
|
|
1023
1093
|
const args = argv.slice(2);
|
|
1024
1094
|
let connectionCode2 = null;
|
|
@@ -1027,7 +1097,10 @@ function parseArgs(argv) {
|
|
|
1027
1097
|
const arg = args[i];
|
|
1028
1098
|
if (arg === "--help" || arg === "-h") {
|
|
1029
1099
|
console.log(`
|
|
1030
|
-
Usage: sharpe
|
|
1100
|
+
Usage: sharpe [connection-code] [options]
|
|
1101
|
+
|
|
1102
|
+
If a connection code is provided, it is saved to ~/.sharpe/config.json
|
|
1103
|
+
for future use. Run "sharpe" with no arguments to reuse the saved code.
|
|
1031
1104
|
|
|
1032
1105
|
Options:
|
|
1033
1106
|
--port, -p Notebook port (default: 8888)
|
|
@@ -1045,5 +1118,12 @@ Options:
|
|
|
1045
1118
|
}
|
|
1046
1119
|
return { connectionCode: connectionCode2, port: port2 };
|
|
1047
1120
|
}
|
|
1048
|
-
var { connectionCode, port } = parseArgs(process.argv);
|
|
1121
|
+
var { connectionCode: cliToken, port } = parseArgs(process.argv);
|
|
1122
|
+
var connectionCode = cliToken;
|
|
1123
|
+
if (connectionCode) {
|
|
1124
|
+
saveToken(connectionCode);
|
|
1125
|
+
ensureShellAlias();
|
|
1126
|
+
} else {
|
|
1127
|
+
connectionCode = loadSavedToken();
|
|
1128
|
+
}
|
|
1049
1129
|
render(/* @__PURE__ */ jsx2(App, { connectionCode, port }));
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sharpe-jupyter/connect",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "Connect a local JupyterHub to Sharpe via Cloudflare Tunnel",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
+
"sharpe": "dist/index.js",
|
|
7
8
|
"sharpe-connect": "dist/index.js"
|
|
8
9
|
},
|
|
9
10
|
"files": [
|