setsuna-microvm 0.5.2 → 0.5.3
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/cli.js +109 -36
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -10,6 +10,9 @@ var __export = (target, all) => {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
// ../setsuna/src/tui/customer/prompt.ts
|
|
13
|
+
function readText(text, now) {
|
|
14
|
+
return typeof text === "function" ? text(now) : text;
|
|
15
|
+
}
|
|
13
16
|
var TUI_CANCEL;
|
|
14
17
|
var init_prompt = __esm({
|
|
15
18
|
"../setsuna/src/tui/customer/prompt.ts"() {
|
|
@@ -76,14 +79,13 @@ function adaptPrompts(clack, guide) {
|
|
|
76
79
|
const { confirm, isCancel, select, text } = clack;
|
|
77
80
|
return {
|
|
78
81
|
async select(options, io) {
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
options
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
return isCancel(result) ? TUI_CANCEL : result;
|
|
82
|
+
const redraw = hasTimedText(options) ? setInterval(() => io.output.emit("resize"), 1e3) : void 0;
|
|
83
|
+
try {
|
|
84
|
+
const result = await select(selectOptions(options, guide(options), io));
|
|
85
|
+
return isCancel(result) ? TUI_CANCEL : result;
|
|
86
|
+
} finally {
|
|
87
|
+
if (redraw !== void 0) clearInterval(redraw);
|
|
88
|
+
}
|
|
87
89
|
},
|
|
88
90
|
async text(options, io) {
|
|
89
91
|
const result = await text({
|
|
@@ -106,6 +108,28 @@ function adaptPrompts(clack, guide) {
|
|
|
106
108
|
}
|
|
107
109
|
};
|
|
108
110
|
}
|
|
111
|
+
function selectOptions(options, guide, io) {
|
|
112
|
+
return {
|
|
113
|
+
get message() {
|
|
114
|
+
return readText(options.message, Date.now());
|
|
115
|
+
},
|
|
116
|
+
options: options.options.map((option) => ({
|
|
117
|
+
value: option.value,
|
|
118
|
+
label: option.label,
|
|
119
|
+
get hint() {
|
|
120
|
+
return option.hint === void 0 ? void 0 : readText(option.hint, Date.now());
|
|
121
|
+
}
|
|
122
|
+
})),
|
|
123
|
+
initialValue: options.initialValue,
|
|
124
|
+
signal: options.signal,
|
|
125
|
+
...guide,
|
|
126
|
+
input: io.input,
|
|
127
|
+
output: io.output
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function hasTimedText(options) {
|
|
131
|
+
return typeof options.message === "function" || options.options.some(({ hint }) => typeof hint === "function");
|
|
132
|
+
}
|
|
109
133
|
function adaptSpinner(clack, io, options, guide) {
|
|
110
134
|
const completion = trackInterruption(clack, io, options.onCancel, guide);
|
|
111
135
|
const spinner = clack.spinner({
|
|
@@ -1248,6 +1272,36 @@ function isRecord(value) {
|
|
|
1248
1272
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1249
1273
|
}
|
|
1250
1274
|
|
|
1275
|
+
// ../setsuna/src/client-affinity.ts
|
|
1276
|
+
var FrontDoorAffinity = class {
|
|
1277
|
+
#origin;
|
|
1278
|
+
#cookies = /* @__PURE__ */ new Map();
|
|
1279
|
+
constructor(serviceUrl) {
|
|
1280
|
+
this.#origin = serviceUrl.origin;
|
|
1281
|
+
}
|
|
1282
|
+
headersFor(url, headers) {
|
|
1283
|
+
if (url.origin !== this.#origin || this.#cookies.size === 0) return headers;
|
|
1284
|
+
const result = new Headers(headers);
|
|
1285
|
+
result.set("cookie", [...this.#cookies].map(([name, value]) => `${name}=${value}`).join("; "));
|
|
1286
|
+
return result;
|
|
1287
|
+
}
|
|
1288
|
+
capture(url, response) {
|
|
1289
|
+
if (url.origin !== this.#origin) return;
|
|
1290
|
+
if (response.url !== "" && new URL(response.url).origin !== this.#origin) return;
|
|
1291
|
+
for (const header of response.headers.getSetCookie()) {
|
|
1292
|
+
const pair = header.split(";", 1)[0]?.trim() ?? "";
|
|
1293
|
+
const match = /^(ASLBSA|ASLBSACORS)=([\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]*)$/u.exec(
|
|
1294
|
+
pair
|
|
1295
|
+
);
|
|
1296
|
+
if (match === null) continue;
|
|
1297
|
+
const [, name, value] = match;
|
|
1298
|
+
if (name === void 0 || value === void 0) continue;
|
|
1299
|
+
if (value === "") this.#cookies.delete(name);
|
|
1300
|
+
else this.#cookies.set(name, value);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
};
|
|
1304
|
+
|
|
1251
1305
|
// ../setsuna/src/client-options.ts
|
|
1252
1306
|
var PREVIEW_SERVICE_URL = "https://api.setsuna.semswitch.com/";
|
|
1253
1307
|
function normalizeServiceUrl(value) {
|
|
@@ -1359,7 +1413,7 @@ function isLoopbackHostname(hostname) {
|
|
|
1359
1413
|
}
|
|
1360
1414
|
|
|
1361
1415
|
// ../setsuna/src/client-request.ts
|
|
1362
|
-
async function requestJson(serviceUrl, defaultTimeoutMs, getAccessToken, path3, options) {
|
|
1416
|
+
async function requestJson(serviceUrl, defaultTimeoutMs, getAccessToken, path3, options, affinity) {
|
|
1363
1417
|
const requestTimeoutMs = options.requestTimeoutMs === void 0 ? defaultTimeoutMs : positiveMilliseconds(options.requestTimeoutMs, "requestTimeoutMs");
|
|
1364
1418
|
const timeoutSignal = AbortSignal.timeout(requestTimeoutMs);
|
|
1365
1419
|
const signal = options.signal === void 0 ? timeoutSignal : AbortSignal.any([options.signal, timeoutSignal]);
|
|
@@ -1376,7 +1430,8 @@ async function requestJson(serviceUrl, defaultTimeoutMs, getAccessToken, path3,
|
|
|
1376
1430
|
requestTimeoutMs,
|
|
1377
1431
|
timeoutSignal,
|
|
1378
1432
|
signal,
|
|
1379
|
-
accessToken
|
|
1433
|
+
accessToken,
|
|
1434
|
+
affinity
|
|
1380
1435
|
);
|
|
1381
1436
|
const responseBody = response.status === 204 ? void 0 : await parseResponseJson(response);
|
|
1382
1437
|
if (!response.ok) throw brokerResponseError(response, responseBody);
|
|
@@ -1417,14 +1472,17 @@ function settleBeforeAbort(pending, signal) {
|
|
|
1417
1472
|
if (signal.aborted) onAbort();
|
|
1418
1473
|
});
|
|
1419
1474
|
}
|
|
1420
|
-
async function fetchService(url, options, requestTimeoutMs, timeoutSignal, signal, accessToken) {
|
|
1475
|
+
async function fetchService(url, options, requestTimeoutMs, timeoutSignal, signal, accessToken, affinity) {
|
|
1421
1476
|
try {
|
|
1422
|
-
|
|
1477
|
+
const headers = requestHeaders(options.body, accessToken);
|
|
1478
|
+
const response = await fetch(url, {
|
|
1423
1479
|
method: options.method,
|
|
1424
|
-
headers:
|
|
1480
|
+
headers: affinity?.headersFor(url, headers) ?? headers,
|
|
1425
1481
|
body: options.body === void 0 ? void 0 : JSON.stringify(options.body),
|
|
1426
1482
|
signal
|
|
1427
1483
|
});
|
|
1484
|
+
affinity?.capture(url, response);
|
|
1485
|
+
return response;
|
|
1428
1486
|
} catch (error) {
|
|
1429
1487
|
throwCancellationFailure(error, requestTimeoutMs, timeoutSignal, options.signal);
|
|
1430
1488
|
throw new SetsunaError("The local Setsuna service could not be reached.", {
|
|
@@ -1559,13 +1617,14 @@ function parseProcessList(value) {
|
|
|
1559
1617
|
if (!Array.isArray(value)) throw invalidResponse2("process list");
|
|
1560
1618
|
return value.map(parseProcessInfo);
|
|
1561
1619
|
}
|
|
1562
|
-
function attachProcessStream(serviceUrl, path3, options, getAccessToken) {
|
|
1620
|
+
function attachProcessStream(serviceUrl, path3, options, getAccessToken, affinity) {
|
|
1563
1621
|
return {
|
|
1564
1622
|
async *[Symbol.asyncIterator]() {
|
|
1565
1623
|
const response = await fetchProcessStream(
|
|
1566
1624
|
new URL(path3, serviceUrl),
|
|
1567
1625
|
options.signal,
|
|
1568
|
-
getAccessToken
|
|
1626
|
+
getAccessToken,
|
|
1627
|
+
affinity
|
|
1569
1628
|
);
|
|
1570
1629
|
if (response.body === null) throw invalidResponse2("process stream");
|
|
1571
1630
|
yield* decodeFrames(response.body, options.signal);
|
|
@@ -1586,15 +1645,17 @@ function processFrameToJson(frame) {
|
|
|
1586
1645
|
}
|
|
1587
1646
|
return frame;
|
|
1588
1647
|
}
|
|
1589
|
-
async function fetchProcessStream(url, signal, getAccessToken) {
|
|
1648
|
+
async function fetchProcessStream(url, signal, getAccessToken, affinity) {
|
|
1590
1649
|
const accessToken = await processAccessToken(getAccessToken, signal);
|
|
1591
1650
|
let response;
|
|
1592
1651
|
try {
|
|
1652
|
+
const headers = accessToken === void 0 ? void 0 : { authorization: `Bearer ${accessToken}` };
|
|
1593
1653
|
response = await fetch(url, {
|
|
1594
1654
|
method: "GET",
|
|
1595
1655
|
signal,
|
|
1596
|
-
...
|
|
1656
|
+
...headers === void 0 ? {} : { headers: affinity?.headersFor(url, headers) ?? headers }
|
|
1597
1657
|
});
|
|
1658
|
+
affinity?.capture(url, response);
|
|
1598
1659
|
} catch (error) {
|
|
1599
1660
|
throw requestError(error, signal);
|
|
1600
1661
|
}
|
|
@@ -1773,6 +1834,7 @@ var SetsunaClient = class {
|
|
|
1773
1834
|
#serviceUrl;
|
|
1774
1835
|
#requestTimeoutMs;
|
|
1775
1836
|
#getAccessToken;
|
|
1837
|
+
#affinity;
|
|
1776
1838
|
constructor(options = {}) {
|
|
1777
1839
|
if (options.serviceUrl !== void 0 && options.remote !== void 0) {
|
|
1778
1840
|
throw new TypeError("serviceUrl and remote are mutually exclusive.");
|
|
@@ -1786,6 +1848,7 @@ var SetsunaClient = class {
|
|
|
1786
1848
|
}
|
|
1787
1849
|
this.#serviceUrl = new URL(PREVIEW_SERVICE_URL);
|
|
1788
1850
|
this.#getAccessToken = options.remote.getAccessToken;
|
|
1851
|
+
this.#affinity = new FrontDoorAffinity(this.#serviceUrl);
|
|
1789
1852
|
}
|
|
1790
1853
|
this.#requestTimeoutMs = positiveMilliseconds(
|
|
1791
1854
|
options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,
|
|
@@ -1876,7 +1939,8 @@ var SetsunaClient = class {
|
|
|
1876
1939
|
this.#serviceUrl,
|
|
1877
1940
|
`${processPath(sandboxId)}/${encodeURIComponent(nonEmptyString(processId, "processId"))}/attach${query}`,
|
|
1878
1941
|
options,
|
|
1879
|
-
this.#getAccessToken
|
|
1942
|
+
this.#getAccessToken,
|
|
1943
|
+
this.#affinity
|
|
1880
1944
|
);
|
|
1881
1945
|
}
|
|
1882
1946
|
async writeProcessStdin(sandboxId, processId, data) {
|
|
@@ -1958,7 +2022,8 @@ var SetsunaClient = class {
|
|
|
1958
2022
|
this.#requestTimeoutMs,
|
|
1959
2023
|
this.#getAccessToken,
|
|
1960
2024
|
path3,
|
|
1961
|
-
options
|
|
2025
|
+
options,
|
|
2026
|
+
this.#affinity
|
|
1962
2027
|
);
|
|
1963
2028
|
}
|
|
1964
2029
|
};
|
|
@@ -2081,14 +2146,19 @@ function enterRawMode(input) {
|
|
|
2081
2146
|
const wasPaused = input.isPaused();
|
|
2082
2147
|
input.setRawMode(true);
|
|
2083
2148
|
input.resume();
|
|
2084
|
-
let restored
|
|
2149
|
+
let restored;
|
|
2085
2150
|
return () => {
|
|
2086
|
-
|
|
2087
|
-
restored
|
|
2088
|
-
input.setRawMode?.(wasRaw);
|
|
2089
|
-
if (wasPaused) input.pause();
|
|
2151
|
+
restored ??= restoreInput(input, wasRaw, wasPaused);
|
|
2152
|
+
return restored;
|
|
2090
2153
|
};
|
|
2091
2154
|
}
|
|
2155
|
+
async function restoreInput(input, wasRaw, wasPaused) {
|
|
2156
|
+
if (wasPaused) {
|
|
2157
|
+
input.pause();
|
|
2158
|
+
await new Promise((resolve) => process.nextTick(resolve));
|
|
2159
|
+
}
|
|
2160
|
+
input.setRawMode?.(wasRaw);
|
|
2161
|
+
}
|
|
2092
2162
|
|
|
2093
2163
|
// ../setsuna/src/lease-keeper.ts
|
|
2094
2164
|
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
@@ -2287,6 +2357,7 @@ function createAttachedSession(client, sandboxId, processId, io, terminal, contr
|
|
|
2287
2357
|
let cleaned = false;
|
|
2288
2358
|
let inputQueue = Promise.resolve();
|
|
2289
2359
|
let resizeQueue = Promise.resolve();
|
|
2360
|
+
let restored;
|
|
2290
2361
|
const restoreRawMode = terminal.enterRawMode();
|
|
2291
2362
|
const cleanup = () => {
|
|
2292
2363
|
if (cleaned) return;
|
|
@@ -2294,7 +2365,7 @@ function createAttachedSession(client, sandboxId, processId, io, terminal, contr
|
|
|
2294
2365
|
io.input.off("data", onInput);
|
|
2295
2366
|
terminal.offResize(onResize);
|
|
2296
2367
|
stopLeaseKeeper();
|
|
2297
|
-
restoreRawMode();
|
|
2368
|
+
restored = restoreRawMode();
|
|
2298
2369
|
};
|
|
2299
2370
|
const fail = (error) => {
|
|
2300
2371
|
if (failure === void 0) failure = error;
|
|
@@ -2339,8 +2410,10 @@ function createAttachedSession(client, sandboxId, processId, io, terminal, contr
|
|
|
2339
2410
|
get failure() {
|
|
2340
2411
|
return failure;
|
|
2341
2412
|
},
|
|
2413
|
+
// Everything the session started has finished, including handing the terminal back.
|
|
2342
2414
|
async settle() {
|
|
2343
2415
|
await Promise.allSettled([inputQueue, resizeQueue]);
|
|
2416
|
+
await restored;
|
|
2344
2417
|
}
|
|
2345
2418
|
};
|
|
2346
2419
|
}
|
|
@@ -2468,6 +2541,7 @@ var DEFAULT_SIGNAL_RUNTIME = {
|
|
|
2468
2541
|
|
|
2469
2542
|
// ../setsuna/src/tui/customer/format.ts
|
|
2470
2543
|
var STANDALONE_BOX = { withGuide: false, width: "auto" };
|
|
2544
|
+
var EXIT_MESSAGE = "Sayonara!";
|
|
2471
2545
|
function formatRunPhase(phase) {
|
|
2472
2546
|
switch (phase) {
|
|
2473
2547
|
case "starting-microvm":
|
|
@@ -2634,7 +2708,7 @@ async function ensureSignedIn(io, auth, promptUi, session = {}) {
|
|
|
2634
2708
|
return false;
|
|
2635
2709
|
}
|
|
2636
2710
|
if (choice === "exit") {
|
|
2637
|
-
promptUi.outro(
|
|
2711
|
+
promptUi.outro(EXIT_MESSAGE, io);
|
|
2638
2712
|
return false;
|
|
2639
2713
|
}
|
|
2640
2714
|
status = await signIn(io, auth, promptUi);
|
|
@@ -2779,7 +2853,7 @@ async function promptRunAgain(io, promptUi) {
|
|
|
2779
2853
|
);
|
|
2780
2854
|
if (next === TUI_CANCEL || next === "back") return "back-to-main";
|
|
2781
2855
|
if (next === "exit") {
|
|
2782
|
-
promptUi.outro(
|
|
2856
|
+
promptUi.outro(EXIT_MESSAGE, io);
|
|
2783
2857
|
return "exit-tui";
|
|
2784
2858
|
}
|
|
2785
2859
|
return "prompt-new-command";
|
|
@@ -2876,7 +2950,6 @@ async function activeMicrovmsMenu(io, actions, promptUi) {
|
|
|
2876
2950
|
}
|
|
2877
2951
|
}
|
|
2878
2952
|
async function selectSandbox(io, promptUi, sandboxes) {
|
|
2879
|
-
const now = Date.now();
|
|
2880
2953
|
const selected = await promptUi.select(
|
|
2881
2954
|
{
|
|
2882
2955
|
message: "Active microVMs",
|
|
@@ -2884,7 +2957,7 @@ async function selectSandbox(io, promptUi, sandboxes) {
|
|
|
2884
2957
|
...sandboxes.map((sandbox, index) => ({
|
|
2885
2958
|
value: `sandbox:${index}`,
|
|
2886
2959
|
label: sandbox.sandboxId,
|
|
2887
|
-
hint: formatExpiry(sandbox.expiresAt, now)
|
|
2960
|
+
hint: (now) => formatExpiry(sandbox.expiresAt, now)
|
|
2888
2961
|
})),
|
|
2889
2962
|
{ value: "back", label: "Back" }
|
|
2890
2963
|
]
|
|
@@ -2897,10 +2970,10 @@ async function selectSandbox(io, promptUi, sandboxes) {
|
|
|
2897
2970
|
async function manageSandbox(io, actions, promptUi, initial) {
|
|
2898
2971
|
let microvm = initial;
|
|
2899
2972
|
while (true) {
|
|
2900
|
-
const
|
|
2973
|
+
const { sandboxId, expiresAt } = microvm;
|
|
2901
2974
|
const choice = await promptUi.select(
|
|
2902
2975
|
{
|
|
2903
|
-
message: `Manage microVM ${
|
|
2976
|
+
message: (now) => `Manage microVM ${sandboxId} \xB7 ${formatExpiry(expiresAt, now)}`,
|
|
2904
2977
|
options: [
|
|
2905
2978
|
{ value: "open-terminal", label: "Open terminal" },
|
|
2906
2979
|
{ value: "reattach-terminal", label: "Reattach terminal" },
|
|
@@ -3049,10 +3122,10 @@ async function mainMenu(io, actions, promptUi, session) {
|
|
|
3049
3122
|
{
|
|
3050
3123
|
message: "What would you like to do?",
|
|
3051
3124
|
options: [
|
|
3052
|
-
{ value: "start", label: "
|
|
3053
|
-
{ value: "active", label: "Active microVMs", hint: "
|
|
3054
|
-
{ value: "run", label: "
|
|
3055
|
-
{ value: "account", label: "Account"
|
|
3125
|
+
{ value: "start", label: "New microVM", hint: "For interactive or ongoing work" },
|
|
3126
|
+
{ value: "active", label: "Active microVMs", hint: "Resume or manage your microVMs" },
|
|
3127
|
+
{ value: "run", label: "One-off run", hint: "Isolated single-run workload" },
|
|
3128
|
+
{ value: "account", label: "Account" },
|
|
3056
3129
|
{ value: "exit", label: "Exit" }
|
|
3057
3130
|
],
|
|
3058
3131
|
initialValue: "start",
|
|
@@ -3065,7 +3138,7 @@ async function mainMenu(io, actions, promptUi, session) {
|
|
|
3065
3138
|
return "exit";
|
|
3066
3139
|
}
|
|
3067
3140
|
if (choice === "exit") {
|
|
3068
|
-
promptUi.outro(
|
|
3141
|
+
promptUi.outro(EXIT_MESSAGE, io);
|
|
3069
3142
|
return "exit";
|
|
3070
3143
|
}
|
|
3071
3144
|
try {
|