@avsbhq/cli 3.8.2 → 3.9.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/cli.js +86 -14
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -26788,14 +26788,14 @@ var require_receiver = __commonJS({
|
|
|
26788
26788
|
* @return {(Error|RangeError)} The error
|
|
26789
26789
|
* @private
|
|
26790
26790
|
*/
|
|
26791
|
-
createError(ErrorCtor, message, prefix, statusCode,
|
|
26791
|
+
createError(ErrorCtor, message, prefix, statusCode, errorCode2) {
|
|
26792
26792
|
this._loop = false;
|
|
26793
26793
|
this._errored = true;
|
|
26794
26794
|
const err = new ErrorCtor(
|
|
26795
26795
|
prefix ? `Invalid WebSocket frame: ${message}` : message
|
|
26796
26796
|
);
|
|
26797
26797
|
Error.captureStackTrace(err, this.createError);
|
|
26798
|
-
err.code =
|
|
26798
|
+
err.code = errorCode2;
|
|
26799
26799
|
err[kStatusCode] = statusCode;
|
|
26800
26800
|
return err;
|
|
26801
26801
|
}
|
|
@@ -27681,7 +27681,7 @@ var require_websocket = __commonJS({
|
|
|
27681
27681
|
var EventEmitter2 = __require("events");
|
|
27682
27682
|
var https = __require("https");
|
|
27683
27683
|
var http2 = __require("http");
|
|
27684
|
-
var
|
|
27684
|
+
var net2 = __require("net");
|
|
27685
27685
|
var tls = __require("tls");
|
|
27686
27686
|
var { randomBytes, createHash: createHash5 } = __require("crypto");
|
|
27687
27687
|
var { Duplex, Readable: Readable2 } = __require("stream");
|
|
@@ -28415,12 +28415,12 @@ var require_websocket = __commonJS({
|
|
|
28415
28415
|
}
|
|
28416
28416
|
function netConnect(options) {
|
|
28417
28417
|
options.path = options.socketPath;
|
|
28418
|
-
return
|
|
28418
|
+
return net2.connect(options);
|
|
28419
28419
|
}
|
|
28420
28420
|
function tlsConnect(options) {
|
|
28421
28421
|
options.path = void 0;
|
|
28422
28422
|
if (!options.servername && options.servername !== "") {
|
|
28423
|
-
options.servername =
|
|
28423
|
+
options.servername = net2.isIP(options.host) ? "" : options.host;
|
|
28424
28424
|
}
|
|
28425
28425
|
return tls.connect(options);
|
|
28426
28426
|
}
|
|
@@ -39213,27 +39213,74 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
|
39213
39213
|
|
|
39214
39214
|
// src/lib/wsServer.ts
|
|
39215
39215
|
import http from "node:http";
|
|
39216
|
+
import net from "node:net";
|
|
39216
39217
|
var PORT_START = 4400;
|
|
39217
39218
|
var PORT_END = 4409;
|
|
39219
|
+
var DEFAULT_HOST = "127.0.0.1";
|
|
39218
39220
|
var PING_INTERVAL_MS = 3e4;
|
|
39221
|
+
var PROBE_TIMEOUT_MS = 300;
|
|
39222
|
+
var LOOPBACK_PROBES = ["127.0.0.1", "::1"];
|
|
39219
39223
|
function portsBusyError(start, end) {
|
|
39220
39224
|
return new CliError("cli_failed", `Ports ${start} to ${end} are all busy.`, [
|
|
39221
39225
|
"Another `avsb dev` (or another program) is holding them. Stop it, then run this again.",
|
|
39222
39226
|
`\`lsof -i :${start}\` shows what is listening on a port.`
|
|
39223
39227
|
]);
|
|
39224
39228
|
}
|
|
39229
|
+
function errorCode(err) {
|
|
39230
|
+
return typeof err === "object" && err !== null && "code" in err ? err.code : void 0;
|
|
39231
|
+
}
|
|
39225
39232
|
function isAddressInUse(err) {
|
|
39226
|
-
return
|
|
39233
|
+
return errorCode(err) === "EADDRINUSE";
|
|
39234
|
+
}
|
|
39235
|
+
var BAD_HOST_CODES = /* @__PURE__ */ new Set(["EADDRNOTAVAIL", "ENOTFOUND", "EAI_AGAIN", "EAI_NONAME"]);
|
|
39236
|
+
function badHostError(host) {
|
|
39237
|
+
return new CliError("cli_failed", `Cannot listen on ${host}: it is not an address of this machine.`, [
|
|
39238
|
+
`Leave out --host to listen on ${DEFAULT_HOST} (this machine only), or pass 0.0.0.0 to listen on every interface.`
|
|
39239
|
+
]);
|
|
39240
|
+
}
|
|
39241
|
+
function isLoopbackHost(host) {
|
|
39242
|
+
return host === "localhost" || host === "::1" || /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host);
|
|
39243
|
+
}
|
|
39244
|
+
function describeDevServer(server) {
|
|
39245
|
+
if (isLoopbackHost(server.host)) {
|
|
39246
|
+
return { line: `Dev server running at ${server.url} (this machine only)`, warning: null };
|
|
39247
|
+
}
|
|
39248
|
+
return {
|
|
39249
|
+
line: `Dev server running at ${server.url}`,
|
|
39250
|
+
warning: `Listening on ${server.host}: other devices on your network can reach this server and read the code it serves.`
|
|
39251
|
+
};
|
|
39252
|
+
}
|
|
39253
|
+
function devServerUrl(host, port) {
|
|
39254
|
+
return `http://${host.includes(":") ? `[${host}]` : host}:${port}`;
|
|
39255
|
+
}
|
|
39256
|
+
function answersOn(address, port) {
|
|
39257
|
+
return new Promise((resolve3) => {
|
|
39258
|
+
const socket = net.connect({ host: address, port });
|
|
39259
|
+
const done = (answered) => {
|
|
39260
|
+
socket.destroy();
|
|
39261
|
+
resolve3(answered);
|
|
39262
|
+
};
|
|
39263
|
+
socket.setTimeout(PROBE_TIMEOUT_MS, () => done(false));
|
|
39264
|
+
socket.once("connect", () => done(true));
|
|
39265
|
+
socket.once("error", () => done(false));
|
|
39266
|
+
});
|
|
39267
|
+
}
|
|
39268
|
+
async function isPortAnsweredOnLoopback(port) {
|
|
39269
|
+
const answers = await Promise.all(LOOPBACK_PROBES.map((address) => answersOn(address, port)));
|
|
39270
|
+
return answers.some(Boolean);
|
|
39227
39271
|
}
|
|
39228
39272
|
async function startDevServer(options) {
|
|
39229
39273
|
const { experimentName, experimentId, onExtensionMessage } = options;
|
|
39230
39274
|
const start = options.portRange?.start ?? PORT_START;
|
|
39231
39275
|
const end = options.portRange?.end ?? PORT_END;
|
|
39276
|
+
const host = options.host ?? DEFAULT_HOST;
|
|
39232
39277
|
let bound = null;
|
|
39233
39278
|
for (let port2 = start; port2 <= end && !bound; port2++) {
|
|
39279
|
+
if (await isPortAnsweredOnLoopback(port2)) continue;
|
|
39234
39280
|
try {
|
|
39235
|
-
bound = { ...await tryPort(port2, experimentName, experimentId, onExtensionMessage), port: port2 };
|
|
39281
|
+
bound = { ...await tryPort(port2, host, experimentName, experimentId, onExtensionMessage), port: port2 };
|
|
39236
39282
|
} catch (err) {
|
|
39283
|
+
if (BAD_HOST_CODES.has(String(errorCode(err)))) throw badHostError(host);
|
|
39237
39284
|
if (!isAddressInUse(err)) throw err;
|
|
39238
39285
|
}
|
|
39239
39286
|
}
|
|
@@ -39252,6 +39299,8 @@ async function startDevServer(options) {
|
|
|
39252
39299
|
}, PING_INTERVAL_MS);
|
|
39253
39300
|
return {
|
|
39254
39301
|
port,
|
|
39302
|
+
host,
|
|
39303
|
+
url: devServerUrl(host, port),
|
|
39255
39304
|
broadcast(msg) {
|
|
39256
39305
|
broadcast(JSON.stringify(msg));
|
|
39257
39306
|
},
|
|
@@ -39275,7 +39324,7 @@ async function startDevServer(options) {
|
|
|
39275
39324
|
}
|
|
39276
39325
|
};
|
|
39277
39326
|
}
|
|
39278
|
-
function tryPort(port, experimentName, experimentId, onExtensionMessage) {
|
|
39327
|
+
function tryPort(port, host, experimentName, experimentId, onExtensionMessage) {
|
|
39279
39328
|
return new Promise((resolve3, reject) => {
|
|
39280
39329
|
const httpServer = http.createServer((req, res) => {
|
|
39281
39330
|
if (req.url === "/health") {
|
|
@@ -39313,7 +39362,7 @@ function tryPort(port, experimentName, experimentId, onExtensionMessage) {
|
|
|
39313
39362
|
}
|
|
39314
39363
|
});
|
|
39315
39364
|
});
|
|
39316
|
-
httpServer.listen(port, () => {
|
|
39365
|
+
httpServer.listen(port, host, () => {
|
|
39317
39366
|
if (settled) return;
|
|
39318
39367
|
settled = true;
|
|
39319
39368
|
resolve3({ httpServer, wss });
|
|
@@ -39322,7 +39371,7 @@ function tryPort(port, experimentName, experimentId, onExtensionMessage) {
|
|
|
39322
39371
|
}
|
|
39323
39372
|
|
|
39324
39373
|
// src/commands/dev.ts
|
|
39325
|
-
var devCommand = new Command("dev").description("Start dev server with live reload for the browser extension").action(async () => {
|
|
39374
|
+
var devCommand = new Command("dev").description("Start dev server with live reload for the browser extension").option("--host <host>", "Address to listen on: 127.0.0.1 is this machine only, 0.0.0.0 is every interface", DEFAULT_HOST).action(async (opts) => {
|
|
39326
39375
|
try {
|
|
39327
39376
|
ensureLoggedIn();
|
|
39328
39377
|
const { manifest, dir } = readManifestOrExit();
|
|
@@ -39340,6 +39389,7 @@ var devCommand = new Command("dev").description("Start dev server with live relo
|
|
|
39340
39389
|
const server = await startDevServer({
|
|
39341
39390
|
experimentName: manifest.experimentName,
|
|
39342
39391
|
experimentId: manifest.experimentId,
|
|
39392
|
+
host: opts.host,
|
|
39343
39393
|
onExtensionMessage(msg, ws) {
|
|
39344
39394
|
if (msg.type === "ext:connect") {
|
|
39345
39395
|
const initMsg = {
|
|
@@ -39366,7 +39416,9 @@ var devCommand = new Command("dev").description("Start dev server with live relo
|
|
|
39366
39416
|
}
|
|
39367
39417
|
});
|
|
39368
39418
|
line();
|
|
39369
|
-
|
|
39419
|
+
const listening = describeDevServer(server);
|
|
39420
|
+
success(listening.line);
|
|
39421
|
+
if (listening.warning) warn(listening.warning);
|
|
39370
39422
|
info("Watching for changes...");
|
|
39371
39423
|
line();
|
|
39372
39424
|
const watcher = startWatcher({
|
|
@@ -39670,7 +39722,7 @@ var projectPushCommand = new Command("push").description("Compile and push proje
|
|
|
39670
39722
|
failCommand(err);
|
|
39671
39723
|
}
|
|
39672
39724
|
});
|
|
39673
|
-
var projectDevCommand = new Command("dev").description("Watch project script and hot-reload via browser extension").action(async () => {
|
|
39725
|
+
var projectDevCommand = new Command("dev").description("Watch project script and hot-reload via browser extension").option("--host <host>", "Address to listen on: 127.0.0.1 is this machine only, 0.0.0.0 is every interface", DEFAULT_HOST).action(async (opts) => {
|
|
39674
39726
|
try {
|
|
39675
39727
|
ensureLoggedIn();
|
|
39676
39728
|
const { manifest, dir } = readProjectManifestOrExit();
|
|
@@ -39696,6 +39748,7 @@ var projectDevCommand = new Command("dev").description("Watch project script and
|
|
|
39696
39748
|
const server = await startDevServer({
|
|
39697
39749
|
experimentName: `${manifest.projectName} (project script)`,
|
|
39698
39750
|
experimentId: manifest.projectId,
|
|
39751
|
+
host: opts.host,
|
|
39699
39752
|
onExtensionMessage(msg, ws) {
|
|
39700
39753
|
if (msg.type === "ext:connect") {
|
|
39701
39754
|
const initMsg = {
|
|
@@ -39711,7 +39764,9 @@ var projectDevCommand = new Command("dev").description("Watch project script and
|
|
|
39711
39764
|
}
|
|
39712
39765
|
});
|
|
39713
39766
|
line();
|
|
39714
|
-
|
|
39767
|
+
const listening = describeDevServer(server);
|
|
39768
|
+
success(listening.line);
|
|
39769
|
+
if (listening.warning) warn(listening.warning);
|
|
39715
39770
|
info("Watching for changes...");
|
|
39716
39771
|
line();
|
|
39717
39772
|
const watcher = startWatcher({
|
|
@@ -39982,6 +40037,21 @@ function readMetricList(data) {
|
|
|
39982
40037
|
function readMetric(data) {
|
|
39983
40038
|
return isRecord5(data) && isCliMetric(data.metric) ? data.metric : null;
|
|
39984
40039
|
}
|
|
40040
|
+
function plural(count, one, many) {
|
|
40041
|
+
return `${count} ${count === 1 ? one : many}`;
|
|
40042
|
+
}
|
|
40043
|
+
function describeArchiveCascade(data) {
|
|
40044
|
+
if (!isRecord5(data) || !isRecord5(data.cascade)) return null;
|
|
40045
|
+
const { experimentCount, flagRuleCount } = data.cascade;
|
|
40046
|
+
const experiments = typeof experimentCount === "number" ? experimentCount : 0;
|
|
40047
|
+
const flagRules = typeof flagRuleCount === "number" ? flagRuleCount : 0;
|
|
40048
|
+
if (experiments === 0 && flagRules === 0) return null;
|
|
40049
|
+
const parts = [
|
|
40050
|
+
experiments > 0 ? plural(experiments, "experiment", "experiments") : null,
|
|
40051
|
+
flagRules > 0 ? plural(flagRules, "flag rule", "flag rules") : null
|
|
40052
|
+
].filter((part) => part !== null);
|
|
40053
|
+
return `Detached from ${parts.join(" and ")}. Unarchiving will not reattach it.`;
|
|
40054
|
+
}
|
|
39985
40055
|
function formatMetricTable(metrics, usage) {
|
|
39986
40056
|
const rows = metrics.map((metric) => ({
|
|
39987
40057
|
id: metricIdLabel(metric),
|
|
@@ -40130,6 +40200,8 @@ var metricsArchiveCommand = new Command("archive").argument("<metricId>", "Metri
|
|
|
40130
40200
|
}
|
|
40131
40201
|
const metric = readMetric(response.data);
|
|
40132
40202
|
spinner.succeed(metric ? `Metric archived: ${metric.name} (${metricIdLabel(metric)})` : `Metric ${metricId} archived`);
|
|
40203
|
+
const cascade = describeArchiveCascade(response.data);
|
|
40204
|
+
if (cascade) info(cascade);
|
|
40133
40205
|
});
|
|
40134
40206
|
var metricsUnarchiveCommand = new Command("unarchive").argument("<metricId>", "Metric ID").description("Restore an archived metric").option("--project <project>", "Project ID (PRJ-42 or numeric); defaults to local manifest").action(async (metricId, opts) => {
|
|
40135
40207
|
const config = ensureLoggedIn();
|
|
@@ -43226,7 +43298,7 @@ function fishCompletion(entries) {
|
|
|
43226
43298
|
for (const entry of entries) {
|
|
43227
43299
|
const condition = `'test (__avsb_path) = "${entry.path}"'`;
|
|
43228
43300
|
if (entry.words.length > 0) {
|
|
43229
|
-
lines.push(`complete -c avsb -n ${condition} -a ${quoteFish(entry.words.join(" "))}`);
|
|
43301
|
+
lines.push(`complete -c avsb -n ${condition} -f -a ${quoteFish(entry.words.join(" "))}`);
|
|
43230
43302
|
}
|
|
43231
43303
|
for (const flag of entry.flags) {
|
|
43232
43304
|
lines.push(`complete -c avsb -n ${condition} -l ${flag.replace(/^--/, "")}`);
|