@avsbhq/cli 3.8.2 → 3.9.1
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 +107 -15
- 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,87 @@ 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";
|
|
39217
|
+
|
|
39218
|
+
// src/lib/devOrigin.ts
|
|
39219
|
+
var LOCAL_PAGE = /^https?:\/\/(localhost|127\.0\.0\.1|\[::1\])(:\d{1,5})?$/;
|
|
39220
|
+
var CHROME_EXTENSION = /^chrome-extension:\/\/[a-p]{32}$/;
|
|
39221
|
+
var FIREFOX_EXTENSION = /^moz-extension:\/\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
39222
|
+
function isAllowedDevOrigin(origin) {
|
|
39223
|
+
if (origin === void 0 || origin === "") return true;
|
|
39224
|
+
const value = origin.trim().toLowerCase();
|
|
39225
|
+
return LOCAL_PAGE.test(value) || CHROME_EXTENSION.test(value) || FIREFOX_EXTENSION.test(value);
|
|
39226
|
+
}
|
|
39227
|
+
var REFUSED_ORIGIN_STATUS = 403;
|
|
39228
|
+
|
|
39229
|
+
// src/lib/wsServer.ts
|
|
39216
39230
|
var PORT_START = 4400;
|
|
39217
39231
|
var PORT_END = 4409;
|
|
39232
|
+
var DEFAULT_HOST = "127.0.0.1";
|
|
39218
39233
|
var PING_INTERVAL_MS = 3e4;
|
|
39234
|
+
var PROBE_TIMEOUT_MS = 300;
|
|
39235
|
+
var LOOPBACK_PROBES = ["127.0.0.1", "::1"];
|
|
39219
39236
|
function portsBusyError(start, end) {
|
|
39220
39237
|
return new CliError("cli_failed", `Ports ${start} to ${end} are all busy.`, [
|
|
39221
39238
|
"Another `avsb dev` (or another program) is holding them. Stop it, then run this again.",
|
|
39222
39239
|
`\`lsof -i :${start}\` shows what is listening on a port.`
|
|
39223
39240
|
]);
|
|
39224
39241
|
}
|
|
39242
|
+
function errorCode(err) {
|
|
39243
|
+
return typeof err === "object" && err !== null && "code" in err ? err.code : void 0;
|
|
39244
|
+
}
|
|
39225
39245
|
function isAddressInUse(err) {
|
|
39226
|
-
return
|
|
39246
|
+
return errorCode(err) === "EADDRINUSE";
|
|
39247
|
+
}
|
|
39248
|
+
var BAD_HOST_CODES = /* @__PURE__ */ new Set(["EADDRNOTAVAIL", "ENOTFOUND", "EAI_AGAIN", "EAI_NONAME"]);
|
|
39249
|
+
function badHostError(host) {
|
|
39250
|
+
return new CliError("cli_failed", `Cannot listen on ${host}: it is not an address of this machine.`, [
|
|
39251
|
+
`Leave out --host to listen on ${DEFAULT_HOST} (this machine only), or pass 0.0.0.0 to listen on every interface.`
|
|
39252
|
+
]);
|
|
39253
|
+
}
|
|
39254
|
+
function isLoopbackHost(host) {
|
|
39255
|
+
return host === "localhost" || host === "::1" || /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host);
|
|
39256
|
+
}
|
|
39257
|
+
function describeDevServer(server) {
|
|
39258
|
+
if (isLoopbackHost(server.host)) {
|
|
39259
|
+
return { line: `Dev server running at ${server.url} (this machine only)`, warning: null };
|
|
39260
|
+
}
|
|
39261
|
+
return {
|
|
39262
|
+
line: `Dev server running at ${server.url}`,
|
|
39263
|
+
warning: `Listening on ${server.host}: other devices on your network can reach this server and read the code it serves.`
|
|
39264
|
+
};
|
|
39265
|
+
}
|
|
39266
|
+
function devServerUrl(host, port) {
|
|
39267
|
+
return `http://${host.includes(":") ? `[${host}]` : host}:${port}`;
|
|
39268
|
+
}
|
|
39269
|
+
function answersOn(address, port) {
|
|
39270
|
+
return new Promise((resolve3) => {
|
|
39271
|
+
const socket = net.connect({ host: address, port });
|
|
39272
|
+
const done = (answered) => {
|
|
39273
|
+
socket.destroy();
|
|
39274
|
+
resolve3(answered);
|
|
39275
|
+
};
|
|
39276
|
+
socket.setTimeout(PROBE_TIMEOUT_MS, () => done(false));
|
|
39277
|
+
socket.once("connect", () => done(true));
|
|
39278
|
+
socket.once("error", () => done(false));
|
|
39279
|
+
});
|
|
39280
|
+
}
|
|
39281
|
+
async function isPortAnsweredOnLoopback(port) {
|
|
39282
|
+
const answers = await Promise.all(LOOPBACK_PROBES.map((address) => answersOn(address, port)));
|
|
39283
|
+
return answers.some(Boolean);
|
|
39227
39284
|
}
|
|
39228
39285
|
async function startDevServer(options) {
|
|
39229
39286
|
const { experimentName, experimentId, onExtensionMessage } = options;
|
|
39230
39287
|
const start = options.portRange?.start ?? PORT_START;
|
|
39231
39288
|
const end = options.portRange?.end ?? PORT_END;
|
|
39289
|
+
const host = options.host ?? DEFAULT_HOST;
|
|
39232
39290
|
let bound = null;
|
|
39233
39291
|
for (let port2 = start; port2 <= end && !bound; port2++) {
|
|
39292
|
+
if (await isPortAnsweredOnLoopback(port2)) continue;
|
|
39234
39293
|
try {
|
|
39235
|
-
bound = { ...await tryPort(port2, experimentName, experimentId, onExtensionMessage), port: port2 };
|
|
39294
|
+
bound = { ...await tryPort(port2, host, experimentName, experimentId, onExtensionMessage), port: port2 };
|
|
39236
39295
|
} catch (err) {
|
|
39296
|
+
if (BAD_HOST_CODES.has(String(errorCode(err)))) throw badHostError(host);
|
|
39237
39297
|
if (!isAddressInUse(err)) throw err;
|
|
39238
39298
|
}
|
|
39239
39299
|
}
|
|
@@ -39252,6 +39312,8 @@ async function startDevServer(options) {
|
|
|
39252
39312
|
}, PING_INTERVAL_MS);
|
|
39253
39313
|
return {
|
|
39254
39314
|
port,
|
|
39315
|
+
host,
|
|
39316
|
+
url: devServerUrl(host, port),
|
|
39255
39317
|
broadcast(msg) {
|
|
39256
39318
|
broadcast(JSON.stringify(msg));
|
|
39257
39319
|
},
|
|
@@ -39275,7 +39337,7 @@ async function startDevServer(options) {
|
|
|
39275
39337
|
}
|
|
39276
39338
|
};
|
|
39277
39339
|
}
|
|
39278
|
-
function tryPort(port, experimentName, experimentId, onExtensionMessage) {
|
|
39340
|
+
function tryPort(port, host, experimentName, experimentId, onExtensionMessage) {
|
|
39279
39341
|
return new Promise((resolve3, reject) => {
|
|
39280
39342
|
const httpServer = http.createServer((req, res) => {
|
|
39281
39343
|
if (req.url === "/health") {
|
|
@@ -39293,7 +39355,14 @@ function tryPort(port, experimentName, experimentId, onExtensionMessage) {
|
|
|
39293
39355
|
})
|
|
39294
39356
|
);
|
|
39295
39357
|
});
|
|
39296
|
-
const wss = new import_websocket_server.default({
|
|
39358
|
+
const wss = new import_websocket_server.default({
|
|
39359
|
+
server: httpServer,
|
|
39360
|
+
// A web page must not reach the code this server streams (devOrigin.ts).
|
|
39361
|
+
verifyClient: (info2, done) => {
|
|
39362
|
+
if (isAllowedDevOrigin(info2.req.headers.origin)) done(true);
|
|
39363
|
+
else done(false, REFUSED_ORIGIN_STATUS, "Origin not allowed");
|
|
39364
|
+
}
|
|
39365
|
+
});
|
|
39297
39366
|
let settled = false;
|
|
39298
39367
|
const fail2 = (err) => {
|
|
39299
39368
|
if (settled) return;
|
|
@@ -39313,7 +39382,7 @@ function tryPort(port, experimentName, experimentId, onExtensionMessage) {
|
|
|
39313
39382
|
}
|
|
39314
39383
|
});
|
|
39315
39384
|
});
|
|
39316
|
-
httpServer.listen(port, () => {
|
|
39385
|
+
httpServer.listen(port, host, () => {
|
|
39317
39386
|
if (settled) return;
|
|
39318
39387
|
settled = true;
|
|
39319
39388
|
resolve3({ httpServer, wss });
|
|
@@ -39322,7 +39391,7 @@ function tryPort(port, experimentName, experimentId, onExtensionMessage) {
|
|
|
39322
39391
|
}
|
|
39323
39392
|
|
|
39324
39393
|
// src/commands/dev.ts
|
|
39325
|
-
var devCommand = new Command("dev").description("Start dev server with live reload for the browser extension").action(async () => {
|
|
39394
|
+
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
39395
|
try {
|
|
39327
39396
|
ensureLoggedIn();
|
|
39328
39397
|
const { manifest, dir } = readManifestOrExit();
|
|
@@ -39340,6 +39409,7 @@ var devCommand = new Command("dev").description("Start dev server with live relo
|
|
|
39340
39409
|
const server = await startDevServer({
|
|
39341
39410
|
experimentName: manifest.experimentName,
|
|
39342
39411
|
experimentId: manifest.experimentId,
|
|
39412
|
+
host: opts.host,
|
|
39343
39413
|
onExtensionMessage(msg, ws) {
|
|
39344
39414
|
if (msg.type === "ext:connect") {
|
|
39345
39415
|
const initMsg = {
|
|
@@ -39366,7 +39436,9 @@ var devCommand = new Command("dev").description("Start dev server with live relo
|
|
|
39366
39436
|
}
|
|
39367
39437
|
});
|
|
39368
39438
|
line();
|
|
39369
|
-
|
|
39439
|
+
const listening = describeDevServer(server);
|
|
39440
|
+
success(listening.line);
|
|
39441
|
+
if (listening.warning) warn(listening.warning);
|
|
39370
39442
|
info("Watching for changes...");
|
|
39371
39443
|
line();
|
|
39372
39444
|
const watcher = startWatcher({
|
|
@@ -39670,7 +39742,7 @@ var projectPushCommand = new Command("push").description("Compile and push proje
|
|
|
39670
39742
|
failCommand(err);
|
|
39671
39743
|
}
|
|
39672
39744
|
});
|
|
39673
|
-
var projectDevCommand = new Command("dev").description("Watch project script and hot-reload via browser extension").action(async () => {
|
|
39745
|
+
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
39746
|
try {
|
|
39675
39747
|
ensureLoggedIn();
|
|
39676
39748
|
const { manifest, dir } = readProjectManifestOrExit();
|
|
@@ -39696,6 +39768,7 @@ var projectDevCommand = new Command("dev").description("Watch project script and
|
|
|
39696
39768
|
const server = await startDevServer({
|
|
39697
39769
|
experimentName: `${manifest.projectName} (project script)`,
|
|
39698
39770
|
experimentId: manifest.projectId,
|
|
39771
|
+
host: opts.host,
|
|
39699
39772
|
onExtensionMessage(msg, ws) {
|
|
39700
39773
|
if (msg.type === "ext:connect") {
|
|
39701
39774
|
const initMsg = {
|
|
@@ -39711,7 +39784,9 @@ var projectDevCommand = new Command("dev").description("Watch project script and
|
|
|
39711
39784
|
}
|
|
39712
39785
|
});
|
|
39713
39786
|
line();
|
|
39714
|
-
|
|
39787
|
+
const listening = describeDevServer(server);
|
|
39788
|
+
success(listening.line);
|
|
39789
|
+
if (listening.warning) warn(listening.warning);
|
|
39715
39790
|
info("Watching for changes...");
|
|
39716
39791
|
line();
|
|
39717
39792
|
const watcher = startWatcher({
|
|
@@ -39982,6 +40057,21 @@ function readMetricList(data) {
|
|
|
39982
40057
|
function readMetric(data) {
|
|
39983
40058
|
return isRecord5(data) && isCliMetric(data.metric) ? data.metric : null;
|
|
39984
40059
|
}
|
|
40060
|
+
function plural(count, one, many) {
|
|
40061
|
+
return `${count} ${count === 1 ? one : many}`;
|
|
40062
|
+
}
|
|
40063
|
+
function describeArchiveCascade(data) {
|
|
40064
|
+
if (!isRecord5(data) || !isRecord5(data.cascade)) return null;
|
|
40065
|
+
const { experimentCount, flagRuleCount } = data.cascade;
|
|
40066
|
+
const experiments = typeof experimentCount === "number" ? experimentCount : 0;
|
|
40067
|
+
const flagRules = typeof flagRuleCount === "number" ? flagRuleCount : 0;
|
|
40068
|
+
if (experiments === 0 && flagRules === 0) return null;
|
|
40069
|
+
const parts = [
|
|
40070
|
+
experiments > 0 ? plural(experiments, "experiment", "experiments") : null,
|
|
40071
|
+
flagRules > 0 ? plural(flagRules, "flag rule", "flag rules") : null
|
|
40072
|
+
].filter((part) => part !== null);
|
|
40073
|
+
return `Detached from ${parts.join(" and ")}. Unarchiving will not reattach it.`;
|
|
40074
|
+
}
|
|
39985
40075
|
function formatMetricTable(metrics, usage) {
|
|
39986
40076
|
const rows = metrics.map((metric) => ({
|
|
39987
40077
|
id: metricIdLabel(metric),
|
|
@@ -40130,6 +40220,8 @@ var metricsArchiveCommand = new Command("archive").argument("<metricId>", "Metri
|
|
|
40130
40220
|
}
|
|
40131
40221
|
const metric = readMetric(response.data);
|
|
40132
40222
|
spinner.succeed(metric ? `Metric archived: ${metric.name} (${metricIdLabel(metric)})` : `Metric ${metricId} archived`);
|
|
40223
|
+
const cascade = describeArchiveCascade(response.data);
|
|
40224
|
+
if (cascade) info(cascade);
|
|
40133
40225
|
});
|
|
40134
40226
|
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
40227
|
const config = ensureLoggedIn();
|
|
@@ -43226,7 +43318,7 @@ function fishCompletion(entries) {
|
|
|
43226
43318
|
for (const entry of entries) {
|
|
43227
43319
|
const condition = `'test (__avsb_path) = "${entry.path}"'`;
|
|
43228
43320
|
if (entry.words.length > 0) {
|
|
43229
|
-
lines.push(`complete -c avsb -n ${condition} -a ${quoteFish(entry.words.join(" "))}`);
|
|
43321
|
+
lines.push(`complete -c avsb -n ${condition} -f -a ${quoteFish(entry.words.join(" "))}`);
|
|
43230
43322
|
}
|
|
43231
43323
|
for (const flag of entry.flags) {
|
|
43232
43324
|
lines.push(`complete -c avsb -n ${condition} -l ${flag.replace(/^--/, "")}`);
|