skydive-cli 0.6.0-beta.34 → 0.6.0-beta.36
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/js/bin.mjs +19 -19
- package/dist/js/{boot-BmJ5CAwU.mjs → boot-Euywbu3m.mjs} +3749 -3585
- package/dist/js/client-CM--T5Fy.mjs +5 -0
- package/dist/js/{client-BKr7GJE0.mjs → client-DCrQwQV7.mjs} +1 -1
- package/dist/js/{daemon-BXvcpx95.mjs → daemon-BxY38n5R.mjs} +2 -2
- package/dist/js/{daemon-DZqfbEqh.mjs → daemon-CXnHsea6.mjs} +43 -13
- package/dist/js/daemon-client-gA9ZrOBm.mjs +8 -0
- package/dist/js/{daemon-client-pcfcfzGs.mjs → daemon-client-n5zNHAld.mjs} +29 -4
- package/dist/js/{install-DfVDCs_q.mjs → install-DFMGB_wi.mjs} +2 -2
- package/dist/js/{print-DK-FfPei.mjs → print-BDW65Ro4.mjs} +1 -1
- package/dist/js/{print-TH3V1aOU.mjs → print-Dd5sckl2.mjs} +2 -2
- package/dist/js/{print-share-R6eas8fM.mjs → print-share-BuXkSsGz.mjs} +1 -1
- package/dist/js/{rest-M6NtmtbL.mjs → rest-CM76e6UD.mjs} +1 -1
- package/dist/js/{rest-ZHnvQy7G.mjs → rest-Dewe3vqZ.mjs} +1 -1
- package/package.json +5 -5
- package/dist/js/client-Dcw8Wl3E.mjs +0 -5
- package/dist/js/daemon-client-DVPTHNhZ.mjs +0 -8
|
@@ -160,7 +160,7 @@ function errorMessage(err) {
|
|
|
160
160
|
* the commit time of the built tree is one monotonic clock they share.
|
|
161
161
|
*/
|
|
162
162
|
function portalDaemonBuild() {
|
|
163
|
-
return "
|
|
163
|
+
return "1790359164";
|
|
164
164
|
}
|
|
165
165
|
/**
|
|
166
166
|
* Whether a client carrying `mine` should replace a running daemon carrying
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./client-
|
|
2
|
+
import "./client-DCrQwQV7.mjs";
|
|
3
3
|
import "./tls-cert-Rua2oV7n.mjs";
|
|
4
4
|
import "./api-BFQ4PQDA.mjs";
|
|
5
|
-
import { a as runPortalDaemon, c as stopDaemonForHandoff, i as queryDaemonStatus, n as ensureDaemonRunning, o as startPortalDaemon, r as isDaemonListening, s as stopDaemon, t as PortalDaemon } from "./daemon-
|
|
5
|
+
import { a as runPortalDaemon, c as stopDaemonForHandoff, i as queryDaemonStatus, n as ensureDaemonRunning, o as startPortalDaemon, r as isDaemonListening, s as stopDaemon, t as PortalDaemon } from "./daemon-CXnHsea6.mjs";
|
|
6
6
|
|
|
7
7
|
export { runPortalDaemon };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { i as isRecord, n as isNewerBuild, r as portalDaemonBuild, t as PortalClient } from "./client-
|
|
2
|
+
import { i as isRecord, n as isNewerBuild, r as portalDaemonBuild, t as PortalClient } from "./client-DCrQwQV7.mjs";
|
|
3
3
|
import { t as defaultTlsCertSource } from "./tls-cert-Rua2oV7n.mjs";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
@@ -66,6 +66,18 @@ function canonicalPortalAppUrl(appUrl) {
|
|
|
66
66
|
if (host === "skydive.com" || host === "www.skydive.com" || host === "api.skydive.com") return "https://api.skydive.com";
|
|
67
67
|
return trimmed;
|
|
68
68
|
}
|
|
69
|
+
/** Prefix every Windows named pipe lives under (the pipe filesystem root). */
|
|
70
|
+
const WINDOWS_PIPE_PREFIX = "\\\\.\\pipe\\";
|
|
71
|
+
/**
|
|
72
|
+
* Whether a daemon control-socket path is a Windows named pipe rather than a
|
|
73
|
+
* Unix-domain-socket FILE. Pipes have no on-disk file: they cannot be
|
|
74
|
+
* `stat`ed, `unlink`ed, or purged by a tmp cleaner, and the OS frees the name
|
|
75
|
+
* the moment the owning server closes — so every file-based lifecycle step
|
|
76
|
+
* (stale-file cleanup, the socket-ownership watch) must be skipped for them.
|
|
77
|
+
*/
|
|
78
|
+
function isNamedPipePath(socketPath) {
|
|
79
|
+
return socketPath.startsWith(WINDOWS_PIPE_PREFIX);
|
|
80
|
+
}
|
|
69
81
|
/**
|
|
70
82
|
* Per-user, per-app daemon socket + state paths. Scoped by OS user and a hash
|
|
71
83
|
* of the CANONICAL appUrl so two logins or two deployments (prod vs a PR
|
|
@@ -74,6 +86,12 @@ function canonicalPortalAppUrl(appUrl) {
|
|
|
74
86
|
* one. Kept under the OS temp dir because Unix socket paths are
|
|
75
87
|
* length-limited (~104 bytes on macOS).
|
|
76
88
|
*
|
|
89
|
+
* On Windows a `node:net` server cannot listen on a filesystem path at all —
|
|
90
|
+
* binding the tmp-dir path fails with EACCES and the daemon dies silently
|
|
91
|
+
* (ANY-7285's Windows facet) — so the socket is a named pipe under
|
|
92
|
+
* `\\.\pipe\`, keyed by the same (user, appUrl-hash) pair. State and log
|
|
93
|
+
* files stay under the temp dir on every platform.
|
|
94
|
+
*
|
|
77
95
|
* Darwin `sockaddr_un.sun_path` is 104 bytes including NUL (103 usable).
|
|
78
96
|
* `os.tmpdir()` on macOS is `$TMPDIR` (`/var/folders/<id>/T`, often ~50
|
|
79
97
|
* chars). Adding `skydive-portal-<user>-<hash>/daemon.sock` overruns that
|
|
@@ -84,18 +102,17 @@ function canonicalPortalAppUrl(appUrl) {
|
|
|
84
102
|
* sun_path, so they keep the long leaf.
|
|
85
103
|
*/
|
|
86
104
|
const SUN_PATH_MAX = 103;
|
|
87
|
-
function daemonPaths(appUrl) {
|
|
105
|
+
function daemonPaths(appUrl, platform = process.platform) {
|
|
88
106
|
const parent = os.tmpdir();
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
const dir = path.join(parent, `skydive-portal-${user}-${app}`);
|
|
107
|
+
const key = `skydive-portal-${safeSegment(os.userInfo().username || "user")}-${createHash("sha256").update(canonicalPortalAppUrl(appUrl)).digest("hex").slice(0, 12)}`;
|
|
108
|
+
const dir = path.join(parent, key);
|
|
92
109
|
const paths = {
|
|
93
110
|
dir,
|
|
94
|
-
socketPath: path.join(dir, "daemon.sock"),
|
|
111
|
+
socketPath: platform === "win32" ? `${WINDOWS_PIPE_PREFIX}${key}` : path.join(dir, "daemon.sock"),
|
|
95
112
|
statePath: path.join(dir, "state.json"),
|
|
96
113
|
logPath: path.join(dir, "daemon.log")
|
|
97
114
|
};
|
|
98
|
-
if (
|
|
115
|
+
if (platform === "win32" || Buffer.byteLength(paths.socketPath) <= SUN_PATH_MAX) return paths;
|
|
99
116
|
const short = createHash("sha256").update(`${os.userInfo().username || "user"}\0${canonicalPortalAppUrl(appUrl)}`).digest("hex").slice(0, 12);
|
|
100
117
|
const shortDir = path.join(parent, `skydive-p-${short}`);
|
|
101
118
|
return {
|
|
@@ -412,8 +429,10 @@ var PortalDaemon = class {
|
|
|
412
429
|
resolve();
|
|
413
430
|
});
|
|
414
431
|
});
|
|
415
|
-
|
|
416
|
-
|
|
432
|
+
if (!isNamedPipePath(this.paths.socketPath)) {
|
|
433
|
+
await this.recordSocketIdentity();
|
|
434
|
+
this.startSocketWatch();
|
|
435
|
+
}
|
|
417
436
|
}
|
|
418
437
|
/**
|
|
419
438
|
* Remember which socket FILE we bound (dev+ino), so the watch can tell "our
|
|
@@ -510,6 +529,10 @@ var PortalDaemon = class {
|
|
|
510
529
|
this.exit();
|
|
511
530
|
}
|
|
512
531
|
async clearStaleSocket() {
|
|
532
|
+
if (isNamedPipePath(this.paths.socketPath)) {
|
|
533
|
+
if (await isDaemonListening(this.paths.socketPath)) throw new Error("daemon already running");
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
513
536
|
if (!await pathExists(this.paths.socketPath)) return;
|
|
514
537
|
if (await new Promise((resolve) => {
|
|
515
538
|
const probe = connect(this.paths.socketPath);
|
|
@@ -806,13 +829,19 @@ var PortalDaemon = class {
|
|
|
806
829
|
* drains attached clients, who reconnect within ~500ms) and spawn from this
|
|
807
830
|
* binary. Both spawners apply the same rule, so a host converges on the
|
|
808
831
|
* newest installed build with at most one bounce.
|
|
832
|
+
*
|
|
833
|
+
* Returns whether a daemon is actually listening when it's done. A spawned
|
|
834
|
+
* daemon that never binds (its process died on startup — e.g. the Windows
|
|
835
|
+
* EACCES bind failure this used to swallow) resolves `false` so callers can
|
|
836
|
+
* SAY so, instead of silently handing the caller a socket nobody serves and
|
|
837
|
+
* leaving the reconnect loop to spin forever with no visible error.
|
|
809
838
|
*/
|
|
810
839
|
async function ensureDaemonRunning(appUrl) {
|
|
811
840
|
const { socketPath } = daemonPaths(appUrl);
|
|
812
841
|
if (await isDaemonListening(socketPath)) {
|
|
813
842
|
const status = await queryDaemonStatus(appUrl);
|
|
814
|
-
if (!status || !isNewerBuild(portalDaemonBuild(), status.build)) return;
|
|
815
|
-
if (await stopDaemonForHandoff(appUrl) === "failed") return;
|
|
843
|
+
if (!status || !isNewerBuild(portalDaemonBuild(), status.build)) return true;
|
|
844
|
+
if (await stopDaemonForHandoff(appUrl) === "failed") return true;
|
|
816
845
|
}
|
|
817
846
|
const entry = process.argv[1];
|
|
818
847
|
const args = entry ? [
|
|
@@ -826,9 +855,10 @@ async function ensureDaemonRunning(appUrl) {
|
|
|
826
855
|
env: process.env
|
|
827
856
|
}).unref();
|
|
828
857
|
for (let i = 0; i < 50; i += 1) {
|
|
829
|
-
if (await isDaemonListening(socketPath)) return;
|
|
858
|
+
if (await isDaemonListening(socketPath)) return true;
|
|
830
859
|
await sleep(100);
|
|
831
860
|
}
|
|
861
|
+
return false;
|
|
832
862
|
}
|
|
833
863
|
/**
|
|
834
864
|
* Entry the CLI calls when launched with PORTAL_DAEMON_FLAG: become the daemon.
|
|
@@ -849,7 +879,7 @@ function startPortalDaemon(appUrl) {
|
|
|
849
879
|
return new PortalDaemon(appUrl, null).listen();
|
|
850
880
|
}
|
|
851
881
|
async function isDaemonListening(socketPath) {
|
|
852
|
-
if (!await pathExists(socketPath)) return false;
|
|
882
|
+
if (!isNamedPipePath(socketPath) && !await pathExists(socketPath)) return false;
|
|
853
883
|
return new Promise((resolve) => {
|
|
854
884
|
const probe = connect(socketPath);
|
|
855
885
|
probe.on("connect", () => {
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { d as daemonPaths, f as encodeLine, l as LOCAL_PROTOCOL_VERSION, m as parseDaemonMessage, n as ensureDaemonRunning, p as makeLineParser } from "./daemon-
|
|
2
|
+
import { d as daemonPaths, f as encodeLine, l as LOCAL_PROTOCOL_VERSION, m as parseDaemonMessage, n as ensureDaemonRunning, p as makeLineParser } from "./daemon-CXnHsea6.mjs";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { connect } from "node:net";
|
|
5
5
|
|
|
6
6
|
//#region ../portal-daemon/src/daemon-client.ts
|
|
7
|
+
/** Reconnect delay when an established daemon connection drops. */
|
|
8
|
+
const RECONNECT_DELAY_MS = 500;
|
|
9
|
+
/** Base delay between daemon SPAWN retries (doubles per consecutive failure). */
|
|
10
|
+
const START_RETRY_BASE_MS = 2e3;
|
|
11
|
+
/** Consecutive failed spawns before start() stops respawning. */
|
|
12
|
+
const MAX_START_ATTEMPTS = 3;
|
|
7
13
|
var PortalDaemonClient = class {
|
|
8
14
|
sessionId = randomUUID();
|
|
9
15
|
socket = null;
|
|
10
16
|
disposed = false;
|
|
11
17
|
reconnectTimer = null;
|
|
18
|
+
startFailures = 0;
|
|
12
19
|
lastBind = null;
|
|
13
20
|
fallbackCwd = null;
|
|
14
21
|
wantEnabled = false;
|
|
@@ -21,7 +28,21 @@ var PortalDaemonClient = class {
|
|
|
21
28
|
/** Spawn the daemon if needed and attach. Idempotent. */
|
|
22
29
|
async start() {
|
|
23
30
|
if (this.disposed) return;
|
|
24
|
-
await ensureDaemonRunning(this.opts.appUrl)
|
|
31
|
+
if (!await ensureDaemonRunning(this.opts.appUrl)) {
|
|
32
|
+
this.startFailures += 1;
|
|
33
|
+
const exhausted = this.startFailures >= MAX_START_ATTEMPTS;
|
|
34
|
+
this.opts.onState({
|
|
35
|
+
status: "error",
|
|
36
|
+
machineName: "",
|
|
37
|
+
friendlyName: "",
|
|
38
|
+
error: exhausted ? "portal daemon failed to start repeatedly; toggle sharing to retry" : "portal daemon failed to start (control socket never came up)",
|
|
39
|
+
grantedAgentIds: [],
|
|
40
|
+
declinedAgentIds: []
|
|
41
|
+
});
|
|
42
|
+
if (!exhausted) this.scheduleReconnect(START_RETRY_BASE_MS * 2 ** (this.startFailures - 1));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
this.startFailures = 0;
|
|
25
46
|
this.connect();
|
|
26
47
|
}
|
|
27
48
|
connect() {
|
|
@@ -74,12 +95,12 @@ var PortalDaemonClient = class {
|
|
|
74
95
|
socket.on("close", onClose);
|
|
75
96
|
socket.on("error", onClose);
|
|
76
97
|
}
|
|
77
|
-
scheduleReconnect() {
|
|
98
|
+
scheduleReconnect(delayMs = RECONNECT_DELAY_MS) {
|
|
78
99
|
if (this.disposed || this.reconnectTimer) return;
|
|
79
100
|
this.reconnectTimer = setTimeout(() => {
|
|
80
101
|
this.reconnectTimer = null;
|
|
81
102
|
this.start();
|
|
82
|
-
},
|
|
103
|
+
}, delayMs);
|
|
83
104
|
this.reconnectTimer.unref();
|
|
84
105
|
}
|
|
85
106
|
onMessage(msg) {
|
|
@@ -109,6 +130,10 @@ var PortalDaemonClient = class {
|
|
|
109
130
|
/** Turn machine sharing on (the daemon connects the portal). */
|
|
110
131
|
enable() {
|
|
111
132
|
this.wantEnabled = true;
|
|
133
|
+
if (this.startFailures >= MAX_START_ATTEMPTS) {
|
|
134
|
+
this.startFailures = 0;
|
|
135
|
+
this.start();
|
|
136
|
+
}
|
|
112
137
|
this.send({ t: "enable" });
|
|
113
138
|
}
|
|
114
139
|
/** Turn machine sharing off. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { c as version$1 } from "./rest-
|
|
3
|
-
import { E as getConfigPath } from "./print-
|
|
2
|
+
import { c as version$1 } from "./rest-CM76e6UD.mjs";
|
|
3
|
+
import { E as getConfigPath } from "./print-BDW65Ro4.mjs";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { err, ok } from "neverthrow";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as errorMessage, i as sendErrorMessage, o as isRecord, t as createRestClient } from "./rest-
|
|
2
|
+
import { a as errorMessage, i as sendErrorMessage, o as isRecord, t as createRestClient } from "./rest-CM76e6UD.mjs";
|
|
3
3
|
import { t as HttpError } from "./http-error-BF2NZZE3.mjs";
|
|
4
4
|
import { i as billingBlockedOutcomeFromSendResponse, n as BillingBlockedError } from "./billing-blocked-D3l5kJlX.mjs";
|
|
5
5
|
import path from "node:path";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./rest-
|
|
3
|
-
import { a as runPrint, i as resolveAgent, n as messageGet, o as toPrintError, r as readStdin, t as collectRunText } from "./print-
|
|
2
|
+
import "./rest-CM76e6UD.mjs";
|
|
3
|
+
import { a as runPrint, i as resolveAgent, n as messageGet, o as toPrintError, r as readStdin, t as collectRunText } from "./print-BDW65Ro4.mjs";
|
|
4
4
|
import "./billing-blocked-D3l5kJlX.mjs";
|
|
5
5
|
|
|
6
6
|
export { messageGet, readStdin, resolveAgent, runPrint };
|
|
@@ -14,7 +14,7 @@ import { n as printError } from "./output-C9mb3sUB.mjs";
|
|
|
14
14
|
* the reply (and to --json).
|
|
15
15
|
*/
|
|
16
16
|
async function connectMachineShare({ appUrl, sessionToken, timeoutHint }) {
|
|
17
|
-
const { PortalClient } = await import("./client-
|
|
17
|
+
const { PortalClient } = await import("./client-CM--T5Fy.mjs");
|
|
18
18
|
const { defaultTlsCertSource } = await import("./tls-cert-BpCaD5AT.mjs");
|
|
19
19
|
let signalConnected;
|
|
20
20
|
const connected = new Promise((resolve) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { i as sendErrorMessage, n as errorDetail, r as filenameFromContentDisposition, t as createRestClient } from "./rest-
|
|
2
|
+
import { i as sendErrorMessage, n as errorDetail, r as filenameFromContentDisposition, t as createRestClient } from "./rest-CM76e6UD.mjs";
|
|
3
3
|
import { t as HttpError } from "./http-error-BF2NZZE3.mjs";
|
|
4
4
|
import "./billing-blocked-D3l5kJlX.mjs";
|
|
5
5
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skydive-cli",
|
|
3
|
-
"version": "0.6.0-beta.
|
|
3
|
+
"version": "0.6.0-beta.36",
|
|
4
4
|
"description": "Skydive CLI — cloud agents from the command line",
|
|
5
5
|
"homepage": "https://skydive.com",
|
|
6
6
|
"license": "MIT",
|
|
@@ -71,9 +71,9 @@
|
|
|
71
71
|
},
|
|
72
72
|
"//optionalDependencies": "The per-platform binary packages (skydive-cli-<os>-<arch>) are INTENTIONALLY not committed here. They are published by release-skydive-cli-binaries.yml and injected — pinned to the exact version being published — into the published launcher's optionalDependencies at release time (scripts/prepare-platform-packages.mjs --pin-version, run in release-skydive-cli.yml). Committing them would make `yarn install` try to resolve versions that only exist post-publish, breaking local dev and CI. npm installs of a published skydive-cli still get them (os/cpu-gated); the launcher (src/launcher.ts) resolves and execs the matching one. Local dev never needs them: it runs the JS bundle or `build:binary` directly.",
|
|
73
73
|
"optionalDependencies": {
|
|
74
|
-
"skydive-cli-darwin-arm64": "0.6.0-beta.
|
|
75
|
-
"skydive-cli-darwin-x64": "0.6.0-beta.
|
|
76
|
-
"skydive-cli-linux-x64": "0.6.0-beta.
|
|
77
|
-
"skydive-cli-linux-arm64": "0.6.0-beta.
|
|
74
|
+
"skydive-cli-darwin-arm64": "0.6.0-beta.36",
|
|
75
|
+
"skydive-cli-darwin-x64": "0.6.0-beta.36",
|
|
76
|
+
"skydive-cli-linux-x64": "0.6.0-beta.36",
|
|
77
|
+
"skydive-cli-linux-arm64": "0.6.0-beta.36"
|
|
78
78
|
}
|
|
79
79
|
}
|