@avsbhq/cli 3.8.1 → 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/LICENSE +21 -0
- package/README.md +17 -8
- package/dist/cli.js +1444 -759
- package/dist/types/avsb.d.ts +9 -5
- package/dist/types/variation-code.d.ts +1 -1
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
|
-
const require =
|
|
2
|
+
import { createRequire as __cliCreateRequire } from "node:module";
|
|
3
|
+
const require = __cliCreateRequire(import.meta.url);
|
|
4
4
|
var __create = Object.create;
|
|
5
5
|
var __defProp = Object.defineProperty;
|
|
6
6
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -1150,8 +1150,8 @@ var require_command = __commonJS({
|
|
|
1150
1150
|
"node_modules/commander/lib/command.js"(exports) {
|
|
1151
1151
|
var EventEmitter2 = __require("node:events").EventEmitter;
|
|
1152
1152
|
var childProcess = __require("node:child_process");
|
|
1153
|
-
var
|
|
1154
|
-
var
|
|
1153
|
+
var path23 = __require("node:path");
|
|
1154
|
+
var fs19 = __require("node:fs");
|
|
1155
1155
|
var process11 = __require("node:process");
|
|
1156
1156
|
var { Argument: Argument2, humanReadableArgName } = require_argument();
|
|
1157
1157
|
var { CommanderError: CommanderError2 } = require_error();
|
|
@@ -2132,7 +2132,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2132
2132
|
* @param {string} subcommandName
|
|
2133
2133
|
*/
|
|
2134
2134
|
_checkForMissingExecutable(executableFile, executableDir, subcommandName) {
|
|
2135
|
-
if (
|
|
2135
|
+
if (fs19.existsSync(executableFile)) return;
|
|
2136
2136
|
const executableDirMessage = executableDir ? `searched for local subcommand relative to directory '${executableDir}'` : "no directory for search for local subcommand, use .executableDir() to supply a custom directory";
|
|
2137
2137
|
const executableMissing = `'${executableFile}' does not exist
|
|
2138
2138
|
- if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
|
|
@@ -2150,11 +2150,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2150
2150
|
let launchWithNode = false;
|
|
2151
2151
|
const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
|
|
2152
2152
|
function findFile(baseDir, baseName) {
|
|
2153
|
-
const localBin =
|
|
2154
|
-
if (
|
|
2155
|
-
if (sourceExt.includes(
|
|
2153
|
+
const localBin = path23.resolve(baseDir, baseName);
|
|
2154
|
+
if (fs19.existsSync(localBin)) return localBin;
|
|
2155
|
+
if (sourceExt.includes(path23.extname(baseName))) return void 0;
|
|
2156
2156
|
const foundExt = sourceExt.find(
|
|
2157
|
-
(ext) =>
|
|
2157
|
+
(ext) => fs19.existsSync(`${localBin}${ext}`)
|
|
2158
2158
|
);
|
|
2159
2159
|
if (foundExt) return `${localBin}${foundExt}`;
|
|
2160
2160
|
return void 0;
|
|
@@ -2166,21 +2166,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2166
2166
|
if (this._scriptPath) {
|
|
2167
2167
|
let resolvedScriptPath;
|
|
2168
2168
|
try {
|
|
2169
|
-
resolvedScriptPath =
|
|
2169
|
+
resolvedScriptPath = fs19.realpathSync(this._scriptPath);
|
|
2170
2170
|
} catch {
|
|
2171
2171
|
resolvedScriptPath = this._scriptPath;
|
|
2172
2172
|
}
|
|
2173
|
-
executableDir =
|
|
2174
|
-
|
|
2173
|
+
executableDir = path23.resolve(
|
|
2174
|
+
path23.dirname(resolvedScriptPath),
|
|
2175
2175
|
executableDir
|
|
2176
2176
|
);
|
|
2177
2177
|
}
|
|
2178
2178
|
if (executableDir) {
|
|
2179
2179
|
let localFile = findFile(executableDir, executableFile);
|
|
2180
2180
|
if (!localFile && !subcommand._executableFile && this._scriptPath) {
|
|
2181
|
-
const legacyName =
|
|
2181
|
+
const legacyName = path23.basename(
|
|
2182
2182
|
this._scriptPath,
|
|
2183
|
-
|
|
2183
|
+
path23.extname(this._scriptPath)
|
|
2184
2184
|
);
|
|
2185
2185
|
if (legacyName !== this._name) {
|
|
2186
2186
|
localFile = findFile(
|
|
@@ -2191,7 +2191,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2191
2191
|
}
|
|
2192
2192
|
executableFile = localFile || executableFile;
|
|
2193
2193
|
}
|
|
2194
|
-
launchWithNode = sourceExt.includes(
|
|
2194
|
+
launchWithNode = sourceExt.includes(path23.extname(executableFile));
|
|
2195
2195
|
let proc;
|
|
2196
2196
|
if (process11.platform !== "win32") {
|
|
2197
2197
|
if (launchWithNode) {
|
|
@@ -3038,7 +3038,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3038
3038
|
* @return {Command}
|
|
3039
3039
|
*/
|
|
3040
3040
|
nameFromFilename(filename) {
|
|
3041
|
-
this._name =
|
|
3041
|
+
this._name = path23.basename(filename, path23.extname(filename));
|
|
3042
3042
|
return this;
|
|
3043
3043
|
}
|
|
3044
3044
|
/**
|
|
@@ -3052,9 +3052,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3052
3052
|
* @param {string} [path]
|
|
3053
3053
|
* @return {(string|null|Command)}
|
|
3054
3054
|
*/
|
|
3055
|
-
executableDir(
|
|
3056
|
-
if (
|
|
3057
|
-
this._executableDir =
|
|
3055
|
+
executableDir(path24) {
|
|
3056
|
+
if (path24 === void 0) return this._executableDir;
|
|
3057
|
+
this._executableDir = path24;
|
|
3058
3058
|
return this;
|
|
3059
3059
|
}
|
|
3060
3060
|
/**
|
|
@@ -3227,13 +3227,13 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3227
3227
|
* @param {(string | Function)} text - string to add, or a function returning a string
|
|
3228
3228
|
* @return {Command} `this` command for chaining
|
|
3229
3229
|
*/
|
|
3230
|
-
addHelpText(
|
|
3230
|
+
addHelpText(position2, text) {
|
|
3231
3231
|
const allowedValues = ["beforeAll", "before", "after", "afterAll"];
|
|
3232
|
-
if (!allowedValues.includes(
|
|
3232
|
+
if (!allowedValues.includes(position2)) {
|
|
3233
3233
|
throw new Error(`Unexpected value for position to addHelpText.
|
|
3234
3234
|
Expecting one of '${allowedValues.join("', '")}'`);
|
|
3235
3235
|
}
|
|
3236
|
-
const helpEvent = `${
|
|
3236
|
+
const helpEvent = `${position2}Help`;
|
|
3237
3237
|
this.on(helpEvent, (context) => {
|
|
3238
3238
|
let helpStr;
|
|
3239
3239
|
if (typeof text === "function") {
|
|
@@ -4418,15 +4418,15 @@ var require_route = __commonJS({
|
|
|
4418
4418
|
};
|
|
4419
4419
|
}
|
|
4420
4420
|
function wrapConversion(toModel, graph) {
|
|
4421
|
-
const
|
|
4421
|
+
const path23 = [graph[toModel].parent, toModel];
|
|
4422
4422
|
let fn = conversions[graph[toModel].parent][toModel];
|
|
4423
4423
|
let cur = graph[toModel].parent;
|
|
4424
4424
|
while (graph[cur].parent) {
|
|
4425
|
-
|
|
4425
|
+
path23.unshift(graph[cur].parent);
|
|
4426
4426
|
fn = link(conversions[graph[cur].parent][cur], fn);
|
|
4427
4427
|
cur = graph[cur].parent;
|
|
4428
4428
|
}
|
|
4429
|
-
fn.conversion =
|
|
4429
|
+
fn.conversion = path23;
|
|
4430
4430
|
return fn;
|
|
4431
4431
|
}
|
|
4432
4432
|
module.exports = function(fromModel) {
|
|
@@ -10482,10 +10482,10 @@ var require_lib2 = __commonJS({
|
|
|
10482
10482
|
exports.analyse = analyse;
|
|
10483
10483
|
var detectFile = (filepath, opts = {}) => new Promise((resolve3, reject) => {
|
|
10484
10484
|
let fd;
|
|
10485
|
-
const
|
|
10485
|
+
const fs19 = (0, node_1.default)();
|
|
10486
10486
|
const handler = (err, buffer) => {
|
|
10487
10487
|
if (fd) {
|
|
10488
|
-
|
|
10488
|
+
fs19.closeSync(fd);
|
|
10489
10489
|
}
|
|
10490
10490
|
if (err) {
|
|
10491
10491
|
reject(err);
|
|
@@ -10497,9 +10497,9 @@ var require_lib2 = __commonJS({
|
|
|
10497
10497
|
};
|
|
10498
10498
|
const sampleSize = (opts === null || opts === void 0 ? void 0 : opts.sampleSize) || 0;
|
|
10499
10499
|
if (sampleSize > 0) {
|
|
10500
|
-
fd =
|
|
10500
|
+
fd = fs19.openSync(filepath, "r");
|
|
10501
10501
|
let sample = Buffer.allocUnsafe(sampleSize);
|
|
10502
|
-
|
|
10502
|
+
fs19.read(fd, sample, 0, sampleSize, opts.offset, (err, bytesRead) => {
|
|
10503
10503
|
if (err) {
|
|
10504
10504
|
handler(err, null);
|
|
10505
10505
|
} else {
|
|
@@ -10511,22 +10511,22 @@ var require_lib2 = __commonJS({
|
|
|
10511
10511
|
});
|
|
10512
10512
|
return;
|
|
10513
10513
|
}
|
|
10514
|
-
|
|
10514
|
+
fs19.readFile(filepath, handler);
|
|
10515
10515
|
});
|
|
10516
10516
|
exports.detectFile = detectFile;
|
|
10517
10517
|
var detectFileSync = (filepath, opts = {}) => {
|
|
10518
|
-
const
|
|
10518
|
+
const fs19 = (0, node_1.default)();
|
|
10519
10519
|
if (opts && opts.sampleSize) {
|
|
10520
|
-
const fd =
|
|
10520
|
+
const fd = fs19.openSync(filepath, "r");
|
|
10521
10521
|
let sample = Buffer.allocUnsafe(opts.sampleSize);
|
|
10522
|
-
const bytesRead =
|
|
10522
|
+
const bytesRead = fs19.readSync(fd, sample, 0, opts.sampleSize, opts.offset);
|
|
10523
10523
|
if (bytesRead < opts.sampleSize) {
|
|
10524
10524
|
sample = sample.subarray(0, bytesRead);
|
|
10525
10525
|
}
|
|
10526
|
-
|
|
10526
|
+
fs19.closeSync(fd);
|
|
10527
10527
|
return (0, exports.detect)(sample);
|
|
10528
10528
|
}
|
|
10529
|
-
return (0, exports.detect)(
|
|
10529
|
+
return (0, exports.detect)(fs19.readFileSync(filepath));
|
|
10530
10530
|
};
|
|
10531
10531
|
exports.detectFileSync = detectFileSync;
|
|
10532
10532
|
exports.default = {
|
|
@@ -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
|
}
|
|
@@ -29097,9 +29097,9 @@ var require_websocket_server = __commonJS({
|
|
|
29097
29097
|
});
|
|
29098
29098
|
|
|
29099
29099
|
// src/cli.ts
|
|
29100
|
-
import
|
|
29100
|
+
import fs18 from "node:fs";
|
|
29101
29101
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
29102
|
-
import
|
|
29102
|
+
import path22 from "node:path";
|
|
29103
29103
|
|
|
29104
29104
|
// node_modules/commander/esm.mjs
|
|
29105
29105
|
var import_index = __toESM(require_commander(), 1);
|
|
@@ -29119,7 +29119,7 @@ var {
|
|
|
29119
29119
|
} = import_index.default;
|
|
29120
29120
|
|
|
29121
29121
|
// src/commands/init.ts
|
|
29122
|
-
import
|
|
29122
|
+
import path10 from "node:path";
|
|
29123
29123
|
|
|
29124
29124
|
// node_modules/@inquirer/core/dist/esm/lib/key.js
|
|
29125
29125
|
var isUpKey = (key, keybindings = []) => (
|
|
@@ -30382,16 +30382,16 @@ var esm_default2 = createPrompt((config, done) => {
|
|
|
30382
30382
|
} else if (isNumberKey(key)) {
|
|
30383
30383
|
const selectedIndex = Number(key.name) - 1;
|
|
30384
30384
|
let selectableIndex = -1;
|
|
30385
|
-
const
|
|
30385
|
+
const position2 = items.findIndex((item) => {
|
|
30386
30386
|
if (Separator.isSeparator(item))
|
|
30387
30387
|
return false;
|
|
30388
30388
|
selectableIndex++;
|
|
30389
30389
|
return selectableIndex === selectedIndex;
|
|
30390
30390
|
});
|
|
30391
|
-
const selectedItem = items[
|
|
30391
|
+
const selectedItem = items[position2];
|
|
30392
30392
|
if (selectedItem && isSelectable(selectedItem)) {
|
|
30393
|
-
setActive(
|
|
30394
|
-
setItems(items.map((choice, i) => i ===
|
|
30393
|
+
setActive(position2);
|
|
30394
|
+
setItems(items.map((choice, i) => i === position2 ? toggle(choice) : choice));
|
|
30395
30395
|
}
|
|
30396
30396
|
}
|
|
30397
30397
|
});
|
|
@@ -31442,15 +31442,15 @@ var esm_default11 = createPrompt((config, done) => {
|
|
|
31442
31442
|
} else if (isNumberKey(key) && !Number.isNaN(Number(rl.line))) {
|
|
31443
31443
|
const selectedIndex = Number(rl.line) - 1;
|
|
31444
31444
|
let selectableIndex = -1;
|
|
31445
|
-
const
|
|
31445
|
+
const position2 = items.findIndex((item2) => {
|
|
31446
31446
|
if (Separator.isSeparator(item2))
|
|
31447
31447
|
return false;
|
|
31448
31448
|
selectableIndex++;
|
|
31449
31449
|
return selectableIndex === selectedIndex;
|
|
31450
31450
|
});
|
|
31451
|
-
const item = items[
|
|
31451
|
+
const item = items[position2];
|
|
31452
31452
|
if (item != null && isSelectable3(item)) {
|
|
31453
|
-
setActive(
|
|
31453
|
+
setActive(position2);
|
|
31454
31454
|
}
|
|
31455
31455
|
searchTimeoutRef.current = setTimeout(() => {
|
|
31456
31456
|
rl.clearLine(0);
|
|
@@ -31529,9 +31529,9 @@ var import_run_async = __toESM(require_run_async(), 1);
|
|
|
31529
31529
|
var import_mute_stream2 = __toESM(require_lib(), 1);
|
|
31530
31530
|
import readline3 from "node:readline";
|
|
31531
31531
|
var _ = {
|
|
31532
|
-
set: (obj,
|
|
31532
|
+
set: (obj, path23 = "", value) => {
|
|
31533
31533
|
let pointer = obj;
|
|
31534
|
-
|
|
31534
|
+
path23.split(".").forEach((key, index, arr) => {
|
|
31535
31535
|
if (key === "__proto__" || key === "constructor")
|
|
31536
31536
|
return;
|
|
31537
31537
|
if (index === arr.length - 1) {
|
|
@@ -31542,8 +31542,8 @@ var _ = {
|
|
|
31542
31542
|
pointer = pointer[key];
|
|
31543
31543
|
});
|
|
31544
31544
|
},
|
|
31545
|
-
get: (obj,
|
|
31546
|
-
const travel = (regexp) => String.prototype.split.call(
|
|
31545
|
+
get: (obj, path23 = "", defaultValue) => {
|
|
31546
|
+
const travel = (regexp) => String.prototype.split.call(path23, regexp).filter(Boolean).reduce(
|
|
31547
31547
|
// @ts-expect-error implicit any on res[key]
|
|
31548
31548
|
(res, key) => res == null ? res : res[key],
|
|
31549
31549
|
obj
|
|
@@ -31853,7 +31853,8 @@ var EXIT_FOR_CODE = {
|
|
|
31853
31853
|
cli_not_found: EXIT.failure,
|
|
31854
31854
|
cli_conflict: EXIT.conflict,
|
|
31855
31855
|
cli_io_failed: EXIT.failure,
|
|
31856
|
-
cli_failed: EXIT.failure
|
|
31856
|
+
cli_failed: EXIT.failure,
|
|
31857
|
+
cli_network: EXIT.failure
|
|
31857
31858
|
};
|
|
31858
31859
|
var CliError = class extends Error {
|
|
31859
31860
|
code;
|
|
@@ -32062,9 +32063,9 @@ import os2 from "node:os";
|
|
|
32062
32063
|
import tty from "node:tty";
|
|
32063
32064
|
function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process4.argv) {
|
|
32064
32065
|
const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
|
|
32065
|
-
const
|
|
32066
|
+
const position2 = argv.indexOf(prefix + flag);
|
|
32066
32067
|
const terminatorPosition = argv.indexOf("--");
|
|
32067
|
-
return
|
|
32068
|
+
return position2 !== -1 && (terminatorPosition === -1 || position2 < terminatorPosition);
|
|
32068
32069
|
}
|
|
32069
32070
|
var { env } = process4;
|
|
32070
32071
|
var flagForceColor;
|
|
@@ -33105,6 +33106,14 @@ function label(key, value) {
|
|
|
33105
33106
|
if (isQuiet()) return;
|
|
33106
33107
|
writeOut(` ${source_default.dim(key + ":")} ${value}`);
|
|
33107
33108
|
}
|
|
33109
|
+
function field(key, value) {
|
|
33110
|
+
if (isJsonOutput()) return;
|
|
33111
|
+
if (isQuiet()) {
|
|
33112
|
+
writeOut(`${key}: ${value}`);
|
|
33113
|
+
return;
|
|
33114
|
+
}
|
|
33115
|
+
writeOut(` ${source_default.dim(key + ":")} ${value}`);
|
|
33116
|
+
}
|
|
33108
33117
|
function fileTree(lines) {
|
|
33109
33118
|
if (isQuiet()) return;
|
|
33110
33119
|
for (const line2 of lines) {
|
|
@@ -33117,6 +33126,62 @@ function line(msg = "") {
|
|
|
33117
33126
|
writeOut(msg);
|
|
33118
33127
|
}
|
|
33119
33128
|
|
|
33129
|
+
// src/lib/apiErrorDetails.ts
|
|
33130
|
+
function isRecord(value) {
|
|
33131
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
33132
|
+
}
|
|
33133
|
+
function readPath(value) {
|
|
33134
|
+
if (!Array.isArray(value)) return void 0;
|
|
33135
|
+
const path23 = value.filter((part) => typeof part === "string" || typeof part === "number");
|
|
33136
|
+
return path23.length === value.length ? path23 : void 0;
|
|
33137
|
+
}
|
|
33138
|
+
function readValidationIssues(details) {
|
|
33139
|
+
const raw = details?.issues;
|
|
33140
|
+
if (!Array.isArray(raw)) return [];
|
|
33141
|
+
const issues = [];
|
|
33142
|
+
for (const entry of raw) {
|
|
33143
|
+
if (!isRecord(entry) || typeof entry.message !== "string") continue;
|
|
33144
|
+
const path23 = readPath(entry.path) ?? [];
|
|
33145
|
+
const param = typeof entry.param === "string" ? entry.param : path23.join(".");
|
|
33146
|
+
issues.push({ path: path23, param, message: entry.message });
|
|
33147
|
+
}
|
|
33148
|
+
return issues;
|
|
33149
|
+
}
|
|
33150
|
+
function readCompilationErrors(details) {
|
|
33151
|
+
const raw = details?.compilationErrors;
|
|
33152
|
+
if (!Array.isArray(raw)) return [];
|
|
33153
|
+
const errors = [];
|
|
33154
|
+
for (const entry of raw) {
|
|
33155
|
+
if (!isRecord(entry) || typeof entry.file !== "string" || typeof entry.message !== "string") continue;
|
|
33156
|
+
const path23 = readPath(entry.path);
|
|
33157
|
+
errors.push({
|
|
33158
|
+
file: entry.file,
|
|
33159
|
+
line: typeof entry.line === "number" ? entry.line : 0,
|
|
33160
|
+
column: typeof entry.column === "number" ? entry.column : 0,
|
|
33161
|
+
message: entry.message,
|
|
33162
|
+
...path23 ? { path: path23 } : {}
|
|
33163
|
+
});
|
|
33164
|
+
}
|
|
33165
|
+
return errors;
|
|
33166
|
+
}
|
|
33167
|
+
function position(file, line2, column) {
|
|
33168
|
+
if (line2 <= 0) return file;
|
|
33169
|
+
return column > 0 ? `${file}:${line2}:${column}` : `${file}:${line2}`;
|
|
33170
|
+
}
|
|
33171
|
+
function describeErrorDetails(details, labelForPath) {
|
|
33172
|
+
const lines = [];
|
|
33173
|
+
for (const issue of readValidationIssues(details)) {
|
|
33174
|
+
const label2 = issue.path.length > 0 ? labelForPath?.(issue.path) : void 0;
|
|
33175
|
+
const where = label2 ? `${label2} (${issue.param})` : issue.param || "(request body)";
|
|
33176
|
+
lines.push(`${where}: ${issue.message}`);
|
|
33177
|
+
}
|
|
33178
|
+
for (const error2 of readCompilationErrors(details)) {
|
|
33179
|
+
const label2 = error2.path ? labelForPath?.(error2.path) : void 0;
|
|
33180
|
+
lines.push(`${position(label2 ?? error2.file, error2.line, error2.column)}: ${error2.message}`);
|
|
33181
|
+
}
|
|
33182
|
+
return lines;
|
|
33183
|
+
}
|
|
33184
|
+
|
|
33120
33185
|
// src/lib/tokenShape.ts
|
|
33121
33186
|
var CREDENTIAL_NAMES = {
|
|
33122
33187
|
personalAccessToken: "personal access token",
|
|
@@ -33171,7 +33236,7 @@ var ApiRequestError = class extends Error {
|
|
|
33171
33236
|
return exitCodeForStatus(this.status);
|
|
33172
33237
|
}
|
|
33173
33238
|
};
|
|
33174
|
-
function
|
|
33239
|
+
function isRecord2(value) {
|
|
33175
33240
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
33176
33241
|
}
|
|
33177
33242
|
function asString(value) {
|
|
@@ -33185,16 +33250,16 @@ function parseRetryAfter(raw) {
|
|
|
33185
33250
|
function toApiRequestError(input) {
|
|
33186
33251
|
const retryAfterSeconds = parseRetryAfter(input.retryAfter);
|
|
33187
33252
|
const headerRequestId = asString(input.requestId ?? void 0);
|
|
33188
|
-
if (
|
|
33253
|
+
if (isRecord2(input.body)) {
|
|
33189
33254
|
const raw = input.body.error;
|
|
33190
|
-
if (
|
|
33255
|
+
if (isRecord2(raw)) {
|
|
33191
33256
|
return new ApiRequestError({
|
|
33192
33257
|
status: input.status,
|
|
33193
33258
|
message: asString(raw.message) ?? `API error ${input.status}`,
|
|
33194
33259
|
code: asString(raw.code),
|
|
33195
33260
|
docUrl: asString(raw.docUrl),
|
|
33196
33261
|
requestId: asString(raw.requestId) ?? headerRequestId,
|
|
33197
|
-
details:
|
|
33262
|
+
details: isRecord2(raw.details) ? raw.details : void 0,
|
|
33198
33263
|
retryAfterSeconds
|
|
33199
33264
|
});
|
|
33200
33265
|
}
|
|
@@ -33215,13 +33280,13 @@ function toApiRequestError(input) {
|
|
|
33215
33280
|
}
|
|
33216
33281
|
return new ApiRequestError({
|
|
33217
33282
|
status: input.status,
|
|
33218
|
-
message: `API error ${input.status}`,
|
|
33283
|
+
message: input.host ? `${input.host} answered HTTP ${input.status}, which is not an A vs B API response. Check the API URL.` : `API error ${input.status}`,
|
|
33219
33284
|
requestId: headerRequestId,
|
|
33220
33285
|
retryAfterSeconds
|
|
33221
33286
|
});
|
|
33222
33287
|
}
|
|
33223
|
-
function describeApiFailure(error2) {
|
|
33224
|
-
const lines =
|
|
33288
|
+
function describeApiFailure(error2, labelForPath) {
|
|
33289
|
+
const lines = describeErrorDetails(error2.details, labelForPath);
|
|
33225
33290
|
if (error2.status === 401) {
|
|
33226
33291
|
lines.push(
|
|
33227
33292
|
`Check the credential: the CLI signs in with a ${CREDENTIAL_NAMES.personalAccessToken} (${CREDENTIAL_NAMES.personalAccessTokenPrefix}\u2026). Run \`avsb whoami\` to see which one is in use, or \`avsb login\` to replace it.`
|
|
@@ -33296,31 +33361,31 @@ function exitCodeFor(error2) {
|
|
|
33296
33361
|
if (error2 instanceof CliError) return error2.exitCode;
|
|
33297
33362
|
return EXIT.failure;
|
|
33298
33363
|
}
|
|
33299
|
-
function hintsFor(error2) {
|
|
33300
|
-
if (error2 instanceof ApiRequestError) return describeApiFailure(error2);
|
|
33364
|
+
function hintsFor(error2, labelForPath) {
|
|
33365
|
+
if (error2 instanceof ApiRequestError) return describeApiFailure(error2, labelForPath);
|
|
33301
33366
|
if (error2 instanceof CliError) return error2.hints;
|
|
33302
33367
|
return [];
|
|
33303
33368
|
}
|
|
33304
|
-
function failCommand(error2) {
|
|
33369
|
+
function failCommand(error2, options = {}) {
|
|
33305
33370
|
const body = toJsonErrorBody(error2);
|
|
33306
33371
|
if (isJsonOutput()) {
|
|
33307
33372
|
writeOut(JSON.stringify({ ok: false, command: commandName, error: body }, null, 2));
|
|
33308
33373
|
}
|
|
33309
33374
|
error(body.message);
|
|
33310
|
-
for (const hint of hintsFor(error2)) errorDetail(hint);
|
|
33375
|
+
for (const hint of hintsFor(error2, options.labelForPath)) errorDetail(hint);
|
|
33311
33376
|
process.exit(exitCodeFor(error2));
|
|
33312
33377
|
}
|
|
33313
33378
|
|
|
33314
33379
|
// src/lib/credentials.ts
|
|
33315
33380
|
var CONFIG_VERSION = 2;
|
|
33316
|
-
function
|
|
33381
|
+
function isRecord3(value) {
|
|
33317
33382
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
33318
33383
|
}
|
|
33319
33384
|
function isNonEmptyString(value) {
|
|
33320
33385
|
return typeof value === "string" && value.trim().length > 0;
|
|
33321
33386
|
}
|
|
33322
33387
|
function isStoredLogin(value) {
|
|
33323
|
-
return
|
|
33388
|
+
return isRecord3(value) && isNonEmptyString(value.orgId) && typeof value.orgName === "string" && isNonEmptyString(value.token);
|
|
33324
33389
|
}
|
|
33325
33390
|
function parseConfigDocument(raw) {
|
|
33326
33391
|
let parsed;
|
|
@@ -33329,7 +33394,7 @@ function parseConfigDocument(raw) {
|
|
|
33329
33394
|
} catch {
|
|
33330
33395
|
return { status: "unreadable", detail: "it is not valid JSON" };
|
|
33331
33396
|
}
|
|
33332
|
-
if (!
|
|
33397
|
+
if (!isRecord3(parsed)) {
|
|
33333
33398
|
return { status: "unreadable", detail: "it does not contain a JSON object" };
|
|
33334
33399
|
}
|
|
33335
33400
|
if (parsed.version !== CONFIG_VERSION) {
|
|
@@ -33534,6 +33599,17 @@ var FRAMEWORK_IDS = [
|
|
|
33534
33599
|
"node",
|
|
33535
33600
|
"none"
|
|
33536
33601
|
];
|
|
33602
|
+
var BUNDLERS = [
|
|
33603
|
+
{ id: "vite", dependency: "vite" },
|
|
33604
|
+
{ id: "create-react-app", dependency: "react-scripts" },
|
|
33605
|
+
{ id: "vue-cli", dependency: "@vue/cli-service" }
|
|
33606
|
+
];
|
|
33607
|
+
function detectBundler(deps) {
|
|
33608
|
+
for (const bundler of BUNDLERS) {
|
|
33609
|
+
if (Object.prototype.hasOwnProperty.call(deps, bundler.dependency)) return bundler;
|
|
33610
|
+
}
|
|
33611
|
+
return null;
|
|
33612
|
+
}
|
|
33537
33613
|
function parsePackageJson(raw) {
|
|
33538
33614
|
let parsed;
|
|
33539
33615
|
try {
|
|
@@ -33566,12 +33642,13 @@ function allDependencies(pkg2) {
|
|
|
33566
33642
|
}
|
|
33567
33643
|
function detectFramework(pkg2) {
|
|
33568
33644
|
if (!pkg2) {
|
|
33569
|
-
return { id: "none", matchedDependency: null, usesVite: false, usesSvelteKit: false };
|
|
33645
|
+
return { id: "none", matchedDependency: null, usesVite: false, usesSvelteKit: false, bundler: null };
|
|
33570
33646
|
}
|
|
33571
33647
|
const deps = allDependencies(pkg2);
|
|
33572
33648
|
const has = (name) => Object.prototype.hasOwnProperty.call(deps, name);
|
|
33573
33649
|
const usesVite = has("vite");
|
|
33574
33650
|
const usesSvelteKit = has("@sveltejs/kit");
|
|
33651
|
+
const bundler = detectBundler(deps);
|
|
33575
33652
|
const ordered = [
|
|
33576
33653
|
{ id: "nuxt", dependency: "nuxt" },
|
|
33577
33654
|
{ id: "next", dependency: "next" },
|
|
@@ -33588,11 +33665,12 @@ function detectFramework(pkg2) {
|
|
|
33588
33665
|
id: candidate.id,
|
|
33589
33666
|
matchedDependency: candidate.dependency,
|
|
33590
33667
|
usesVite,
|
|
33591
|
-
usesSvelteKit
|
|
33668
|
+
usesSvelteKit,
|
|
33669
|
+
bundler
|
|
33592
33670
|
};
|
|
33593
33671
|
}
|
|
33594
33672
|
}
|
|
33595
|
-
return { id: "node", matchedDependency: null, usesVite, usesSvelteKit };
|
|
33673
|
+
return { id: "node", matchedDependency: null, usesVite, usesSvelteKit, bundler };
|
|
33596
33674
|
}
|
|
33597
33675
|
function isFrameworkId(value) {
|
|
33598
33676
|
return FRAMEWORK_IDS.includes(value);
|
|
@@ -33640,9 +33718,26 @@ function installArgv(manager, npmPackage) {
|
|
|
33640
33718
|
}
|
|
33641
33719
|
|
|
33642
33720
|
// src/lib/frameworkProfiles.ts
|
|
33643
|
-
var
|
|
33644
|
-
var
|
|
33645
|
-
var
|
|
33721
|
+
var BROWSER_ENV_FILE = ".env.local";
|
|
33722
|
+
var UNPREFIXED_CAVEAT = "No Vite, Create React App or Vue CLI in this project, so the browser variable prefix is your bundler's call. Rename AVSB_SDK_KEY to the prefix your bundler exposes to the browser, or pass the key to the provider directly.";
|
|
33723
|
+
var BUNDLER_VARIABLES = {
|
|
33724
|
+
vite: { envVar: "VITE_AVSB_SDK_KEY", label: "Vite", prefix: "VITE_" },
|
|
33725
|
+
"create-react-app": { envVar: "REACT_APP_AVSB_SDK_KEY", label: "Create React App", prefix: "REACT_APP_" },
|
|
33726
|
+
"vue-cli": { envVar: "VUE_APP_AVSB_SDK_KEY", label: "Vue CLI", prefix: "VUE_APP_" }
|
|
33727
|
+
};
|
|
33728
|
+
function browserVariable(detection) {
|
|
33729
|
+
const bundler = detection.bundler;
|
|
33730
|
+
const applies = bundler !== null && (bundler.id === "vite" || bundler.id === "create-react-app" && detection.id === "react" || bundler.id === "vue-cli" && detection.id === "vue");
|
|
33731
|
+
if (!applies || bundler === null) {
|
|
33732
|
+
return { envVar: "AVSB_SDK_KEY", envVarReason: null, caveat: UNPREFIXED_CAVEAT };
|
|
33733
|
+
}
|
|
33734
|
+
const known = BUNDLER_VARIABLES[bundler.id];
|
|
33735
|
+
return {
|
|
33736
|
+
envVar: known.envVar,
|
|
33737
|
+
envVarReason: `found ${bundler.dependency}, and ${known.label} only exposes ${known.prefix} variables to the browser`,
|
|
33738
|
+
caveat: null
|
|
33739
|
+
};
|
|
33740
|
+
}
|
|
33646
33741
|
function buildFrameworkProfile(detection) {
|
|
33647
33742
|
switch (detection.id) {
|
|
33648
33743
|
case "next":
|
|
@@ -33654,6 +33749,7 @@ function buildFrameworkProfile(detection) {
|
|
|
33654
33749
|
envVar: "AVSB_SDK_KEY",
|
|
33655
33750
|
exampleFile: "avsb-example.tsx",
|
|
33656
33751
|
caveat: null,
|
|
33752
|
+
envVarReason: null,
|
|
33657
33753
|
docsPath: "/docs/integrations/nextjs-app-router"
|
|
33658
33754
|
};
|
|
33659
33755
|
case "nuxt":
|
|
@@ -33665,6 +33761,7 @@ function buildFrameworkProfile(detection) {
|
|
|
33665
33761
|
envVar: "NUXT_PUBLIC_AVSB_SDK_KEY",
|
|
33666
33762
|
exampleFile: "components/AvsbExample.vue",
|
|
33667
33763
|
caveat: 'Add "@avsbhq/nuxt" to the modules array in nuxt.config.ts. The module reads the key from this variable.',
|
|
33764
|
+
envVarReason: null,
|
|
33668
33765
|
docsPath: "/docs/integrations/nuxt"
|
|
33669
33766
|
};
|
|
33670
33767
|
case "angular":
|
|
@@ -33676,6 +33773,7 @@ function buildFrameworkProfile(detection) {
|
|
|
33676
33773
|
envVar: null,
|
|
33677
33774
|
exampleFile: "src/app/avsb-example.component.ts",
|
|
33678
33775
|
caveat: "Angular reads configuration from src/environments/environment.ts rather than an environment file, so the example carries the key inline. Move it into your environment file if you keep one per build target.",
|
|
33776
|
+
envVarReason: null,
|
|
33679
33777
|
docsPath: "/docs/integrations/angular-cli"
|
|
33680
33778
|
};
|
|
33681
33779
|
case "svelte":
|
|
@@ -33687,15 +33785,15 @@ function buildFrameworkProfile(detection) {
|
|
|
33687
33785
|
envVar: "PUBLIC_AVSB_SDK_KEY",
|
|
33688
33786
|
exampleFile: "src/lib/AvsbExample.svelte",
|
|
33689
33787
|
caveat: null,
|
|
33788
|
+
envVarReason: null,
|
|
33690
33789
|
docsPath: "/docs/integrations/sveltekit"
|
|
33691
33790
|
} : {
|
|
33692
33791
|
id: "svelte",
|
|
33693
33792
|
label: "Svelte",
|
|
33694
33793
|
sdkPackage: "@avsbhq/svelte",
|
|
33695
|
-
envFile:
|
|
33696
|
-
|
|
33794
|
+
envFile: BROWSER_ENV_FILE,
|
|
33795
|
+
...browserVariable(detection),
|
|
33697
33796
|
exampleFile: "src/AvsbExample.svelte",
|
|
33698
|
-
caveat: detection.usesVite ? null : UNPREFIXED_CAVEAT,
|
|
33699
33797
|
docsPath: "/docs/integrations/vite-svelte"
|
|
33700
33798
|
};
|
|
33701
33799
|
case "vue":
|
|
@@ -33703,10 +33801,9 @@ function buildFrameworkProfile(detection) {
|
|
|
33703
33801
|
id: "vue",
|
|
33704
33802
|
label: "Vue",
|
|
33705
33803
|
sdkPackage: "@avsbhq/vue",
|
|
33706
|
-
envFile:
|
|
33707
|
-
|
|
33804
|
+
envFile: BROWSER_ENV_FILE,
|
|
33805
|
+
...browserVariable(detection),
|
|
33708
33806
|
exampleFile: "src/AvsbExample.vue",
|
|
33709
|
-
caveat: detection.usesVite ? null : UNPREFIXED_CAVEAT,
|
|
33710
33807
|
docsPath: "/docs/integrations/vite-vue"
|
|
33711
33808
|
};
|
|
33712
33809
|
case "solid":
|
|
@@ -33714,10 +33811,9 @@ function buildFrameworkProfile(detection) {
|
|
|
33714
33811
|
id: "solid",
|
|
33715
33812
|
label: "Solid",
|
|
33716
33813
|
sdkPackage: "@avsbhq/solid",
|
|
33717
|
-
envFile:
|
|
33718
|
-
|
|
33814
|
+
envFile: BROWSER_ENV_FILE,
|
|
33815
|
+
...browserVariable(detection),
|
|
33719
33816
|
exampleFile: "src/avsb-example.tsx",
|
|
33720
|
-
caveat: detection.usesVite ? null : UNPREFIXED_CAVEAT,
|
|
33721
33817
|
docsPath: "/docs/integrations/vite-solid"
|
|
33722
33818
|
};
|
|
33723
33819
|
case "react":
|
|
@@ -33725,10 +33821,9 @@ function buildFrameworkProfile(detection) {
|
|
|
33725
33821
|
id: "react",
|
|
33726
33822
|
label: "React",
|
|
33727
33823
|
sdkPackage: "@avsbhq/react",
|
|
33728
|
-
envFile:
|
|
33729
|
-
|
|
33824
|
+
envFile: BROWSER_ENV_FILE,
|
|
33825
|
+
...browserVariable(detection),
|
|
33730
33826
|
exampleFile: "src/avsb-example.tsx",
|
|
33731
|
-
caveat: detection.usesVite ? null : UNPREFIXED_CAVEAT,
|
|
33732
33827
|
docsPath: "/docs/integrations/vite-react"
|
|
33733
33828
|
};
|
|
33734
33829
|
case "node":
|
|
@@ -33740,6 +33835,7 @@ function buildFrameworkProfile(detection) {
|
|
|
33740
33835
|
envVar: "AVSB_SDK_KEY",
|
|
33741
33836
|
exampleFile: "avsb-example.ts",
|
|
33742
33837
|
caveat: null,
|
|
33838
|
+
envVarReason: null,
|
|
33743
33839
|
docsPath: "/docs/developer-reference/avsb-node"
|
|
33744
33840
|
};
|
|
33745
33841
|
case "none":
|
|
@@ -33754,6 +33850,7 @@ function buildFrameworkProfile(detection) {
|
|
|
33754
33850
|
// a feature flags project set up from a folder with no package.json was
|
|
33755
33851
|
// being told it was a web experiments project.
|
|
33756
33852
|
caveat: "No package.json here, so no framework was detected. Run this in your application directory to have the SDK wired up for you.",
|
|
33853
|
+
envVarReason: null,
|
|
33757
33854
|
docsPath: "/docs/getting-started/installing-the-snippet"
|
|
33758
33855
|
};
|
|
33759
33856
|
}
|
|
@@ -33762,13 +33859,19 @@ function buildFrameworkProfile(detection) {
|
|
|
33762
33859
|
// src/lib/heartbeatWait.ts
|
|
33763
33860
|
var HEARTBEAT_INTERVAL_MS = 3e3;
|
|
33764
33861
|
var HEARTBEAT_TIMEOUT_MS = 12e4;
|
|
33765
|
-
var CLOCK_ALLOWANCE_MS =
|
|
33862
|
+
var CLOCK_ALLOWANCE_MS = 5e3;
|
|
33766
33863
|
function isFreshHeartbeat(lastSdkSeenAt, startedAt, allowanceMs = CLOCK_ALLOWANCE_MS) {
|
|
33767
33864
|
if (!lastSdkSeenAt) return false;
|
|
33768
33865
|
const seenAt = Date.parse(lastSdkSeenAt);
|
|
33769
33866
|
if (Number.isNaN(seenAt)) return false;
|
|
33770
33867
|
return seenAt >= startedAt - allowanceMs;
|
|
33771
33868
|
}
|
|
33869
|
+
function isNewerThan(stamp, previous) {
|
|
33870
|
+
if (!stamp || !previous) return false;
|
|
33871
|
+
const seenAt = Date.parse(stamp);
|
|
33872
|
+
const before = Date.parse(previous);
|
|
33873
|
+
return !Number.isNaN(seenAt) && !Number.isNaN(before) && seenAt > before;
|
|
33874
|
+
}
|
|
33772
33875
|
async function waitForHeartbeat(options) {
|
|
33773
33876
|
const timeoutMs = options.timeoutMs ?? HEARTBEAT_TIMEOUT_MS;
|
|
33774
33877
|
const intervalMs = options.intervalMs ?? HEARTBEAT_INTERVAL_MS;
|
|
@@ -33788,7 +33891,8 @@ async function waitForHeartbeat(options) {
|
|
|
33788
33891
|
previousSeenAt = sample.lastSdkSeenAt;
|
|
33789
33892
|
first = false;
|
|
33790
33893
|
}
|
|
33791
|
-
|
|
33894
|
+
const arrivedWhileWatching = isNewerThan(sample.lastSdkSeenAt, previousSeenAt);
|
|
33895
|
+
if (sample.lastSdkSeenAt && (arrivedWhileWatching || isFreshHeartbeat(sample.lastSdkSeenAt, options.startedAt))) {
|
|
33792
33896
|
return {
|
|
33793
33897
|
seen: true,
|
|
33794
33898
|
at: sample.lastSdkSeenAt,
|
|
@@ -33809,6 +33913,7 @@ function heartbeatTimeoutGuidance(input) {
|
|
|
33809
33913
|
const seconds = Math.round(input.waitedMs / 1e3);
|
|
33810
33914
|
const lines = [
|
|
33811
33915
|
`No check-in from ${input.environmentName} in ${seconds}s. Everything above is written; only the proof is missing.`,
|
|
33916
|
+
`${turnFlagOnCommand(input.flagKey ?? null, input.environmentKey ?? "production")} An environment where nothing has been turned on has no configuration for the SDK to load, and the SDK checks in only after it loads one.`,
|
|
33812
33917
|
"Start your app once, then run `avsb init --wait 120` again to watch for it.",
|
|
33813
33918
|
`Check that the key your code loads is ${input.sdkKey}.`,
|
|
33814
33919
|
`Check that outbound requests to ${input.cdnHost} and ${input.appHost} are allowed.`
|
|
@@ -33820,14 +33925,109 @@ function heartbeatTimeoutGuidance(input) {
|
|
|
33820
33925
|
}
|
|
33821
33926
|
return lines;
|
|
33822
33927
|
}
|
|
33928
|
+
function turnFlagOnCommand(flagKey, environmentKey) {
|
|
33929
|
+
return `Turn a flag on with \`avsb flags toggle ${flagKey ?? "<key>"} --env ${environmentKey} --on\`.`;
|
|
33930
|
+
}
|
|
33823
33931
|
function defaultSleep(ms) {
|
|
33824
33932
|
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
33825
33933
|
}
|
|
33826
33934
|
|
|
33935
|
+
// src/lib/networkError.ts
|
|
33936
|
+
var DNS_CODES = /* @__PURE__ */ new Set(["ENOTFOUND", "EAI_AGAIN", "EAI_NONAME", "EAI_FAIL"]);
|
|
33937
|
+
var TIMEOUT_CODES = /* @__PURE__ */ new Set(["ETIMEDOUT", "UND_ERR_CONNECT_TIMEOUT", "UND_ERR_HEADERS_TIMEOUT", "ESOCKETTIMEDOUT"]);
|
|
33938
|
+
var RESET_CODES = /* @__PURE__ */ new Set(["ECONNRESET", "EPIPE", "UND_ERR_SOCKET", "ECONNABORTED"]);
|
|
33939
|
+
function isRecord4(value) {
|
|
33940
|
+
return typeof value === "object" && value !== null;
|
|
33941
|
+
}
|
|
33942
|
+
function networkErrorCode(err) {
|
|
33943
|
+
let current = err;
|
|
33944
|
+
for (let depth = 0; depth < 5 && isRecord4(current); depth++) {
|
|
33945
|
+
if (typeof current.code === "string") return current.code;
|
|
33946
|
+
if (Array.isArray(current.errors)) {
|
|
33947
|
+
for (const inner of current.errors) {
|
|
33948
|
+
const code = networkErrorCode(inner);
|
|
33949
|
+
if (code) return code;
|
|
33950
|
+
}
|
|
33951
|
+
}
|
|
33952
|
+
current = current.cause;
|
|
33953
|
+
}
|
|
33954
|
+
return null;
|
|
33955
|
+
}
|
|
33956
|
+
function networkErrorReason(err) {
|
|
33957
|
+
let current = err;
|
|
33958
|
+
let reason = null;
|
|
33959
|
+
for (let depth = 0; depth < 5 && isRecord4(current); depth++) {
|
|
33960
|
+
const message = current.message;
|
|
33961
|
+
if (typeof message === "string" && message.length > 0 && message !== "fetch failed") reason = message;
|
|
33962
|
+
current = current.cause;
|
|
33963
|
+
}
|
|
33964
|
+
return reason;
|
|
33965
|
+
}
|
|
33966
|
+
function classifyNetworkError(code) {
|
|
33967
|
+
if (code === null) return "other";
|
|
33968
|
+
if (DNS_CODES.has(code)) return "dns";
|
|
33969
|
+
if (code === "ECONNREFUSED") return "refused";
|
|
33970
|
+
if (TIMEOUT_CODES.has(code)) return "timeout";
|
|
33971
|
+
if (RESET_CODES.has(code)) return "reset";
|
|
33972
|
+
if (/CERT|TLS|SSL|SELF_SIGNED|UNABLE_TO_VERIFY|UNABLE_TO_GET_ISSUER/.test(code)) return "tls";
|
|
33973
|
+
return "other";
|
|
33974
|
+
}
|
|
33975
|
+
function hostOf(baseUrl) {
|
|
33976
|
+
try {
|
|
33977
|
+
return new URL(baseUrl).host;
|
|
33978
|
+
} catch {
|
|
33979
|
+
return baseUrl;
|
|
33980
|
+
}
|
|
33981
|
+
}
|
|
33982
|
+
function describeNetworkFailure(baseUrl, err) {
|
|
33983
|
+
const host = hostOf(baseUrl);
|
|
33984
|
+
const code = networkErrorCode(err);
|
|
33985
|
+
const kind = classifyNetworkError(code);
|
|
33986
|
+
const urlHint = `The API URL in use is ${baseUrl}. Check AVSB_API_URL, or the URL \`avsb login --url\` saved.`;
|
|
33987
|
+
const networkHint = "Check your network connection, VPN or proxy.";
|
|
33988
|
+
switch (kind) {
|
|
33989
|
+
case "dns":
|
|
33990
|
+
return new CliError("cli_network", `Cannot find ${host}: the name does not resolve (DNS lookup failed).`, [urlHint, networkHint]);
|
|
33991
|
+
case "refused":
|
|
33992
|
+
return new CliError("cli_network", `Nothing is listening at ${host}: the connection was refused.`, [urlHint]);
|
|
33993
|
+
case "timeout":
|
|
33994
|
+
return new CliError("cli_network", `${host} did not answer in time (the connection timed out).`, [networkHint]);
|
|
33995
|
+
case "reset":
|
|
33996
|
+
return new CliError("cli_network", `${host} closed the connection before answering.`, [networkHint]);
|
|
33997
|
+
case "tls":
|
|
33998
|
+
return new CliError("cli_network", `Could not make a secure connection to ${host} (${code}).`, [
|
|
33999
|
+
"A proxy or antivirus that inspects HTTPS can cause this. Check its certificate settings."
|
|
34000
|
+
]);
|
|
34001
|
+
case "other": {
|
|
34002
|
+
const reason = code ?? networkErrorReason(err) ?? "no answer";
|
|
34003
|
+
return new CliError("cli_network", `Could not reach ${host} (${reason}).`, [urlHint, networkHint]);
|
|
34004
|
+
}
|
|
34005
|
+
}
|
|
34006
|
+
}
|
|
34007
|
+
function invalidBaseUrl(baseUrl) {
|
|
34008
|
+
let protocol = "";
|
|
34009
|
+
try {
|
|
34010
|
+
protocol = new URL(baseUrl).protocol;
|
|
34011
|
+
} catch {
|
|
34012
|
+
protocol = "";
|
|
34013
|
+
}
|
|
34014
|
+
if (protocol === "http:" || protocol === "https:") return null;
|
|
34015
|
+
return new CliError("cli_usage", `"${baseUrl}" is not a valid API URL.`, [
|
|
34016
|
+
"Use a full address such as https://app.avsb.cloud, in AVSB_API_URL or `avsb login --url`."
|
|
34017
|
+
]);
|
|
34018
|
+
}
|
|
34019
|
+
|
|
33827
34020
|
// src/lib/apiTransport.ts
|
|
33828
34021
|
var API_VERSION = "v1";
|
|
33829
34022
|
var API_VERSION_HEADER = "AvsB-API-Version";
|
|
33830
34023
|
var REQUEST_ID_HEADER = "X-Request-Id";
|
|
34024
|
+
function hostOf2(baseUrl) {
|
|
34025
|
+
try {
|
|
34026
|
+
return new URL(baseUrl).host;
|
|
34027
|
+
} catch {
|
|
34028
|
+
return void 0;
|
|
34029
|
+
}
|
|
34030
|
+
}
|
|
33831
34031
|
var ApiTransport = class {
|
|
33832
34032
|
baseUrl;
|
|
33833
34033
|
token;
|
|
@@ -33858,18 +34058,25 @@ var ApiTransport = class {
|
|
|
33858
34058
|
* body parsing. It reports what came back and judges nothing, so both the
|
|
33859
34059
|
* plain and the conditional paths below share exactly one implementation.
|
|
33860
34060
|
*/
|
|
33861
|
-
async send(
|
|
34061
|
+
async send(path23, options) {
|
|
33862
34062
|
this.assertUsableToken();
|
|
33863
|
-
const
|
|
33864
|
-
|
|
33865
|
-
|
|
33866
|
-
|
|
33867
|
-
|
|
33868
|
-
|
|
33869
|
-
|
|
33870
|
-
|
|
33871
|
-
|
|
33872
|
-
|
|
34063
|
+
const badUrl = invalidBaseUrl(this.baseUrl);
|
|
34064
|
+
if (badUrl) throw badUrl;
|
|
34065
|
+
const url = `${this.baseUrl}${path23}`;
|
|
34066
|
+
let res;
|
|
34067
|
+
try {
|
|
34068
|
+
res = await fetch(url, {
|
|
34069
|
+
...options,
|
|
34070
|
+
headers: {
|
|
34071
|
+
"Content-Type": "application/json",
|
|
34072
|
+
Authorization: `Bearer ${this.token}`,
|
|
34073
|
+
[API_VERSION_HEADER]: API_VERSION,
|
|
34074
|
+
...options.headers
|
|
34075
|
+
}
|
|
34076
|
+
});
|
|
34077
|
+
} catch (err) {
|
|
34078
|
+
throw describeNetworkFailure(this.baseUrl, err);
|
|
34079
|
+
}
|
|
33873
34080
|
let body = null;
|
|
33874
34081
|
const text = await res.text();
|
|
33875
34082
|
if (text.length > 0) {
|
|
@@ -33886,7 +34093,8 @@ var ApiTransport = class {
|
|
|
33886
34093
|
status: res.status,
|
|
33887
34094
|
body,
|
|
33888
34095
|
requestId: res.headers.get(REQUEST_ID_HEADER),
|
|
33889
|
-
retryAfter: res.headers.get("Retry-After")
|
|
34096
|
+
retryAfter: res.headers.get("Retry-After"),
|
|
34097
|
+
host: hostOf2(this.baseUrl)
|
|
33890
34098
|
});
|
|
33891
34099
|
}
|
|
33892
34100
|
/**
|
|
@@ -33894,8 +34102,8 @@ var ApiTransport = class {
|
|
|
33894
34102
|
* named methods. Returns the body and the ETag; `request` unwraps it for the
|
|
33895
34103
|
* common case.
|
|
33896
34104
|
*/
|
|
33897
|
-
async requestWithMeta(
|
|
33898
|
-
const { res, body } = await this.send(
|
|
34105
|
+
async requestWithMeta(path23, options = {}) {
|
|
34106
|
+
const { res, body } = await this.send(path23, options);
|
|
33899
34107
|
if (!res.ok) throw this.refusal(res, body);
|
|
33900
34108
|
return { body, etag: res.headers.get("ETag") };
|
|
33901
34109
|
}
|
|
@@ -33907,9 +34115,9 @@ var ApiTransport = class {
|
|
|
33907
34115
|
* back to comparing what it generated. Nothing here assumes the platform
|
|
33908
34116
|
* supports conditional reads.
|
|
33909
34117
|
*/
|
|
33910
|
-
async requestIfNoneMatch(
|
|
34118
|
+
async requestIfNoneMatch(path23, etag) {
|
|
33911
34119
|
const { res, body } = await this.send(
|
|
33912
|
-
|
|
34120
|
+
path23,
|
|
33913
34121
|
etag ? { headers: { "If-None-Match": etag } } : {}
|
|
33914
34122
|
);
|
|
33915
34123
|
if (res.status === 304) {
|
|
@@ -33918,8 +34126,8 @@ var ApiTransport = class {
|
|
|
33918
34126
|
if (!res.ok) throw this.refusal(res, body);
|
|
33919
34127
|
return { status: "ok", body, etag: res.headers.get("ETag") };
|
|
33920
34128
|
}
|
|
33921
|
-
async request(
|
|
33922
|
-
const result = await this.requestWithMeta(
|
|
34129
|
+
async request(path23, options = {}) {
|
|
34130
|
+
const result = await this.requestWithMeta(path23, options);
|
|
33923
34131
|
return result.body;
|
|
33924
34132
|
}
|
|
33925
34133
|
};
|
|
@@ -33947,6 +34155,11 @@ var ApiClient = class extends ApiTransport {
|
|
|
33947
34155
|
body: JSON.stringify(data)
|
|
33948
34156
|
});
|
|
33949
34157
|
}
|
|
34158
|
+
/**
|
|
34159
|
+
* Publish an experiment's pending changes, as the dashboard's Publish
|
|
34160
|
+
* changes button does. `warning` is set when the change is saved but the
|
|
34161
|
+
* live datafile could not be written yet (the platform retries it).
|
|
34162
|
+
*/
|
|
33950
34163
|
async publishChanges(shortId) {
|
|
33951
34164
|
return this.request(`/api/cli/experiments/${shortId}/publish`, {
|
|
33952
34165
|
method: "POST"
|
|
@@ -33961,11 +34174,6 @@ var ApiClient = class extends ApiTransport {
|
|
|
33961
34174
|
body: JSON.stringify(data)
|
|
33962
34175
|
});
|
|
33963
34176
|
}
|
|
33964
|
-
async publishProject(shortId) {
|
|
33965
|
-
return this.request(`/api/cli/projects/${shortId}/publish`, {
|
|
33966
|
-
method: "POST"
|
|
33967
|
-
});
|
|
33968
|
-
}
|
|
33969
34177
|
/**
|
|
33970
34178
|
* Fetch a project's published datafile for a given environment.
|
|
33971
34179
|
*
|
|
@@ -33983,8 +34191,8 @@ var ApiClient = class extends ApiTransport {
|
|
|
33983
34191
|
if (opts.type) params.set("type", opts.type);
|
|
33984
34192
|
if (opts.includeArchived) params.set("includeArchived", "true");
|
|
33985
34193
|
const qs = params.toString();
|
|
33986
|
-
const
|
|
33987
|
-
return this.request(
|
|
34194
|
+
const path23 = `/api/cli/projects/${projectShortId}/metrics${qs ? `?${qs}` : ""}`;
|
|
34195
|
+
return this.request(path23);
|
|
33988
34196
|
}
|
|
33989
34197
|
async createMetric(projectShortId, body) {
|
|
33990
34198
|
return this.request(
|
|
@@ -33995,14 +34203,9 @@ var ApiClient = class extends ApiTransport {
|
|
|
33995
34203
|
}
|
|
33996
34204
|
);
|
|
33997
34205
|
}
|
|
33998
|
-
async getMetric(projectShortId, metricId) {
|
|
33999
|
-
return this.request(
|
|
34000
|
-
`/api/cli/projects/${projectShortId}/metrics/${metricId}`
|
|
34001
|
-
);
|
|
34002
|
-
}
|
|
34003
34206
|
async updateMetric(projectShortId, metricId, patch) {
|
|
34004
34207
|
return this.request(
|
|
34005
|
-
`/api/cli/projects/${projectShortId}/metrics/${metricId}`,
|
|
34208
|
+
`/api/cli/projects/${projectShortId}/metrics/${encodeURIComponent(metricId)}`,
|
|
34006
34209
|
{
|
|
34007
34210
|
method: "PATCH",
|
|
34008
34211
|
body: JSON.stringify(patch)
|
|
@@ -34011,19 +34214,19 @@ var ApiClient = class extends ApiTransport {
|
|
|
34011
34214
|
}
|
|
34012
34215
|
async archiveMetric(projectShortId, metricId) {
|
|
34013
34216
|
return this.request(
|
|
34014
|
-
`/api/cli/projects/${projectShortId}/metrics/${metricId}/archive`,
|
|
34217
|
+
`/api/cli/projects/${projectShortId}/metrics/${encodeURIComponent(metricId)}/archive`,
|
|
34015
34218
|
{ method: "POST" }
|
|
34016
34219
|
);
|
|
34017
34220
|
}
|
|
34018
34221
|
async unarchiveMetric(projectShortId, metricId) {
|
|
34019
34222
|
return this.request(
|
|
34020
|
-
`/api/cli/projects/${projectShortId}/metrics/${metricId}/unarchive`,
|
|
34223
|
+
`/api/cli/projects/${projectShortId}/metrics/${encodeURIComponent(metricId)}/unarchive`,
|
|
34021
34224
|
{ method: "POST" }
|
|
34022
34225
|
);
|
|
34023
34226
|
}
|
|
34024
34227
|
async deleteMetric(projectShortId, metricId) {
|
|
34025
34228
|
return this.request(
|
|
34026
|
-
`/api/cli/projects/${projectShortId}/metrics/${metricId}`,
|
|
34229
|
+
`/api/cli/projects/${projectShortId}/metrics/${encodeURIComponent(metricId)}`,
|
|
34027
34230
|
{ method: "DELETE" }
|
|
34028
34231
|
);
|
|
34029
34232
|
}
|
|
@@ -34035,8 +34238,8 @@ var ApiClient = class extends ApiTransport {
|
|
|
34035
34238
|
if (opts.includeArchived) params.set("includeArchived", "true");
|
|
34036
34239
|
if (opts.limit != null) params.set("limit", String(opts.limit));
|
|
34037
34240
|
const qs = params.toString();
|
|
34038
|
-
const
|
|
34039
|
-
return this.request(
|
|
34241
|
+
const path23 = `/api/cli/projects/${projectShortId}/metric-bindings${qs ? `?${qs}` : ""}`;
|
|
34242
|
+
return this.request(path23);
|
|
34040
34243
|
}
|
|
34041
34244
|
async createMetricBinding(projectShortId, body) {
|
|
34042
34245
|
return this.request(
|
|
@@ -34047,14 +34250,9 @@ var ApiClient = class extends ApiTransport {
|
|
|
34047
34250
|
}
|
|
34048
34251
|
);
|
|
34049
34252
|
}
|
|
34050
|
-
async getMetricBinding(projectShortId, metricBindingId) {
|
|
34051
|
-
return this.request(
|
|
34052
|
-
`/api/cli/projects/${projectShortId}/metric-bindings/${metricBindingId}`
|
|
34053
|
-
);
|
|
34054
|
-
}
|
|
34055
34253
|
async updateMetricBinding(projectShortId, metricBindingId, patch) {
|
|
34056
34254
|
return this.request(
|
|
34057
|
-
`/api/cli/projects/${projectShortId}/metric-bindings/${metricBindingId}`,
|
|
34255
|
+
`/api/cli/projects/${projectShortId}/metric-bindings/${encodeURIComponent(metricBindingId)}`,
|
|
34058
34256
|
{
|
|
34059
34257
|
method: "PATCH",
|
|
34060
34258
|
body: JSON.stringify(patch)
|
|
@@ -34063,19 +34261,19 @@ var ApiClient = class extends ApiTransport {
|
|
|
34063
34261
|
}
|
|
34064
34262
|
async archiveMetricBinding(projectShortId, metricBindingId) {
|
|
34065
34263
|
return this.request(
|
|
34066
|
-
`/api/cli/projects/${projectShortId}/metric-bindings/${metricBindingId}/archive`,
|
|
34264
|
+
`/api/cli/projects/${projectShortId}/metric-bindings/${encodeURIComponent(metricBindingId)}/archive`,
|
|
34067
34265
|
{ method: "POST" }
|
|
34068
34266
|
);
|
|
34069
34267
|
}
|
|
34070
34268
|
async unarchiveMetricBinding(projectShortId, metricBindingId) {
|
|
34071
34269
|
return this.request(
|
|
34072
|
-
`/api/cli/projects/${projectShortId}/metric-bindings/${metricBindingId}/unarchive`,
|
|
34270
|
+
`/api/cli/projects/${projectShortId}/metric-bindings/${encodeURIComponent(metricBindingId)}/unarchive`,
|
|
34073
34271
|
{ method: "POST" }
|
|
34074
34272
|
);
|
|
34075
34273
|
}
|
|
34076
34274
|
async deleteMetricBinding(projectShortId, metricBindingId) {
|
|
34077
34275
|
return this.request(
|
|
34078
|
-
`/api/cli/projects/${projectShortId}/metric-bindings/${metricBindingId}`,
|
|
34276
|
+
`/api/cli/projects/${projectShortId}/metric-bindings/${encodeURIComponent(metricBindingId)}`,
|
|
34079
34277
|
{ method: "DELETE" }
|
|
34080
34278
|
);
|
|
34081
34279
|
}
|
|
@@ -34123,11 +34321,16 @@ var ApiClient = class extends ApiTransport {
|
|
|
34123
34321
|
* presigned PUTs reject chunked transfer encoding).
|
|
34124
34322
|
*/
|
|
34125
34323
|
async uploadNdjson(uploadUrl, body) {
|
|
34126
|
-
|
|
34127
|
-
|
|
34128
|
-
|
|
34129
|
-
|
|
34130
|
-
|
|
34324
|
+
let res;
|
|
34325
|
+
try {
|
|
34326
|
+
res = await fetch(uploadUrl, {
|
|
34327
|
+
method: "PUT",
|
|
34328
|
+
headers: { "Content-Type": "application/x-ndjson" },
|
|
34329
|
+
body
|
|
34330
|
+
});
|
|
34331
|
+
} catch (err) {
|
|
34332
|
+
throw describeNetworkFailure(new URL(uploadUrl).origin, err);
|
|
34333
|
+
}
|
|
34131
34334
|
if (!res.ok) {
|
|
34132
34335
|
throw new Error(`Upload failed: HTTP ${res.status}`);
|
|
34133
34336
|
}
|
|
@@ -34411,9 +34614,9 @@ function servedValueFor(flag) {
|
|
|
34411
34614
|
if (!variation) return null;
|
|
34412
34615
|
return { variationKey: variation.key, value: variation.value };
|
|
34413
34616
|
}
|
|
34414
|
-
function describeServedValue(flagKey, served) {
|
|
34617
|
+
function describeServedValue(flagKey, served, environmentKey = "production") {
|
|
34415
34618
|
if (!served) {
|
|
34416
|
-
return `${flagKey} is not being served in this environment yet, so your code gets the fallback you pass
|
|
34619
|
+
return `${flagKey} is not being served in this environment yet, so your code gets the fallback you pass. Turn it on with \`avsb flags toggle ${flagKey} --env ${environmentKey} --on\`.`;
|
|
34417
34620
|
}
|
|
34418
34621
|
return `${flagKey} serves ${JSON.stringify(served.value)} (variation ${served.variationKey}) when no targeting rule matches.`;
|
|
34419
34622
|
}
|
|
@@ -34426,12 +34629,46 @@ function canPrompt(ctx) {
|
|
|
34426
34629
|
if (envFlagEnabled(ctx.env.CI)) return false;
|
|
34427
34630
|
return true;
|
|
34428
34631
|
}
|
|
34429
|
-
function
|
|
34430
|
-
return
|
|
34632
|
+
function currentPromptContext(options = {}) {
|
|
34633
|
+
return {
|
|
34431
34634
|
input: options.input !== false,
|
|
34432
34635
|
quiet: isQuiet(),
|
|
34433
34636
|
isTty: process.stdin.isTTY === true,
|
|
34434
34637
|
env: process.env
|
|
34638
|
+
};
|
|
34639
|
+
}
|
|
34640
|
+
function isInteractiveRun(options = {}) {
|
|
34641
|
+
return canPrompt(currentPromptContext(options));
|
|
34642
|
+
}
|
|
34643
|
+
function refusalWithoutPrompt(refusal, ctx = currentPromptContext()) {
|
|
34644
|
+
if (canPrompt(ctx)) return null;
|
|
34645
|
+
return new CliError(refusal.code, refusal.message, refusal.hints);
|
|
34646
|
+
}
|
|
34647
|
+
var STDIN_IDLE_TIMEOUT_MS = 3e3;
|
|
34648
|
+
function readPipedStdin(stream = process.stdin, idleMs = STDIN_IDLE_TIMEOUT_MS) {
|
|
34649
|
+
return new Promise((resolve3) => {
|
|
34650
|
+
const chunks = [];
|
|
34651
|
+
let timer = null;
|
|
34652
|
+
const finish = () => {
|
|
34653
|
+
if (timer) clearTimeout(timer);
|
|
34654
|
+
stream.removeListener("data", onData);
|
|
34655
|
+
stream.removeListener("end", finish);
|
|
34656
|
+
stream.removeListener("error", finish);
|
|
34657
|
+
if ("pause" in stream && typeof stream.pause === "function") stream.pause();
|
|
34658
|
+
resolve3(Buffer.concat(chunks).toString("utf-8"));
|
|
34659
|
+
};
|
|
34660
|
+
const arm = () => {
|
|
34661
|
+
if (timer) clearTimeout(timer);
|
|
34662
|
+
timer = setTimeout(finish, idleMs);
|
|
34663
|
+
};
|
|
34664
|
+
const onData = (chunk) => {
|
|
34665
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
34666
|
+
arm();
|
|
34667
|
+
};
|
|
34668
|
+
stream.on("data", onData);
|
|
34669
|
+
stream.once("end", finish);
|
|
34670
|
+
stream.once("error", finish);
|
|
34671
|
+
arm();
|
|
34435
34672
|
});
|
|
34436
34673
|
}
|
|
34437
34674
|
|
|
@@ -34514,10 +34751,40 @@ function unquote(value) {
|
|
|
34514
34751
|
return quote2 === '"' ? body.replace(/\\(["\\])/g, "$1") : body;
|
|
34515
34752
|
}
|
|
34516
34753
|
|
|
34517
|
-
// src/
|
|
34754
|
+
// src/lib/gitignore.ts
|
|
34518
34755
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
34519
34756
|
import fs3 from "node:fs";
|
|
34520
34757
|
import path4 from "node:path";
|
|
34758
|
+
var GITIGNORE_BANNER = "# A vs B: the SDK key file `avsb init` writes";
|
|
34759
|
+
function gitIgnoreState(dir, file) {
|
|
34760
|
+
const result = spawnSync2("git", ["check-ignore", "-q", "--", file], { cwd: dir, stdio: "ignore" });
|
|
34761
|
+
if (result.error) return "no-repo";
|
|
34762
|
+
if (result.status === 0) return "ignored";
|
|
34763
|
+
if (result.status === 1) return "not-ignored";
|
|
34764
|
+
return "no-repo";
|
|
34765
|
+
}
|
|
34766
|
+
function appendGitignoreEntry(existing, file) {
|
|
34767
|
+
const entry = `${GITIGNORE_BANNER}
|
|
34768
|
+
${file}
|
|
34769
|
+
`;
|
|
34770
|
+
if (existing === null || existing.length === 0) return entry;
|
|
34771
|
+
const separator = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
34772
|
+
return `${existing}${separator}${entry}`;
|
|
34773
|
+
}
|
|
34774
|
+
function ensureGitignored(dir, file, state = gitIgnoreState) {
|
|
34775
|
+
const current = state(dir, file);
|
|
34776
|
+
if (current === "ignored") return { outcome: "already-ignored", gitignorePath: null };
|
|
34777
|
+
if (current === "no-repo") return { outcome: "no-repo", gitignorePath: null };
|
|
34778
|
+
const gitignorePath = path4.join(dir, ".gitignore");
|
|
34779
|
+
const existing = fs3.existsSync(gitignorePath) ? fs3.readFileSync(gitignorePath, "utf-8") : null;
|
|
34780
|
+
fs3.writeFileSync(gitignorePath, appendGitignoreEntry(existing, file), "utf-8");
|
|
34781
|
+
return { outcome: "added", gitignorePath };
|
|
34782
|
+
}
|
|
34783
|
+
|
|
34784
|
+
// src/commands/init.write.ts
|
|
34785
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
34786
|
+
import fs4 from "node:fs";
|
|
34787
|
+
import path5 from "node:path";
|
|
34521
34788
|
|
|
34522
34789
|
// src/lib/jsonTypeInference.ts
|
|
34523
34790
|
var MAX_JSON_DEPTH = 4;
|
|
@@ -35023,14 +35290,14 @@ console.log('${input.flagKey}', flag.value, 'from', flag.source)
|
|
|
35023
35290
|
|
|
35024
35291
|
// src/commands/init.write.ts
|
|
35025
35292
|
function exampleFileExists(dir, profile, overridePath) {
|
|
35026
|
-
return
|
|
35293
|
+
return fs4.existsSync(resolveExamplePath(dir, profile, overridePath));
|
|
35027
35294
|
}
|
|
35028
35295
|
function resolveExamplePath(dir, profile, overridePath) {
|
|
35029
|
-
return
|
|
35296
|
+
return path5.resolve(dir, overridePath ?? profile.exampleFile);
|
|
35030
35297
|
}
|
|
35031
35298
|
function writeExampleFile(input) {
|
|
35032
35299
|
const filePath = resolveExamplePath(input.dir, input.profile, input.overridePath);
|
|
35033
|
-
if (
|
|
35300
|
+
if (fs4.existsSync(filePath)) {
|
|
35034
35301
|
return { filePath, wrote: false, existed: true };
|
|
35035
35302
|
}
|
|
35036
35303
|
const contents = buildSample({
|
|
@@ -35042,14 +35309,14 @@ function writeExampleFile(input) {
|
|
|
35042
35309
|
if (contents === null) {
|
|
35043
35310
|
return { filePath, wrote: false, existed: false };
|
|
35044
35311
|
}
|
|
35045
|
-
|
|
35046
|
-
|
|
35312
|
+
fs4.mkdirSync(path5.dirname(filePath), { recursive: true });
|
|
35313
|
+
fs4.writeFileSync(filePath, contents, "utf-8");
|
|
35047
35314
|
return { filePath, wrote: true, existed: false };
|
|
35048
35315
|
}
|
|
35049
35316
|
function runInstall(dir, manager, npmPackage, options) {
|
|
35050
35317
|
const { command, args } = installArgv(manager, npmPackage);
|
|
35051
35318
|
const printable = [command, ...args].join(" ");
|
|
35052
|
-
const result =
|
|
35319
|
+
const result = spawnSync3(command, args, {
|
|
35053
35320
|
cwd: dir,
|
|
35054
35321
|
// A quiet or JSON run must leave stdout carrying only the result, so the
|
|
35055
35322
|
// package manager's own output is captured and dropped instead of printed.
|
|
@@ -35079,10 +35346,10 @@ async function runCodegen(input) {
|
|
|
35079
35346
|
projectId: String(input.projectShortId),
|
|
35080
35347
|
env: input.environmentKey
|
|
35081
35348
|
});
|
|
35082
|
-
const filePath =
|
|
35349
|
+
const filePath = path5.resolve(input.dir, input.outputPath ?? DEFAULT_CODEGEN_OUTPUT);
|
|
35083
35350
|
try {
|
|
35084
|
-
|
|
35085
|
-
|
|
35351
|
+
fs4.mkdirSync(path5.dirname(filePath), { recursive: true });
|
|
35352
|
+
fs4.writeFileSync(filePath, generated, "utf-8");
|
|
35086
35353
|
} catch (err) {
|
|
35087
35354
|
throw new CliError(
|
|
35088
35355
|
"cli_io_failed",
|
|
@@ -35093,14 +35360,14 @@ async function runCodegen(input) {
|
|
|
35093
35360
|
}
|
|
35094
35361
|
function readFileOrNull(filePath) {
|
|
35095
35362
|
try {
|
|
35096
|
-
return
|
|
35363
|
+
return fs4.readFileSync(filePath, "utf-8");
|
|
35097
35364
|
} catch {
|
|
35098
35365
|
return null;
|
|
35099
35366
|
}
|
|
35100
35367
|
}
|
|
35101
35368
|
function listDirectory(dir) {
|
|
35102
35369
|
try {
|
|
35103
|
-
return
|
|
35370
|
+
return fs4.readdirSync(dir);
|
|
35104
35371
|
} catch {
|
|
35105
35372
|
return [];
|
|
35106
35373
|
}
|
|
@@ -35108,7 +35375,7 @@ function listDirectory(dir) {
|
|
|
35108
35375
|
|
|
35109
35376
|
// src/commands/init.execute.ts
|
|
35110
35377
|
async function executePlan(input) {
|
|
35111
|
-
const files = { env: null, envOutcome: null, example: null, codegen: null };
|
|
35378
|
+
const files = { env: null, envOutcome: null, example: null, codegen: null, gitignore: null };
|
|
35112
35379
|
let install = null;
|
|
35113
35380
|
const { profile, environment } = input;
|
|
35114
35381
|
if (stepFor(input.plan, "env").action === "write" && environment && profile.envFile && profile.envVar) {
|
|
@@ -35121,6 +35388,11 @@ async function executePlan(input) {
|
|
|
35121
35388
|
files.env = result.filePath;
|
|
35122
35389
|
files.envOutcome = result.outcome;
|
|
35123
35390
|
info(describeEnvWrite(result, profile.envVar));
|
|
35391
|
+
const ignored = ensureGitignored(input.dir, profile.envFile);
|
|
35392
|
+
if (ignored.outcome === "added" && ignored.gitignorePath) {
|
|
35393
|
+
files.gitignore = ignored.gitignorePath;
|
|
35394
|
+
info(`Added ${profile.envFile} to ${ignored.gitignorePath}, so git does not pick it up`);
|
|
35395
|
+
}
|
|
35124
35396
|
}
|
|
35125
35397
|
const installStep = stepFor(input.plan, "install");
|
|
35126
35398
|
if (profile.sdkPackage) {
|
|
@@ -35188,17 +35460,17 @@ async function watchForCheckIn(api, projectShortId, environment, wait) {
|
|
|
35188
35460
|
|
|
35189
35461
|
// src/lib/projectScaffold.ts
|
|
35190
35462
|
import { createHash as createHash2 } from "node:crypto";
|
|
35191
|
-
import
|
|
35192
|
-
import
|
|
35463
|
+
import fs8 from "node:fs";
|
|
35464
|
+
import path9 from "node:path";
|
|
35193
35465
|
|
|
35194
35466
|
// src/lib/scaffold.ts
|
|
35195
|
-
import
|
|
35196
|
-
import
|
|
35467
|
+
import fs7 from "node:fs";
|
|
35468
|
+
import path8 from "node:path";
|
|
35197
35469
|
|
|
35198
35470
|
// src/lib/manifest.ts
|
|
35199
35471
|
import { createHash } from "node:crypto";
|
|
35200
|
-
import
|
|
35201
|
-
import
|
|
35472
|
+
import fs5 from "node:fs";
|
|
35473
|
+
import path6 from "node:path";
|
|
35202
35474
|
var MANIFEST_FILENAME = ".avsb.json";
|
|
35203
35475
|
function parseExperimentInput(input) {
|
|
35204
35476
|
const urlMatch = input.match(/experiments\/(\d+)/);
|
|
@@ -35213,12 +35485,12 @@ function parseExperimentInput(input) {
|
|
|
35213
35485
|
function findManifest(startDir) {
|
|
35214
35486
|
let dir = startDir ?? process.cwd();
|
|
35215
35487
|
for (let i = 0; i < 10; i++) {
|
|
35216
|
-
const manifestPath =
|
|
35217
|
-
if (
|
|
35218
|
-
const raw =
|
|
35488
|
+
const manifestPath = path6.join(dir, MANIFEST_FILENAME);
|
|
35489
|
+
if (fs5.existsSync(manifestPath)) {
|
|
35490
|
+
const raw = fs5.readFileSync(manifestPath, "utf-8");
|
|
35219
35491
|
return JSON.parse(raw);
|
|
35220
35492
|
}
|
|
35221
|
-
const parent =
|
|
35493
|
+
const parent = path6.dirname(dir);
|
|
35222
35494
|
if (parent === dir) break;
|
|
35223
35495
|
dir = parent;
|
|
35224
35496
|
}
|
|
@@ -35227,17 +35499,17 @@ function findManifest(startDir) {
|
|
|
35227
35499
|
function findManifestDir(startDir) {
|
|
35228
35500
|
let dir = startDir ?? process.cwd();
|
|
35229
35501
|
for (let i = 0; i < 10; i++) {
|
|
35230
|
-
const manifestPath =
|
|
35231
|
-
if (
|
|
35232
|
-
const parent =
|
|
35502
|
+
const manifestPath = path6.join(dir, MANIFEST_FILENAME);
|
|
35503
|
+
if (fs5.existsSync(manifestPath)) return dir;
|
|
35504
|
+
const parent = path6.dirname(dir);
|
|
35233
35505
|
if (parent === dir) break;
|
|
35234
35506
|
dir = parent;
|
|
35235
35507
|
}
|
|
35236
35508
|
return null;
|
|
35237
35509
|
}
|
|
35238
35510
|
function writeManifest(dir, manifest) {
|
|
35239
|
-
const manifestPath =
|
|
35240
|
-
|
|
35511
|
+
const manifestPath = path6.join(dir, MANIFEST_FILENAME);
|
|
35512
|
+
fs5.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
35241
35513
|
}
|
|
35242
35514
|
function readManifestOrExit(startDir) {
|
|
35243
35515
|
const dir = findManifestDir(startDir);
|
|
@@ -35275,30 +35547,30 @@ function computeSyncHashes(triggersJs, variations) {
|
|
|
35275
35547
|
}
|
|
35276
35548
|
|
|
35277
35549
|
// src/lib/snippetTypeDefs.ts
|
|
35278
|
-
import
|
|
35279
|
-
import
|
|
35550
|
+
import fs6 from "node:fs";
|
|
35551
|
+
import path7 from "node:path";
|
|
35280
35552
|
import { createRequire } from "node:module";
|
|
35281
35553
|
import { fileURLToPath } from "node:url";
|
|
35282
35554
|
var SNIPPET_TYPES_PACKAGE = "@avsbhq/snippet-types";
|
|
35283
35555
|
var SNIPPET_TYPE_DEF_FILES = ["avsb.d.ts", "variation-code.d.ts"];
|
|
35284
35556
|
var MAX_WALK_UP = 24;
|
|
35285
35557
|
function snippetTypeDefCandidates(filename, lookup) {
|
|
35286
|
-
const candidates = [
|
|
35558
|
+
const candidates = [path7.join(lookup.moduleDir, "types", filename)];
|
|
35287
35559
|
const packageDir = lookup.packageDir();
|
|
35288
|
-
if (packageDir) candidates.push(
|
|
35560
|
+
if (packageDir) candidates.push(path7.join(packageDir, filename));
|
|
35289
35561
|
const isRepoRoot = lookup.isRepoRoot ?? defaultIsRepoRoot;
|
|
35290
35562
|
let dir = lookup.moduleDir;
|
|
35291
35563
|
for (let depth = 0; depth < MAX_WALK_UP; depth += 1) {
|
|
35292
|
-
candidates.push(
|
|
35564
|
+
candidates.push(path7.join(dir, "packages", "avsb-snippet-types", filename));
|
|
35293
35565
|
if (isRepoRoot(dir)) break;
|
|
35294
|
-
const parent =
|
|
35566
|
+
const parent = path7.dirname(dir);
|
|
35295
35567
|
if (parent === dir) break;
|
|
35296
35568
|
dir = parent;
|
|
35297
35569
|
}
|
|
35298
35570
|
return candidates;
|
|
35299
35571
|
}
|
|
35300
35572
|
function defaultIsRepoRoot(dir) {
|
|
35301
|
-
return
|
|
35573
|
+
return fs6.existsSync(path7.join(dir, ".git"));
|
|
35302
35574
|
}
|
|
35303
35575
|
function findSnippetTypeDefPath(filename, lookup) {
|
|
35304
35576
|
for (const candidate of snippetTypeDefCandidates(filename, lookup)) {
|
|
@@ -35316,15 +35588,15 @@ function typeDefBanner(filename) {
|
|
|
35316
35588
|
function defaultPackageDir() {
|
|
35317
35589
|
try {
|
|
35318
35590
|
const moduleRequire = createRequire(import.meta.url);
|
|
35319
|
-
return
|
|
35591
|
+
return path7.dirname(moduleRequire.resolve(`${SNIPPET_TYPES_PACKAGE}/package.json`));
|
|
35320
35592
|
} catch {
|
|
35321
35593
|
return null;
|
|
35322
35594
|
}
|
|
35323
35595
|
}
|
|
35324
35596
|
function defaultLookup() {
|
|
35325
35597
|
return {
|
|
35326
|
-
moduleDir:
|
|
35327
|
-
exists: (candidate) =>
|
|
35598
|
+
moduleDir: path7.dirname(fileURLToPath(import.meta.url)),
|
|
35599
|
+
exists: (candidate) => fs6.existsSync(candidate),
|
|
35328
35600
|
packageDir: defaultPackageDir
|
|
35329
35601
|
};
|
|
35330
35602
|
}
|
|
@@ -35341,7 +35613,7 @@ function readSnippetTypeDefs(lookup = defaultLookup()) {
|
|
|
35341
35613
|
]
|
|
35342
35614
|
);
|
|
35343
35615
|
}
|
|
35344
|
-
return { filename, contents: typeDefBanner(filename) +
|
|
35616
|
+
return { filename, contents: typeDefBanner(filename) + fs6.readFileSync(found, "utf-8") };
|
|
35345
35617
|
});
|
|
35346
35618
|
}
|
|
35347
35619
|
|
|
@@ -35366,7 +35638,7 @@ function writeTypeDefinitions(dir) {
|
|
|
35366
35638
|
try {
|
|
35367
35639
|
const written = [];
|
|
35368
35640
|
for (const file of readSnippetTypeDefs()) {
|
|
35369
|
-
|
|
35641
|
+
fs7.writeFileSync(path8.join(dir, file.filename), file.contents);
|
|
35370
35642
|
written.push(file.filename);
|
|
35371
35643
|
}
|
|
35372
35644
|
return written;
|
|
@@ -35382,24 +35654,24 @@ function scaffoldExperiment(options) {
|
|
|
35382
35654
|
const { data, script, style, apiBaseUrl, targetDir } = options;
|
|
35383
35655
|
const { experiment, project, org } = data;
|
|
35384
35656
|
const folderName = toKebabCase(experiment.name);
|
|
35385
|
-
const baseDir = targetDir ??
|
|
35386
|
-
if (
|
|
35657
|
+
const baseDir = targetDir ?? path8.join(process.cwd(), folderName);
|
|
35658
|
+
if (fs7.existsSync(baseDir)) {
|
|
35387
35659
|
throw new Error(`Directory already exists: ${baseDir}`);
|
|
35388
35660
|
}
|
|
35389
|
-
|
|
35661
|
+
fs7.mkdirSync(baseDir, { recursive: true });
|
|
35390
35662
|
const files = [];
|
|
35391
35663
|
const manifestVariations = [];
|
|
35392
|
-
const
|
|
35393
|
-
|
|
35394
|
-
files.push(
|
|
35664
|
+
const triggerFile2 = `trigger.${script}`;
|
|
35665
|
+
fs7.writeFileSync(path8.join(baseDir, triggerFile2), experiment.triggersJs);
|
|
35666
|
+
files.push(triggerFile2);
|
|
35395
35667
|
for (const variation of experiment.variations) {
|
|
35396
35668
|
const folder = variationFolderName(variation.name);
|
|
35397
|
-
const varDir =
|
|
35398
|
-
|
|
35669
|
+
const varDir = path8.join(baseDir, folder);
|
|
35670
|
+
fs7.mkdirSync(varDir, { recursive: true });
|
|
35399
35671
|
const scriptFile = `${folder}.${script}`;
|
|
35400
35672
|
const styleFile = `${folder}.${style}`;
|
|
35401
|
-
|
|
35402
|
-
|
|
35673
|
+
fs7.writeFileSync(path8.join(varDir, scriptFile), variation.controlJs);
|
|
35674
|
+
fs7.writeFileSync(path8.join(varDir, styleFile), variation.controlCss);
|
|
35403
35675
|
files.push(`${folder}/${scriptFile}`);
|
|
35404
35676
|
files.push(`${folder}/${styleFile}`);
|
|
35405
35677
|
manifestVariations.push({
|
|
@@ -35438,7 +35710,7 @@ function scaffoldExperiment(options) {
|
|
|
35438
35710
|
writeManifest(baseDir, manifest);
|
|
35439
35711
|
files.push(".avsb.json");
|
|
35440
35712
|
if (script === "ts") {
|
|
35441
|
-
|
|
35713
|
+
fs7.writeFileSync(path8.join(baseDir, "tsconfig.json"), TSCONFIG_TEMPLATE);
|
|
35442
35714
|
files.push("tsconfig.json");
|
|
35443
35715
|
files.push(...writeTypeDefinitions(baseDir));
|
|
35444
35716
|
}
|
|
@@ -35454,10 +35726,10 @@ function refreshTypeDefinitions(dir, script) {
|
|
|
35454
35726
|
}
|
|
35455
35727
|
const changed = [];
|
|
35456
35728
|
for (const file of current) {
|
|
35457
|
-
const target =
|
|
35458
|
-
const onDisk =
|
|
35729
|
+
const target = path8.join(dir, file.filename);
|
|
35730
|
+
const onDisk = fs7.existsSync(target) ? fs7.readFileSync(target, "utf-8") : null;
|
|
35459
35731
|
if (onDisk === file.contents) continue;
|
|
35460
|
-
|
|
35732
|
+
fs7.writeFileSync(target, file.contents);
|
|
35461
35733
|
changed.push(file.filename);
|
|
35462
35734
|
}
|
|
35463
35735
|
return changed;
|
|
@@ -35467,26 +35739,26 @@ function updateLocalFiles(dir, manifest, data) {
|
|
|
35467
35739
|
const { script, style } = manifest.languages;
|
|
35468
35740
|
const updated = [];
|
|
35469
35741
|
updated.push(...refreshTypeDefinitions(dir, script));
|
|
35470
|
-
const
|
|
35471
|
-
const triggerPath =
|
|
35472
|
-
const currentTrigger =
|
|
35742
|
+
const triggerFile2 = `trigger.${script}`;
|
|
35743
|
+
const triggerPath = path8.join(dir, triggerFile2);
|
|
35744
|
+
const currentTrigger = fs7.existsSync(triggerPath) ? fs7.readFileSync(triggerPath, "utf-8") : "";
|
|
35473
35745
|
if (currentTrigger !== experiment.triggersJs) {
|
|
35474
|
-
|
|
35475
|
-
updated.push(
|
|
35746
|
+
fs7.writeFileSync(triggerPath, experiment.triggersJs);
|
|
35747
|
+
updated.push(triggerFile2);
|
|
35476
35748
|
}
|
|
35477
35749
|
for (const variation of experiment.variations) {
|
|
35478
35750
|
const mv = manifest.variations.find((v) => v.id === variation.id);
|
|
35479
35751
|
if (!mv) continue;
|
|
35480
|
-
const jsPath =
|
|
35481
|
-
const cssPath =
|
|
35482
|
-
const currentJs =
|
|
35483
|
-
const currentCss =
|
|
35752
|
+
const jsPath = path8.join(dir, mv.folder, mv.scriptFile);
|
|
35753
|
+
const cssPath = path8.join(dir, mv.folder, mv.styleFile);
|
|
35754
|
+
const currentJs = fs7.existsSync(jsPath) ? fs7.readFileSync(jsPath, "utf-8") : "";
|
|
35755
|
+
const currentCss = fs7.existsSync(cssPath) ? fs7.readFileSync(cssPath, "utf-8") : "";
|
|
35484
35756
|
if (currentJs !== variation.controlJs) {
|
|
35485
|
-
|
|
35757
|
+
fs7.writeFileSync(jsPath, variation.controlJs);
|
|
35486
35758
|
updated.push(`${mv.folder}/${mv.scriptFile}`);
|
|
35487
35759
|
}
|
|
35488
35760
|
if (currentCss !== variation.controlCss) {
|
|
35489
|
-
|
|
35761
|
+
fs7.writeFileSync(cssPath, variation.controlCss);
|
|
35490
35762
|
updated.push(`${mv.folder}/${mv.styleFile}`);
|
|
35491
35763
|
}
|
|
35492
35764
|
}
|
|
@@ -35519,27 +35791,27 @@ function diffLocalFiles(dir, manifest, data) {
|
|
|
35519
35791
|
const { experiment } = data;
|
|
35520
35792
|
const { script } = manifest.languages;
|
|
35521
35793
|
const changed = [];
|
|
35522
|
-
const
|
|
35523
|
-
const triggerPath =
|
|
35524
|
-
if (
|
|
35525
|
-
const localContent =
|
|
35794
|
+
const triggerFile2 = `trigger.${script}`;
|
|
35795
|
+
const triggerPath = path8.join(dir, triggerFile2);
|
|
35796
|
+
if (fs7.existsSync(triggerPath)) {
|
|
35797
|
+
const localContent = fs7.readFileSync(triggerPath, "utf-8");
|
|
35526
35798
|
if (localContent !== experiment.triggersJs) {
|
|
35527
|
-
changed.push(
|
|
35799
|
+
changed.push(triggerFile2);
|
|
35528
35800
|
}
|
|
35529
35801
|
}
|
|
35530
35802
|
for (const variation of experiment.variations) {
|
|
35531
35803
|
const mv = manifest.variations.find((v) => v.id === variation.id);
|
|
35532
35804
|
if (!mv) continue;
|
|
35533
|
-
const jsPath =
|
|
35534
|
-
const cssPath =
|
|
35535
|
-
if (
|
|
35536
|
-
const localJs =
|
|
35805
|
+
const jsPath = path8.join(dir, mv.folder, mv.scriptFile);
|
|
35806
|
+
const cssPath = path8.join(dir, mv.folder, mv.styleFile);
|
|
35807
|
+
if (fs7.existsSync(jsPath)) {
|
|
35808
|
+
const localJs = fs7.readFileSync(jsPath, "utf-8");
|
|
35537
35809
|
if (localJs !== variation.controlJs) {
|
|
35538
35810
|
changed.push(`${mv.folder}/${mv.scriptFile}`);
|
|
35539
35811
|
}
|
|
35540
35812
|
}
|
|
35541
|
-
if (
|
|
35542
|
-
const localCss =
|
|
35813
|
+
if (fs7.existsSync(cssPath)) {
|
|
35814
|
+
const localCss = fs7.readFileSync(cssPath, "utf-8");
|
|
35543
35815
|
if (localCss !== variation.controlCss) {
|
|
35544
35816
|
changed.push(`${mv.folder}/${mv.styleFile}`);
|
|
35545
35817
|
}
|
|
@@ -35580,21 +35852,21 @@ function parseProjectInput(input) {
|
|
|
35580
35852
|
return num;
|
|
35581
35853
|
}
|
|
35582
35854
|
function writeProjectManifest(dir, manifest) {
|
|
35583
|
-
const manifestPath =
|
|
35584
|
-
|
|
35855
|
+
const manifestPath = path9.join(dir, PROJECT_MANIFEST_FILENAME);
|
|
35856
|
+
fs8.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
35585
35857
|
}
|
|
35586
35858
|
function readProjectManifest(dir) {
|
|
35587
|
-
const manifestPath =
|
|
35588
|
-
if (!
|
|
35589
|
-
const raw =
|
|
35859
|
+
const manifestPath = path9.join(dir, PROJECT_MANIFEST_FILENAME);
|
|
35860
|
+
if (!fs8.existsSync(manifestPath)) return null;
|
|
35861
|
+
const raw = fs8.readFileSync(manifestPath, "utf-8");
|
|
35590
35862
|
return JSON.parse(raw);
|
|
35591
35863
|
}
|
|
35592
35864
|
function findProjectManifestDir(startDir) {
|
|
35593
35865
|
let dir = startDir ?? process.cwd();
|
|
35594
35866
|
for (let i = 0; i < 10; i++) {
|
|
35595
|
-
const manifestPath =
|
|
35596
|
-
if (
|
|
35597
|
-
const parent =
|
|
35867
|
+
const manifestPath = path9.join(dir, PROJECT_MANIFEST_FILENAME);
|
|
35868
|
+
if (fs8.existsSync(manifestPath)) return dir;
|
|
35869
|
+
const parent = path9.dirname(dir);
|
|
35598
35870
|
if (parent === dir) break;
|
|
35599
35871
|
dir = parent;
|
|
35600
35872
|
}
|
|
@@ -35614,20 +35886,29 @@ function readProjectManifestOrExit(startDir) {
|
|
|
35614
35886
|
const manifest = readProjectManifest(dir);
|
|
35615
35887
|
return { manifest, dir };
|
|
35616
35888
|
}
|
|
35889
|
+
function resolveProjectPullTarget(cwd, shortId) {
|
|
35890
|
+
const enclosing = findProjectManifestDir(cwd);
|
|
35891
|
+
if (enclosing && readProjectManifest(enclosing)?.projectShortId === shortId) {
|
|
35892
|
+
return { kind: "existing", dir: enclosing };
|
|
35893
|
+
}
|
|
35894
|
+
const beside = path9.join(cwd, `project-${shortId}`);
|
|
35895
|
+
if (!fs8.existsSync(beside)) return { kind: "fresh", dir: beside };
|
|
35896
|
+
return readProjectManifest(beside)?.projectShortId === shortId ? { kind: "existing", dir: beside } : { kind: "blocked", dir: beside };
|
|
35897
|
+
}
|
|
35617
35898
|
function scaffoldProject(options) {
|
|
35618
35899
|
const { data, apiBaseUrl, targetDir } = options;
|
|
35619
35900
|
const { project } = data;
|
|
35620
35901
|
const lang = project.projectJsLang === "TYPESCRIPT" ? "ts" : "js";
|
|
35621
35902
|
const folderName = `project-${project.shortId}`;
|
|
35622
|
-
const baseDir = targetDir ??
|
|
35623
|
-
if (
|
|
35903
|
+
const baseDir = targetDir ?? path9.join(process.cwd(), folderName);
|
|
35904
|
+
if (fs8.existsSync(baseDir)) {
|
|
35624
35905
|
throw new Error(`Directory already exists: ${baseDir}`);
|
|
35625
35906
|
}
|
|
35626
|
-
|
|
35907
|
+
fs8.mkdirSync(baseDir, { recursive: true });
|
|
35627
35908
|
const files = [];
|
|
35628
35909
|
const scriptFile = `project.${lang}`;
|
|
35629
35910
|
const content = lang === "ts" ? project.editorProjectTs ?? project.editorProjectJs ?? "" : project.editorProjectJs ?? "";
|
|
35630
|
-
|
|
35911
|
+
fs8.writeFileSync(path9.join(baseDir, scriptFile), content);
|
|
35631
35912
|
files.push(scriptFile);
|
|
35632
35913
|
const manifest = {
|
|
35633
35914
|
version: 1,
|
|
@@ -35644,7 +35925,7 @@ function scaffoldProject(options) {
|
|
|
35644
35925
|
writeProjectManifest(baseDir, manifest);
|
|
35645
35926
|
files.push(".avsb-project.json");
|
|
35646
35927
|
if (lang === "ts") {
|
|
35647
|
-
|
|
35928
|
+
fs8.writeFileSync(path9.join(baseDir, "tsconfig.json"), PROJECT_TSCONFIG);
|
|
35648
35929
|
files.push("tsconfig.json");
|
|
35649
35930
|
files.push(...writeTypeDefinitions(baseDir));
|
|
35650
35931
|
}
|
|
@@ -35655,11 +35936,11 @@ function updateProjectFile(dir, manifest, data) {
|
|
|
35655
35936
|
const updated = [];
|
|
35656
35937
|
updated.push(...refreshTypeDefinitions(dir, manifest.language));
|
|
35657
35938
|
const scriptFile = `project.${manifest.language}`;
|
|
35658
|
-
const scriptPath =
|
|
35659
|
-
const current =
|
|
35939
|
+
const scriptPath = path9.join(dir, scriptFile);
|
|
35940
|
+
const current = fs8.existsSync(scriptPath) ? fs8.readFileSync(scriptPath, "utf-8") : "";
|
|
35660
35941
|
const remote = manifest.language === "ts" ? project.editorProjectTs ?? project.editorProjectJs ?? "" : project.editorProjectJs ?? "";
|
|
35661
35942
|
if (current !== remote) {
|
|
35662
|
-
|
|
35943
|
+
fs8.writeFileSync(scriptPath, remote);
|
|
35663
35944
|
updated.push(scriptFile);
|
|
35664
35945
|
}
|
|
35665
35946
|
manifest.lastSyncedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -35878,28 +36159,47 @@ function renderInitSummary(input) {
|
|
|
35878
36159
|
}
|
|
35879
36160
|
function describeDetection(input) {
|
|
35880
36161
|
const manager = `installs with ${input.packageManager}`;
|
|
36162
|
+
const variable = input.profile.envVar && input.profile.envVarReason ? `, key in ${input.profile.envVar}: ${input.profile.envVarReason}` : "";
|
|
35881
36163
|
if (input.detection.matchedDependency) {
|
|
35882
|
-
return `${input.profile.label} (found ${input.detection.matchedDependency} in package.json, ${manager})`;
|
|
36164
|
+
return `${input.profile.label} (found ${input.detection.matchedDependency} in package.json${variable}, ${manager})`;
|
|
35883
36165
|
}
|
|
35884
36166
|
if (input.profile.id === "none") {
|
|
35885
36167
|
return "no package.json found here";
|
|
35886
36168
|
}
|
|
35887
36169
|
return `${input.profile.label} (${manager})`;
|
|
35888
36170
|
}
|
|
35889
|
-
function
|
|
35890
|
-
const
|
|
36171
|
+
function fileReport(files) {
|
|
36172
|
+
const envWritten = files.envOutcome === "created" || files.envOutcome === "appended";
|
|
36173
|
+
const written = [envWritten ? files.env : null, files.example, files.codegen, files.gitignore].filter(
|
|
35891
36174
|
(file) => file !== null
|
|
35892
36175
|
);
|
|
36176
|
+
const leftAlone = !envWritten && files.env !== null ? [files.env] : [];
|
|
36177
|
+
return { written, leftAlone };
|
|
36178
|
+
}
|
|
36179
|
+
function renderSteps(input) {
|
|
36180
|
+
const { written, leftAlone } = fileReport(input.files);
|
|
35893
36181
|
if (written.length > 0) {
|
|
35894
36182
|
heading("Written");
|
|
35895
36183
|
fileTree(written);
|
|
35896
36184
|
line();
|
|
35897
36185
|
}
|
|
36186
|
+
if (leftAlone.length > 0) {
|
|
36187
|
+
heading("Left alone");
|
|
36188
|
+
fileTree(leftAlone);
|
|
36189
|
+
line();
|
|
36190
|
+
}
|
|
35898
36191
|
const skipped = input.plan.filter((step) => step.action === "skip");
|
|
35899
36192
|
for (const step of skipped) dim(` Skipped ${step.id}: ${step.detail}`);
|
|
35900
36193
|
if (skipped.length > 0) line();
|
|
35901
36194
|
}
|
|
35902
36195
|
function renderWhatIsLeft(input) {
|
|
36196
|
+
const todo = whatIsLeftLines(input);
|
|
36197
|
+
if (todo.length === 0) return;
|
|
36198
|
+
line();
|
|
36199
|
+
heading("Next");
|
|
36200
|
+
for (const line2 of todo) dim(` ${line2}`);
|
|
36201
|
+
}
|
|
36202
|
+
function whatIsLeftLines(input) {
|
|
35903
36203
|
const todo = [];
|
|
35904
36204
|
if (input.install && !input.install.ran) {
|
|
35905
36205
|
todo.push(`Install the SDK: ${input.install.command}`);
|
|
@@ -35911,17 +36211,15 @@ function renderWhatIsLeft(input) {
|
|
|
35911
36211
|
todo.push(input.profile.caveat);
|
|
35912
36212
|
}
|
|
35913
36213
|
if (input.flagKey && !input.flagExists) {
|
|
36214
|
+
const envKey = input.environment?.key ?? "production";
|
|
35914
36215
|
todo.push(
|
|
35915
|
-
`The example reads "${input.flagKey}", which does not exist yet. Create it with \`avsb flags create ${input.flagKey}\`.`
|
|
36216
|
+
`The example reads "${input.flagKey}", which does not exist yet. Create it with \`avsb flags create ${input.flagKey}\`, then turn it on with \`avsb flags toggle ${input.flagKey} --env ${envKey} --on\`.`
|
|
35916
36217
|
);
|
|
35917
36218
|
}
|
|
35918
36219
|
if (input.servedLine) {
|
|
35919
36220
|
todo.push(input.servedLine);
|
|
35920
36221
|
}
|
|
35921
|
-
|
|
35922
|
-
line();
|
|
35923
|
-
heading("Next");
|
|
35924
|
-
for (const line2 of todo) dim(` ${line2}`);
|
|
36222
|
+
return todo;
|
|
35925
36223
|
}
|
|
35926
36224
|
function renderAssistantPrompt(input) {
|
|
35927
36225
|
if (!input.environment && input.project.type === "FEATURE_FLAG") return;
|
|
@@ -35978,11 +36276,11 @@ var initCommand = withOutputOptions(
|
|
|
35978
36276
|
).action(async (opts) => {
|
|
35979
36277
|
beginCommand("init", opts);
|
|
35980
36278
|
try {
|
|
35981
|
-
const dir =
|
|
36279
|
+
const dir = path10.resolve(opts.dir ?? ".");
|
|
35982
36280
|
parseWaitSeconds(opts.wait);
|
|
35983
36281
|
assertHeartbeatFlagsAgree(opts);
|
|
35984
36282
|
const interactive = isInteractiveRun({ input: opts.input });
|
|
35985
|
-
const pkg2 = parsePackageJson(readFileOrNull(
|
|
36283
|
+
const pkg2 = parsePackageJson(readFileOrNull(path10.join(dir, "package.json")) ?? "");
|
|
35986
36284
|
const detection = resolveDetection(pkg2, opts.framework);
|
|
35987
36285
|
const profile = buildFrameworkProfile(detection);
|
|
35988
36286
|
const packageManager = detectPackageManager(pkg2, listDirectory(dir));
|
|
@@ -36056,7 +36354,9 @@ var initCommand = withOutputOptions(
|
|
|
36056
36354
|
id: profile.id,
|
|
36057
36355
|
label: profile.label,
|
|
36058
36356
|
detectedFrom: detection.matchedDependency,
|
|
36059
|
-
packageManager
|
|
36357
|
+
packageManager,
|
|
36358
|
+
bundler: detection.bundler?.id ?? null,
|
|
36359
|
+
envVar: profile.envVar
|
|
36060
36360
|
},
|
|
36061
36361
|
flag: sample ? { key: sample.flagKey, exists: sample.flag !== null, served } : null,
|
|
36062
36362
|
steps: plan,
|
|
@@ -36079,12 +36379,14 @@ var initCommand = withOutputOptions(
|
|
|
36079
36379
|
install: outcome.install,
|
|
36080
36380
|
flagKey: sample?.flagKey ?? null,
|
|
36081
36381
|
flagExists: sample !== null && sample.flag !== null,
|
|
36082
|
-
servedLine: sample?.flag ? describeServedValue(sample.flagKey, served) : null,
|
|
36382
|
+
servedLine: sample?.flag ? describeServedValue(sample.flagKey, served, opts.env) : null,
|
|
36083
36383
|
heartbeat,
|
|
36084
36384
|
proof,
|
|
36085
36385
|
timeoutGuidance: heartbeat && !heartbeat.seen && environment ? heartbeatTimeoutGuidance({
|
|
36086
36386
|
sdkKey: environment.sdkKey,
|
|
36087
36387
|
environmentName: environment.name,
|
|
36388
|
+
environmentKey: environment.key,
|
|
36389
|
+
flagKey: sample?.flagKey ?? null,
|
|
36088
36390
|
previousSeenAt: heartbeat.previousSeenAt,
|
|
36089
36391
|
waitedMs: heartbeat.elapsedMs,
|
|
36090
36392
|
cdnHost: new URL(DEFAULT_CDN_BASE_URL).host,
|
|
@@ -36134,8 +36436,8 @@ async function decideCodegen(opts, interactive, hasSdkKey) {
|
|
|
36134
36436
|
}
|
|
36135
36437
|
|
|
36136
36438
|
// src/commands/agents-md.ts
|
|
36137
|
-
import
|
|
36138
|
-
import
|
|
36439
|
+
import fs9 from "node:fs";
|
|
36440
|
+
import path11 from "node:path";
|
|
36139
36441
|
|
|
36140
36442
|
// src/lib/agentsMd.ts
|
|
36141
36443
|
var AGENTS_MARKER_START = "<!-- avsb:start -->";
|
|
@@ -36285,8 +36587,8 @@ var agentsMdInstallCommand = withOutputOptions(
|
|
|
36285
36587
|
).action((opts) => {
|
|
36286
36588
|
beginCommand("agents-md install", opts);
|
|
36287
36589
|
try {
|
|
36288
|
-
const dir =
|
|
36289
|
-
const pkg2 = parsePackageJson(readFileOrNull(
|
|
36590
|
+
const dir = path11.resolve(opts.dir ?? ".");
|
|
36591
|
+
const pkg2 = parsePackageJson(readFileOrNull(path11.join(dir, "package.json")) ?? "");
|
|
36290
36592
|
const profile = buildFrameworkProfile(detectFramework(pkg2));
|
|
36291
36593
|
const section = renderAgentsSection({
|
|
36292
36594
|
projectShortId: readProjectShortId(opts.project),
|
|
@@ -36310,12 +36612,12 @@ var agentsMdInstallCommand = withOutputOptions(
|
|
|
36310
36612
|
}
|
|
36311
36613
|
});
|
|
36312
36614
|
function writeTarget(dir, file, section) {
|
|
36313
|
-
const filePath =
|
|
36615
|
+
const filePath = path11.join(dir, file);
|
|
36314
36616
|
const existing = readFileOrNull(filePath);
|
|
36315
36617
|
const applied = applyAgentsSection(existing, section);
|
|
36316
36618
|
if (applied.outcome !== "unchanged") {
|
|
36317
36619
|
try {
|
|
36318
|
-
|
|
36620
|
+
fs9.writeFileSync(filePath, applied.content, "utf-8");
|
|
36319
36621
|
} catch (err) {
|
|
36320
36622
|
throw new CliError(
|
|
36321
36623
|
"cli_io_failed",
|
|
@@ -36351,7 +36653,8 @@ var loginCommand = new Command("login").description("Save a personal access toke
|
|
|
36351
36653
|
`Replacing the saved login in ${getConfigPath()}: ${existing.detail}.`
|
|
36352
36654
|
);
|
|
36353
36655
|
}
|
|
36354
|
-
|
|
36656
|
+
const interactive = canPrompt(currentPromptContext({ input: options.input }));
|
|
36657
|
+
if (existingConfig && interactive && !explicitToken && !envToken) {
|
|
36355
36658
|
const active = existingConfig.logins.find(
|
|
36356
36659
|
(login) => login.orgId === existingConfig.activeOrgId
|
|
36357
36660
|
);
|
|
@@ -36373,7 +36676,12 @@ var loginCommand = new Command("login").description("Save a personal access toke
|
|
|
36373
36676
|
const token = await resolveToken({
|
|
36374
36677
|
explicitToken,
|
|
36375
36678
|
envToken,
|
|
36376
|
-
|
|
36679
|
+
source: tokenSourceFor({
|
|
36680
|
+
explicitToken,
|
|
36681
|
+
envToken,
|
|
36682
|
+
input: options.input,
|
|
36683
|
+
isTty: process.stdin.isTTY === true
|
|
36684
|
+
})
|
|
36377
36685
|
});
|
|
36378
36686
|
const spinner = ora("Verifying token...").start();
|
|
36379
36687
|
const client = new ApiClient({ token, apiBaseUrl }, { useEnvOverrides: false });
|
|
@@ -36381,8 +36689,9 @@ var loginCommand = new Command("login").description("Save a personal access toke
|
|
|
36381
36689
|
try {
|
|
36382
36690
|
verified = await client.verifyAuth();
|
|
36383
36691
|
} catch (err) {
|
|
36384
|
-
|
|
36385
|
-
|
|
36692
|
+
const rejected = err instanceof ApiRequestError && err.status === 401;
|
|
36693
|
+
spinner.fail(rejected ? "Token rejected" : "Could not check the token");
|
|
36694
|
+
if (rejected) {
|
|
36386
36695
|
dim(` ${WHERE_TO_GET_A_TOKEN}`);
|
|
36387
36696
|
}
|
|
36388
36697
|
failCommand(err);
|
|
@@ -36417,13 +36726,22 @@ var loginCommand = new Command("login").description("Save a personal access toke
|
|
|
36417
36726
|
failCommand(err);
|
|
36418
36727
|
}
|
|
36419
36728
|
});
|
|
36729
|
+
function tokenSourceFor(input) {
|
|
36730
|
+
if (input.explicitToken) return "flag";
|
|
36731
|
+
if (input.envToken) return "env";
|
|
36732
|
+
if (!input.input) return "refuse";
|
|
36733
|
+
return input.isTty ? "prompt" : "stdin";
|
|
36734
|
+
}
|
|
36735
|
+
var NO_TOKEN_HINTS = [
|
|
36736
|
+
`Pass --token <token>, set ${TOKEN_ENV_VAR}, or pipe it in: echo "$TOKEN" | avsb login`
|
|
36737
|
+
];
|
|
36420
36738
|
async function resolveToken(input) {
|
|
36421
|
-
if (input.explicitToken) return input.explicitToken;
|
|
36422
|
-
if (input.envToken) {
|
|
36739
|
+
if (input.source === "flag" && input.explicitToken) return input.explicitToken;
|
|
36740
|
+
if (input.source === "env" && input.envToken) {
|
|
36423
36741
|
info(`Using the token from ${TOKEN_ENV_VAR}.`);
|
|
36424
36742
|
return input.envToken;
|
|
36425
36743
|
}
|
|
36426
|
-
if (
|
|
36744
|
+
if (input.source === "refuse") {
|
|
36427
36745
|
failCommand(
|
|
36428
36746
|
new CliError(
|
|
36429
36747
|
"cli_usage",
|
|
@@ -36431,6 +36749,13 @@ async function resolveToken(input) {
|
|
|
36431
36749
|
)
|
|
36432
36750
|
);
|
|
36433
36751
|
}
|
|
36752
|
+
if (input.source === "stdin") {
|
|
36753
|
+
const piped = (await readPipedStdin()).trim();
|
|
36754
|
+
if (piped.length === 0) {
|
|
36755
|
+
failCommand(new CliError("cli_usage", "No token supplied, and there is no terminal to ask in.", NO_TOKEN_HINTS));
|
|
36756
|
+
}
|
|
36757
|
+
return piped;
|
|
36758
|
+
}
|
|
36434
36759
|
const { token } = await esm_default12.prompt([
|
|
36435
36760
|
{
|
|
36436
36761
|
type: "password",
|
|
@@ -36467,12 +36792,19 @@ var logoutCommand = new Command("logout").description("Delete the saved logins o
|
|
|
36467
36792
|
});
|
|
36468
36793
|
|
|
36469
36794
|
// src/commands/whoami.ts
|
|
36795
|
+
function roleName(role) {
|
|
36796
|
+
if (typeof role === "string") return role;
|
|
36797
|
+
if (typeof role === "object" && role !== null && "name" in role && typeof role.name === "string") {
|
|
36798
|
+
return role.name;
|
|
36799
|
+
}
|
|
36800
|
+
return "unknown";
|
|
36801
|
+
}
|
|
36470
36802
|
function buildWhoamiRows(verified, session, configPath) {
|
|
36471
36803
|
return [
|
|
36472
36804
|
{ label: "Email", value: verified.email },
|
|
36473
36805
|
{ label: "Organization", value: verified.orgName },
|
|
36474
36806
|
{ label: "Org ID", value: verified.orgId },
|
|
36475
|
-
{ label: "Role", value: verified.role },
|
|
36807
|
+
{ label: "Role", value: roleName(verified.role) },
|
|
36476
36808
|
{ label: "API URL", value: session.apiBaseUrl },
|
|
36477
36809
|
{ label: "Token from", value: describeTokenSource(session, configPath) }
|
|
36478
36810
|
];
|
|
@@ -36498,7 +36830,7 @@ var whoamiCommand = withOutputOptions(
|
|
|
36498
36830
|
email: verified.data.email,
|
|
36499
36831
|
orgId: verified.data.orgId,
|
|
36500
36832
|
orgName: verified.data.orgName,
|
|
36501
|
-
role: verified.data.role,
|
|
36833
|
+
role: roleName(verified.data.role),
|
|
36502
36834
|
apiBaseUrl: session.apiBaseUrl,
|
|
36503
36835
|
tokenSource: session.tokenSource,
|
|
36504
36836
|
configPath: getConfigPath()
|
|
@@ -36508,7 +36840,7 @@ var whoamiCommand = withOutputOptions(
|
|
|
36508
36840
|
line();
|
|
36509
36841
|
heading("Signed in");
|
|
36510
36842
|
for (const row of buildWhoamiRows(verified.data, session, getConfigPath())) {
|
|
36511
|
-
|
|
36843
|
+
field(row.label, row.value);
|
|
36512
36844
|
}
|
|
36513
36845
|
line();
|
|
36514
36846
|
});
|
|
@@ -36525,12 +36857,14 @@ function configForSession(session) {
|
|
|
36525
36857
|
apiBaseUrl: session.apiBaseUrl
|
|
36526
36858
|
};
|
|
36527
36859
|
}
|
|
36860
|
+
function describeCandidates(candidates) {
|
|
36861
|
+
return candidates.map((c) => c.shortId != null ? `${c.shortId} (${c.name})` : c.name).join(", ");
|
|
36862
|
+
}
|
|
36528
36863
|
function describeSwitchFailure(candidates, needle) {
|
|
36529
36864
|
if (candidates.length === 0) {
|
|
36530
36865
|
return `No organization matches "${needle}". This token cannot reach any organization.`;
|
|
36531
36866
|
}
|
|
36532
|
-
|
|
36533
|
-
return `No organization matches "${needle}". Available: ${known}.`;
|
|
36867
|
+
return `No organization matches "${needle}". Available: ${describeCandidates(candidates)}.`;
|
|
36534
36868
|
}
|
|
36535
36869
|
var orgCommand = withOutputOptions(
|
|
36536
36870
|
new Command("org").description("Show, list, and switch the active organization")
|
|
@@ -36548,13 +36882,13 @@ var orgCommand = withOutputOptions(
|
|
|
36548
36882
|
}
|
|
36549
36883
|
heading("Current organization");
|
|
36550
36884
|
if (session.orgName) {
|
|
36551
|
-
|
|
36552
|
-
|
|
36885
|
+
field("Name", session.orgName);
|
|
36886
|
+
field("ID", session.orgId);
|
|
36553
36887
|
} else {
|
|
36554
|
-
|
|
36888
|
+
field("Name", "not saved on this machine");
|
|
36555
36889
|
}
|
|
36556
|
-
|
|
36557
|
-
|
|
36890
|
+
field("API URL", session.apiBaseUrl);
|
|
36891
|
+
field("Token from", describeTokenSource(session, getConfigPath()));
|
|
36558
36892
|
if (session.tokenSource === "env") {
|
|
36559
36893
|
dim(
|
|
36560
36894
|
` ${TOKEN_ENV_VAR} is set, so commands use that token. Run \`avsb whoami\` to see which account and organization it reaches.`
|
|
@@ -36564,8 +36898,8 @@ var orgCommand = withOutputOptions(
|
|
|
36564
36898
|
orgCommand.addCommand(
|
|
36565
36899
|
withOutputOptions(
|
|
36566
36900
|
new Command("list").description("List the organizations this token can reach")
|
|
36567
|
-
).action(async (
|
|
36568
|
-
beginCommand("org list",
|
|
36901
|
+
).action(async (_opts, command) => {
|
|
36902
|
+
beginCommand("org list", command.optsWithGlobals());
|
|
36569
36903
|
try {
|
|
36570
36904
|
const session = ensureLoggedIn();
|
|
36571
36905
|
const config = configForSession(session);
|
|
@@ -36629,12 +36963,16 @@ orgCommand.command("switch").description("Choose which organization commands use
|
|
|
36629
36963
|
failCommand(err);
|
|
36630
36964
|
}
|
|
36631
36965
|
});
|
|
36632
|
-
async function promptForOrg(candidates,
|
|
36633
|
-
|
|
36634
|
-
|
|
36635
|
-
|
|
36636
|
-
|
|
36637
|
-
|
|
36966
|
+
async function promptForOrg(candidates, input) {
|
|
36967
|
+
const refusal = refusalWithoutPrompt(
|
|
36968
|
+
{
|
|
36969
|
+
code: "cli_usage",
|
|
36970
|
+
message: "Name an organization: `avsb org switch <short id | id | name>`.",
|
|
36971
|
+
hints: [`Available: ${describeCandidates(candidates)}.`]
|
|
36972
|
+
},
|
|
36973
|
+
currentPromptContext({ input })
|
|
36974
|
+
);
|
|
36975
|
+
if (refusal) failCommand(refusal);
|
|
36638
36976
|
const { chosen } = await esm_default12.prompt([
|
|
36639
36977
|
{
|
|
36640
36978
|
type: "list",
|
|
@@ -36750,6 +37088,15 @@ var pullCommand = new Command("pull").description("Pull latest experiment data f
|
|
|
36750
37088
|
dim(` ${file}`);
|
|
36751
37089
|
}
|
|
36752
37090
|
line();
|
|
37091
|
+
const refusal = refusalWithoutPrompt({
|
|
37092
|
+
code: "cli_conflict",
|
|
37093
|
+
message: "Pulling would overwrite these local changes, and this run cannot ask first.",
|
|
37094
|
+
hints: [
|
|
37095
|
+
"Run `avsb pull --force` to overwrite them,",
|
|
37096
|
+
"or `avsb push` first to keep them on the platform."
|
|
37097
|
+
]
|
|
37098
|
+
});
|
|
37099
|
+
if (refusal) failCommand(refusal);
|
|
36753
37100
|
const { proceed } = await esm_default12.prompt([
|
|
36754
37101
|
{
|
|
36755
37102
|
type: "confirm",
|
|
@@ -36785,14 +37132,16 @@ var pullCommand = new Command("pull").description("Pull latest experiment data f
|
|
|
36785
37132
|
|
|
36786
37133
|
// src/commands/push.ts
|
|
36787
37134
|
import { createHash as createHash3 } from "node:crypto";
|
|
36788
|
-
|
|
36789
|
-
|
|
37135
|
+
|
|
37136
|
+
// src/commands/push.payload.ts
|
|
37137
|
+
import fs11 from "node:fs";
|
|
37138
|
+
import path13 from "node:path";
|
|
36790
37139
|
|
|
36791
37140
|
// src/lib/compiler.ts
|
|
36792
37141
|
import { transform } from "esbuild";
|
|
36793
37142
|
import * as sass from "sass";
|
|
36794
|
-
import
|
|
36795
|
-
import
|
|
37143
|
+
import fs10 from "node:fs";
|
|
37144
|
+
import path12 from "node:path";
|
|
36796
37145
|
async function compileTypeScript(source) {
|
|
36797
37146
|
const result = await transform(source, {
|
|
36798
37147
|
loader: "ts",
|
|
@@ -36803,7 +37152,7 @@ async function compileTypeScript(source) {
|
|
|
36803
37152
|
function compileScss(filePath) {
|
|
36804
37153
|
const result = sass.compile(filePath, {
|
|
36805
37154
|
style: "expanded",
|
|
36806
|
-
loadPaths: [
|
|
37155
|
+
loadPaths: [path12.dirname(filePath)]
|
|
36807
37156
|
});
|
|
36808
37157
|
return result.css;
|
|
36809
37158
|
}
|
|
@@ -36879,8 +37228,8 @@ function parseEsbuildErrors(err, fallbackFile) {
|
|
|
36879
37228
|
];
|
|
36880
37229
|
}
|
|
36881
37230
|
async function compileFile(filePath) {
|
|
36882
|
-
const ext =
|
|
36883
|
-
const source =
|
|
37231
|
+
const ext = path12.extname(filePath);
|
|
37232
|
+
const source = fs10.readFileSync(filePath, "utf-8");
|
|
36884
37233
|
switch (ext) {
|
|
36885
37234
|
case ".ts":
|
|
36886
37235
|
return compileTypeScript(source);
|
|
@@ -36895,21 +37244,21 @@ async function compileFile(filePath) {
|
|
|
36895
37244
|
}
|
|
36896
37245
|
async function compileExperiment(dir, manifest) {
|
|
36897
37246
|
const { script } = manifest.languages;
|
|
36898
|
-
const triggerPath =
|
|
37247
|
+
const triggerPath = path12.join(dir, `trigger.${script}`);
|
|
36899
37248
|
let triggersJs = "";
|
|
36900
|
-
if (
|
|
37249
|
+
if (fs10.existsSync(triggerPath)) {
|
|
36901
37250
|
triggersJs = await compileFile(triggerPath);
|
|
36902
37251
|
}
|
|
36903
37252
|
const variations = [];
|
|
36904
37253
|
for (const v of manifest.variations) {
|
|
36905
|
-
const jsPath =
|
|
36906
|
-
const cssPath =
|
|
37254
|
+
const jsPath = path12.join(dir, v.folder, v.scriptFile);
|
|
37255
|
+
const cssPath = path12.join(dir, v.folder, v.styleFile);
|
|
36907
37256
|
let js = "";
|
|
36908
37257
|
let css = "";
|
|
36909
|
-
if (
|
|
37258
|
+
if (fs10.existsSync(jsPath)) {
|
|
36910
37259
|
js = await compileFile(jsPath);
|
|
36911
37260
|
}
|
|
36912
|
-
if (
|
|
37261
|
+
if (fs10.existsSync(cssPath)) {
|
|
36913
37262
|
css = await compileFile(cssPath);
|
|
36914
37263
|
}
|
|
36915
37264
|
variations.push({
|
|
@@ -36928,14 +37277,91 @@ async function compileExperiment(dir, manifest) {
|
|
|
36928
37277
|
};
|
|
36929
37278
|
}
|
|
36930
37279
|
function detectChangeType(filePath) {
|
|
36931
|
-
const basename3 =
|
|
36932
|
-
const ext =
|
|
37280
|
+
const basename3 = path12.basename(filePath, path12.extname(filePath));
|
|
37281
|
+
const ext = path12.extname(filePath);
|
|
36933
37282
|
if (basename3 === "trigger") return "trigger";
|
|
36934
37283
|
if (ext === ".scss" || ext === ".css") return "css-only";
|
|
36935
37284
|
if (ext === ".ts" || ext === ".js") return "js-only";
|
|
36936
37285
|
return "both";
|
|
36937
37286
|
}
|
|
36938
37287
|
|
|
37288
|
+
// src/commands/push.payload.ts
|
|
37289
|
+
function triggerFile(manifest) {
|
|
37290
|
+
return `trigger.${manifest.languages.script}`;
|
|
37291
|
+
}
|
|
37292
|
+
function variationFile(manifest, index, kind) {
|
|
37293
|
+
const variation = manifest.variations[index];
|
|
37294
|
+
if (!variation) return void 0;
|
|
37295
|
+
return path13.posix.join(variation.folder, kind === "script" ? variation.scriptFile : variation.styleFile);
|
|
37296
|
+
}
|
|
37297
|
+
function labelForPushPath(manifest, bodyPath) {
|
|
37298
|
+
const [head, index, field2] = bodyPath;
|
|
37299
|
+
if (head === "triggersJs") return triggerFile(manifest);
|
|
37300
|
+
if (head !== "variations" || typeof index !== "number") return void 0;
|
|
37301
|
+
if (field2 === "controlJs") return variationFile(manifest, index, "script");
|
|
37302
|
+
if (field2 === "controlCss") return variationFile(manifest, index, "style");
|
|
37303
|
+
return manifest.variations[index]?.folder;
|
|
37304
|
+
}
|
|
37305
|
+
function readSource(dir, relativePath) {
|
|
37306
|
+
const absolute = path13.join(dir, relativePath);
|
|
37307
|
+
if (!fs11.existsSync(absolute)) {
|
|
37308
|
+
return {
|
|
37309
|
+
missing: {
|
|
37310
|
+
file: relativePath,
|
|
37311
|
+
line: 0,
|
|
37312
|
+
column: 0,
|
|
37313
|
+
message: "File not found. Restore it, or run `avsb pull --force` to get the platform copy back."
|
|
37314
|
+
}
|
|
37315
|
+
};
|
|
37316
|
+
}
|
|
37317
|
+
return { source: fs11.readFileSync(absolute, "utf-8") };
|
|
37318
|
+
}
|
|
37319
|
+
async function buildPushPayload(dir, manifest) {
|
|
37320
|
+
const scriptLang = manifest.languages.script === "ts" ? "TYPESCRIPT" : "JAVASCRIPT";
|
|
37321
|
+
const styleLang = manifest.languages.style === "scss" ? "SCSS" : "CSS";
|
|
37322
|
+
const triggerPath = triggerFile(manifest);
|
|
37323
|
+
const trigger = readSource(dir, triggerPath);
|
|
37324
|
+
if ("missing" in trigger) return { ok: false, summary: `${triggerPath} is missing`, errors: [trigger.missing] };
|
|
37325
|
+
const triggerCompile = await compileScript(trigger.source, scriptLang, triggerPath);
|
|
37326
|
+
if (!triggerCompile.success) {
|
|
37327
|
+
return { ok: false, summary: "Trigger compilation failed", errors: triggerCompile.errors };
|
|
37328
|
+
}
|
|
37329
|
+
const payload = {
|
|
37330
|
+
triggersJs: { source: trigger.source, compiled: triggerCompile.code },
|
|
37331
|
+
variations: []
|
|
37332
|
+
};
|
|
37333
|
+
const sources = { triggersJs: trigger.source, variations: [] };
|
|
37334
|
+
for (const [index, variation] of manifest.variations.entries()) {
|
|
37335
|
+
const scriptPath = variationFile(manifest, index, "script") ?? variation.scriptFile;
|
|
37336
|
+
const stylePath = variationFile(manifest, index, "style") ?? variation.styleFile;
|
|
37337
|
+
const script = readSource(dir, scriptPath);
|
|
37338
|
+
if ("missing" in script) return { ok: false, summary: `${scriptPath} is missing`, errors: [script.missing] };
|
|
37339
|
+
const style = readSource(dir, stylePath);
|
|
37340
|
+
if ("missing" in style) return { ok: false, summary: `${stylePath} is missing`, errors: [style.missing] };
|
|
37341
|
+
const jsCompile = await compileScript(script.source, scriptLang, scriptPath);
|
|
37342
|
+
if (!jsCompile.success) {
|
|
37343
|
+
return { ok: false, summary: `Variation "${variation.name}" script compilation failed`, errors: jsCompile.errors };
|
|
37344
|
+
}
|
|
37345
|
+
const cssCompile = compileStyle(style.source, styleLang, stylePath);
|
|
37346
|
+
if (!cssCompile.success) {
|
|
37347
|
+
return { ok: false, summary: `Variation "${variation.name}" style compilation failed`, errors: cssCompile.errors };
|
|
37348
|
+
}
|
|
37349
|
+
payload.variations.push({
|
|
37350
|
+
id: variation.id,
|
|
37351
|
+
controlJs: { source: script.source, compiled: jsCompile.code },
|
|
37352
|
+
controlCss: { source: style.source, compiled: cssCompile.code }
|
|
37353
|
+
});
|
|
37354
|
+
sources.variations.push({ id: variation.id, controlJs: script.source, controlCss: style.source });
|
|
37355
|
+
}
|
|
37356
|
+
return { ok: true, payload, sources };
|
|
37357
|
+
}
|
|
37358
|
+
function publishPromptFor(status) {
|
|
37359
|
+
if (status === "PAUSED") {
|
|
37360
|
+
return "The experiment is paused. Publish the changes now? (Visitors see them when it resumes.)";
|
|
37361
|
+
}
|
|
37362
|
+
return "The experiment is running. Publish the changes now?";
|
|
37363
|
+
}
|
|
37364
|
+
|
|
36939
37365
|
// src/commands/push.ts
|
|
36940
37366
|
function sha2563(content) {
|
|
36941
37367
|
return createHash3("sha256").update(content).digest("hex");
|
|
@@ -36960,35 +37386,60 @@ function detectRemoteChanges(remoteExp, syncedHashes) {
|
|
|
36960
37386
|
}
|
|
36961
37387
|
return changed;
|
|
36962
37388
|
}
|
|
36963
|
-
|
|
37389
|
+
async function confirmOverwrite() {
|
|
37390
|
+
if (!isInteractiveRun()) {
|
|
37391
|
+
failCommand(
|
|
37392
|
+
new CliError(
|
|
37393
|
+
"cli_conflict",
|
|
37394
|
+
"The platform copy changed since your last pull, and this run cannot ask before overwriting it.",
|
|
37395
|
+
[
|
|
37396
|
+
"Run `avsb pull` to fetch the remote changes first (it overwrites your local files),",
|
|
37397
|
+
"or `avsb push --force` to overwrite the remote changes."
|
|
37398
|
+
]
|
|
37399
|
+
)
|
|
37400
|
+
);
|
|
37401
|
+
}
|
|
37402
|
+
const { proceed } = await esm_default12.prompt([
|
|
37403
|
+
{ type: "confirm", name: "proceed", message: "Pushing will overwrite remote changes. Continue?", default: false }
|
|
37404
|
+
]);
|
|
37405
|
+
return proceed;
|
|
37406
|
+
}
|
|
37407
|
+
async function shouldPublish(options, status) {
|
|
37408
|
+
if (options.publish !== void 0) return options.publish;
|
|
37409
|
+
if (!isInteractiveRun()) return false;
|
|
37410
|
+
const { publish } = await esm_default12.prompt([
|
|
37411
|
+
{ type: "confirm", name: "publish", message: publishPromptFor(status), default: false }
|
|
37412
|
+
]);
|
|
37413
|
+
return publish;
|
|
37414
|
+
}
|
|
37415
|
+
var pushCommand = new Command("push").description("Compile and push local changes to the platform").option("-f, --force", "Skip conflict check and push without confirmation").option("--publish", "Publish the changes right away when the experiment is running or paused").option("--no-publish", "Leave the changes pending without asking").action(async (options) => {
|
|
37416
|
+
let manifest = null;
|
|
36964
37417
|
try {
|
|
36965
37418
|
const config = ensureLoggedIn();
|
|
36966
|
-
const
|
|
37419
|
+
const read = readManifestOrExit();
|
|
37420
|
+
manifest = read.manifest;
|
|
37421
|
+
const { dir } = read;
|
|
36967
37422
|
let spinner = ora("Compiling experiment...").start();
|
|
36968
|
-
await
|
|
37423
|
+
const built = await buildPushPayload(dir, manifest);
|
|
37424
|
+
if (!built.ok) {
|
|
37425
|
+
spinner.fail(built.summary);
|
|
37426
|
+
for (const e of built.errors) {
|
|
37427
|
+
error(` ${e.line > 0 ? `${e.file}:${e.line}:${e.column}` : e.file}: ${e.message}`);
|
|
37428
|
+
}
|
|
37429
|
+
process.exit(EXIT.failure);
|
|
37430
|
+
}
|
|
36969
37431
|
const client = new ApiClient(config);
|
|
36970
37432
|
if (!options.force && manifest.syncedHashes) {
|
|
36971
37433
|
spinner.text = "Checking for remote changes...";
|
|
36972
37434
|
const remote = await client.getExperiment(manifest.experimentShortId);
|
|
36973
|
-
const
|
|
36974
|
-
const remoteChanges = detectRemoteChanges(remoteExp, manifest.syncedHashes);
|
|
37435
|
+
const remoteChanges = detectRemoteChanges(remote.data.experiment, manifest.syncedHashes);
|
|
36975
37436
|
if (remoteChanges.length > 0) {
|
|
36976
37437
|
spinner.warn("Remote has changes you haven't pulled");
|
|
36977
37438
|
line();
|
|
36978
37439
|
heading("Files changed on remote:");
|
|
36979
|
-
for (const file of remoteChanges) {
|
|
36980
|
-
dim(` ${file}`);
|
|
36981
|
-
}
|
|
37440
|
+
for (const file of remoteChanges) dim(` ${file}`);
|
|
36982
37441
|
line();
|
|
36983
|
-
|
|
36984
|
-
{
|
|
36985
|
-
type: "confirm",
|
|
36986
|
-
name: "proceed",
|
|
36987
|
-
message: "Pushing will overwrite remote changes. Continue?",
|
|
36988
|
-
default: false
|
|
36989
|
-
}
|
|
36990
|
-
]);
|
|
36991
|
-
if (!proceed) {
|
|
37442
|
+
if (!await confirmOverwrite()) {
|
|
36992
37443
|
info("Push aborted. Run `avsb pull` to fetch remote changes first.");
|
|
36993
37444
|
warn("Note: pulling will overwrite your local files.");
|
|
36994
37445
|
process.exit(EXIT.success);
|
|
@@ -37000,78 +37451,37 @@ var pushCommand = new Command("push").description("Compile and push local change
|
|
|
37000
37451
|
} else {
|
|
37001
37452
|
spinner.text = "Pushing to platform...";
|
|
37002
37453
|
}
|
|
37003
|
-
|
|
37004
|
-
|
|
37005
|
-
|
|
37006
|
-
|
|
37007
|
-
"
|
|
37008
|
-
|
|
37009
|
-
const triggerCompile = await compileScript(rawTrigger, scriptLang, `trigger.${manifest.languages.script}`);
|
|
37010
|
-
if (!triggerCompile.success) {
|
|
37011
|
-
spinner.fail("Trigger compilation failed");
|
|
37012
|
-
for (const e of triggerCompile.errors) {
|
|
37013
|
-
error(` trigger.${manifest.languages.script}:${e.line}:${e.column}: ${e.message}`);
|
|
37014
|
-
}
|
|
37015
|
-
process.exit(EXIT.failure);
|
|
37016
|
-
}
|
|
37017
|
-
const rawVariations = [];
|
|
37018
|
-
const compiledVariations = [];
|
|
37019
|
-
for (const v of manifest.variations) {
|
|
37020
|
-
const rawJs = fs10.readFileSync(path12.join(dir, v.folder, v.scriptFile), "utf-8");
|
|
37021
|
-
const rawCss = fs10.readFileSync(path12.join(dir, v.folder, v.styleFile), "utf-8");
|
|
37022
|
-
const jsCompile = await compileScript(rawJs, scriptLang, `${v.name}.${manifest.languages.script}`);
|
|
37023
|
-
const cssCompile = compileStyle(rawCss, styleLang, `${v.name}.${manifest.languages.style}`);
|
|
37024
|
-
if (!jsCompile.success) {
|
|
37025
|
-
spinner.fail(`Variation "${v.name}" JS compilation failed`);
|
|
37026
|
-
for (const e of jsCompile.errors) error(` ${e.file}:${e.line}:${e.column}: ${e.message}`);
|
|
37027
|
-
process.exit(EXIT.failure);
|
|
37028
|
-
}
|
|
37029
|
-
if (!cssCompile.success) {
|
|
37030
|
-
spinner.fail(`Variation "${v.name}" CSS compilation failed`);
|
|
37031
|
-
for (const e of cssCompile.errors) error(` ${e.file}:${e.line}:${e.column}: ${e.message}`);
|
|
37032
|
-
process.exit(EXIT.failure);
|
|
37033
|
-
}
|
|
37034
|
-
rawVariations.push({ id: v.id, controlJs: rawJs, controlCss: rawCss });
|
|
37035
|
-
compiledVariations.push({
|
|
37036
|
-
id: v.id,
|
|
37037
|
-
controlJs: { source: rawJs, compiled: jsCompile.code },
|
|
37038
|
-
controlCss: { source: rawCss, compiled: cssCompile.code }
|
|
37039
|
-
});
|
|
37454
|
+
let response;
|
|
37455
|
+
try {
|
|
37456
|
+
response = await client.pushExperiment(manifest.experimentShortId, built.payload);
|
|
37457
|
+
} catch (err) {
|
|
37458
|
+
spinner.fail("Push refused");
|
|
37459
|
+
throw err;
|
|
37040
37460
|
}
|
|
37041
|
-
|
|
37042
|
-
triggersJs: { source: rawTrigger, compiled: triggerCompile.code },
|
|
37043
|
-
variations: compiledVariations
|
|
37044
|
-
});
|
|
37045
|
-
manifest.syncedHashes = computeSyncHashes(rawTrigger, rawVariations);
|
|
37461
|
+
manifest.syncedHashes = computeSyncHashes(built.sources.triggersJs, built.sources.variations);
|
|
37046
37462
|
writeManifest(dir, manifest);
|
|
37047
37463
|
spinner.succeed("Pushed successfully");
|
|
37048
37464
|
line();
|
|
37049
37465
|
if (response.data.hasPendingChanges) {
|
|
37050
|
-
|
|
37051
|
-
{
|
|
37052
|
-
type: "confirm",
|
|
37053
|
-
name: "publish",
|
|
37054
|
-
message: "Experiment is running. Publish changes now?",
|
|
37055
|
-
default: false
|
|
37056
|
-
}
|
|
37057
|
-
]);
|
|
37058
|
-
if (publish) {
|
|
37466
|
+
if (await shouldPublish(options, response.data.status)) {
|
|
37059
37467
|
const publishSpinner = ora("Publishing changes...").start();
|
|
37060
|
-
await client.publishChanges(manifest.experimentShortId);
|
|
37468
|
+
const published = await client.publishChanges(manifest.experimentShortId);
|
|
37061
37469
|
publishSpinner.succeed("Changes published");
|
|
37470
|
+
if (published.data.warning) warn(published.data.warning.message);
|
|
37062
37471
|
} else {
|
|
37063
|
-
info("Changes saved as pending. Publish from the
|
|
37472
|
+
info("Changes saved as pending. Publish them with `avsb push --publish`, or from the dashboard.");
|
|
37064
37473
|
}
|
|
37065
37474
|
}
|
|
37066
37475
|
line();
|
|
37067
37476
|
success("Push complete");
|
|
37068
37477
|
} catch (err) {
|
|
37069
|
-
|
|
37478
|
+
const current = manifest;
|
|
37479
|
+
failCommand(err, current ? { labelForPath: (bodyPath) => labelForPushPath(current, bodyPath) } : {});
|
|
37070
37480
|
}
|
|
37071
37481
|
});
|
|
37072
37482
|
|
|
37073
37483
|
// src/commands/dev.ts
|
|
37074
|
-
import
|
|
37484
|
+
import path15 from "node:path";
|
|
37075
37485
|
|
|
37076
37486
|
// node_modules/chokidar/esm/index.js
|
|
37077
37487
|
import { stat as statcb } from "fs";
|
|
@@ -37149,7 +37559,7 @@ var ReaddirpStream = class extends Readable {
|
|
|
37149
37559
|
this._directoryFilter = normalizeFilter(opts.directoryFilter);
|
|
37150
37560
|
const statMethod = opts.lstat ? lstat : stat;
|
|
37151
37561
|
if (wantBigintFsStats) {
|
|
37152
|
-
this._stat = (
|
|
37562
|
+
this._stat = (path23) => statMethod(path23, { bigint: true });
|
|
37153
37563
|
} else {
|
|
37154
37564
|
this._stat = statMethod;
|
|
37155
37565
|
}
|
|
@@ -37174,8 +37584,8 @@ var ReaddirpStream = class extends Readable {
|
|
|
37174
37584
|
const par = this.parent;
|
|
37175
37585
|
const fil = par && par.files;
|
|
37176
37586
|
if (fil && fil.length > 0) {
|
|
37177
|
-
const { path:
|
|
37178
|
-
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent,
|
|
37587
|
+
const { path: path23, depth } = par;
|
|
37588
|
+
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path23));
|
|
37179
37589
|
const awaited = await Promise.all(slice);
|
|
37180
37590
|
for (const entry of awaited) {
|
|
37181
37591
|
if (!entry)
|
|
@@ -37215,20 +37625,20 @@ var ReaddirpStream = class extends Readable {
|
|
|
37215
37625
|
this.reading = false;
|
|
37216
37626
|
}
|
|
37217
37627
|
}
|
|
37218
|
-
async _exploreDir(
|
|
37628
|
+
async _exploreDir(path23, depth) {
|
|
37219
37629
|
let files;
|
|
37220
37630
|
try {
|
|
37221
|
-
files = await readdir(
|
|
37631
|
+
files = await readdir(path23, this._rdOptions);
|
|
37222
37632
|
} catch (error2) {
|
|
37223
37633
|
this._onError(error2);
|
|
37224
37634
|
}
|
|
37225
|
-
return { files, depth, path:
|
|
37635
|
+
return { files, depth, path: path23 };
|
|
37226
37636
|
}
|
|
37227
|
-
async _formatEntry(dirent,
|
|
37637
|
+
async _formatEntry(dirent, path23) {
|
|
37228
37638
|
let entry;
|
|
37229
37639
|
const basename3 = this._isDirent ? dirent.name : dirent;
|
|
37230
37640
|
try {
|
|
37231
|
-
const fullPath = presolve(pjoin(
|
|
37641
|
+
const fullPath = presolve(pjoin(path23, basename3));
|
|
37232
37642
|
entry = { path: prelative(this._root, fullPath), fullPath, basename: basename3 };
|
|
37233
37643
|
entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
|
|
37234
37644
|
} catch (err) {
|
|
@@ -37628,16 +38038,16 @@ var delFromSet = (main3, prop, item) => {
|
|
|
37628
38038
|
};
|
|
37629
38039
|
var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
|
|
37630
38040
|
var FsWatchInstances = /* @__PURE__ */ new Map();
|
|
37631
|
-
function createFsWatchInstance(
|
|
38041
|
+
function createFsWatchInstance(path23, options, listener, errHandler, emitRaw) {
|
|
37632
38042
|
const handleEvent = (rawEvent, evPath) => {
|
|
37633
|
-
listener(
|
|
37634
|
-
emitRaw(rawEvent, evPath, { watchedPath:
|
|
37635
|
-
if (evPath &&
|
|
37636
|
-
fsWatchBroadcast(sysPath.resolve(
|
|
38043
|
+
listener(path23);
|
|
38044
|
+
emitRaw(rawEvent, evPath, { watchedPath: path23 });
|
|
38045
|
+
if (evPath && path23 !== evPath) {
|
|
38046
|
+
fsWatchBroadcast(sysPath.resolve(path23, evPath), KEY_LISTENERS, sysPath.join(path23, evPath));
|
|
37637
38047
|
}
|
|
37638
38048
|
};
|
|
37639
38049
|
try {
|
|
37640
|
-
return fs_watch(
|
|
38050
|
+
return fs_watch(path23, {
|
|
37641
38051
|
persistent: options.persistent
|
|
37642
38052
|
}, handleEvent);
|
|
37643
38053
|
} catch (error2) {
|
|
@@ -37653,12 +38063,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
|
|
|
37653
38063
|
listener(val1, val2, val3);
|
|
37654
38064
|
});
|
|
37655
38065
|
};
|
|
37656
|
-
var setFsWatchListener = (
|
|
38066
|
+
var setFsWatchListener = (path23, fullPath, options, handlers) => {
|
|
37657
38067
|
const { listener, errHandler, rawEmitter } = handlers;
|
|
37658
38068
|
let cont = FsWatchInstances.get(fullPath);
|
|
37659
38069
|
let watcher;
|
|
37660
38070
|
if (!options.persistent) {
|
|
37661
|
-
watcher = createFsWatchInstance(
|
|
38071
|
+
watcher = createFsWatchInstance(path23, options, listener, errHandler, rawEmitter);
|
|
37662
38072
|
if (!watcher)
|
|
37663
38073
|
return;
|
|
37664
38074
|
return watcher.close.bind(watcher);
|
|
@@ -37669,7 +38079,7 @@ var setFsWatchListener = (path22, fullPath, options, handlers) => {
|
|
|
37669
38079
|
addAndConvert(cont, KEY_RAW, rawEmitter);
|
|
37670
38080
|
} else {
|
|
37671
38081
|
watcher = createFsWatchInstance(
|
|
37672
|
-
|
|
38082
|
+
path23,
|
|
37673
38083
|
options,
|
|
37674
38084
|
fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
|
|
37675
38085
|
errHandler,
|
|
@@ -37684,7 +38094,7 @@ var setFsWatchListener = (path22, fullPath, options, handlers) => {
|
|
|
37684
38094
|
cont.watcherUnusable = true;
|
|
37685
38095
|
if (isWindows && error2.code === "EPERM") {
|
|
37686
38096
|
try {
|
|
37687
|
-
const fd = await open(
|
|
38097
|
+
const fd = await open(path23, "r");
|
|
37688
38098
|
await fd.close();
|
|
37689
38099
|
broadcastErr(error2);
|
|
37690
38100
|
} catch (err) {
|
|
@@ -37715,7 +38125,7 @@ var setFsWatchListener = (path22, fullPath, options, handlers) => {
|
|
|
37715
38125
|
};
|
|
37716
38126
|
};
|
|
37717
38127
|
var FsWatchFileInstances = /* @__PURE__ */ new Map();
|
|
37718
|
-
var setFsWatchFileListener = (
|
|
38128
|
+
var setFsWatchFileListener = (path23, fullPath, options, handlers) => {
|
|
37719
38129
|
const { listener, rawEmitter } = handlers;
|
|
37720
38130
|
let cont = FsWatchFileInstances.get(fullPath);
|
|
37721
38131
|
const copts = cont && cont.options;
|
|
@@ -37737,7 +38147,7 @@ var setFsWatchFileListener = (path22, fullPath, options, handlers) => {
|
|
|
37737
38147
|
});
|
|
37738
38148
|
const currmtime = curr.mtimeMs;
|
|
37739
38149
|
if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
|
|
37740
|
-
foreach(cont.listeners, (listener2) => listener2(
|
|
38150
|
+
foreach(cont.listeners, (listener2) => listener2(path23, curr));
|
|
37741
38151
|
}
|
|
37742
38152
|
})
|
|
37743
38153
|
};
|
|
@@ -37765,13 +38175,13 @@ var NodeFsHandler = class {
|
|
|
37765
38175
|
* @param listener on fs change
|
|
37766
38176
|
* @returns closer for the watcher instance
|
|
37767
38177
|
*/
|
|
37768
|
-
_watchWithNodeFs(
|
|
38178
|
+
_watchWithNodeFs(path23, listener) {
|
|
37769
38179
|
const opts = this.fsw.options;
|
|
37770
|
-
const directory = sysPath.dirname(
|
|
37771
|
-
const basename3 = sysPath.basename(
|
|
38180
|
+
const directory = sysPath.dirname(path23);
|
|
38181
|
+
const basename3 = sysPath.basename(path23);
|
|
37772
38182
|
const parent = this.fsw._getWatchedDir(directory);
|
|
37773
38183
|
parent.add(basename3);
|
|
37774
|
-
const absolutePath = sysPath.resolve(
|
|
38184
|
+
const absolutePath = sysPath.resolve(path23);
|
|
37775
38185
|
const options = {
|
|
37776
38186
|
persistent: opts.persistent
|
|
37777
38187
|
};
|
|
@@ -37781,12 +38191,12 @@ var NodeFsHandler = class {
|
|
|
37781
38191
|
if (opts.usePolling) {
|
|
37782
38192
|
const enableBin = opts.interval !== opts.binaryInterval;
|
|
37783
38193
|
options.interval = enableBin && isBinaryPath(basename3) ? opts.binaryInterval : opts.interval;
|
|
37784
|
-
closer = setFsWatchFileListener(
|
|
38194
|
+
closer = setFsWatchFileListener(path23, absolutePath, options, {
|
|
37785
38195
|
listener,
|
|
37786
38196
|
rawEmitter: this.fsw._emitRaw
|
|
37787
38197
|
});
|
|
37788
38198
|
} else {
|
|
37789
|
-
closer = setFsWatchListener(
|
|
38199
|
+
closer = setFsWatchListener(path23, absolutePath, options, {
|
|
37790
38200
|
listener,
|
|
37791
38201
|
errHandler: this._boundHandleError,
|
|
37792
38202
|
rawEmitter: this.fsw._emitRaw
|
|
@@ -37808,7 +38218,7 @@ var NodeFsHandler = class {
|
|
|
37808
38218
|
let prevStats = stats;
|
|
37809
38219
|
if (parent.has(basename3))
|
|
37810
38220
|
return;
|
|
37811
|
-
const listener = async (
|
|
38221
|
+
const listener = async (path23, newStats) => {
|
|
37812
38222
|
if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
|
|
37813
38223
|
return;
|
|
37814
38224
|
if (!newStats || newStats.mtimeMs === 0) {
|
|
@@ -37822,11 +38232,11 @@ var NodeFsHandler = class {
|
|
|
37822
38232
|
this.fsw._emit(EV.CHANGE, file, newStats2);
|
|
37823
38233
|
}
|
|
37824
38234
|
if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
|
|
37825
|
-
this.fsw._closeFile(
|
|
38235
|
+
this.fsw._closeFile(path23);
|
|
37826
38236
|
prevStats = newStats2;
|
|
37827
38237
|
const closer2 = this._watchWithNodeFs(file, listener);
|
|
37828
38238
|
if (closer2)
|
|
37829
|
-
this.fsw._addPathCloser(
|
|
38239
|
+
this.fsw._addPathCloser(path23, closer2);
|
|
37830
38240
|
} else {
|
|
37831
38241
|
prevStats = newStats2;
|
|
37832
38242
|
}
|
|
@@ -37858,7 +38268,7 @@ var NodeFsHandler = class {
|
|
|
37858
38268
|
* @param item basename of this item
|
|
37859
38269
|
* @returns true if no more processing is needed for this entry.
|
|
37860
38270
|
*/
|
|
37861
|
-
async _handleSymlink(entry, directory,
|
|
38271
|
+
async _handleSymlink(entry, directory, path23, item) {
|
|
37862
38272
|
if (this.fsw.closed) {
|
|
37863
38273
|
return;
|
|
37864
38274
|
}
|
|
@@ -37868,7 +38278,7 @@ var NodeFsHandler = class {
|
|
|
37868
38278
|
this.fsw._incrReadyCount();
|
|
37869
38279
|
let linkPath;
|
|
37870
38280
|
try {
|
|
37871
|
-
linkPath = await fsrealpath(
|
|
38281
|
+
linkPath = await fsrealpath(path23);
|
|
37872
38282
|
} catch (e) {
|
|
37873
38283
|
this.fsw._emitReady();
|
|
37874
38284
|
return true;
|
|
@@ -37878,12 +38288,12 @@ var NodeFsHandler = class {
|
|
|
37878
38288
|
if (dir.has(item)) {
|
|
37879
38289
|
if (this.fsw._symlinkPaths.get(full) !== linkPath) {
|
|
37880
38290
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
37881
|
-
this.fsw._emit(EV.CHANGE,
|
|
38291
|
+
this.fsw._emit(EV.CHANGE, path23, entry.stats);
|
|
37882
38292
|
}
|
|
37883
38293
|
} else {
|
|
37884
38294
|
dir.add(item);
|
|
37885
38295
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
37886
|
-
this.fsw._emit(EV.ADD,
|
|
38296
|
+
this.fsw._emit(EV.ADD, path23, entry.stats);
|
|
37887
38297
|
}
|
|
37888
38298
|
this.fsw._emitReady();
|
|
37889
38299
|
return true;
|
|
@@ -37912,9 +38322,9 @@ var NodeFsHandler = class {
|
|
|
37912
38322
|
return;
|
|
37913
38323
|
}
|
|
37914
38324
|
const item = entry.path;
|
|
37915
|
-
let
|
|
38325
|
+
let path23 = sysPath.join(directory, item);
|
|
37916
38326
|
current.add(item);
|
|
37917
|
-
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory,
|
|
38327
|
+
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path23, item)) {
|
|
37918
38328
|
return;
|
|
37919
38329
|
}
|
|
37920
38330
|
if (this.fsw.closed) {
|
|
@@ -37923,8 +38333,8 @@ var NodeFsHandler = class {
|
|
|
37923
38333
|
}
|
|
37924
38334
|
if (item === target || !target && !previous.has(item)) {
|
|
37925
38335
|
this.fsw._incrReadyCount();
|
|
37926
|
-
|
|
37927
|
-
this._addToNodeFs(
|
|
38336
|
+
path23 = sysPath.join(dir, sysPath.relative(dir, path23));
|
|
38337
|
+
this._addToNodeFs(path23, initialAdd, wh, depth + 1);
|
|
37928
38338
|
}
|
|
37929
38339
|
}).on(EV.ERROR, this._boundHandleError);
|
|
37930
38340
|
return new Promise((resolve3, reject) => {
|
|
@@ -37993,13 +38403,13 @@ var NodeFsHandler = class {
|
|
|
37993
38403
|
* @param depth Child path actually targeted for watch
|
|
37994
38404
|
* @param target Child path actually targeted for watch
|
|
37995
38405
|
*/
|
|
37996
|
-
async _addToNodeFs(
|
|
38406
|
+
async _addToNodeFs(path23, initialAdd, priorWh, depth, target) {
|
|
37997
38407
|
const ready = this.fsw._emitReady;
|
|
37998
|
-
if (this.fsw._isIgnored(
|
|
38408
|
+
if (this.fsw._isIgnored(path23) || this.fsw.closed) {
|
|
37999
38409
|
ready();
|
|
38000
38410
|
return false;
|
|
38001
38411
|
}
|
|
38002
|
-
const wh = this.fsw._getWatchHelpers(
|
|
38412
|
+
const wh = this.fsw._getWatchHelpers(path23);
|
|
38003
38413
|
if (priorWh) {
|
|
38004
38414
|
wh.filterPath = (entry) => priorWh.filterPath(entry);
|
|
38005
38415
|
wh.filterDir = (entry) => priorWh.filterDir(entry);
|
|
@@ -38015,8 +38425,8 @@ var NodeFsHandler = class {
|
|
|
38015
38425
|
const follow = this.fsw.options.followSymlinks;
|
|
38016
38426
|
let closer;
|
|
38017
38427
|
if (stats.isDirectory()) {
|
|
38018
|
-
const absPath = sysPath.resolve(
|
|
38019
|
-
const targetPath = follow ? await fsrealpath(
|
|
38428
|
+
const absPath = sysPath.resolve(path23);
|
|
38429
|
+
const targetPath = follow ? await fsrealpath(path23) : path23;
|
|
38020
38430
|
if (this.fsw.closed)
|
|
38021
38431
|
return;
|
|
38022
38432
|
closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
|
|
@@ -38026,29 +38436,29 @@ var NodeFsHandler = class {
|
|
|
38026
38436
|
this.fsw._symlinkPaths.set(absPath, targetPath);
|
|
38027
38437
|
}
|
|
38028
38438
|
} else if (stats.isSymbolicLink()) {
|
|
38029
|
-
const targetPath = follow ? await fsrealpath(
|
|
38439
|
+
const targetPath = follow ? await fsrealpath(path23) : path23;
|
|
38030
38440
|
if (this.fsw.closed)
|
|
38031
38441
|
return;
|
|
38032
38442
|
const parent = sysPath.dirname(wh.watchPath);
|
|
38033
38443
|
this.fsw._getWatchedDir(parent).add(wh.watchPath);
|
|
38034
38444
|
this.fsw._emit(EV.ADD, wh.watchPath, stats);
|
|
38035
|
-
closer = await this._handleDir(parent, stats, initialAdd, depth,
|
|
38445
|
+
closer = await this._handleDir(parent, stats, initialAdd, depth, path23, wh, targetPath);
|
|
38036
38446
|
if (this.fsw.closed)
|
|
38037
38447
|
return;
|
|
38038
38448
|
if (targetPath !== void 0) {
|
|
38039
|
-
this.fsw._symlinkPaths.set(sysPath.resolve(
|
|
38449
|
+
this.fsw._symlinkPaths.set(sysPath.resolve(path23), targetPath);
|
|
38040
38450
|
}
|
|
38041
38451
|
} else {
|
|
38042
38452
|
closer = this._handleFile(wh.watchPath, stats, initialAdd);
|
|
38043
38453
|
}
|
|
38044
38454
|
ready();
|
|
38045
38455
|
if (closer)
|
|
38046
|
-
this.fsw._addPathCloser(
|
|
38456
|
+
this.fsw._addPathCloser(path23, closer);
|
|
38047
38457
|
return false;
|
|
38048
38458
|
} catch (error2) {
|
|
38049
38459
|
if (this.fsw._handleError(error2)) {
|
|
38050
38460
|
ready();
|
|
38051
|
-
return
|
|
38461
|
+
return path23;
|
|
38052
38462
|
}
|
|
38053
38463
|
}
|
|
38054
38464
|
}
|
|
@@ -38091,26 +38501,26 @@ function createPattern(matcher) {
|
|
|
38091
38501
|
}
|
|
38092
38502
|
return () => false;
|
|
38093
38503
|
}
|
|
38094
|
-
function normalizePath(
|
|
38095
|
-
if (typeof
|
|
38504
|
+
function normalizePath(path23) {
|
|
38505
|
+
if (typeof path23 !== "string")
|
|
38096
38506
|
throw new Error("string expected");
|
|
38097
|
-
|
|
38098
|
-
|
|
38507
|
+
path23 = sysPath2.normalize(path23);
|
|
38508
|
+
path23 = path23.replace(/\\/g, "/");
|
|
38099
38509
|
let prepend = false;
|
|
38100
|
-
if (
|
|
38510
|
+
if (path23.startsWith("//"))
|
|
38101
38511
|
prepend = true;
|
|
38102
38512
|
const DOUBLE_SLASH_RE2 = /\/\//;
|
|
38103
|
-
while (
|
|
38104
|
-
|
|
38513
|
+
while (path23.match(DOUBLE_SLASH_RE2))
|
|
38514
|
+
path23 = path23.replace(DOUBLE_SLASH_RE2, "/");
|
|
38105
38515
|
if (prepend)
|
|
38106
|
-
|
|
38107
|
-
return
|
|
38516
|
+
path23 = "/" + path23;
|
|
38517
|
+
return path23;
|
|
38108
38518
|
}
|
|
38109
38519
|
function matchPatterns(patterns, testString, stats) {
|
|
38110
|
-
const
|
|
38520
|
+
const path23 = normalizePath(testString);
|
|
38111
38521
|
for (let index = 0; index < patterns.length; index++) {
|
|
38112
38522
|
const pattern = patterns[index];
|
|
38113
|
-
if (pattern(
|
|
38523
|
+
if (pattern(path23, stats)) {
|
|
38114
38524
|
return true;
|
|
38115
38525
|
}
|
|
38116
38526
|
}
|
|
@@ -38150,19 +38560,19 @@ var toUnix = (string) => {
|
|
|
38150
38560
|
}
|
|
38151
38561
|
return str;
|
|
38152
38562
|
};
|
|
38153
|
-
var normalizePathToUnix = (
|
|
38154
|
-
var normalizeIgnored = (cwd = "") => (
|
|
38155
|
-
if (typeof
|
|
38156
|
-
return normalizePathToUnix(sysPath2.isAbsolute(
|
|
38563
|
+
var normalizePathToUnix = (path23) => toUnix(sysPath2.normalize(toUnix(path23)));
|
|
38564
|
+
var normalizeIgnored = (cwd = "") => (path23) => {
|
|
38565
|
+
if (typeof path23 === "string") {
|
|
38566
|
+
return normalizePathToUnix(sysPath2.isAbsolute(path23) ? path23 : sysPath2.join(cwd, path23));
|
|
38157
38567
|
} else {
|
|
38158
|
-
return
|
|
38568
|
+
return path23;
|
|
38159
38569
|
}
|
|
38160
38570
|
};
|
|
38161
|
-
var getAbsolutePath = (
|
|
38162
|
-
if (sysPath2.isAbsolute(
|
|
38163
|
-
return
|
|
38571
|
+
var getAbsolutePath = (path23, cwd) => {
|
|
38572
|
+
if (sysPath2.isAbsolute(path23)) {
|
|
38573
|
+
return path23;
|
|
38164
38574
|
}
|
|
38165
|
-
return sysPath2.join(cwd,
|
|
38575
|
+
return sysPath2.join(cwd, path23);
|
|
38166
38576
|
};
|
|
38167
38577
|
var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
|
|
38168
38578
|
var DirEntry = class {
|
|
@@ -38217,10 +38627,10 @@ var DirEntry = class {
|
|
|
38217
38627
|
var STAT_METHOD_F = "stat";
|
|
38218
38628
|
var STAT_METHOD_L = "lstat";
|
|
38219
38629
|
var WatchHelper = class {
|
|
38220
|
-
constructor(
|
|
38630
|
+
constructor(path23, follow, fsw) {
|
|
38221
38631
|
this.fsw = fsw;
|
|
38222
|
-
const watchPath =
|
|
38223
|
-
this.path =
|
|
38632
|
+
const watchPath = path23;
|
|
38633
|
+
this.path = path23 = path23.replace(REPLACER_RE, "");
|
|
38224
38634
|
this.watchPath = watchPath;
|
|
38225
38635
|
this.fullWatchPath = sysPath2.resolve(watchPath);
|
|
38226
38636
|
this.dirParts = [];
|
|
@@ -38342,20 +38752,20 @@ var FSWatcher = class extends EventEmitter {
|
|
|
38342
38752
|
this._closePromise = void 0;
|
|
38343
38753
|
let paths = unifyPaths(paths_);
|
|
38344
38754
|
if (cwd) {
|
|
38345
|
-
paths = paths.map((
|
|
38346
|
-
const absPath = getAbsolutePath(
|
|
38755
|
+
paths = paths.map((path23) => {
|
|
38756
|
+
const absPath = getAbsolutePath(path23, cwd);
|
|
38347
38757
|
return absPath;
|
|
38348
38758
|
});
|
|
38349
38759
|
}
|
|
38350
|
-
paths.forEach((
|
|
38351
|
-
this._removeIgnoredPath(
|
|
38760
|
+
paths.forEach((path23) => {
|
|
38761
|
+
this._removeIgnoredPath(path23);
|
|
38352
38762
|
});
|
|
38353
38763
|
this._userIgnored = void 0;
|
|
38354
38764
|
if (!this._readyCount)
|
|
38355
38765
|
this._readyCount = 0;
|
|
38356
38766
|
this._readyCount += paths.length;
|
|
38357
|
-
Promise.all(paths.map(async (
|
|
38358
|
-
const res = await this._nodeFsHandler._addToNodeFs(
|
|
38767
|
+
Promise.all(paths.map(async (path23) => {
|
|
38768
|
+
const res = await this._nodeFsHandler._addToNodeFs(path23, !_internal, void 0, 0, _origAdd);
|
|
38359
38769
|
if (res)
|
|
38360
38770
|
this._emitReady();
|
|
38361
38771
|
return res;
|
|
@@ -38377,17 +38787,17 @@ var FSWatcher = class extends EventEmitter {
|
|
|
38377
38787
|
return this;
|
|
38378
38788
|
const paths = unifyPaths(paths_);
|
|
38379
38789
|
const { cwd } = this.options;
|
|
38380
|
-
paths.forEach((
|
|
38381
|
-
if (!sysPath2.isAbsolute(
|
|
38790
|
+
paths.forEach((path23) => {
|
|
38791
|
+
if (!sysPath2.isAbsolute(path23) && !this._closers.has(path23)) {
|
|
38382
38792
|
if (cwd)
|
|
38383
|
-
|
|
38384
|
-
|
|
38793
|
+
path23 = sysPath2.join(cwd, path23);
|
|
38794
|
+
path23 = sysPath2.resolve(path23);
|
|
38385
38795
|
}
|
|
38386
|
-
this._closePath(
|
|
38387
|
-
this._addIgnoredPath(
|
|
38388
|
-
if (this._watched.has(
|
|
38796
|
+
this._closePath(path23);
|
|
38797
|
+
this._addIgnoredPath(path23);
|
|
38798
|
+
if (this._watched.has(path23)) {
|
|
38389
38799
|
this._addIgnoredPath({
|
|
38390
|
-
path:
|
|
38800
|
+
path: path23,
|
|
38391
38801
|
recursive: true
|
|
38392
38802
|
});
|
|
38393
38803
|
}
|
|
@@ -38451,38 +38861,38 @@ var FSWatcher = class extends EventEmitter {
|
|
|
38451
38861
|
* @param stats arguments to be passed with event
|
|
38452
38862
|
* @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
|
|
38453
38863
|
*/
|
|
38454
|
-
async _emit(event,
|
|
38864
|
+
async _emit(event, path23, stats) {
|
|
38455
38865
|
if (this.closed)
|
|
38456
38866
|
return;
|
|
38457
38867
|
const opts = this.options;
|
|
38458
38868
|
if (isWindows)
|
|
38459
|
-
|
|
38869
|
+
path23 = sysPath2.normalize(path23);
|
|
38460
38870
|
if (opts.cwd)
|
|
38461
|
-
|
|
38462
|
-
const args = [
|
|
38871
|
+
path23 = sysPath2.relative(opts.cwd, path23);
|
|
38872
|
+
const args = [path23];
|
|
38463
38873
|
if (stats != null)
|
|
38464
38874
|
args.push(stats);
|
|
38465
38875
|
const awf = opts.awaitWriteFinish;
|
|
38466
38876
|
let pw;
|
|
38467
|
-
if (awf && (pw = this._pendingWrites.get(
|
|
38877
|
+
if (awf && (pw = this._pendingWrites.get(path23))) {
|
|
38468
38878
|
pw.lastChange = /* @__PURE__ */ new Date();
|
|
38469
38879
|
return this;
|
|
38470
38880
|
}
|
|
38471
38881
|
if (opts.atomic) {
|
|
38472
38882
|
if (event === EVENTS.UNLINK) {
|
|
38473
|
-
this._pendingUnlinks.set(
|
|
38883
|
+
this._pendingUnlinks.set(path23, [event, ...args]);
|
|
38474
38884
|
setTimeout(() => {
|
|
38475
|
-
this._pendingUnlinks.forEach((entry,
|
|
38885
|
+
this._pendingUnlinks.forEach((entry, path24) => {
|
|
38476
38886
|
this.emit(...entry);
|
|
38477
38887
|
this.emit(EVENTS.ALL, ...entry);
|
|
38478
|
-
this._pendingUnlinks.delete(
|
|
38888
|
+
this._pendingUnlinks.delete(path24);
|
|
38479
38889
|
});
|
|
38480
38890
|
}, typeof opts.atomic === "number" ? opts.atomic : 100);
|
|
38481
38891
|
return this;
|
|
38482
38892
|
}
|
|
38483
|
-
if (event === EVENTS.ADD && this._pendingUnlinks.has(
|
|
38893
|
+
if (event === EVENTS.ADD && this._pendingUnlinks.has(path23)) {
|
|
38484
38894
|
event = EVENTS.CHANGE;
|
|
38485
|
-
this._pendingUnlinks.delete(
|
|
38895
|
+
this._pendingUnlinks.delete(path23);
|
|
38486
38896
|
}
|
|
38487
38897
|
}
|
|
38488
38898
|
if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
|
|
@@ -38500,16 +38910,16 @@ var FSWatcher = class extends EventEmitter {
|
|
|
38500
38910
|
this.emitWithAll(event, args);
|
|
38501
38911
|
}
|
|
38502
38912
|
};
|
|
38503
|
-
this._awaitWriteFinish(
|
|
38913
|
+
this._awaitWriteFinish(path23, awf.stabilityThreshold, event, awfEmit);
|
|
38504
38914
|
return this;
|
|
38505
38915
|
}
|
|
38506
38916
|
if (event === EVENTS.CHANGE) {
|
|
38507
|
-
const isThrottled = !this._throttle(EVENTS.CHANGE,
|
|
38917
|
+
const isThrottled = !this._throttle(EVENTS.CHANGE, path23, 50);
|
|
38508
38918
|
if (isThrottled)
|
|
38509
38919
|
return this;
|
|
38510
38920
|
}
|
|
38511
38921
|
if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
|
|
38512
|
-
const fullPath = opts.cwd ? sysPath2.join(opts.cwd,
|
|
38922
|
+
const fullPath = opts.cwd ? sysPath2.join(opts.cwd, path23) : path23;
|
|
38513
38923
|
let stats2;
|
|
38514
38924
|
try {
|
|
38515
38925
|
stats2 = await stat3(fullPath);
|
|
@@ -38540,23 +38950,23 @@ var FSWatcher = class extends EventEmitter {
|
|
|
38540
38950
|
* @param timeout duration of time to suppress duplicate actions
|
|
38541
38951
|
* @returns tracking object or false if action should be suppressed
|
|
38542
38952
|
*/
|
|
38543
|
-
_throttle(actionType,
|
|
38953
|
+
_throttle(actionType, path23, timeout) {
|
|
38544
38954
|
if (!this._throttled.has(actionType)) {
|
|
38545
38955
|
this._throttled.set(actionType, /* @__PURE__ */ new Map());
|
|
38546
38956
|
}
|
|
38547
38957
|
const action = this._throttled.get(actionType);
|
|
38548
38958
|
if (!action)
|
|
38549
38959
|
throw new Error("invalid throttle");
|
|
38550
|
-
const actionPath = action.get(
|
|
38960
|
+
const actionPath = action.get(path23);
|
|
38551
38961
|
if (actionPath) {
|
|
38552
38962
|
actionPath.count++;
|
|
38553
38963
|
return false;
|
|
38554
38964
|
}
|
|
38555
38965
|
let timeoutObject;
|
|
38556
38966
|
const clear = () => {
|
|
38557
|
-
const item = action.get(
|
|
38967
|
+
const item = action.get(path23);
|
|
38558
38968
|
const count = item ? item.count : 0;
|
|
38559
|
-
action.delete(
|
|
38969
|
+
action.delete(path23);
|
|
38560
38970
|
clearTimeout(timeoutObject);
|
|
38561
38971
|
if (item)
|
|
38562
38972
|
clearTimeout(item.timeoutObject);
|
|
@@ -38564,7 +38974,7 @@ var FSWatcher = class extends EventEmitter {
|
|
|
38564
38974
|
};
|
|
38565
38975
|
timeoutObject = setTimeout(clear, timeout);
|
|
38566
38976
|
const thr = { timeoutObject, clear, count: 0 };
|
|
38567
|
-
action.set(
|
|
38977
|
+
action.set(path23, thr);
|
|
38568
38978
|
return thr;
|
|
38569
38979
|
}
|
|
38570
38980
|
_incrReadyCount() {
|
|
@@ -38578,44 +38988,44 @@ var FSWatcher = class extends EventEmitter {
|
|
|
38578
38988
|
* @param event
|
|
38579
38989
|
* @param awfEmit Callback to be called when ready for event to be emitted.
|
|
38580
38990
|
*/
|
|
38581
|
-
_awaitWriteFinish(
|
|
38991
|
+
_awaitWriteFinish(path23, threshold, event, awfEmit) {
|
|
38582
38992
|
const awf = this.options.awaitWriteFinish;
|
|
38583
38993
|
if (typeof awf !== "object")
|
|
38584
38994
|
return;
|
|
38585
38995
|
const pollInterval = awf.pollInterval;
|
|
38586
38996
|
let timeoutHandler;
|
|
38587
|
-
let fullPath =
|
|
38588
|
-
if (this.options.cwd && !sysPath2.isAbsolute(
|
|
38589
|
-
fullPath = sysPath2.join(this.options.cwd,
|
|
38997
|
+
let fullPath = path23;
|
|
38998
|
+
if (this.options.cwd && !sysPath2.isAbsolute(path23)) {
|
|
38999
|
+
fullPath = sysPath2.join(this.options.cwd, path23);
|
|
38590
39000
|
}
|
|
38591
39001
|
const now = /* @__PURE__ */ new Date();
|
|
38592
39002
|
const writes = this._pendingWrites;
|
|
38593
39003
|
function awaitWriteFinishFn(prevStat) {
|
|
38594
39004
|
statcb(fullPath, (err, curStat) => {
|
|
38595
|
-
if (err || !writes.has(
|
|
39005
|
+
if (err || !writes.has(path23)) {
|
|
38596
39006
|
if (err && err.code !== "ENOENT")
|
|
38597
39007
|
awfEmit(err);
|
|
38598
39008
|
return;
|
|
38599
39009
|
}
|
|
38600
39010
|
const now2 = Number(/* @__PURE__ */ new Date());
|
|
38601
39011
|
if (prevStat && curStat.size !== prevStat.size) {
|
|
38602
|
-
writes.get(
|
|
39012
|
+
writes.get(path23).lastChange = now2;
|
|
38603
39013
|
}
|
|
38604
|
-
const pw = writes.get(
|
|
39014
|
+
const pw = writes.get(path23);
|
|
38605
39015
|
const df = now2 - pw.lastChange;
|
|
38606
39016
|
if (df >= threshold) {
|
|
38607
|
-
writes.delete(
|
|
39017
|
+
writes.delete(path23);
|
|
38608
39018
|
awfEmit(void 0, curStat);
|
|
38609
39019
|
} else {
|
|
38610
39020
|
timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
|
|
38611
39021
|
}
|
|
38612
39022
|
});
|
|
38613
39023
|
}
|
|
38614
|
-
if (!writes.has(
|
|
38615
|
-
writes.set(
|
|
39024
|
+
if (!writes.has(path23)) {
|
|
39025
|
+
writes.set(path23, {
|
|
38616
39026
|
lastChange: now,
|
|
38617
39027
|
cancelWait: () => {
|
|
38618
|
-
writes.delete(
|
|
39028
|
+
writes.delete(path23);
|
|
38619
39029
|
clearTimeout(timeoutHandler);
|
|
38620
39030
|
return event;
|
|
38621
39031
|
}
|
|
@@ -38626,8 +39036,8 @@ var FSWatcher = class extends EventEmitter {
|
|
|
38626
39036
|
/**
|
|
38627
39037
|
* Determines whether user has asked to ignore this path.
|
|
38628
39038
|
*/
|
|
38629
|
-
_isIgnored(
|
|
38630
|
-
if (this.options.atomic && DOT_RE.test(
|
|
39039
|
+
_isIgnored(path23, stats) {
|
|
39040
|
+
if (this.options.atomic && DOT_RE.test(path23))
|
|
38631
39041
|
return true;
|
|
38632
39042
|
if (!this._userIgnored) {
|
|
38633
39043
|
const { cwd } = this.options;
|
|
@@ -38637,17 +39047,17 @@ var FSWatcher = class extends EventEmitter {
|
|
|
38637
39047
|
const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
|
|
38638
39048
|
this._userIgnored = anymatch(list, void 0);
|
|
38639
39049
|
}
|
|
38640
|
-
return this._userIgnored(
|
|
39050
|
+
return this._userIgnored(path23, stats);
|
|
38641
39051
|
}
|
|
38642
|
-
_isntIgnored(
|
|
38643
|
-
return !this._isIgnored(
|
|
39052
|
+
_isntIgnored(path23, stat4) {
|
|
39053
|
+
return !this._isIgnored(path23, stat4);
|
|
38644
39054
|
}
|
|
38645
39055
|
/**
|
|
38646
39056
|
* Provides a set of common helpers and properties relating to symlink handling.
|
|
38647
39057
|
* @param path file or directory pattern being watched
|
|
38648
39058
|
*/
|
|
38649
|
-
_getWatchHelpers(
|
|
38650
|
-
return new WatchHelper(
|
|
39059
|
+
_getWatchHelpers(path23) {
|
|
39060
|
+
return new WatchHelper(path23, this.options.followSymlinks, this);
|
|
38651
39061
|
}
|
|
38652
39062
|
// Directory helpers
|
|
38653
39063
|
// -----------------
|
|
@@ -38679,63 +39089,63 @@ var FSWatcher = class extends EventEmitter {
|
|
|
38679
39089
|
* @param item base path of item/directory
|
|
38680
39090
|
*/
|
|
38681
39091
|
_remove(directory, item, isDirectory) {
|
|
38682
|
-
const
|
|
38683
|
-
const fullPath = sysPath2.resolve(
|
|
38684
|
-
isDirectory = isDirectory != null ? isDirectory : this._watched.has(
|
|
38685
|
-
if (!this._throttle("remove",
|
|
39092
|
+
const path23 = sysPath2.join(directory, item);
|
|
39093
|
+
const fullPath = sysPath2.resolve(path23);
|
|
39094
|
+
isDirectory = isDirectory != null ? isDirectory : this._watched.has(path23) || this._watched.has(fullPath);
|
|
39095
|
+
if (!this._throttle("remove", path23, 100))
|
|
38686
39096
|
return;
|
|
38687
39097
|
if (!isDirectory && this._watched.size === 1) {
|
|
38688
39098
|
this.add(directory, item, true);
|
|
38689
39099
|
}
|
|
38690
|
-
const wp = this._getWatchedDir(
|
|
39100
|
+
const wp = this._getWatchedDir(path23);
|
|
38691
39101
|
const nestedDirectoryChildren = wp.getChildren();
|
|
38692
|
-
nestedDirectoryChildren.forEach((nested) => this._remove(
|
|
39102
|
+
nestedDirectoryChildren.forEach((nested) => this._remove(path23, nested));
|
|
38693
39103
|
const parent = this._getWatchedDir(directory);
|
|
38694
39104
|
const wasTracked = parent.has(item);
|
|
38695
39105
|
parent.remove(item);
|
|
38696
39106
|
if (this._symlinkPaths.has(fullPath)) {
|
|
38697
39107
|
this._symlinkPaths.delete(fullPath);
|
|
38698
39108
|
}
|
|
38699
|
-
let relPath =
|
|
39109
|
+
let relPath = path23;
|
|
38700
39110
|
if (this.options.cwd)
|
|
38701
|
-
relPath = sysPath2.relative(this.options.cwd,
|
|
39111
|
+
relPath = sysPath2.relative(this.options.cwd, path23);
|
|
38702
39112
|
if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
|
|
38703
39113
|
const event = this._pendingWrites.get(relPath).cancelWait();
|
|
38704
39114
|
if (event === EVENTS.ADD)
|
|
38705
39115
|
return;
|
|
38706
39116
|
}
|
|
38707
|
-
this._watched.delete(
|
|
39117
|
+
this._watched.delete(path23);
|
|
38708
39118
|
this._watched.delete(fullPath);
|
|
38709
39119
|
const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
|
|
38710
|
-
if (wasTracked && !this._isIgnored(
|
|
38711
|
-
this._emit(eventName,
|
|
38712
|
-
this._closePath(
|
|
39120
|
+
if (wasTracked && !this._isIgnored(path23))
|
|
39121
|
+
this._emit(eventName, path23);
|
|
39122
|
+
this._closePath(path23);
|
|
38713
39123
|
}
|
|
38714
39124
|
/**
|
|
38715
39125
|
* Closes all watchers for a path
|
|
38716
39126
|
*/
|
|
38717
|
-
_closePath(
|
|
38718
|
-
this._closeFile(
|
|
38719
|
-
const dir = sysPath2.dirname(
|
|
38720
|
-
this._getWatchedDir(dir).remove(sysPath2.basename(
|
|
39127
|
+
_closePath(path23) {
|
|
39128
|
+
this._closeFile(path23);
|
|
39129
|
+
const dir = sysPath2.dirname(path23);
|
|
39130
|
+
this._getWatchedDir(dir).remove(sysPath2.basename(path23));
|
|
38721
39131
|
}
|
|
38722
39132
|
/**
|
|
38723
39133
|
* Closes only file-specific watchers
|
|
38724
39134
|
*/
|
|
38725
|
-
_closeFile(
|
|
38726
|
-
const closers = this._closers.get(
|
|
39135
|
+
_closeFile(path23) {
|
|
39136
|
+
const closers = this._closers.get(path23);
|
|
38727
39137
|
if (!closers)
|
|
38728
39138
|
return;
|
|
38729
39139
|
closers.forEach((closer) => closer());
|
|
38730
|
-
this._closers.delete(
|
|
39140
|
+
this._closers.delete(path23);
|
|
38731
39141
|
}
|
|
38732
|
-
_addPathCloser(
|
|
39142
|
+
_addPathCloser(path23, closer) {
|
|
38733
39143
|
if (!closer)
|
|
38734
39144
|
return;
|
|
38735
|
-
let list = this._closers.get(
|
|
39145
|
+
let list = this._closers.get(path23);
|
|
38736
39146
|
if (!list) {
|
|
38737
39147
|
list = [];
|
|
38738
|
-
this._closers.set(
|
|
39148
|
+
this._closers.set(path23, list);
|
|
38739
39149
|
}
|
|
38740
39150
|
list.push(closer);
|
|
38741
39151
|
}
|
|
@@ -38765,7 +39175,7 @@ function watch(paths, options = {}) {
|
|
|
38765
39175
|
var esm_default13 = { watch, FSWatcher };
|
|
38766
39176
|
|
|
38767
39177
|
// src/lib/watcher.ts
|
|
38768
|
-
import
|
|
39178
|
+
import path14 from "node:path";
|
|
38769
39179
|
function startWatcher(options) {
|
|
38770
39180
|
const { dir, extensions, onChange } = options;
|
|
38771
39181
|
const extSet = new Set(extensions);
|
|
@@ -38779,7 +39189,7 @@ function startWatcher(options) {
|
|
|
38779
39189
|
});
|
|
38780
39190
|
let debounceTimer = null;
|
|
38781
39191
|
const handleChange2 = (filePath) => {
|
|
38782
|
-
const ext =
|
|
39192
|
+
const ext = path14.extname(filePath);
|
|
38783
39193
|
if (!extSet.has(ext)) return;
|
|
38784
39194
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
38785
39195
|
debounceTimer = setTimeout(() => {
|
|
@@ -38803,31 +39213,79 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
|
38803
39213
|
|
|
38804
39214
|
// src/lib/wsServer.ts
|
|
38805
39215
|
import http from "node:http";
|
|
39216
|
+
import net from "node:net";
|
|
38806
39217
|
var PORT_START = 4400;
|
|
38807
39218
|
var PORT_END = 4409;
|
|
39219
|
+
var DEFAULT_HOST = "127.0.0.1";
|
|
39220
|
+
var PING_INTERVAL_MS = 3e4;
|
|
39221
|
+
var PROBE_TIMEOUT_MS = 300;
|
|
39222
|
+
var LOOPBACK_PROBES = ["127.0.0.1", "::1"];
|
|
39223
|
+
function portsBusyError(start, end) {
|
|
39224
|
+
return new CliError("cli_failed", `Ports ${start} to ${end} are all busy.`, [
|
|
39225
|
+
"Another `avsb dev` (or another program) is holding them. Stop it, then run this again.",
|
|
39226
|
+
`\`lsof -i :${start}\` shows what is listening on a port.`
|
|
39227
|
+
]);
|
|
39228
|
+
}
|
|
39229
|
+
function errorCode(err) {
|
|
39230
|
+
return typeof err === "object" && err !== null && "code" in err ? err.code : void 0;
|
|
39231
|
+
}
|
|
39232
|
+
function isAddressInUse(err) {
|
|
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);
|
|
39271
|
+
}
|
|
38808
39272
|
async function startDevServer(options) {
|
|
38809
39273
|
const { experimentName, experimentId, onExtensionMessage } = options;
|
|
38810
|
-
|
|
38811
|
-
|
|
38812
|
-
|
|
38813
|
-
|
|
39274
|
+
const start = options.portRange?.start ?? PORT_START;
|
|
39275
|
+
const end = options.portRange?.end ?? PORT_END;
|
|
39276
|
+
const host = options.host ?? DEFAULT_HOST;
|
|
39277
|
+
let bound = null;
|
|
39278
|
+
for (let port2 = start; port2 <= end && !bound; port2++) {
|
|
39279
|
+
if (await isPortAnsweredOnLoopback(port2)) continue;
|
|
38814
39280
|
try {
|
|
38815
|
-
|
|
38816
|
-
|
|
38817
|
-
|
|
38818
|
-
|
|
38819
|
-
break;
|
|
38820
|
-
} catch {
|
|
38821
|
-
continue;
|
|
39281
|
+
bound = { ...await tryPort(port2, host, experimentName, experimentId, onExtensionMessage), port: port2 };
|
|
39282
|
+
} catch (err) {
|
|
39283
|
+
if (BAD_HOST_CODES.has(String(errorCode(err)))) throw badHostError(host);
|
|
39284
|
+
if (!isAddressInUse(err)) throw err;
|
|
38822
39285
|
}
|
|
38823
39286
|
}
|
|
38824
|
-
if (!
|
|
38825
|
-
|
|
38826
|
-
}
|
|
38827
|
-
const pingInterval = setInterval(() => {
|
|
38828
|
-
const msg = { type: "ping", timestamp: Date.now() };
|
|
38829
|
-
broadcast(JSON.stringify(msg));
|
|
38830
|
-
}, 3e4);
|
|
39287
|
+
if (!bound) throw portsBusyError(start, end);
|
|
39288
|
+
const { httpServer, wss, port } = bound;
|
|
38831
39289
|
function broadcast(data) {
|
|
38832
39290
|
wss.clients.forEach((client) => {
|
|
38833
39291
|
if (client.readyState === import_websocket.default.OPEN) {
|
|
@@ -38835,8 +39293,14 @@ async function startDevServer(options) {
|
|
|
38835
39293
|
}
|
|
38836
39294
|
});
|
|
38837
39295
|
}
|
|
39296
|
+
const pingInterval = setInterval(() => {
|
|
39297
|
+
const msg = { type: "ping", timestamp: Date.now() };
|
|
39298
|
+
broadcast(JSON.stringify(msg));
|
|
39299
|
+
}, PING_INTERVAL_MS);
|
|
38838
39300
|
return {
|
|
38839
|
-
port
|
|
39301
|
+
port,
|
|
39302
|
+
host,
|
|
39303
|
+
url: devServerUrl(host, port),
|
|
38840
39304
|
broadcast(msg) {
|
|
38841
39305
|
broadcast(JSON.stringify(msg));
|
|
38842
39306
|
},
|
|
@@ -38860,7 +39324,7 @@ async function startDevServer(options) {
|
|
|
38860
39324
|
}
|
|
38861
39325
|
};
|
|
38862
39326
|
}
|
|
38863
|
-
function tryPort(port, experimentName, experimentId, onExtensionMessage) {
|
|
39327
|
+
function tryPort(port, host, experimentName, experimentId, onExtensionMessage) {
|
|
38864
39328
|
return new Promise((resolve3, reject) => {
|
|
38865
39329
|
const httpServer = http.createServer((req, res) => {
|
|
38866
39330
|
if (req.url === "/health") {
|
|
@@ -38879,6 +39343,16 @@ function tryPort(port, experimentName, experimentId, onExtensionMessage) {
|
|
|
38879
39343
|
);
|
|
38880
39344
|
});
|
|
38881
39345
|
const wss = new import_websocket_server.default({ server: httpServer });
|
|
39346
|
+
let settled = false;
|
|
39347
|
+
const fail2 = (err) => {
|
|
39348
|
+
if (settled) return;
|
|
39349
|
+
settled = true;
|
|
39350
|
+
wss.close();
|
|
39351
|
+
httpServer.close();
|
|
39352
|
+
reject(err);
|
|
39353
|
+
};
|
|
39354
|
+
httpServer.on("error", fail2);
|
|
39355
|
+
wss.on("error", fail2);
|
|
38882
39356
|
wss.on("connection", (ws) => {
|
|
38883
39357
|
ws.on("message", (raw) => {
|
|
38884
39358
|
try {
|
|
@@ -38888,13 +39362,16 @@ function tryPort(port, experimentName, experimentId, onExtensionMessage) {
|
|
|
38888
39362
|
}
|
|
38889
39363
|
});
|
|
38890
39364
|
});
|
|
38891
|
-
httpServer.
|
|
38892
|
-
|
|
39365
|
+
httpServer.listen(port, host, () => {
|
|
39366
|
+
if (settled) return;
|
|
39367
|
+
settled = true;
|
|
39368
|
+
resolve3({ httpServer, wss });
|
|
39369
|
+
});
|
|
38893
39370
|
});
|
|
38894
39371
|
}
|
|
38895
39372
|
|
|
38896
39373
|
// src/commands/dev.ts
|
|
38897
|
-
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) => {
|
|
38898
39375
|
try {
|
|
38899
39376
|
ensureLoggedIn();
|
|
38900
39377
|
const { manifest, dir } = readManifestOrExit();
|
|
@@ -38912,6 +39389,7 @@ var devCommand = new Command("dev").description("Start dev server with live relo
|
|
|
38912
39389
|
const server = await startDevServer({
|
|
38913
39390
|
experimentName: manifest.experimentName,
|
|
38914
39391
|
experimentId: manifest.experimentId,
|
|
39392
|
+
host: opts.host,
|
|
38915
39393
|
onExtensionMessage(msg, ws) {
|
|
38916
39394
|
if (msg.type === "ext:connect") {
|
|
38917
39395
|
const initMsg = {
|
|
@@ -38938,14 +39416,16 @@ var devCommand = new Command("dev").description("Start dev server with live relo
|
|
|
38938
39416
|
}
|
|
38939
39417
|
});
|
|
38940
39418
|
line();
|
|
38941
|
-
|
|
39419
|
+
const listening = describeDevServer(server);
|
|
39420
|
+
success(listening.line);
|
|
39421
|
+
if (listening.warning) warn(listening.warning);
|
|
38942
39422
|
info("Watching for changes...");
|
|
38943
39423
|
line();
|
|
38944
39424
|
const watcher = startWatcher({
|
|
38945
39425
|
dir,
|
|
38946
39426
|
extensions: [".ts", ".js", ".scss", ".css"],
|
|
38947
39427
|
onChange: async (filePath) => {
|
|
38948
|
-
const relativePath =
|
|
39428
|
+
const relativePath = path15.relative(dir, filePath);
|
|
38949
39429
|
info(`File changed: ${relativePath}`);
|
|
38950
39430
|
try {
|
|
38951
39431
|
compiled = await compileExperiment(dir, manifest);
|
|
@@ -39061,15 +39541,15 @@ var statusCommand = withOutputOptions(
|
|
|
39061
39541
|
heading(manifest.experimentName);
|
|
39062
39542
|
line();
|
|
39063
39543
|
for (const row of buildStatusRows(manifest)) {
|
|
39064
|
-
|
|
39544
|
+
field(row.label, row.value);
|
|
39065
39545
|
}
|
|
39066
39546
|
line();
|
|
39067
39547
|
});
|
|
39068
39548
|
|
|
39069
39549
|
// src/commands/project.ts
|
|
39070
39550
|
import { createHash as createHash4 } from "node:crypto";
|
|
39071
|
-
import
|
|
39072
|
-
import
|
|
39551
|
+
import fs12 from "node:fs";
|
|
39552
|
+
import path16 from "node:path";
|
|
39073
39553
|
function sha2564(content) {
|
|
39074
39554
|
return createHash4("sha256").update(content).digest("hex");
|
|
39075
39555
|
}
|
|
@@ -39100,16 +39580,29 @@ var projectPullCommand = new Command("pull").description("Pull project script fr
|
|
|
39100
39580
|
label("ID", `PRJ-${project.shortId}`);
|
|
39101
39581
|
label("Language", project.projectJsLang === "TYPESCRIPT" ? "TypeScript" : "JavaScript");
|
|
39102
39582
|
line();
|
|
39103
|
-
const
|
|
39104
|
-
const dir =
|
|
39105
|
-
if (
|
|
39583
|
+
const target = resolveProjectPullTarget(process.cwd(), project.shortId);
|
|
39584
|
+
const dir = target.dir;
|
|
39585
|
+
if (target.kind === "blocked") {
|
|
39586
|
+
failCommand(
|
|
39587
|
+
new CliError("cli_usage", `${dir} already exists and is not a pulled copy of PRJ-${project.shortId}.`, [
|
|
39588
|
+
"Move or rename it, or run `avsb project pull` from another folder."
|
|
39589
|
+
])
|
|
39590
|
+
);
|
|
39591
|
+
}
|
|
39592
|
+
if (target.kind === "existing") {
|
|
39106
39593
|
const { manifest } = readProjectManifestOrExit(dir);
|
|
39107
39594
|
if (!options.force) {
|
|
39108
39595
|
const scriptFile = `project.${manifest.language}`;
|
|
39109
|
-
const scriptPath =
|
|
39110
|
-
const localContent =
|
|
39596
|
+
const scriptPath = path16.join(dir, scriptFile);
|
|
39597
|
+
const localContent = fs12.existsSync(scriptPath) ? fs12.readFileSync(scriptPath, "utf-8") : "";
|
|
39111
39598
|
const remoteContent = manifest.language === "ts" ? project.editorProjectTs ?? project.editorProjectJs ?? "" : project.editorProjectJs ?? "";
|
|
39112
39599
|
if (localContent !== remoteContent && localContent !== "") {
|
|
39600
|
+
const refusal = refusalWithoutPrompt({
|
|
39601
|
+
code: "cli_conflict",
|
|
39602
|
+
message: `Pulling would overwrite your local ${scriptFile}, and this run cannot ask first.`,
|
|
39603
|
+
hints: [`Run \`avsb project pull ${projectInput} --force\` to overwrite it.`]
|
|
39604
|
+
});
|
|
39605
|
+
if (refusal) failCommand(refusal);
|
|
39113
39606
|
const { proceed } = await esm_default12.prompt([
|
|
39114
39607
|
{
|
|
39115
39608
|
type: "confirm",
|
|
@@ -39127,7 +39620,7 @@ var projectPullCommand = new Command("pull").description("Pull project script fr
|
|
|
39127
39620
|
const updateSpinner = ora("Updating files...").start();
|
|
39128
39621
|
const updated = updateProjectFile(dir, manifest, response.data);
|
|
39129
39622
|
if (updated.length > 0) {
|
|
39130
|
-
updateSpinner.succeed(
|
|
39623
|
+
updateSpinner.succeed(`Files updated in ${path16.relative(process.cwd(), dir) || "this folder"}`);
|
|
39131
39624
|
fileTree(updated);
|
|
39132
39625
|
} else {
|
|
39133
39626
|
updateSpinner.succeed("Already up to date");
|
|
@@ -39135,12 +39628,12 @@ var projectPullCommand = new Command("pull").description("Pull project script fr
|
|
|
39135
39628
|
} else {
|
|
39136
39629
|
const scaffoldSpinner = ora("Scaffolding project files...").start();
|
|
39137
39630
|
try {
|
|
39138
|
-
const result = scaffoldProject({ data: response.data, apiBaseUrl: config.apiBaseUrl });
|
|
39631
|
+
const result = scaffoldProject({ data: response.data, apiBaseUrl: config.apiBaseUrl, targetDir: dir });
|
|
39139
39632
|
scaffoldSpinner.succeed("Files created");
|
|
39140
39633
|
line();
|
|
39141
39634
|
fileTree(result.files);
|
|
39142
39635
|
line();
|
|
39143
|
-
success(`Project cloned to ${
|
|
39636
|
+
success(`Project cloned to ${path16.relative(process.cwd(), dir) || "."}/`);
|
|
39144
39637
|
} catch (err) {
|
|
39145
39638
|
scaffoldSpinner.fail("Scaffolding failed");
|
|
39146
39639
|
failCommand(err);
|
|
@@ -39156,11 +39649,11 @@ var projectPushCommand = new Command("push").description("Compile and push proje
|
|
|
39156
39649
|
const { manifest, dir } = readProjectManifestOrExit();
|
|
39157
39650
|
const api = new ApiClient(config);
|
|
39158
39651
|
const scriptFile = `project.${manifest.language}`;
|
|
39159
|
-
const scriptPath =
|
|
39160
|
-
if (!
|
|
39652
|
+
const scriptPath = path16.join(dir, scriptFile);
|
|
39653
|
+
if (!fs12.existsSync(scriptPath)) {
|
|
39161
39654
|
failCommand(new CliError("cli_no_manifest", `File not found: ${scriptFile}`));
|
|
39162
39655
|
}
|
|
39163
|
-
const source =
|
|
39656
|
+
const source = fs12.readFileSync(scriptPath, "utf-8");
|
|
39164
39657
|
const projLang = manifest.language === "ts" ? "TYPESCRIPT" : "JAVASCRIPT";
|
|
39165
39658
|
const compileSpinner = ora(`Compiling ${scriptFile}...`).start();
|
|
39166
39659
|
const compileResult = await compileScript(source, projLang, scriptFile, { minify: true });
|
|
@@ -39182,6 +39675,18 @@ var projectPushCommand = new Command("push").description("Compile and push proje
|
|
|
39182
39675
|
if (remoteHash !== manifest.syncedHash) {
|
|
39183
39676
|
spinner.warn("Remote has changes you haven't pulled");
|
|
39184
39677
|
line();
|
|
39678
|
+
if (!isInteractiveRun()) {
|
|
39679
|
+
failCommand(
|
|
39680
|
+
new CliError(
|
|
39681
|
+
"cli_conflict",
|
|
39682
|
+
"The platform copy changed since your last pull, and this run cannot ask before overwriting it.",
|
|
39683
|
+
[
|
|
39684
|
+
"Run `avsb project pull --force` to fetch the remote script first (it overwrites your local file),",
|
|
39685
|
+
"or `avsb project push --force` to overwrite the remote changes."
|
|
39686
|
+
]
|
|
39687
|
+
)
|
|
39688
|
+
);
|
|
39689
|
+
}
|
|
39185
39690
|
const { proceed } = await esm_default12.prompt([
|
|
39186
39691
|
{
|
|
39187
39692
|
type: "confirm",
|
|
@@ -39200,27 +39705,33 @@ var projectPushCommand = new Command("push").description("Compile and push proje
|
|
|
39200
39705
|
}
|
|
39201
39706
|
}
|
|
39202
39707
|
const payload = manifest.language === "ts" ? { editorProjectTs: { source, compiled: compiledSource } } : { editorProjectJs: { source, compiled: compiledSource } };
|
|
39203
|
-
|
|
39708
|
+
try {
|
|
39709
|
+
await api.pushProject(manifest.projectShortId, payload);
|
|
39710
|
+
} catch (err) {
|
|
39711
|
+
spinner.fail("Push refused");
|
|
39712
|
+
failCommand(err, { labelForPath: (bodyPath) => bodyPath.length > 0 ? scriptFile : void 0 });
|
|
39713
|
+
}
|
|
39204
39714
|
manifest.syncedHash = sha2564(source);
|
|
39205
39715
|
manifest.lastSyncedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
39206
39716
|
writeProjectManifest(dir, manifest);
|
|
39207
39717
|
spinner.succeed("Pushed successfully");
|
|
39208
39718
|
line();
|
|
39719
|
+
info("Saved as unpublished changes. Publish the script from the project's Snippet settings to put it live.");
|
|
39209
39720
|
success("Push complete");
|
|
39210
39721
|
} catch (err) {
|
|
39211
39722
|
failCommand(err);
|
|
39212
39723
|
}
|
|
39213
39724
|
});
|
|
39214
|
-
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) => {
|
|
39215
39726
|
try {
|
|
39216
39727
|
ensureLoggedIn();
|
|
39217
39728
|
const { manifest, dir } = readProjectManifestOrExit();
|
|
39218
39729
|
const scriptFile = `project.${manifest.language}`;
|
|
39219
|
-
const scriptPath =
|
|
39730
|
+
const scriptPath = path16.join(dir, scriptFile);
|
|
39220
39731
|
heading(`Dev mode: ${manifest.projectName} (project script)`);
|
|
39221
39732
|
line();
|
|
39222
39733
|
let compiledJs;
|
|
39223
|
-
const source =
|
|
39734
|
+
const source = fs12.readFileSync(scriptPath, "utf-8");
|
|
39224
39735
|
if (manifest.language === "ts") {
|
|
39225
39736
|
const spinner = ora("Compiling...").start();
|
|
39226
39737
|
try {
|
|
@@ -39237,6 +39748,7 @@ var projectDevCommand = new Command("dev").description("Watch project script and
|
|
|
39237
39748
|
const server = await startDevServer({
|
|
39238
39749
|
experimentName: `${manifest.projectName} (project script)`,
|
|
39239
39750
|
experimentId: manifest.projectId,
|
|
39751
|
+
host: opts.host,
|
|
39240
39752
|
onExtensionMessage(msg, ws) {
|
|
39241
39753
|
if (msg.type === "ext:connect") {
|
|
39242
39754
|
const initMsg = {
|
|
@@ -39252,17 +39764,19 @@ var projectDevCommand = new Command("dev").description("Watch project script and
|
|
|
39252
39764
|
}
|
|
39253
39765
|
});
|
|
39254
39766
|
line();
|
|
39255
|
-
|
|
39767
|
+
const listening = describeDevServer(server);
|
|
39768
|
+
success(listening.line);
|
|
39769
|
+
if (listening.warning) warn(listening.warning);
|
|
39256
39770
|
info("Watching for changes...");
|
|
39257
39771
|
line();
|
|
39258
39772
|
const watcher = startWatcher({
|
|
39259
39773
|
dir,
|
|
39260
39774
|
extensions: [".ts", ".js"],
|
|
39261
39775
|
onChange: async (filePath) => {
|
|
39262
|
-
const relativePath =
|
|
39776
|
+
const relativePath = path16.relative(dir, filePath);
|
|
39263
39777
|
info(`File changed: ${relativePath}`);
|
|
39264
39778
|
try {
|
|
39265
|
-
const newSource =
|
|
39779
|
+
const newSource = fs12.readFileSync(filePath, "utf-8");
|
|
39266
39780
|
compiledJs = manifest.language === "ts" ? await compileTypeScript(newSource) : newSource;
|
|
39267
39781
|
const updateMsg = {
|
|
39268
39782
|
type: "project:update",
|
|
@@ -39508,6 +40022,72 @@ function isConflictError(err) {
|
|
|
39508
40022
|
if (!(err instanceof Error)) return false;
|
|
39509
40023
|
return /409|conflict|referenc/i.test(err.message);
|
|
39510
40024
|
}
|
|
40025
|
+
function isRecord5(value) {
|
|
40026
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
40027
|
+
}
|
|
40028
|
+
function isCliMetric(value) {
|
|
40029
|
+
return isRecord5(value) && typeof value.id === "string" && typeof value.shortId === "number" && typeof value.name === "string" && typeof value.type === "string";
|
|
40030
|
+
}
|
|
40031
|
+
function readMetricList(data) {
|
|
40032
|
+
if (!isRecord5(data) || !Array.isArray(data.metrics)) return null;
|
|
40033
|
+
const metrics = data.metrics.filter(isCliMetric);
|
|
40034
|
+
const usage = isRecord5(data.usage) ? data.usage : {};
|
|
40035
|
+
return { metrics, usage };
|
|
40036
|
+
}
|
|
40037
|
+
function readMetric(data) {
|
|
40038
|
+
return isRecord5(data) && isCliMetric(data.metric) ? data.metric : null;
|
|
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
|
+
}
|
|
40055
|
+
function formatMetricTable(metrics, usage) {
|
|
40056
|
+
const rows = metrics.map((metric) => ({
|
|
40057
|
+
id: metricIdLabel(metric),
|
|
40058
|
+
type: metric.type,
|
|
40059
|
+
name: truncate(metric.name, 32),
|
|
40060
|
+
definition: truncate(describeMetric(metric), 48),
|
|
40061
|
+
usage: formatUsage(usage[metric.id]),
|
|
40062
|
+
archived: metric.archivedAt != null
|
|
40063
|
+
}));
|
|
40064
|
+
const widths = {
|
|
40065
|
+
id: Math.max(2, ...rows.map((r) => r.id.length)),
|
|
40066
|
+
type: Math.max(4, ...rows.map((r) => r.type.length)),
|
|
40067
|
+
name: Math.max(4, ...rows.map((r) => r.name.length)),
|
|
40068
|
+
definition: Math.max(10, ...rows.map((r) => r.definition.length))
|
|
40069
|
+
};
|
|
40070
|
+
const heading2 = [
|
|
40071
|
+
"ID".padEnd(widths.id),
|
|
40072
|
+
"Type".padEnd(widths.type),
|
|
40073
|
+
"Name".padEnd(widths.name),
|
|
40074
|
+
"Definition".padEnd(widths.definition),
|
|
40075
|
+
"Usage"
|
|
40076
|
+
].join(" ");
|
|
40077
|
+
return {
|
|
40078
|
+
heading: heading2,
|
|
40079
|
+
rows: rows.map((row) => {
|
|
40080
|
+
const line2 = [
|
|
40081
|
+
row.id.padEnd(widths.id),
|
|
40082
|
+
row.type.padEnd(widths.type),
|
|
40083
|
+
row.name.padEnd(widths.name),
|
|
40084
|
+
row.definition.padEnd(widths.definition),
|
|
40085
|
+
row.usage
|
|
40086
|
+
].join(" ");
|
|
40087
|
+
return row.archived ? `${line2} (archived)` : line2;
|
|
40088
|
+
})
|
|
40089
|
+
};
|
|
40090
|
+
}
|
|
39511
40091
|
|
|
39512
40092
|
// src/commands/metrics.ts
|
|
39513
40093
|
var metricsListCommand = withOutputOptions(
|
|
@@ -39527,49 +40107,26 @@ var metricsListCommand = withOutputOptions(
|
|
|
39527
40107
|
spinner.fail("Failed to fetch metrics");
|
|
39528
40108
|
failCommand(err);
|
|
39529
40109
|
}
|
|
39530
|
-
const { metrics, usage } = response.data;
|
|
39531
40110
|
if (emitJsonResult(response.data)) return;
|
|
39532
|
-
|
|
40111
|
+
const list = readMetricList(response.data);
|
|
40112
|
+
if (!list) {
|
|
40113
|
+
failCommand(
|
|
40114
|
+
new CliError("cli_failed", "The platform answered in a shape this CLI does not read.", [
|
|
40115
|
+
"Upgrade the CLI with `npm install -g @avsbhq/cli`, or pass --json to see the raw answer."
|
|
40116
|
+
])
|
|
40117
|
+
);
|
|
40118
|
+
}
|
|
40119
|
+
if (list.metrics.length === 0) {
|
|
39533
40120
|
line();
|
|
39534
40121
|
info(
|
|
39535
40122
|
'No metrics yet. Create one with `avsb metrics create --type click --name "..." --selector ".cta"`'
|
|
39536
40123
|
);
|
|
39537
40124
|
return;
|
|
39538
40125
|
}
|
|
39539
|
-
const
|
|
39540
|
-
id: metricIdLabel(metric),
|
|
39541
|
-
type: metric.type,
|
|
39542
|
-
name: truncate(metric.name, 32),
|
|
39543
|
-
definition: truncate(describeMetric(metric), 48),
|
|
39544
|
-
usage: formatUsage(usage[metric.id]),
|
|
39545
|
-
archived: metric.archivedAt != null
|
|
39546
|
-
}));
|
|
39547
|
-
const widths = {
|
|
39548
|
-
id: Math.max(2, ...rows.map((r) => r.id.length)),
|
|
39549
|
-
type: Math.max(4, ...rows.map((r) => r.type.length)),
|
|
39550
|
-
name: Math.max(4, ...rows.map((r) => r.name.length)),
|
|
39551
|
-
definition: Math.max(10, ...rows.map((r) => r.definition.length))
|
|
39552
|
-
};
|
|
40126
|
+
const table = formatMetricTable(list.metrics, list.usage);
|
|
39553
40127
|
line();
|
|
39554
|
-
heading(
|
|
39555
|
-
|
|
39556
|
-
"ID".padEnd(widths.id),
|
|
39557
|
-
"Type".padEnd(widths.type),
|
|
39558
|
-
"Name".padEnd(widths.name),
|
|
39559
|
-
"Definition".padEnd(widths.definition),
|
|
39560
|
-
"Usage"
|
|
39561
|
-
].join(" ")
|
|
39562
|
-
);
|
|
39563
|
-
for (const row of rows) {
|
|
39564
|
-
const line2 = [
|
|
39565
|
-
row.id.padEnd(widths.id),
|
|
39566
|
-
row.type.padEnd(widths.type),
|
|
39567
|
-
row.name.padEnd(widths.name),
|
|
39568
|
-
row.definition.padEnd(widths.definition),
|
|
39569
|
-
row.usage
|
|
39570
|
-
].join(" ");
|
|
39571
|
-
line(row.archived ? `${line2} (archived)` : line2);
|
|
39572
|
-
}
|
|
40128
|
+
heading(table.heading);
|
|
40129
|
+
for (const row of table.rows) line(row);
|
|
39573
40130
|
line();
|
|
39574
40131
|
});
|
|
39575
40132
|
var metricsCreateCommand = new Command("create").description("Create a new metric").option("--project <project>", "Project ID (PRJ-42 or numeric); defaults to local manifest").option("--type <type>", "Metric type (CLICK | PAGEVIEW | CUSTOM)").option("--name <name>", "Display name").option("--description <text>", "Optional description").option("--selector <selector>", "CSS selector (CLICK only)").option("--page-url <url>", "Page URL filter (CLICK only)").option("--url-match <pattern>", "URL match pattern (PAGEVIEW only)").option("--match-mode <mode>", "URL match mode for PAGEVIEW (SIMPLE | EXACT | PATH | SUBSTRING | PATTERN | REGEX)").option("--key <key>", "Metric key (CUSTOM only)").option("--has-value", "Toggle value-field tracking on a CUSTOM metric").action(async (opts) => {
|
|
@@ -39585,21 +40142,20 @@ var metricsCreateCommand = new Command("create").description("Create a new metri
|
|
|
39585
40142
|
const body = buildCreateBody(type, opts, opts.name);
|
|
39586
40143
|
const api = new ApiClient(config);
|
|
39587
40144
|
const spinner = ora("Creating metric...").start();
|
|
40145
|
+
let response;
|
|
39588
40146
|
try {
|
|
39589
|
-
|
|
39590
|
-
const { metric } = response.data;
|
|
39591
|
-
spinner.succeed(`Metric created: ${metric.name} (${metricIdLabel(metric)})`);
|
|
39592
|
-
printMetricDetails(metric);
|
|
40147
|
+
response = await api.createMetric(shortId, body);
|
|
39593
40148
|
} catch (err) {
|
|
39594
40149
|
spinner.fail("Failed to create metric");
|
|
39595
|
-
|
|
39596
|
-
if (/already exists|conflict|409|unique/i.test(message)) {
|
|
39597
|
-
error(`Conflict: a metric with that name or key already exists. ${message}`);
|
|
39598
|
-
} else {
|
|
39599
|
-
error(message);
|
|
39600
|
-
}
|
|
39601
|
-
process.exit(EXIT.failure);
|
|
40150
|
+
failCommand(err);
|
|
39602
40151
|
}
|
|
40152
|
+
const metric = readMetric(response.data);
|
|
40153
|
+
if (!metric) {
|
|
40154
|
+
spinner.warn("Metric created. Run `avsb metrics list` to see it.");
|
|
40155
|
+
return;
|
|
40156
|
+
}
|
|
40157
|
+
spinner.succeed(`Metric created: ${metric.name} (${metricIdLabel(metric)})`);
|
|
40158
|
+
printMetricDetails(metric);
|
|
39603
40159
|
});
|
|
39604
40160
|
var metricsUpdateCommand = new Command("update").argument("<metricId>", "Metric ID (cuid or numeric short ID)").description("Update a metric").option("--project <project>", "Project ID (PRJ-42 or numeric); defaults to local manifest").option("--name <name>", "New display name").option("--description <text>", "New description").option("--selector <selector>", "New CSS selector (CLICK)").option("--page-url <url>", "New page URL filter (CLICK)").option("--url-match <pattern>", "New URL match pattern (PAGEVIEW)").option("--match-mode <mode>", "New URL match mode (PAGEVIEW)").option("--key <key>", "New metric key (CUSTOM)").option("--has-value", "Enable value-field tracking (CUSTOM)").option("--no-has-value", "Disable value-field tracking (CUSTOM)").action(async (metricId, opts) => {
|
|
39605
40161
|
const config = ensureLoggedIn();
|
|
@@ -39615,49 +40171,67 @@ var metricsUpdateCommand = new Command("update").argument("<metricId>", "Metric
|
|
|
39615
40171
|
}
|
|
39616
40172
|
const api = new ApiClient(config);
|
|
39617
40173
|
const spinner = ora("Updating metric...").start();
|
|
40174
|
+
let response;
|
|
39618
40175
|
try {
|
|
39619
|
-
|
|
39620
|
-
const { metric } = response.data;
|
|
39621
|
-
spinner.succeed(`Metric updated: ${metric.name} (${metricIdLabel(metric)})`);
|
|
39622
|
-
printMetricDetails(metric);
|
|
40176
|
+
response = await api.updateMetric(shortId, metricId, patch);
|
|
39623
40177
|
} catch (err) {
|
|
39624
40178
|
spinner.fail("Failed to update metric");
|
|
39625
40179
|
failCommand(err);
|
|
39626
40180
|
}
|
|
40181
|
+
const metric = readMetric(response.data);
|
|
40182
|
+
if (!metric) {
|
|
40183
|
+
spinner.warn(`Metric ${metricId} updated.`);
|
|
40184
|
+
return;
|
|
40185
|
+
}
|
|
40186
|
+
spinner.succeed(`Metric updated: ${metric.name} (${metricIdLabel(metric)})`);
|
|
40187
|
+
printMetricDetails(metric);
|
|
39627
40188
|
});
|
|
39628
40189
|
var metricsArchiveCommand = new Command("archive").argument("<metricId>", "Metric ID").description("Archive a metric (soft-delete, preserves history)").option("--project <project>", "Project ID (PRJ-42 or numeric); defaults to local manifest").action(async (metricId, opts) => {
|
|
39629
40190
|
const config = ensureLoggedIn();
|
|
39630
40191
|
const shortId = resolveProjectShortId(opts);
|
|
39631
40192
|
const api = new ApiClient(config);
|
|
39632
40193
|
const spinner = ora("Archiving metric...").start();
|
|
40194
|
+
let response;
|
|
39633
40195
|
try {
|
|
39634
|
-
|
|
39635
|
-
const { metric } = response.data;
|
|
39636
|
-
spinner.succeed(`Metric archived: ${metric.name} (${metricIdLabel(metric)})`);
|
|
40196
|
+
response = await api.archiveMetric(shortId, metricId);
|
|
39637
40197
|
} catch (err) {
|
|
39638
40198
|
spinner.fail("Failed to archive metric");
|
|
39639
40199
|
failCommand(err);
|
|
39640
40200
|
}
|
|
40201
|
+
const metric = readMetric(response.data);
|
|
40202
|
+
spinner.succeed(metric ? `Metric archived: ${metric.name} (${metricIdLabel(metric)})` : `Metric ${metricId} archived`);
|
|
40203
|
+
const cascade = describeArchiveCascade(response.data);
|
|
40204
|
+
if (cascade) info(cascade);
|
|
39641
40205
|
});
|
|
39642
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) => {
|
|
39643
40207
|
const config = ensureLoggedIn();
|
|
39644
40208
|
const shortId = resolveProjectShortId(opts);
|
|
39645
40209
|
const api = new ApiClient(config);
|
|
39646
40210
|
const spinner = ora("Restoring metric...").start();
|
|
40211
|
+
let response;
|
|
39647
40212
|
try {
|
|
39648
|
-
|
|
39649
|
-
const { metric } = response.data;
|
|
39650
|
-
spinner.succeed(`Metric restored from archive: ${metric.name} (${metricIdLabel(metric)})`);
|
|
40213
|
+
response = await api.unarchiveMetric(shortId, metricId);
|
|
39651
40214
|
} catch (err) {
|
|
39652
40215
|
spinner.fail("Failed to unarchive metric");
|
|
39653
40216
|
failCommand(err);
|
|
39654
40217
|
}
|
|
40218
|
+
const metric = readMetric(response.data);
|
|
40219
|
+
spinner.succeed(
|
|
40220
|
+
metric ? `Metric restored from archive: ${metric.name} (${metricIdLabel(metric)})` : `Metric ${metricId} restored from archive`
|
|
40221
|
+
);
|
|
39655
40222
|
});
|
|
39656
40223
|
var metricsDeleteCommand = new Command("delete").argument("<metricId>", "Metric ID").description("Delete a metric (rejected if metric bindings reference it)").option("--project <project>", "Project ID (PRJ-42 or numeric); defaults to local manifest").option("-f, --force", "Skip confirmation prompt").action(async (metricId, opts) => {
|
|
39657
40224
|
const config = ensureLoggedIn();
|
|
39658
40225
|
const shortId = resolveProjectShortId(opts);
|
|
39659
40226
|
const api = new ApiClient(config);
|
|
39660
40227
|
if (!opts.force) {
|
|
40228
|
+
if (!isInteractiveRun()) {
|
|
40229
|
+
failCommand(
|
|
40230
|
+
new CliError("cli_usage", `Deleting metric ${metricId} needs a confirmation this run cannot ask for.`, [
|
|
40231
|
+
`Pass -f to delete without asking: \`avsb metrics delete ${metricId} -f\``
|
|
40232
|
+
])
|
|
40233
|
+
);
|
|
40234
|
+
}
|
|
39661
40235
|
const { proceed } = await esm_default12.prompt([
|
|
39662
40236
|
{
|
|
39663
40237
|
type: "confirm",
|
|
@@ -39677,19 +40251,12 @@ var metricsDeleteCommand = new Command("delete").argument("<metricId>", "Metric
|
|
|
39677
40251
|
spinner.succeed(`Metric deleted: ${metricId}`);
|
|
39678
40252
|
} catch (err) {
|
|
39679
40253
|
spinner.fail("Failed to delete metric");
|
|
39680
|
-
const message = err instanceof Error ? err.message : "Unknown error";
|
|
39681
40254
|
if (isConflictError(err)) {
|
|
39682
40255
|
const refs = extractBindingsReferencing(err);
|
|
39683
|
-
if (refs.length > 0) {
|
|
39684
|
-
error(`Cannot delete: referenced by ${refs.join(", ")}.`);
|
|
39685
|
-
} else {
|
|
39686
|
-
error(`Cannot delete: metric bindings still reference this metric. ${message}`);
|
|
39687
|
-
}
|
|
40256
|
+
if (refs.length > 0) error(`Referenced by ${refs.join(", ")}.`);
|
|
39688
40257
|
info(`Tip: run \`avsb metrics archive ${metricId}\` instead to hide it without losing history.`);
|
|
39689
|
-
} else {
|
|
39690
|
-
error(message);
|
|
39691
40258
|
}
|
|
39692
|
-
|
|
40259
|
+
failCommand(err);
|
|
39693
40260
|
}
|
|
39694
40261
|
});
|
|
39695
40262
|
var metricsCommand = new Command("metrics").description("Manage metric tracking definitions").addCommand(metricsListCommand).addCommand(metricsCreateCommand).addCommand(metricsUpdateCommand).addCommand(metricsArchiveCommand).addCommand(metricsUnarchiveCommand).addCommand(metricsDeleteCommand);
|
|
@@ -39904,6 +40471,40 @@ function isConflictError2(err) {
|
|
|
39904
40471
|
if (!(err instanceof Error)) return false;
|
|
39905
40472
|
return /409|conflict|attached|referenc/i.test(err.message);
|
|
39906
40473
|
}
|
|
40474
|
+
function isRecord6(value) {
|
|
40475
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
40476
|
+
}
|
|
40477
|
+
function isCliMetricBinding(value) {
|
|
40478
|
+
return isRecord6(value) && typeof value.id === "string" && typeof value.shortId === "number" && typeof value.name === "string" && typeof value.measure === "string";
|
|
40479
|
+
}
|
|
40480
|
+
function readMetricBindingList(data) {
|
|
40481
|
+
if (!isRecord6(data) || !Array.isArray(data.metricBindings)) return null;
|
|
40482
|
+
return data.metricBindings.filter(isCliMetricBinding);
|
|
40483
|
+
}
|
|
40484
|
+
function readMetricBinding(data) {
|
|
40485
|
+
return isRecord6(data) && isCliMetricBinding(data.metricBinding) ? data.metricBinding : null;
|
|
40486
|
+
}
|
|
40487
|
+
function formatMetricBindingTable(bindings) {
|
|
40488
|
+
const rows = bindings.map((binding) => ({
|
|
40489
|
+
id: metricBindingIdLabel(binding),
|
|
40490
|
+
measure: measureBadge(binding.measure),
|
|
40491
|
+
name: truncate2(binding.name, 28),
|
|
40492
|
+
sentence: truncate2(renderMetricBindingSentence(binding), 64),
|
|
40493
|
+
archived: binding.archivedAt != null
|
|
40494
|
+
}));
|
|
40495
|
+
const widths = {
|
|
40496
|
+
id: Math.max(2, ...rows.map((r) => r.id.length)),
|
|
40497
|
+
measure: Math.max(7, ...rows.map((r) => r.measure.length)),
|
|
40498
|
+
name: Math.max(4, ...rows.map((r) => r.name.length))
|
|
40499
|
+
};
|
|
40500
|
+
return {
|
|
40501
|
+
heading: ["ID".padEnd(widths.id), "Measure".padEnd(widths.measure), "Name".padEnd(widths.name), "Sentence"].join(" "),
|
|
40502
|
+
rows: rows.map((row) => {
|
|
40503
|
+
const line2 = [row.id.padEnd(widths.id), row.measure.padEnd(widths.measure), row.name.padEnd(widths.name), row.sentence].join(" ");
|
|
40504
|
+
return row.archived ? `${line2} (archived)` : line2;
|
|
40505
|
+
})
|
|
40506
|
+
};
|
|
40507
|
+
}
|
|
39907
40508
|
|
|
39908
40509
|
// src/commands/metric-bindings.body.ts
|
|
39909
40510
|
function parseComposition(opts) {
|
|
@@ -39965,12 +40566,11 @@ function parseWinsorization(opts) {
|
|
|
39965
40566
|
}
|
|
39966
40567
|
return { enabled: true, upperPercentile: upper, lowerPercentile: lower };
|
|
39967
40568
|
}
|
|
39968
|
-
function buildCreateMetricBindingBody(
|
|
40569
|
+
function buildCreateMetricBindingBody(measure, opts, name) {
|
|
39969
40570
|
const direction = opts.direction ? normalizeDirection(opts.direction) : "INCREASE";
|
|
39970
40571
|
const body = {
|
|
39971
40572
|
name,
|
|
39972
40573
|
description: opts.description ?? void 0,
|
|
39973
|
-
projectId,
|
|
39974
40574
|
measure,
|
|
39975
40575
|
direction,
|
|
39976
40576
|
isLibrary: true
|
|
@@ -40081,8 +40681,15 @@ var metricBindingsListCommand = withOutputOptions(
|
|
|
40081
40681
|
spinner.fail("Failed to fetch metric bindings");
|
|
40082
40682
|
failCommand(err);
|
|
40083
40683
|
}
|
|
40084
|
-
const { metricBindings } = response.data;
|
|
40085
40684
|
if (emitJsonResult(response.data)) return;
|
|
40685
|
+
const metricBindings = readMetricBindingList(response.data);
|
|
40686
|
+
if (!metricBindings) {
|
|
40687
|
+
failCommand(
|
|
40688
|
+
new CliError("cli_failed", "The platform answered in a shape this CLI does not read.", [
|
|
40689
|
+
"Upgrade the CLI with `npm install -g @avsbhq/cli`, or pass --json to see the raw answer."
|
|
40690
|
+
])
|
|
40691
|
+
);
|
|
40692
|
+
}
|
|
40086
40693
|
if (metricBindings.length === 0) {
|
|
40087
40694
|
line();
|
|
40088
40695
|
info(
|
|
@@ -40090,45 +40697,19 @@ var metricBindingsListCommand = withOutputOptions(
|
|
|
40090
40697
|
);
|
|
40091
40698
|
return;
|
|
40092
40699
|
}
|
|
40093
|
-
const
|
|
40094
|
-
id: metricBindingIdLabel(binding),
|
|
40095
|
-
measure: measureBadge(binding.measure),
|
|
40096
|
-
name: truncate2(binding.name, 28),
|
|
40097
|
-
sentence: truncate2(renderMetricBindingSentence(binding), 64),
|
|
40098
|
-
archived: binding.archivedAt != null
|
|
40099
|
-
}));
|
|
40100
|
-
const widths = {
|
|
40101
|
-
id: Math.max(2, ...rows.map((r) => r.id.length)),
|
|
40102
|
-
measure: Math.max(7, ...rows.map((r) => r.measure.length)),
|
|
40103
|
-
name: Math.max(4, ...rows.map((r) => r.name.length))
|
|
40104
|
-
};
|
|
40700
|
+
const table = formatMetricBindingTable(metricBindings);
|
|
40105
40701
|
line();
|
|
40106
|
-
heading(
|
|
40107
|
-
|
|
40108
|
-
"ID".padEnd(widths.id),
|
|
40109
|
-
"Measure".padEnd(widths.measure),
|
|
40110
|
-
"Name".padEnd(widths.name),
|
|
40111
|
-
"Sentence"
|
|
40112
|
-
].join(" ")
|
|
40113
|
-
);
|
|
40114
|
-
for (const row of rows) {
|
|
40115
|
-
const line2 = [
|
|
40116
|
-
row.id.padEnd(widths.id),
|
|
40117
|
-
row.measure.padEnd(widths.measure),
|
|
40118
|
-
row.name.padEnd(widths.name),
|
|
40119
|
-
row.sentence
|
|
40120
|
-
].join(" ");
|
|
40121
|
-
line(row.archived ? `${line2} (archived)` : line2);
|
|
40122
|
-
}
|
|
40702
|
+
heading(table.heading);
|
|
40703
|
+
for (const row of table.rows) line(row);
|
|
40123
40704
|
line();
|
|
40124
40705
|
}
|
|
40125
40706
|
);
|
|
40126
40707
|
var metricBindingsCreateCommand = new Command("create").description("Create a new metric binding").option("--project <project>", "Project ID (PRJ-42 or numeric); defaults to local manifest").option(
|
|
40127
40708
|
"--measure <measure>",
|
|
40128
40709
|
"Measure (UNIQUE_CONVERSIONS_PER_VISITOR | TOTAL_EVENTS | UNIQUE_VISITORS_WHO_FIRED | TOTAL_VALUE_PER_VISITOR | TOTAL_VALUE | PERCENTILE | RATE | COMPOSITE)"
|
|
40129
|
-
).option("--direction <direction>", "Direction (INCREASE | DECREASE)", "INCREASE").option("--name <name>", "Display name").option("--description <text>", "Optional description").option("--metric <metricId>", "Source metric (required for single-metric measures)").option("--numerator-metric <metricId>", "Numerator metric (required for RATE)").option("--denominator-metric <metricId>", "Denominator metric (required for RATE)").option("--composition <json>", "COMPOSITE
|
|
40130
|
-
"--component <
|
|
40131
|
-
"Repeatable component
|
|
40710
|
+
).option("--direction <direction>", "Direction (INCREASE | DECREASE)", "INCREASE").option("--name <name>", "Display name").option("--description <text>", "Optional description").option("--metric <metricId>", "Source metric: MET-17, 17 or the long id (required for single-metric measures)").option("--numerator-metric <metricId>", "Numerator metric (required for RATE)").option("--denominator-metric <metricId>", "Denominator metric (required for RATE)").option("--composition <json>", "COMPOSITE components as a JSON array of { metricId, weight }, where metricId names a metric binding").option(
|
|
40711
|
+
"--component <bindingId:weight>",
|
|
40712
|
+
"Repeatable COMPOSITE component, a metric binding and its weight (e.g. --component MBD-8:0.5 --component MBD-9:0.5)",
|
|
40132
40713
|
(value, prev = []) => prev.concat(value),
|
|
40133
40714
|
[]
|
|
40134
40715
|
).option("--percentile <n>", "Percentile (required for PERCENTILE measure, 0 < n < 100)").option("--value-source <name>", "Numeric field on the metric (value/percentile measures)", "value").option("--winsorize-upper <pct>", "Upper winsorization percentile (enables winsorization)").option("--winsorize-lower <pct>", "Lower winsorization percentile (enables winsorization)").action(async (opts) => {
|
|
@@ -40142,29 +40723,26 @@ var metricBindingsCreateCommand = new Command("create").description("Create a ne
|
|
|
40142
40723
|
const shortId = resolveProjectShortId2(opts);
|
|
40143
40724
|
const measure = normalizeMeasure(opts.measure);
|
|
40144
40725
|
const api = new ApiClient(config);
|
|
40145
|
-
const
|
|
40146
|
-
const projectId = projectResponse.data.project.id;
|
|
40147
|
-
const body = buildCreateMetricBindingBody(projectId, measure, opts, opts.name);
|
|
40726
|
+
const body = buildCreateMetricBindingBody(measure, opts, opts.name);
|
|
40148
40727
|
const spinner = ora("Creating metric binding...").start();
|
|
40728
|
+
let response;
|
|
40149
40729
|
try {
|
|
40150
|
-
|
|
40151
|
-
const { metricBinding } = response.data;
|
|
40152
|
-
spinner.succeed(`Metric binding created: ${metricBinding.name} (${metricBindingIdLabel(metricBinding)})`);
|
|
40153
|
-
printMetricBindingDetails(metricBinding);
|
|
40730
|
+
response = await api.createMetricBinding(shortId, body);
|
|
40154
40731
|
} catch (err) {
|
|
40155
40732
|
spinner.fail("Failed to create metric binding");
|
|
40156
|
-
|
|
40157
|
-
|
|
40158
|
-
|
|
40159
|
-
|
|
40160
|
-
|
|
40161
|
-
|
|
40162
|
-
process.exit(EXIT.failure);
|
|
40733
|
+
failCommand(err);
|
|
40734
|
+
}
|
|
40735
|
+
const metricBinding = readMetricBinding(response.data);
|
|
40736
|
+
if (!metricBinding) {
|
|
40737
|
+
spinner.warn("Metric binding created. Run `avsb metric-bindings list` to see it.");
|
|
40738
|
+
return;
|
|
40163
40739
|
}
|
|
40740
|
+
spinner.succeed(`Metric binding created: ${metricBinding.name} (${metricBindingIdLabel(metricBinding)})`);
|
|
40741
|
+
printMetricBindingDetails(metricBinding);
|
|
40164
40742
|
});
|
|
40165
40743
|
var metricBindingsUpdateCommand = new Command("update").argument("<metricBindingId>", "Metric binding ID (cuid or numeric short ID)").description("Update a metric binding").option("--project <project>", "Project ID (PRJ-42 or numeric); defaults to local manifest").option("--name <name>", "New display name").option("--description <text>", "New description").option("--direction <direction>", "New direction (INCREASE | DECREASE)").option("--metric <metricId>", "New source metric").option("--numerator-metric <metricId>", "New numerator metric (RATE)").option("--denominator-metric <metricId>", "New denominator metric (RATE)").option("--composition <json>", "New COMPOSITE composition as JSON array of { metricId,weight}").option(
|
|
40166
|
-
"--component <
|
|
40167
|
-
"Repeatable component (replaces composition)",
|
|
40744
|
+
"--component <bindingId:weight>",
|
|
40745
|
+
"Repeatable COMPOSITE component, a metric binding and its weight (replaces the composition)",
|
|
40168
40746
|
(value, prev = []) => prev.concat(value),
|
|
40169
40747
|
[]
|
|
40170
40748
|
).option("--percentile <n>", "New percentile (PERCENTILE measure)").option("--value-source <name>", "New value source field name").option("--winsorize-upper <pct>", "New upper winsorization percentile").option("--winsorize-lower <pct>", "New lower winsorization percentile").option("--library", "Mark as a library metric binding").option("--no-library", "Mark as a non-library metric binding").action(async (metricBindingId, opts) => {
|
|
@@ -40172,54 +40750,73 @@ var metricBindingsUpdateCommand = new Command("update").argument("<metricBinding
|
|
|
40172
40750
|
const shortId = resolveProjectShortId2(opts);
|
|
40173
40751
|
const patch = buildUpdateMetricBindingPatch(opts);
|
|
40174
40752
|
if (Object.keys(patch).length === 0) {
|
|
40175
|
-
|
|
40176
|
-
|
|
40753
|
+
failCommand(
|
|
40754
|
+
new CliError("cli_usage", "No update fields provided. Pass at least one of --name, --description, --direction, etc.")
|
|
40755
|
+
);
|
|
40177
40756
|
}
|
|
40178
40757
|
const api = new ApiClient(config);
|
|
40179
40758
|
const spinner = ora("Updating metric binding...").start();
|
|
40759
|
+
let response;
|
|
40180
40760
|
try {
|
|
40181
|
-
|
|
40182
|
-
const { metricBinding } = response.data;
|
|
40183
|
-
spinner.succeed(`Metric binding updated: ${metricBinding.name} (${metricBindingIdLabel(metricBinding)})`);
|
|
40184
|
-
printMetricBindingDetails(metricBinding);
|
|
40761
|
+
response = await api.updateMetricBinding(shortId, metricBindingId, patch);
|
|
40185
40762
|
} catch (err) {
|
|
40186
40763
|
spinner.fail("Failed to update metric binding");
|
|
40187
40764
|
failCommand(err);
|
|
40188
40765
|
}
|
|
40766
|
+
const metricBinding = readMetricBinding(response.data);
|
|
40767
|
+
if (!metricBinding) {
|
|
40768
|
+
spinner.warn(`Metric binding ${metricBindingId} updated.`);
|
|
40769
|
+
return;
|
|
40770
|
+
}
|
|
40771
|
+
spinner.succeed(`Metric binding updated: ${metricBinding.name} (${metricBindingIdLabel(metricBinding)})`);
|
|
40772
|
+
printMetricBindingDetails(metricBinding);
|
|
40189
40773
|
});
|
|
40190
40774
|
var metricBindingsArchiveCommand = new Command("archive").argument("<metricBindingId>", "Metric binding ID").description("Archive a metric binding (soft-delete, preserves history)").option("--project <project>", "Project ID (PRJ-42 or numeric); defaults to local manifest").action(async (metricBindingId, opts) => {
|
|
40191
40775
|
const config = ensureLoggedIn();
|
|
40192
40776
|
const shortId = resolveProjectShortId2(opts);
|
|
40193
40777
|
const api = new ApiClient(config);
|
|
40194
40778
|
const spinner = ora("Archiving metric binding...").start();
|
|
40779
|
+
let response;
|
|
40195
40780
|
try {
|
|
40196
|
-
|
|
40197
|
-
const { metricBinding } = response.data;
|
|
40198
|
-
spinner.succeed(`Metric binding archived: ${metricBinding.name} (${metricBindingIdLabel(metricBinding)})`);
|
|
40781
|
+
response = await api.archiveMetricBinding(shortId, metricBindingId);
|
|
40199
40782
|
} catch (err) {
|
|
40200
40783
|
spinner.fail("Failed to archive metric binding");
|
|
40201
40784
|
failCommand(err);
|
|
40202
40785
|
}
|
|
40786
|
+
const metricBinding = readMetricBinding(response.data);
|
|
40787
|
+
spinner.succeed(
|
|
40788
|
+
metricBinding ? `Metric binding archived: ${metricBinding.name} (${metricBindingIdLabel(metricBinding)})` : `Metric binding ${metricBindingId} archived`
|
|
40789
|
+
);
|
|
40203
40790
|
});
|
|
40204
40791
|
var metricBindingsUnarchiveCommand = new Command("unarchive").argument("<metricBindingId>", "Metric binding ID").description("Restore an archived metric binding").option("--project <project>", "Project ID (PRJ-42 or numeric); defaults to local manifest").action(async (metricBindingId, opts) => {
|
|
40205
40792
|
const config = ensureLoggedIn();
|
|
40206
40793
|
const shortId = resolveProjectShortId2(opts);
|
|
40207
40794
|
const api = new ApiClient(config);
|
|
40208
40795
|
const spinner = ora("Restoring metric binding...").start();
|
|
40796
|
+
let response;
|
|
40209
40797
|
try {
|
|
40210
|
-
|
|
40211
|
-
const { metricBinding } = response.data;
|
|
40212
|
-
spinner.succeed(`Metric binding restored from archive: ${metricBinding.name} (${metricBindingIdLabel(metricBinding)})`);
|
|
40798
|
+
response = await api.unarchiveMetricBinding(shortId, metricBindingId);
|
|
40213
40799
|
} catch (err) {
|
|
40214
40800
|
spinner.fail("Failed to unarchive metric binding");
|
|
40215
40801
|
failCommand(err);
|
|
40216
40802
|
}
|
|
40803
|
+
const metricBinding = readMetricBinding(response.data);
|
|
40804
|
+
spinner.succeed(
|
|
40805
|
+
metricBinding ? `Metric binding restored from archive: ${metricBinding.name} (${metricBindingIdLabel(metricBinding)})` : `Metric binding ${metricBindingId} restored from archive`
|
|
40806
|
+
);
|
|
40217
40807
|
});
|
|
40218
40808
|
var metricBindingsDeleteCommand = new Command("delete").argument("<metricBindingId>", "Metric binding ID").description("Delete a metric binding (rejected if experiments still attach it)").option("--project <project>", "Project ID (PRJ-42 or numeric); defaults to local manifest").option("-f, --force", "Skip confirmation prompt").action(async (metricBindingId, opts) => {
|
|
40219
40809
|
const config = ensureLoggedIn();
|
|
40220
40810
|
const shortId = resolveProjectShortId2(opts);
|
|
40221
40811
|
const api = new ApiClient(config);
|
|
40222
40812
|
if (!opts.force) {
|
|
40813
|
+
if (!isInteractiveRun()) {
|
|
40814
|
+
failCommand(
|
|
40815
|
+
new CliError("cli_usage", `Deleting metric binding ${metricBindingId} needs a confirmation this run cannot ask for.`, [
|
|
40816
|
+
`Pass -f to delete without asking: \`avsb metric-bindings delete ${metricBindingId} -f\``
|
|
40817
|
+
])
|
|
40818
|
+
);
|
|
40819
|
+
}
|
|
40223
40820
|
const { proceed } = await esm_default12.prompt([
|
|
40224
40821
|
{
|
|
40225
40822
|
type: "confirm",
|
|
@@ -40239,28 +40836,21 @@ var metricBindingsDeleteCommand = new Command("delete").argument("<metricBinding
|
|
|
40239
40836
|
spinner.succeed(`Metric binding deleted: ${metricBindingId}`);
|
|
40240
40837
|
} catch (err) {
|
|
40241
40838
|
spinner.fail("Failed to delete metric binding");
|
|
40242
|
-
const message = err instanceof Error ? err.message : "Unknown error";
|
|
40243
40839
|
if (isConflictError2(err)) {
|
|
40244
40840
|
const refs = extractExperimentsReferencing(err);
|
|
40245
|
-
if (refs.length > 0) {
|
|
40246
|
-
error(`Cannot delete: referenced by ${refs.join(", ")}.`);
|
|
40247
|
-
} else {
|
|
40248
|
-
error(`Cannot delete: experiments still attach this metric binding. ${message}`);
|
|
40249
|
-
}
|
|
40841
|
+
if (refs.length > 0) error(`Attached to ${refs.join(", ")}.`);
|
|
40250
40842
|
info(
|
|
40251
40843
|
`Tip: run \`avsb metric-bindings archive ${metricBindingId}\` instead to hide it without losing history.`
|
|
40252
40844
|
);
|
|
40253
|
-
} else {
|
|
40254
|
-
error(message);
|
|
40255
40845
|
}
|
|
40256
|
-
|
|
40846
|
+
failCommand(err);
|
|
40257
40847
|
}
|
|
40258
40848
|
});
|
|
40259
40849
|
var metricBindingsCommand = new Command("metric-bindings").description("Manage saved/library metric bindings").addCommand(metricBindingsListCommand).addCommand(metricBindingsCreateCommand).addCommand(metricBindingsUpdateCommand).addCommand(metricBindingsArchiveCommand).addCommand(metricBindingsUnarchiveCommand).addCommand(metricBindingsDeleteCommand);
|
|
40260
40850
|
|
|
40261
40851
|
// src/commands/codegen.ts
|
|
40262
|
-
import
|
|
40263
|
-
import
|
|
40852
|
+
import path17 from "node:path";
|
|
40853
|
+
import fs13 from "node:fs/promises";
|
|
40264
40854
|
|
|
40265
40855
|
// src/lib/codegenApi.ts
|
|
40266
40856
|
var CodegenApiClient = class extends ApiClient {
|
|
@@ -40317,6 +40907,9 @@ function serialiseManifest(manifest) {
|
|
|
40317
40907
|
}
|
|
40318
40908
|
|
|
40319
40909
|
// src/lib/codegenCheck.ts
|
|
40910
|
+
function withoutPublishStamp(text) {
|
|
40911
|
+
return text.replace(/^(\/\/ Source: .*\bpublishedAt=)\S+/m, "$1*").replace(/^(\s*"publishedAt": )(?:"[^"\n]*"|null)/gm, "$1*");
|
|
40912
|
+
}
|
|
40320
40913
|
function diffArtifact(artifact) {
|
|
40321
40914
|
const expectedLines = artifact.expected.split("\n");
|
|
40322
40915
|
if (artifact.actual === null) {
|
|
@@ -40331,11 +40924,13 @@ function diffArtifact(artifact) {
|
|
|
40331
40924
|
};
|
|
40332
40925
|
}
|
|
40333
40926
|
const actual = artifact.actual.replace(/\r\n/g, "\n");
|
|
40334
|
-
|
|
40927
|
+
const expectedComparable = withoutPublishStamp(artifact.expected).split("\n");
|
|
40928
|
+
const actualComparable = withoutPublishStamp(actual).split("\n");
|
|
40929
|
+
if (expectedComparable.join("\n") === actualComparable.join("\n")) return null;
|
|
40335
40930
|
const actualLines = actual.split("\n");
|
|
40336
|
-
const shared = Math.min(
|
|
40931
|
+
const shared = Math.min(expectedComparable.length, actualComparable.length);
|
|
40337
40932
|
let index = 0;
|
|
40338
|
-
while (index < shared &&
|
|
40933
|
+
while (index < shared && expectedComparable[index] === actualComparable[index]) index += 1;
|
|
40339
40934
|
return {
|
|
40340
40935
|
path: artifact.path,
|
|
40341
40936
|
reason: "different",
|
|
@@ -40489,7 +41084,7 @@ function planArtifacts(datafile, targets) {
|
|
|
40489
41084
|
}
|
|
40490
41085
|
async function readIfPresent(filePath) {
|
|
40491
41086
|
try {
|
|
40492
|
-
return await
|
|
41087
|
+
return await fs13.readFile(filePath, "utf-8");
|
|
40493
41088
|
} catch (err) {
|
|
40494
41089
|
if (isMissingFile(err)) return null;
|
|
40495
41090
|
throw err;
|
|
@@ -40503,8 +41098,8 @@ async function writeArtifacts(artifacts) {
|
|
|
40503
41098
|
for (const artifact of artifacts) {
|
|
40504
41099
|
const existing = await readIfPresent(artifact.path);
|
|
40505
41100
|
if (existing === artifact.contents) continue;
|
|
40506
|
-
await
|
|
40507
|
-
await
|
|
41101
|
+
await fs13.mkdir(path17.dirname(artifact.path), { recursive: true });
|
|
41102
|
+
await fs13.writeFile(artifact.path, artifact.contents, "utf-8");
|
|
40508
41103
|
written.push(artifact.path);
|
|
40509
41104
|
}
|
|
40510
41105
|
return written;
|
|
@@ -40526,6 +41121,7 @@ function flagCountLine(datafile) {
|
|
|
40526
41121
|
}
|
|
40527
41122
|
async function runCheck(artifacts, datafile, targets) {
|
|
40528
41123
|
const drifts = diffArtifacts(await toComparisons(artifacts));
|
|
41124
|
+
const exitCode = drifts.length === 0 ? EXIT.success : EXIT.failure;
|
|
40529
41125
|
if (emitJsonResult({
|
|
40530
41126
|
upToDate: drifts.length === 0,
|
|
40531
41127
|
checked: artifacts.map((a) => a.path),
|
|
@@ -40534,25 +41130,25 @@ async function runCheck(artifacts, datafile, targets) {
|
|
|
40534
41130
|
flagCount: datafile.flags.length,
|
|
40535
41131
|
publishedAt: datafile.publishedAt ?? null
|
|
40536
41132
|
})) {
|
|
40537
|
-
|
|
41133
|
+
return exitCode;
|
|
40538
41134
|
}
|
|
40539
41135
|
if (drifts.length === 0) {
|
|
40540
41136
|
success(describeDriftSummary(drifts));
|
|
40541
41137
|
dim(` Checked ${artifacts.length === 1 ? "1 file" : `${artifacts.length} files`} against ${flagCountLine(datafile)} in ${targets.env}.`);
|
|
40542
|
-
|
|
41138
|
+
return exitCode;
|
|
40543
41139
|
}
|
|
40544
41140
|
error(describeDriftSummary(drifts));
|
|
40545
41141
|
for (const drift of drifts) {
|
|
40546
41142
|
for (const line2 of describeDrift(drift)) errorDetail(line2);
|
|
40547
41143
|
}
|
|
40548
41144
|
errorDetail("Run the same command without --check, then commit the result.");
|
|
40549
|
-
|
|
41145
|
+
return exitCode;
|
|
40550
41146
|
}
|
|
40551
41147
|
function reportWatchEvent(event, targets) {
|
|
40552
41148
|
switch (event.kind) {
|
|
40553
41149
|
case "polled":
|
|
40554
41150
|
if (event.changed) {
|
|
40555
|
-
success(`Updated ${event.written.map((p) =>
|
|
41151
|
+
success(`Updated ${event.written.map((p) => path17.relative(process.cwd(), p)).join(", ")}`);
|
|
40556
41152
|
}
|
|
40557
41153
|
return;
|
|
40558
41154
|
case "failed":
|
|
@@ -40643,6 +41239,7 @@ var codegenCommand = withOutputOptions(
|
|
|
40643
41239
|
const project = resolveProjectRefOrExit(opts);
|
|
40644
41240
|
const config = ensureLoggedIn();
|
|
40645
41241
|
const spinner = startSpinner("Fetching datafile...");
|
|
41242
|
+
let checkExitCode = null;
|
|
40646
41243
|
try {
|
|
40647
41244
|
const client = new CodegenApiClient(config);
|
|
40648
41245
|
const first = await client.getDatafileConditional(project.shortId, opts.env, null);
|
|
@@ -40655,15 +41252,15 @@ var codegenCommand = withOutputOptions(
|
|
|
40655
41252
|
const datafile = first.body;
|
|
40656
41253
|
spinner.succeed("Datafile fetched");
|
|
40657
41254
|
const targets = {
|
|
40658
|
-
typesPath:
|
|
40659
|
-
manifestPath: opts.manifest ?
|
|
41255
|
+
typesPath: path17.resolve(opts.output),
|
|
41256
|
+
manifestPath: opts.manifest ? path17.resolve(opts.manifest) : null,
|
|
40660
41257
|
projectShortId: project.shortId,
|
|
40661
41258
|
env: opts.env,
|
|
40662
41259
|
augment: opts.augment !== false
|
|
40663
41260
|
};
|
|
40664
41261
|
const artifacts = planArtifacts(datafile, targets);
|
|
40665
41262
|
if (opts.check) {
|
|
40666
|
-
await runCheck(artifacts, datafile, targets);
|
|
41263
|
+
checkExitCode = await runCheck(artifacts, datafile, targets);
|
|
40667
41264
|
return;
|
|
40668
41265
|
}
|
|
40669
41266
|
const written = await writeArtifacts(artifacts);
|
|
@@ -40693,12 +41290,14 @@ var codegenCommand = withOutputOptions(
|
|
|
40693
41290
|
} catch (err) {
|
|
40694
41291
|
spinner.fail("Codegen failed");
|
|
40695
41292
|
failCommand(err);
|
|
41293
|
+
} finally {
|
|
41294
|
+
if (checkExitCode !== null) process.exit(checkExitCode);
|
|
40696
41295
|
}
|
|
40697
41296
|
});
|
|
40698
41297
|
|
|
40699
41298
|
// src/commands/find-refs.ts
|
|
40700
|
-
import
|
|
40701
|
-
import
|
|
41299
|
+
import fs14 from "node:fs/promises";
|
|
41300
|
+
import path19 from "node:path";
|
|
40702
41301
|
|
|
40703
41302
|
// src/lib/findRefs.ts
|
|
40704
41303
|
var FLAG_GETTERS = /* @__PURE__ */ new Map([
|
|
@@ -40861,7 +41460,7 @@ function byKeyName(a, b) {
|
|
|
40861
41460
|
}
|
|
40862
41461
|
|
|
40863
41462
|
// src/lib/ignoreRules.ts
|
|
40864
|
-
import
|
|
41463
|
+
import path18 from "node:path";
|
|
40865
41464
|
var ALWAYS_SKIPPED_DIRECTORIES = [
|
|
40866
41465
|
".git",
|
|
40867
41466
|
"node_modules",
|
|
@@ -40965,7 +41564,7 @@ function relativeToBase(relPath, base) {
|
|
|
40965
41564
|
return relPath.slice(base.length + 1);
|
|
40966
41565
|
}
|
|
40967
41566
|
function toRelativePosix(root, target) {
|
|
40968
|
-
return
|
|
41567
|
+
return path18.relative(root, target).split(path18.sep).join("/");
|
|
40969
41568
|
}
|
|
40970
41569
|
|
|
40971
41570
|
// src/commands/find-refs.ts
|
|
@@ -41021,13 +41620,13 @@ async function scanTree(root, extraRules, knownKeys, maxFiles) {
|
|
|
41021
41620
|
if (truncated) return;
|
|
41022
41621
|
let entries;
|
|
41023
41622
|
try {
|
|
41024
|
-
entries = await
|
|
41623
|
+
entries = await fs14.readdir(dir, { withFileTypes: true });
|
|
41025
41624
|
} catch {
|
|
41026
41625
|
return;
|
|
41027
41626
|
}
|
|
41028
41627
|
let rules = inherited;
|
|
41029
41628
|
if (entries.some((e) => e.isFile() && e.name === ".gitignore")) {
|
|
41030
|
-
const contents = await readTextFile(
|
|
41629
|
+
const contents = await readTextFile(path19.join(dir, ".gitignore"));
|
|
41031
41630
|
if (contents !== null) {
|
|
41032
41631
|
rules = [...inherited, ...parseIgnoreFile(contents, toRelativePosix(root, dir))];
|
|
41033
41632
|
}
|
|
@@ -41035,7 +41634,7 @@ async function scanTree(root, extraRules, knownKeys, maxFiles) {
|
|
|
41035
41634
|
for (const entry of entries) {
|
|
41036
41635
|
if (truncated) return;
|
|
41037
41636
|
if (entry.isSymbolicLink()) continue;
|
|
41038
|
-
const full =
|
|
41637
|
+
const full = path19.join(dir, entry.name);
|
|
41039
41638
|
const rel = toRelativePosix(root, full);
|
|
41040
41639
|
if (entry.isDirectory()) {
|
|
41041
41640
|
if (ALWAYS_SKIPPED_DIRECTORIES.includes(entry.name)) continue;
|
|
@@ -41044,7 +41643,7 @@ async function scanTree(root, extraRules, knownKeys, maxFiles) {
|
|
|
41044
41643
|
continue;
|
|
41045
41644
|
}
|
|
41046
41645
|
if (!entry.isFile()) continue;
|
|
41047
|
-
if (!SOURCE_EXTENSIONS.has(
|
|
41646
|
+
if (!SOURCE_EXTENSIONS.has(path19.extname(entry.name))) continue;
|
|
41048
41647
|
if (isIgnored(rel, false, rules)) continue;
|
|
41049
41648
|
if (filesScanned >= maxFiles) {
|
|
41050
41649
|
truncated = true;
|
|
@@ -41061,9 +41660,9 @@ async function scanTree(root, extraRules, knownKeys, maxFiles) {
|
|
|
41061
41660
|
}
|
|
41062
41661
|
async function readTextFile(filePath) {
|
|
41063
41662
|
try {
|
|
41064
|
-
const stat4 = await
|
|
41663
|
+
const stat4 = await fs14.stat(filePath);
|
|
41065
41664
|
if (stat4.size > MAX_FILE_BYTES) return null;
|
|
41066
|
-
const contents = await
|
|
41665
|
+
const contents = await fs14.readFile(filePath, "utf-8");
|
|
41067
41666
|
return contents.includes("\0") ? null : contents;
|
|
41068
41667
|
} catch {
|
|
41069
41668
|
return null;
|
|
@@ -41129,7 +41728,7 @@ var findRefsCommand = withOutputOptions(
|
|
|
41129
41728
|
const api = new FlagsApiClient(config);
|
|
41130
41729
|
const registry = await readRegistry(api, project.shortId);
|
|
41131
41730
|
spinner.succeed(`Read ${registry.length} flag${registry.length === 1 ? "" : "s"}`);
|
|
41132
|
-
const root =
|
|
41731
|
+
const root = path19.resolve(opts.dir);
|
|
41133
41732
|
await assertDirectory(root, opts.dir);
|
|
41134
41733
|
const extraRules = (opts.ignore ?? []).flatMap((pattern) => parseIgnoreFile(pattern));
|
|
41135
41734
|
const knownKeys = new Set(registry.map((f) => f.key));
|
|
@@ -41164,7 +41763,7 @@ function parseMaxFiles(raw) {
|
|
|
41164
41763
|
}
|
|
41165
41764
|
async function assertDirectory(root, asTyped) {
|
|
41166
41765
|
try {
|
|
41167
|
-
const stat4 = await
|
|
41766
|
+
const stat4 = await fs14.stat(root);
|
|
41168
41767
|
if (stat4.isDirectory()) return;
|
|
41169
41768
|
} catch {
|
|
41170
41769
|
}
|
|
@@ -41172,11 +41771,11 @@ async function assertDirectory(root, asTyped) {
|
|
|
41172
41771
|
}
|
|
41173
41772
|
|
|
41174
41773
|
// src/commands/datasets.ts
|
|
41175
|
-
import
|
|
41774
|
+
import fs16 from "node:fs";
|
|
41176
41775
|
|
|
41177
41776
|
// src/commands/datasets.helpers.ts
|
|
41178
|
-
import
|
|
41179
|
-
import
|
|
41777
|
+
import fs15 from "node:fs";
|
|
41778
|
+
import path20 from "node:path";
|
|
41180
41779
|
function parseVersionArg(raw) {
|
|
41181
41780
|
const normalized = raw.trim().replace(/^[vV]/, "");
|
|
41182
41781
|
if (!/^\d+$/.test(normalized)) {
|
|
@@ -41191,13 +41790,13 @@ function parseVersionArg(raw) {
|
|
|
41191
41790
|
return shortId;
|
|
41192
41791
|
}
|
|
41193
41792
|
function validateNdjsonPath(filePath) {
|
|
41194
|
-
const resolved =
|
|
41195
|
-
if (!
|
|
41793
|
+
const resolved = path20.resolve(filePath);
|
|
41794
|
+
if (!fs15.existsSync(resolved) || !fs15.statSync(resolved).isFile()) {
|
|
41196
41795
|
throw new Error(`File not found: ${resolved}`);
|
|
41197
41796
|
}
|
|
41198
|
-
if (
|
|
41797
|
+
if (path20.extname(resolved).toLowerCase() !== ".ndjson") {
|
|
41199
41798
|
throw new Error(
|
|
41200
|
-
`Expected an .ndjson file, got "${
|
|
41799
|
+
`Expected an .ndjson file, got "${path20.basename(resolved)}". Convert CSV files in the dashboard upload modal, or export NDJSON directly.`
|
|
41201
41800
|
);
|
|
41202
41801
|
}
|
|
41203
41802
|
return resolved;
|
|
@@ -41386,7 +41985,7 @@ var datasetsPushCommand = new Command("push").argument("<slug>", "Dataset slug")
|
|
|
41386
41985
|
try {
|
|
41387
41986
|
const result = await runPushFlow(api, projectShortId, {
|
|
41388
41987
|
slug,
|
|
41389
|
-
fileBody:
|
|
41988
|
+
fileBody: fs16.readFileSync(resolvedPath),
|
|
41390
41989
|
label: opts.label,
|
|
41391
41990
|
activate: opts.activate,
|
|
41392
41991
|
poll: { timeoutMs: timeoutSeconds * 1e3 },
|
|
@@ -41480,6 +42079,12 @@ var datasetsRollbackCommand = new Command("rollback").argument("<slug>", "Datase
|
|
|
41480
42079
|
fail(spinner, "Failed to resolve rollback target", err);
|
|
41481
42080
|
}
|
|
41482
42081
|
if (!opts.force) {
|
|
42082
|
+
const refusal = refusalWithoutPrompt({
|
|
42083
|
+
code: "cli_usage",
|
|
42084
|
+
message: `Rolling back "${slug}" (${summary}) needs a confirmation this run cannot ask for.`,
|
|
42085
|
+
hints: [`Pass -f to roll back without asking: \`avsb datasets rollback ${slug} -f\``]
|
|
42086
|
+
});
|
|
42087
|
+
if (refusal) failCommand(refusal);
|
|
41483
42088
|
const { proceed } = await esm_default12.prompt([
|
|
41484
42089
|
{
|
|
41485
42090
|
type: "confirm",
|
|
@@ -41755,7 +42360,7 @@ function printSecretHint() {
|
|
|
41755
42360
|
" A vs B shows a webhook signing secret only when the webhook is created and when its"
|
|
41756
42361
|
);
|
|
41757
42362
|
dim(" secret is rotated, so copy it then. If you no longer have it, rotate it:");
|
|
41758
|
-
dim(" Dashboard:
|
|
42363
|
+
dim(" Dashboard: Organization Settings > Integrations > Webhooks");
|
|
41759
42364
|
dim(" API: POST /api/v1/projects/{projectId}/webhooks/{webhookId}/secret");
|
|
41760
42365
|
dim(
|
|
41761
42366
|
" Put it in AVSB_WEBHOOK_SECRET and verify with verifyWebhookSignature from @avsbhq/node."
|
|
@@ -41777,8 +42382,7 @@ var listenCommand = new Command("listen").description("Watch this project's webh
|
|
|
41777
42382
|
if (opts.forward) {
|
|
41778
42383
|
const validated = validateForwardUrl(opts.forward);
|
|
41779
42384
|
if (!validated.ok) {
|
|
41780
|
-
|
|
41781
|
-
process.exit(EXIT.failure);
|
|
42385
|
+
failCommand(new CliError("cli_usage", validated.reason));
|
|
41782
42386
|
}
|
|
41783
42387
|
forwardUrl = validated.url;
|
|
41784
42388
|
}
|
|
@@ -42304,7 +42908,7 @@ var flagsCreateCommand = withOutputOptions(
|
|
|
42304
42908
|
var flagsUpdateCommand = withOutputOptions(
|
|
42305
42909
|
withProjectOption(
|
|
42306
42910
|
new Command("update").argument("<flag>", "Flag key, numeric short id, or id").description("Rename a flag or change its description").option("--name <name>", "New display name").option("--description <text>", 'New description. Pass "" to clear it').option("--key <key>", "New key (refused once the flag has served traffic)").option(
|
|
42307
|
-
"--if-match <
|
|
42911
|
+
"--if-match <version>",
|
|
42308
42912
|
"Only write if the flag still matches this version (from `avsb flags get`)"
|
|
42309
42913
|
).option("--force", "Write without checking the version first")
|
|
42310
42914
|
)
|
|
@@ -42608,46 +43212,127 @@ var openCommand = withOutputOptions(
|
|
|
42608
43212
|
|
|
42609
43213
|
// src/commands/completion.ts
|
|
42610
43214
|
var COMPLETION_SHELLS = ["bash", "zsh", "fish"];
|
|
42611
|
-
|
|
42612
|
-
|
|
43215
|
+
var ARGUMENT_CHOICES = {
|
|
43216
|
+
completion: COMPLETION_SHELLS,
|
|
43217
|
+
open: OPEN_TARGETS
|
|
43218
|
+
};
|
|
43219
|
+
function flagsOf(command, isRoot) {
|
|
43220
|
+
const flags = command.options.map((option) => option.long).filter((long) => typeof long === "string");
|
|
43221
|
+
flags.push("--help");
|
|
43222
|
+
if (isRoot) flags.push("--version");
|
|
43223
|
+
return [...new Set(flags)].sort();
|
|
43224
|
+
}
|
|
43225
|
+
function completionEntries(program3) {
|
|
43226
|
+
const entries = [];
|
|
43227
|
+
function visit(command, path23) {
|
|
43228
|
+
const subcommands = command.commands.map((child) => child.name()).sort();
|
|
43229
|
+
entries.push({
|
|
43230
|
+
path: path23,
|
|
43231
|
+
words: [...subcommands, ...ARGUMENT_CHOICES[path23] ?? []],
|
|
43232
|
+
flags: flagsOf(command, path23 === "")
|
|
43233
|
+
});
|
|
43234
|
+
for (const child of command.commands) {
|
|
43235
|
+
visit(child, path23 === "" ? child.name() : `${path23} ${child.name()}`);
|
|
43236
|
+
}
|
|
43237
|
+
}
|
|
43238
|
+
visit(program3, "");
|
|
43239
|
+
return entries;
|
|
42613
43240
|
}
|
|
42614
|
-
|
|
43241
|
+
var quoteBash = (value) => `"${value}"`;
|
|
43242
|
+
function bashCompletion(entries) {
|
|
43243
|
+
const known = entries.filter((entry) => entry.path !== "").map((entry) => quoteBash(entry.path));
|
|
43244
|
+
const cases = entries.map((entry) => {
|
|
43245
|
+
const offer = [...entry.words, ...entry.flags].join(" ");
|
|
43246
|
+
return ` ${quoteBash(entry.path)}) offer="${offer}" ;;`;
|
|
43247
|
+
});
|
|
42615
43248
|
return `# avsb completion for bash. Add to ~/.bashrc:
|
|
42616
43249
|
# eval "$(avsb completion bash)"
|
|
42617
43250
|
_avsb_completions() {
|
|
42618
|
-
local
|
|
42619
|
-
|
|
43251
|
+
local cur="\${COMP_WORDS[COMP_CWORD]}" cmdpath="" word candidate i offer=""
|
|
43252
|
+
for ((i = 1; i < COMP_CWORD; i++)); do
|
|
43253
|
+
word="\${COMP_WORDS[i]}"
|
|
43254
|
+
[[ "$word" == -* ]] && continue
|
|
43255
|
+
candidate="\${cmdpath:+$cmdpath }$word"
|
|
43256
|
+
case "$candidate" in
|
|
43257
|
+
${known.length > 0 ? known.join("|") : '"__none__"'}) cmdpath="$candidate" ;;
|
|
43258
|
+
esac
|
|
43259
|
+
done
|
|
43260
|
+
case "$cmdpath" in
|
|
43261
|
+
${cases.join("\n")}
|
|
43262
|
+
esac
|
|
43263
|
+
COMPREPLY=($(compgen -W "$offer" -- "$cur"))
|
|
42620
43264
|
}
|
|
42621
43265
|
complete -F _avsb_completions avsb`;
|
|
42622
43266
|
}
|
|
42623
|
-
|
|
43267
|
+
var quoteZsh = (value) => `'${value}'`;
|
|
43268
|
+
function zshCompletion(entries) {
|
|
43269
|
+
const known = entries.filter((entry) => entry.path !== "").map((entry) => quoteZsh(entry.path));
|
|
43270
|
+
const cases = entries.map((entry) => {
|
|
43271
|
+
const offer = [...entry.words, ...entry.flags].map(quoteZsh).join(" ");
|
|
43272
|
+
return ` (${quoteZsh(entry.path)}) offer=(${offer}) ;;`;
|
|
43273
|
+
});
|
|
42624
43274
|
return `# avsb completion for zsh. Add to ~/.zshrc:
|
|
42625
43275
|
# eval "$(avsb completion zsh)"
|
|
42626
43276
|
_avsb() {
|
|
42627
|
-
local
|
|
42628
|
-
|
|
42629
|
-
|
|
43277
|
+
local cmdpath="" word candidate i
|
|
43278
|
+
local -a offer
|
|
43279
|
+
for ((i = 2; i < CURRENT; i++)); do
|
|
43280
|
+
word="\${words[i]}"
|
|
43281
|
+
[[ "$word" == -* ]] && continue
|
|
43282
|
+
candidate="\${cmdpath:+$cmdpath }$word"
|
|
43283
|
+
case "$candidate" in
|
|
43284
|
+
(${known.length > 0 ? known.join("|") : "'__none__'"}) cmdpath="$candidate" ;;
|
|
43285
|
+
esac
|
|
43286
|
+
done
|
|
43287
|
+
case "$cmdpath" in
|
|
43288
|
+
${cases.join("\n")}
|
|
43289
|
+
esac
|
|
43290
|
+
compadd -- $offer
|
|
42630
43291
|
}
|
|
42631
43292
|
compdef _avsb avsb`;
|
|
42632
43293
|
}
|
|
42633
|
-
|
|
42634
|
-
|
|
42635
|
-
|
|
42636
|
-
|
|
43294
|
+
var quoteFish = (value) => `'${value}'`;
|
|
43295
|
+
function fishCompletion(entries) {
|
|
43296
|
+
const known = entries.filter((entry) => entry.path !== "").map((entry) => quoteFish(entry.path));
|
|
43297
|
+
const lines = [];
|
|
43298
|
+
for (const entry of entries) {
|
|
43299
|
+
const condition = `'test (__avsb_path) = "${entry.path}"'`;
|
|
43300
|
+
if (entry.words.length > 0) {
|
|
43301
|
+
lines.push(`complete -c avsb -n ${condition} -f -a ${quoteFish(entry.words.join(" "))}`);
|
|
43302
|
+
}
|
|
43303
|
+
for (const flag of entry.flags) {
|
|
43304
|
+
lines.push(`complete -c avsb -n ${condition} -l ${flag.replace(/^--/, "")}`);
|
|
43305
|
+
}
|
|
43306
|
+
}
|
|
42637
43307
|
return [
|
|
42638
43308
|
"# avsb completion for fish. Save as:",
|
|
42639
43309
|
"# avsb completion fish > ~/.config/fish/completions/avsb.fish",
|
|
43310
|
+
`set -g __avsb_known_paths ${known.join(" ")}`,
|
|
43311
|
+
// Drops the first token (`avsb`) with `set -e` rather than a [2..-1]
|
|
43312
|
+
// slice, and always prints exactly one line, so `test (__avsb_path) = ""`
|
|
43313
|
+
// compares one value even at the top level.
|
|
43314
|
+
"function __avsb_path",
|
|
43315
|
+
" set -l tokens (commandline -opc)",
|
|
43316
|
+
" set -e tokens[1]",
|
|
43317
|
+
" set -l cmdpath",
|
|
43318
|
+
" for word in $tokens",
|
|
43319
|
+
" string match -q -- '-*' $word; and continue",
|
|
43320
|
+
" set -l candidate (string join ' ' $cmdpath $word)",
|
|
43321
|
+
" contains -- $candidate $__avsb_known_paths; and set cmdpath $cmdpath $word",
|
|
43322
|
+
" end",
|
|
43323
|
+
" echo (string join ' ' $cmdpath)",
|
|
43324
|
+
"end",
|
|
42640
43325
|
...lines
|
|
42641
43326
|
].join("\n");
|
|
42642
43327
|
}
|
|
42643
|
-
function completionScript(shell,
|
|
43328
|
+
function completionScript(shell, entries) {
|
|
42644
43329
|
switch (shell) {
|
|
42645
43330
|
case "bash":
|
|
42646
|
-
return bashCompletion(
|
|
43331
|
+
return bashCompletion(entries);
|
|
42647
43332
|
case "zsh":
|
|
42648
|
-
return zshCompletion(
|
|
43333
|
+
return zshCompletion(entries);
|
|
42649
43334
|
case "fish":
|
|
42650
|
-
return fishCompletion(
|
|
43335
|
+
return fishCompletion(entries);
|
|
42651
43336
|
}
|
|
42652
43337
|
}
|
|
42653
43338
|
function buildCompletionCommand(program3) {
|
|
@@ -42661,16 +43346,16 @@ function buildCompletionCommand(program3) {
|
|
|
42661
43346
|
])
|
|
42662
43347
|
);
|
|
42663
43348
|
}
|
|
42664
|
-
line(completionScript(requested,
|
|
43349
|
+
line(completionScript(requested, completionEntries(program3)));
|
|
42665
43350
|
});
|
|
42666
43351
|
}
|
|
42667
43352
|
|
|
42668
43353
|
// src/lib/updateNotifier.ts
|
|
42669
|
-
import
|
|
43354
|
+
import fs17 from "node:fs";
|
|
42670
43355
|
import os4 from "node:os";
|
|
42671
|
-
import
|
|
43356
|
+
import path21 from "node:path";
|
|
42672
43357
|
var REGISTRY_URL = "https://registry.npmjs.org/@avsbhq/cli/latest";
|
|
42673
|
-
var CACHE_FILE =
|
|
43358
|
+
var CACHE_FILE = path21.join(os4.homedir(), ".avsb", "update-check.json");
|
|
42674
43359
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
42675
43360
|
var FETCH_TIMEOUT_MS = 1500;
|
|
42676
43361
|
function isNewerVersion(latest, current) {
|
|
@@ -42710,8 +43395,8 @@ function updateMessage(latest, current) {
|
|
|
42710
43395
|
}
|
|
42711
43396
|
function writeCache(cache) {
|
|
42712
43397
|
try {
|
|
42713
|
-
|
|
42714
|
-
|
|
43398
|
+
fs17.mkdirSync(path21.dirname(CACHE_FILE), { recursive: true });
|
|
43399
|
+
fs17.writeFileSync(CACHE_FILE, JSON.stringify(cache));
|
|
42715
43400
|
} catch {
|
|
42716
43401
|
}
|
|
42717
43402
|
}
|
|
@@ -42735,7 +43420,7 @@ async function checkForUpdate(currentVersion, notify) {
|
|
|
42735
43420
|
}
|
|
42736
43421
|
let cache = null;
|
|
42737
43422
|
try {
|
|
42738
|
-
cache = readCache(() =>
|
|
43423
|
+
cache = readCache(() => fs17.readFileSync(CACHE_FILE, "utf-8"));
|
|
42739
43424
|
} catch {
|
|
42740
43425
|
cache = null;
|
|
42741
43426
|
}
|
|
@@ -42748,8 +43433,8 @@ async function checkForUpdate(currentVersion, notify) {
|
|
|
42748
43433
|
}
|
|
42749
43434
|
|
|
42750
43435
|
// src/cli.ts
|
|
42751
|
-
var __dirname =
|
|
42752
|
-
var pkg = JSON.parse(
|
|
43436
|
+
var __dirname = path22.dirname(fileURLToPath2(import.meta.url));
|
|
43437
|
+
var pkg = JSON.parse(fs18.readFileSync(path22.resolve(__dirname, "..", "package.json"), "utf-8"));
|
|
42753
43438
|
var program2 = new Command();
|
|
42754
43439
|
program2.name("avsb").description(
|
|
42755
43440
|
"A vs B command line tool: experiments, flags, project scripts, metrics, datasets, webhooks, and typed flags"
|