@avsbhq/cli 3.7.0 → 3.8.2
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 +1371 -760
- package/dist/types/avsb.d.ts +9 -5
- package/dist/types/variation-code.d.ts +2 -8
- 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 = {
|
|
@@ -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
|
}
|
|
@@ -34135,6 +34338,7 @@ var ApiClient = class extends ApiTransport {
|
|
|
34135
34338
|
};
|
|
34136
34339
|
|
|
34137
34340
|
// src/lib/flagsApi.ts
|
|
34341
|
+
var IF_MATCH_HEADER = "X-Avsb-If-Match";
|
|
34138
34342
|
var FlagsApiClient = class extends ApiClient {
|
|
34139
34343
|
v1Project(projectRef) {
|
|
34140
34344
|
return `/api/v1/projects/${encodeURIComponent(String(projectRef))}`;
|
|
@@ -34153,9 +34357,9 @@ var FlagsApiClient = class extends ApiClient {
|
|
|
34153
34357
|
}
|
|
34154
34358
|
/**
|
|
34155
34359
|
* One flag, WITH its ETag. The ETag is what makes a safe update possible:
|
|
34156
|
-
* `avsb flags update` sends it back
|
|
34157
|
-
* made between the read and the write is refused (412)
|
|
34158
|
-
* overwritten.
|
|
34360
|
+
* `avsb flags update` sends it back on the conditional-write header, so a
|
|
34361
|
+
* change someone else made between the read and the write is refused (412)
|
|
34362
|
+
* instead of silently overwritten.
|
|
34159
34363
|
*/
|
|
34160
34364
|
async getFlag(projectRef, flagRef) {
|
|
34161
34365
|
return this.requestWithMeta(
|
|
@@ -34179,7 +34383,7 @@ var FlagsApiClient = class extends ApiClient {
|
|
|
34179
34383
|
{
|
|
34180
34384
|
method: "PATCH",
|
|
34181
34385
|
body: JSON.stringify(patch),
|
|
34182
|
-
headers: ifMatch ? {
|
|
34386
|
+
headers: ifMatch ? { [IF_MATCH_HEADER]: ifMatch } : {}
|
|
34183
34387
|
}
|
|
34184
34388
|
);
|
|
34185
34389
|
}
|
|
@@ -34410,9 +34614,9 @@ function servedValueFor(flag) {
|
|
|
34410
34614
|
if (!variation) return null;
|
|
34411
34615
|
return { variationKey: variation.key, value: variation.value };
|
|
34412
34616
|
}
|
|
34413
|
-
function describeServedValue(flagKey, served) {
|
|
34617
|
+
function describeServedValue(flagKey, served, environmentKey = "production") {
|
|
34414
34618
|
if (!served) {
|
|
34415
|
-
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\`.`;
|
|
34416
34620
|
}
|
|
34417
34621
|
return `${flagKey} serves ${JSON.stringify(served.value)} (variation ${served.variationKey}) when no targeting rule matches.`;
|
|
34418
34622
|
}
|
|
@@ -34425,12 +34629,46 @@ function canPrompt(ctx) {
|
|
|
34425
34629
|
if (envFlagEnabled(ctx.env.CI)) return false;
|
|
34426
34630
|
return true;
|
|
34427
34631
|
}
|
|
34428
|
-
function
|
|
34429
|
-
return
|
|
34632
|
+
function currentPromptContext(options = {}) {
|
|
34633
|
+
return {
|
|
34430
34634
|
input: options.input !== false,
|
|
34431
34635
|
quiet: isQuiet(),
|
|
34432
34636
|
isTty: process.stdin.isTTY === true,
|
|
34433
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();
|
|
34434
34672
|
});
|
|
34435
34673
|
}
|
|
34436
34674
|
|
|
@@ -34513,10 +34751,40 @@ function unquote(value) {
|
|
|
34513
34751
|
return quote2 === '"' ? body.replace(/\\(["\\])/g, "$1") : body;
|
|
34514
34752
|
}
|
|
34515
34753
|
|
|
34516
|
-
// src/
|
|
34754
|
+
// src/lib/gitignore.ts
|
|
34517
34755
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
34518
34756
|
import fs3 from "node:fs";
|
|
34519
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";
|
|
34520
34788
|
|
|
34521
34789
|
// src/lib/jsonTypeInference.ts
|
|
34522
34790
|
var MAX_JSON_DEPTH = 4;
|
|
@@ -35022,14 +35290,14 @@ console.log('${input.flagKey}', flag.value, 'from', flag.source)
|
|
|
35022
35290
|
|
|
35023
35291
|
// src/commands/init.write.ts
|
|
35024
35292
|
function exampleFileExists(dir, profile, overridePath) {
|
|
35025
|
-
return
|
|
35293
|
+
return fs4.existsSync(resolveExamplePath(dir, profile, overridePath));
|
|
35026
35294
|
}
|
|
35027
35295
|
function resolveExamplePath(dir, profile, overridePath) {
|
|
35028
|
-
return
|
|
35296
|
+
return path5.resolve(dir, overridePath ?? profile.exampleFile);
|
|
35029
35297
|
}
|
|
35030
35298
|
function writeExampleFile(input) {
|
|
35031
35299
|
const filePath = resolveExamplePath(input.dir, input.profile, input.overridePath);
|
|
35032
|
-
if (
|
|
35300
|
+
if (fs4.existsSync(filePath)) {
|
|
35033
35301
|
return { filePath, wrote: false, existed: true };
|
|
35034
35302
|
}
|
|
35035
35303
|
const contents = buildSample({
|
|
@@ -35041,14 +35309,14 @@ function writeExampleFile(input) {
|
|
|
35041
35309
|
if (contents === null) {
|
|
35042
35310
|
return { filePath, wrote: false, existed: false };
|
|
35043
35311
|
}
|
|
35044
|
-
|
|
35045
|
-
|
|
35312
|
+
fs4.mkdirSync(path5.dirname(filePath), { recursive: true });
|
|
35313
|
+
fs4.writeFileSync(filePath, contents, "utf-8");
|
|
35046
35314
|
return { filePath, wrote: true, existed: false };
|
|
35047
35315
|
}
|
|
35048
35316
|
function runInstall(dir, manager, npmPackage, options) {
|
|
35049
35317
|
const { command, args } = installArgv(manager, npmPackage);
|
|
35050
35318
|
const printable = [command, ...args].join(" ");
|
|
35051
|
-
const result =
|
|
35319
|
+
const result = spawnSync3(command, args, {
|
|
35052
35320
|
cwd: dir,
|
|
35053
35321
|
// A quiet or JSON run must leave stdout carrying only the result, so the
|
|
35054
35322
|
// package manager's own output is captured and dropped instead of printed.
|
|
@@ -35078,10 +35346,10 @@ async function runCodegen(input) {
|
|
|
35078
35346
|
projectId: String(input.projectShortId),
|
|
35079
35347
|
env: input.environmentKey
|
|
35080
35348
|
});
|
|
35081
|
-
const filePath =
|
|
35349
|
+
const filePath = path5.resolve(input.dir, input.outputPath ?? DEFAULT_CODEGEN_OUTPUT);
|
|
35082
35350
|
try {
|
|
35083
|
-
|
|
35084
|
-
|
|
35351
|
+
fs4.mkdirSync(path5.dirname(filePath), { recursive: true });
|
|
35352
|
+
fs4.writeFileSync(filePath, generated, "utf-8");
|
|
35085
35353
|
} catch (err) {
|
|
35086
35354
|
throw new CliError(
|
|
35087
35355
|
"cli_io_failed",
|
|
@@ -35092,14 +35360,14 @@ async function runCodegen(input) {
|
|
|
35092
35360
|
}
|
|
35093
35361
|
function readFileOrNull(filePath) {
|
|
35094
35362
|
try {
|
|
35095
|
-
return
|
|
35363
|
+
return fs4.readFileSync(filePath, "utf-8");
|
|
35096
35364
|
} catch {
|
|
35097
35365
|
return null;
|
|
35098
35366
|
}
|
|
35099
35367
|
}
|
|
35100
35368
|
function listDirectory(dir) {
|
|
35101
35369
|
try {
|
|
35102
|
-
return
|
|
35370
|
+
return fs4.readdirSync(dir);
|
|
35103
35371
|
} catch {
|
|
35104
35372
|
return [];
|
|
35105
35373
|
}
|
|
@@ -35107,7 +35375,7 @@ function listDirectory(dir) {
|
|
|
35107
35375
|
|
|
35108
35376
|
// src/commands/init.execute.ts
|
|
35109
35377
|
async function executePlan(input) {
|
|
35110
|
-
const files = { env: null, envOutcome: null, example: null, codegen: null };
|
|
35378
|
+
const files = { env: null, envOutcome: null, example: null, codegen: null, gitignore: null };
|
|
35111
35379
|
let install = null;
|
|
35112
35380
|
const { profile, environment } = input;
|
|
35113
35381
|
if (stepFor(input.plan, "env").action === "write" && environment && profile.envFile && profile.envVar) {
|
|
@@ -35120,6 +35388,11 @@ async function executePlan(input) {
|
|
|
35120
35388
|
files.env = result.filePath;
|
|
35121
35389
|
files.envOutcome = result.outcome;
|
|
35122
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
|
+
}
|
|
35123
35396
|
}
|
|
35124
35397
|
const installStep = stepFor(input.plan, "install");
|
|
35125
35398
|
if (profile.sdkPackage) {
|
|
@@ -35187,17 +35460,17 @@ async function watchForCheckIn(api, projectShortId, environment, wait) {
|
|
|
35187
35460
|
|
|
35188
35461
|
// src/lib/projectScaffold.ts
|
|
35189
35462
|
import { createHash as createHash2 } from "node:crypto";
|
|
35190
|
-
import
|
|
35191
|
-
import
|
|
35463
|
+
import fs8 from "node:fs";
|
|
35464
|
+
import path9 from "node:path";
|
|
35192
35465
|
|
|
35193
35466
|
// src/lib/scaffold.ts
|
|
35194
|
-
import
|
|
35195
|
-
import
|
|
35467
|
+
import fs7 from "node:fs";
|
|
35468
|
+
import path8 from "node:path";
|
|
35196
35469
|
|
|
35197
35470
|
// src/lib/manifest.ts
|
|
35198
35471
|
import { createHash } from "node:crypto";
|
|
35199
|
-
import
|
|
35200
|
-
import
|
|
35472
|
+
import fs5 from "node:fs";
|
|
35473
|
+
import path6 from "node:path";
|
|
35201
35474
|
var MANIFEST_FILENAME = ".avsb.json";
|
|
35202
35475
|
function parseExperimentInput(input) {
|
|
35203
35476
|
const urlMatch = input.match(/experiments\/(\d+)/);
|
|
@@ -35212,12 +35485,12 @@ function parseExperimentInput(input) {
|
|
|
35212
35485
|
function findManifest(startDir) {
|
|
35213
35486
|
let dir = startDir ?? process.cwd();
|
|
35214
35487
|
for (let i = 0; i < 10; i++) {
|
|
35215
|
-
const manifestPath =
|
|
35216
|
-
if (
|
|
35217
|
-
const raw =
|
|
35488
|
+
const manifestPath = path6.join(dir, MANIFEST_FILENAME);
|
|
35489
|
+
if (fs5.existsSync(manifestPath)) {
|
|
35490
|
+
const raw = fs5.readFileSync(manifestPath, "utf-8");
|
|
35218
35491
|
return JSON.parse(raw);
|
|
35219
35492
|
}
|
|
35220
|
-
const parent =
|
|
35493
|
+
const parent = path6.dirname(dir);
|
|
35221
35494
|
if (parent === dir) break;
|
|
35222
35495
|
dir = parent;
|
|
35223
35496
|
}
|
|
@@ -35226,17 +35499,17 @@ function findManifest(startDir) {
|
|
|
35226
35499
|
function findManifestDir(startDir) {
|
|
35227
35500
|
let dir = startDir ?? process.cwd();
|
|
35228
35501
|
for (let i = 0; i < 10; i++) {
|
|
35229
|
-
const manifestPath =
|
|
35230
|
-
if (
|
|
35231
|
-
const parent =
|
|
35502
|
+
const manifestPath = path6.join(dir, MANIFEST_FILENAME);
|
|
35503
|
+
if (fs5.existsSync(manifestPath)) return dir;
|
|
35504
|
+
const parent = path6.dirname(dir);
|
|
35232
35505
|
if (parent === dir) break;
|
|
35233
35506
|
dir = parent;
|
|
35234
35507
|
}
|
|
35235
35508
|
return null;
|
|
35236
35509
|
}
|
|
35237
35510
|
function writeManifest(dir, manifest) {
|
|
35238
|
-
const manifestPath =
|
|
35239
|
-
|
|
35511
|
+
const manifestPath = path6.join(dir, MANIFEST_FILENAME);
|
|
35512
|
+
fs5.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
35240
35513
|
}
|
|
35241
35514
|
function readManifestOrExit(startDir) {
|
|
35242
35515
|
const dir = findManifestDir(startDir);
|
|
@@ -35274,30 +35547,30 @@ function computeSyncHashes(triggersJs, variations) {
|
|
|
35274
35547
|
}
|
|
35275
35548
|
|
|
35276
35549
|
// src/lib/snippetTypeDefs.ts
|
|
35277
|
-
import
|
|
35278
|
-
import
|
|
35550
|
+
import fs6 from "node:fs";
|
|
35551
|
+
import path7 from "node:path";
|
|
35279
35552
|
import { createRequire } from "node:module";
|
|
35280
35553
|
import { fileURLToPath } from "node:url";
|
|
35281
35554
|
var SNIPPET_TYPES_PACKAGE = "@avsbhq/snippet-types";
|
|
35282
35555
|
var SNIPPET_TYPE_DEF_FILES = ["avsb.d.ts", "variation-code.d.ts"];
|
|
35283
35556
|
var MAX_WALK_UP = 24;
|
|
35284
35557
|
function snippetTypeDefCandidates(filename, lookup) {
|
|
35285
|
-
const candidates = [
|
|
35558
|
+
const candidates = [path7.join(lookup.moduleDir, "types", filename)];
|
|
35286
35559
|
const packageDir = lookup.packageDir();
|
|
35287
|
-
if (packageDir) candidates.push(
|
|
35560
|
+
if (packageDir) candidates.push(path7.join(packageDir, filename));
|
|
35288
35561
|
const isRepoRoot = lookup.isRepoRoot ?? defaultIsRepoRoot;
|
|
35289
35562
|
let dir = lookup.moduleDir;
|
|
35290
35563
|
for (let depth = 0; depth < MAX_WALK_UP; depth += 1) {
|
|
35291
|
-
candidates.push(
|
|
35564
|
+
candidates.push(path7.join(dir, "packages", "avsb-snippet-types", filename));
|
|
35292
35565
|
if (isRepoRoot(dir)) break;
|
|
35293
|
-
const parent =
|
|
35566
|
+
const parent = path7.dirname(dir);
|
|
35294
35567
|
if (parent === dir) break;
|
|
35295
35568
|
dir = parent;
|
|
35296
35569
|
}
|
|
35297
35570
|
return candidates;
|
|
35298
35571
|
}
|
|
35299
35572
|
function defaultIsRepoRoot(dir) {
|
|
35300
|
-
return
|
|
35573
|
+
return fs6.existsSync(path7.join(dir, ".git"));
|
|
35301
35574
|
}
|
|
35302
35575
|
function findSnippetTypeDefPath(filename, lookup) {
|
|
35303
35576
|
for (const candidate of snippetTypeDefCandidates(filename, lookup)) {
|
|
@@ -35315,15 +35588,15 @@ function typeDefBanner(filename) {
|
|
|
35315
35588
|
function defaultPackageDir() {
|
|
35316
35589
|
try {
|
|
35317
35590
|
const moduleRequire = createRequire(import.meta.url);
|
|
35318
|
-
return
|
|
35591
|
+
return path7.dirname(moduleRequire.resolve(`${SNIPPET_TYPES_PACKAGE}/package.json`));
|
|
35319
35592
|
} catch {
|
|
35320
35593
|
return null;
|
|
35321
35594
|
}
|
|
35322
35595
|
}
|
|
35323
35596
|
function defaultLookup() {
|
|
35324
35597
|
return {
|
|
35325
|
-
moduleDir:
|
|
35326
|
-
exists: (candidate) =>
|
|
35598
|
+
moduleDir: path7.dirname(fileURLToPath(import.meta.url)),
|
|
35599
|
+
exists: (candidate) => fs6.existsSync(candidate),
|
|
35327
35600
|
packageDir: defaultPackageDir
|
|
35328
35601
|
};
|
|
35329
35602
|
}
|
|
@@ -35340,7 +35613,7 @@ function readSnippetTypeDefs(lookup = defaultLookup()) {
|
|
|
35340
35613
|
]
|
|
35341
35614
|
);
|
|
35342
35615
|
}
|
|
35343
|
-
return { filename, contents: typeDefBanner(filename) +
|
|
35616
|
+
return { filename, contents: typeDefBanner(filename) + fs6.readFileSync(found, "utf-8") };
|
|
35344
35617
|
});
|
|
35345
35618
|
}
|
|
35346
35619
|
|
|
@@ -35365,7 +35638,7 @@ function writeTypeDefinitions(dir) {
|
|
|
35365
35638
|
try {
|
|
35366
35639
|
const written = [];
|
|
35367
35640
|
for (const file of readSnippetTypeDefs()) {
|
|
35368
|
-
|
|
35641
|
+
fs7.writeFileSync(path8.join(dir, file.filename), file.contents);
|
|
35369
35642
|
written.push(file.filename);
|
|
35370
35643
|
}
|
|
35371
35644
|
return written;
|
|
@@ -35381,24 +35654,24 @@ function scaffoldExperiment(options) {
|
|
|
35381
35654
|
const { data, script, style, apiBaseUrl, targetDir } = options;
|
|
35382
35655
|
const { experiment, project, org } = data;
|
|
35383
35656
|
const folderName = toKebabCase(experiment.name);
|
|
35384
|
-
const baseDir = targetDir ??
|
|
35385
|
-
if (
|
|
35657
|
+
const baseDir = targetDir ?? path8.join(process.cwd(), folderName);
|
|
35658
|
+
if (fs7.existsSync(baseDir)) {
|
|
35386
35659
|
throw new Error(`Directory already exists: ${baseDir}`);
|
|
35387
35660
|
}
|
|
35388
|
-
|
|
35661
|
+
fs7.mkdirSync(baseDir, { recursive: true });
|
|
35389
35662
|
const files = [];
|
|
35390
35663
|
const manifestVariations = [];
|
|
35391
|
-
const
|
|
35392
|
-
|
|
35393
|
-
files.push(
|
|
35664
|
+
const triggerFile2 = `trigger.${script}`;
|
|
35665
|
+
fs7.writeFileSync(path8.join(baseDir, triggerFile2), experiment.triggersJs);
|
|
35666
|
+
files.push(triggerFile2);
|
|
35394
35667
|
for (const variation of experiment.variations) {
|
|
35395
35668
|
const folder = variationFolderName(variation.name);
|
|
35396
|
-
const varDir =
|
|
35397
|
-
|
|
35669
|
+
const varDir = path8.join(baseDir, folder);
|
|
35670
|
+
fs7.mkdirSync(varDir, { recursive: true });
|
|
35398
35671
|
const scriptFile = `${folder}.${script}`;
|
|
35399
35672
|
const styleFile = `${folder}.${style}`;
|
|
35400
|
-
|
|
35401
|
-
|
|
35673
|
+
fs7.writeFileSync(path8.join(varDir, scriptFile), variation.controlJs);
|
|
35674
|
+
fs7.writeFileSync(path8.join(varDir, styleFile), variation.controlCss);
|
|
35402
35675
|
files.push(`${folder}/${scriptFile}`);
|
|
35403
35676
|
files.push(`${folder}/${styleFile}`);
|
|
35404
35677
|
manifestVariations.push({
|
|
@@ -35437,7 +35710,7 @@ function scaffoldExperiment(options) {
|
|
|
35437
35710
|
writeManifest(baseDir, manifest);
|
|
35438
35711
|
files.push(".avsb.json");
|
|
35439
35712
|
if (script === "ts") {
|
|
35440
|
-
|
|
35713
|
+
fs7.writeFileSync(path8.join(baseDir, "tsconfig.json"), TSCONFIG_TEMPLATE);
|
|
35441
35714
|
files.push("tsconfig.json");
|
|
35442
35715
|
files.push(...writeTypeDefinitions(baseDir));
|
|
35443
35716
|
}
|
|
@@ -35453,10 +35726,10 @@ function refreshTypeDefinitions(dir, script) {
|
|
|
35453
35726
|
}
|
|
35454
35727
|
const changed = [];
|
|
35455
35728
|
for (const file of current) {
|
|
35456
|
-
const target =
|
|
35457
|
-
const onDisk =
|
|
35729
|
+
const target = path8.join(dir, file.filename);
|
|
35730
|
+
const onDisk = fs7.existsSync(target) ? fs7.readFileSync(target, "utf-8") : null;
|
|
35458
35731
|
if (onDisk === file.contents) continue;
|
|
35459
|
-
|
|
35732
|
+
fs7.writeFileSync(target, file.contents);
|
|
35460
35733
|
changed.push(file.filename);
|
|
35461
35734
|
}
|
|
35462
35735
|
return changed;
|
|
@@ -35466,26 +35739,26 @@ function updateLocalFiles(dir, manifest, data) {
|
|
|
35466
35739
|
const { script, style } = manifest.languages;
|
|
35467
35740
|
const updated = [];
|
|
35468
35741
|
updated.push(...refreshTypeDefinitions(dir, script));
|
|
35469
|
-
const
|
|
35470
|
-
const triggerPath =
|
|
35471
|
-
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") : "";
|
|
35472
35745
|
if (currentTrigger !== experiment.triggersJs) {
|
|
35473
|
-
|
|
35474
|
-
updated.push(
|
|
35746
|
+
fs7.writeFileSync(triggerPath, experiment.triggersJs);
|
|
35747
|
+
updated.push(triggerFile2);
|
|
35475
35748
|
}
|
|
35476
35749
|
for (const variation of experiment.variations) {
|
|
35477
35750
|
const mv = manifest.variations.find((v) => v.id === variation.id);
|
|
35478
35751
|
if (!mv) continue;
|
|
35479
|
-
const jsPath =
|
|
35480
|
-
const cssPath =
|
|
35481
|
-
const currentJs =
|
|
35482
|
-
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") : "";
|
|
35483
35756
|
if (currentJs !== variation.controlJs) {
|
|
35484
|
-
|
|
35757
|
+
fs7.writeFileSync(jsPath, variation.controlJs);
|
|
35485
35758
|
updated.push(`${mv.folder}/${mv.scriptFile}`);
|
|
35486
35759
|
}
|
|
35487
35760
|
if (currentCss !== variation.controlCss) {
|
|
35488
|
-
|
|
35761
|
+
fs7.writeFileSync(cssPath, variation.controlCss);
|
|
35489
35762
|
updated.push(`${mv.folder}/${mv.styleFile}`);
|
|
35490
35763
|
}
|
|
35491
35764
|
}
|
|
@@ -35518,27 +35791,27 @@ function diffLocalFiles(dir, manifest, data) {
|
|
|
35518
35791
|
const { experiment } = data;
|
|
35519
35792
|
const { script } = manifest.languages;
|
|
35520
35793
|
const changed = [];
|
|
35521
|
-
const
|
|
35522
|
-
const triggerPath =
|
|
35523
|
-
if (
|
|
35524
|
-
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");
|
|
35525
35798
|
if (localContent !== experiment.triggersJs) {
|
|
35526
|
-
changed.push(
|
|
35799
|
+
changed.push(triggerFile2);
|
|
35527
35800
|
}
|
|
35528
35801
|
}
|
|
35529
35802
|
for (const variation of experiment.variations) {
|
|
35530
35803
|
const mv = manifest.variations.find((v) => v.id === variation.id);
|
|
35531
35804
|
if (!mv) continue;
|
|
35532
|
-
const jsPath =
|
|
35533
|
-
const cssPath =
|
|
35534
|
-
if (
|
|
35535
|
-
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");
|
|
35536
35809
|
if (localJs !== variation.controlJs) {
|
|
35537
35810
|
changed.push(`${mv.folder}/${mv.scriptFile}`);
|
|
35538
35811
|
}
|
|
35539
35812
|
}
|
|
35540
|
-
if (
|
|
35541
|
-
const localCss =
|
|
35813
|
+
if (fs7.existsSync(cssPath)) {
|
|
35814
|
+
const localCss = fs7.readFileSync(cssPath, "utf-8");
|
|
35542
35815
|
if (localCss !== variation.controlCss) {
|
|
35543
35816
|
changed.push(`${mv.folder}/${mv.styleFile}`);
|
|
35544
35817
|
}
|
|
@@ -35579,21 +35852,21 @@ function parseProjectInput(input) {
|
|
|
35579
35852
|
return num;
|
|
35580
35853
|
}
|
|
35581
35854
|
function writeProjectManifest(dir, manifest) {
|
|
35582
|
-
const manifestPath =
|
|
35583
|
-
|
|
35855
|
+
const manifestPath = path9.join(dir, PROJECT_MANIFEST_FILENAME);
|
|
35856
|
+
fs8.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
35584
35857
|
}
|
|
35585
35858
|
function readProjectManifest(dir) {
|
|
35586
|
-
const manifestPath =
|
|
35587
|
-
if (!
|
|
35588
|
-
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");
|
|
35589
35862
|
return JSON.parse(raw);
|
|
35590
35863
|
}
|
|
35591
35864
|
function findProjectManifestDir(startDir) {
|
|
35592
35865
|
let dir = startDir ?? process.cwd();
|
|
35593
35866
|
for (let i = 0; i < 10; i++) {
|
|
35594
|
-
const manifestPath =
|
|
35595
|
-
if (
|
|
35596
|
-
const parent =
|
|
35867
|
+
const manifestPath = path9.join(dir, PROJECT_MANIFEST_FILENAME);
|
|
35868
|
+
if (fs8.existsSync(manifestPath)) return dir;
|
|
35869
|
+
const parent = path9.dirname(dir);
|
|
35597
35870
|
if (parent === dir) break;
|
|
35598
35871
|
dir = parent;
|
|
35599
35872
|
}
|
|
@@ -35613,20 +35886,29 @@ function readProjectManifestOrExit(startDir) {
|
|
|
35613
35886
|
const manifest = readProjectManifest(dir);
|
|
35614
35887
|
return { manifest, dir };
|
|
35615
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
|
+
}
|
|
35616
35898
|
function scaffoldProject(options) {
|
|
35617
35899
|
const { data, apiBaseUrl, targetDir } = options;
|
|
35618
35900
|
const { project } = data;
|
|
35619
35901
|
const lang = project.projectJsLang === "TYPESCRIPT" ? "ts" : "js";
|
|
35620
35902
|
const folderName = `project-${project.shortId}`;
|
|
35621
|
-
const baseDir = targetDir ??
|
|
35622
|
-
if (
|
|
35903
|
+
const baseDir = targetDir ?? path9.join(process.cwd(), folderName);
|
|
35904
|
+
if (fs8.existsSync(baseDir)) {
|
|
35623
35905
|
throw new Error(`Directory already exists: ${baseDir}`);
|
|
35624
35906
|
}
|
|
35625
|
-
|
|
35907
|
+
fs8.mkdirSync(baseDir, { recursive: true });
|
|
35626
35908
|
const files = [];
|
|
35627
35909
|
const scriptFile = `project.${lang}`;
|
|
35628
35910
|
const content = lang === "ts" ? project.editorProjectTs ?? project.editorProjectJs ?? "" : project.editorProjectJs ?? "";
|
|
35629
|
-
|
|
35911
|
+
fs8.writeFileSync(path9.join(baseDir, scriptFile), content);
|
|
35630
35912
|
files.push(scriptFile);
|
|
35631
35913
|
const manifest = {
|
|
35632
35914
|
version: 1,
|
|
@@ -35643,7 +35925,7 @@ function scaffoldProject(options) {
|
|
|
35643
35925
|
writeProjectManifest(baseDir, manifest);
|
|
35644
35926
|
files.push(".avsb-project.json");
|
|
35645
35927
|
if (lang === "ts") {
|
|
35646
|
-
|
|
35928
|
+
fs8.writeFileSync(path9.join(baseDir, "tsconfig.json"), PROJECT_TSCONFIG);
|
|
35647
35929
|
files.push("tsconfig.json");
|
|
35648
35930
|
files.push(...writeTypeDefinitions(baseDir));
|
|
35649
35931
|
}
|
|
@@ -35654,11 +35936,11 @@ function updateProjectFile(dir, manifest, data) {
|
|
|
35654
35936
|
const updated = [];
|
|
35655
35937
|
updated.push(...refreshTypeDefinitions(dir, manifest.language));
|
|
35656
35938
|
const scriptFile = `project.${manifest.language}`;
|
|
35657
|
-
const scriptPath =
|
|
35658
|
-
const current =
|
|
35939
|
+
const scriptPath = path9.join(dir, scriptFile);
|
|
35940
|
+
const current = fs8.existsSync(scriptPath) ? fs8.readFileSync(scriptPath, "utf-8") : "";
|
|
35659
35941
|
const remote = manifest.language === "ts" ? project.editorProjectTs ?? project.editorProjectJs ?? "" : project.editorProjectJs ?? "";
|
|
35660
35942
|
if (current !== remote) {
|
|
35661
|
-
|
|
35943
|
+
fs8.writeFileSync(scriptPath, remote);
|
|
35662
35944
|
updated.push(scriptFile);
|
|
35663
35945
|
}
|
|
35664
35946
|
manifest.lastSyncedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -35877,28 +36159,47 @@ function renderInitSummary(input) {
|
|
|
35877
36159
|
}
|
|
35878
36160
|
function describeDetection(input) {
|
|
35879
36161
|
const manager = `installs with ${input.packageManager}`;
|
|
36162
|
+
const variable = input.profile.envVar && input.profile.envVarReason ? `, key in ${input.profile.envVar}: ${input.profile.envVarReason}` : "";
|
|
35880
36163
|
if (input.detection.matchedDependency) {
|
|
35881
|
-
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})`;
|
|
35882
36165
|
}
|
|
35883
36166
|
if (input.profile.id === "none") {
|
|
35884
36167
|
return "no package.json found here";
|
|
35885
36168
|
}
|
|
35886
36169
|
return `${input.profile.label} (${manager})`;
|
|
35887
36170
|
}
|
|
35888
|
-
function
|
|
35889
|
-
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(
|
|
35890
36174
|
(file) => file !== null
|
|
35891
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);
|
|
35892
36181
|
if (written.length > 0) {
|
|
35893
36182
|
heading("Written");
|
|
35894
36183
|
fileTree(written);
|
|
35895
36184
|
line();
|
|
35896
36185
|
}
|
|
36186
|
+
if (leftAlone.length > 0) {
|
|
36187
|
+
heading("Left alone");
|
|
36188
|
+
fileTree(leftAlone);
|
|
36189
|
+
line();
|
|
36190
|
+
}
|
|
35897
36191
|
const skipped = input.plan.filter((step) => step.action === "skip");
|
|
35898
36192
|
for (const step of skipped) dim(` Skipped ${step.id}: ${step.detail}`);
|
|
35899
36193
|
if (skipped.length > 0) line();
|
|
35900
36194
|
}
|
|
35901
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) {
|
|
35902
36203
|
const todo = [];
|
|
35903
36204
|
if (input.install && !input.install.ran) {
|
|
35904
36205
|
todo.push(`Install the SDK: ${input.install.command}`);
|
|
@@ -35910,17 +36211,15 @@ function renderWhatIsLeft(input) {
|
|
|
35910
36211
|
todo.push(input.profile.caveat);
|
|
35911
36212
|
}
|
|
35912
36213
|
if (input.flagKey && !input.flagExists) {
|
|
36214
|
+
const envKey = input.environment?.key ?? "production";
|
|
35913
36215
|
todo.push(
|
|
35914
|
-
`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\`.`
|
|
35915
36217
|
);
|
|
35916
36218
|
}
|
|
35917
36219
|
if (input.servedLine) {
|
|
35918
36220
|
todo.push(input.servedLine);
|
|
35919
36221
|
}
|
|
35920
|
-
|
|
35921
|
-
line();
|
|
35922
|
-
heading("Next");
|
|
35923
|
-
for (const line2 of todo) dim(` ${line2}`);
|
|
36222
|
+
return todo;
|
|
35924
36223
|
}
|
|
35925
36224
|
function renderAssistantPrompt(input) {
|
|
35926
36225
|
if (!input.environment && input.project.type === "FEATURE_FLAG") return;
|
|
@@ -35977,11 +36276,11 @@ var initCommand = withOutputOptions(
|
|
|
35977
36276
|
).action(async (opts) => {
|
|
35978
36277
|
beginCommand("init", opts);
|
|
35979
36278
|
try {
|
|
35980
|
-
const dir =
|
|
36279
|
+
const dir = path10.resolve(opts.dir ?? ".");
|
|
35981
36280
|
parseWaitSeconds(opts.wait);
|
|
35982
36281
|
assertHeartbeatFlagsAgree(opts);
|
|
35983
36282
|
const interactive = isInteractiveRun({ input: opts.input });
|
|
35984
|
-
const pkg2 = parsePackageJson(readFileOrNull(
|
|
36283
|
+
const pkg2 = parsePackageJson(readFileOrNull(path10.join(dir, "package.json")) ?? "");
|
|
35985
36284
|
const detection = resolveDetection(pkg2, opts.framework);
|
|
35986
36285
|
const profile = buildFrameworkProfile(detection);
|
|
35987
36286
|
const packageManager = detectPackageManager(pkg2, listDirectory(dir));
|
|
@@ -36055,7 +36354,9 @@ var initCommand = withOutputOptions(
|
|
|
36055
36354
|
id: profile.id,
|
|
36056
36355
|
label: profile.label,
|
|
36057
36356
|
detectedFrom: detection.matchedDependency,
|
|
36058
|
-
packageManager
|
|
36357
|
+
packageManager,
|
|
36358
|
+
bundler: detection.bundler?.id ?? null,
|
|
36359
|
+
envVar: profile.envVar
|
|
36059
36360
|
},
|
|
36060
36361
|
flag: sample ? { key: sample.flagKey, exists: sample.flag !== null, served } : null,
|
|
36061
36362
|
steps: plan,
|
|
@@ -36078,12 +36379,14 @@ var initCommand = withOutputOptions(
|
|
|
36078
36379
|
install: outcome.install,
|
|
36079
36380
|
flagKey: sample?.flagKey ?? null,
|
|
36080
36381
|
flagExists: sample !== null && sample.flag !== null,
|
|
36081
|
-
servedLine: sample?.flag ? describeServedValue(sample.flagKey, served) : null,
|
|
36382
|
+
servedLine: sample?.flag ? describeServedValue(sample.flagKey, served, opts.env) : null,
|
|
36082
36383
|
heartbeat,
|
|
36083
36384
|
proof,
|
|
36084
36385
|
timeoutGuidance: heartbeat && !heartbeat.seen && environment ? heartbeatTimeoutGuidance({
|
|
36085
36386
|
sdkKey: environment.sdkKey,
|
|
36086
36387
|
environmentName: environment.name,
|
|
36388
|
+
environmentKey: environment.key,
|
|
36389
|
+
flagKey: sample?.flagKey ?? null,
|
|
36087
36390
|
previousSeenAt: heartbeat.previousSeenAt,
|
|
36088
36391
|
waitedMs: heartbeat.elapsedMs,
|
|
36089
36392
|
cdnHost: new URL(DEFAULT_CDN_BASE_URL).host,
|
|
@@ -36133,8 +36436,8 @@ async function decideCodegen(opts, interactive, hasSdkKey) {
|
|
|
36133
36436
|
}
|
|
36134
36437
|
|
|
36135
36438
|
// src/commands/agents-md.ts
|
|
36136
|
-
import
|
|
36137
|
-
import
|
|
36439
|
+
import fs9 from "node:fs";
|
|
36440
|
+
import path11 from "node:path";
|
|
36138
36441
|
|
|
36139
36442
|
// src/lib/agentsMd.ts
|
|
36140
36443
|
var AGENTS_MARKER_START = "<!-- avsb:start -->";
|
|
@@ -36284,8 +36587,8 @@ var agentsMdInstallCommand = withOutputOptions(
|
|
|
36284
36587
|
).action((opts) => {
|
|
36285
36588
|
beginCommand("agents-md install", opts);
|
|
36286
36589
|
try {
|
|
36287
|
-
const dir =
|
|
36288
|
-
const pkg2 = parsePackageJson(readFileOrNull(
|
|
36590
|
+
const dir = path11.resolve(opts.dir ?? ".");
|
|
36591
|
+
const pkg2 = parsePackageJson(readFileOrNull(path11.join(dir, "package.json")) ?? "");
|
|
36289
36592
|
const profile = buildFrameworkProfile(detectFramework(pkg2));
|
|
36290
36593
|
const section = renderAgentsSection({
|
|
36291
36594
|
projectShortId: readProjectShortId(opts.project),
|
|
@@ -36309,12 +36612,12 @@ var agentsMdInstallCommand = withOutputOptions(
|
|
|
36309
36612
|
}
|
|
36310
36613
|
});
|
|
36311
36614
|
function writeTarget(dir, file, section) {
|
|
36312
|
-
const filePath =
|
|
36615
|
+
const filePath = path11.join(dir, file);
|
|
36313
36616
|
const existing = readFileOrNull(filePath);
|
|
36314
36617
|
const applied = applyAgentsSection(existing, section);
|
|
36315
36618
|
if (applied.outcome !== "unchanged") {
|
|
36316
36619
|
try {
|
|
36317
|
-
|
|
36620
|
+
fs9.writeFileSync(filePath, applied.content, "utf-8");
|
|
36318
36621
|
} catch (err) {
|
|
36319
36622
|
throw new CliError(
|
|
36320
36623
|
"cli_io_failed",
|
|
@@ -36350,7 +36653,8 @@ var loginCommand = new Command("login").description("Save a personal access toke
|
|
|
36350
36653
|
`Replacing the saved login in ${getConfigPath()}: ${existing.detail}.`
|
|
36351
36654
|
);
|
|
36352
36655
|
}
|
|
36353
|
-
|
|
36656
|
+
const interactive = canPrompt(currentPromptContext({ input: options.input }));
|
|
36657
|
+
if (existingConfig && interactive && !explicitToken && !envToken) {
|
|
36354
36658
|
const active = existingConfig.logins.find(
|
|
36355
36659
|
(login) => login.orgId === existingConfig.activeOrgId
|
|
36356
36660
|
);
|
|
@@ -36372,7 +36676,12 @@ var loginCommand = new Command("login").description("Save a personal access toke
|
|
|
36372
36676
|
const token = await resolveToken({
|
|
36373
36677
|
explicitToken,
|
|
36374
36678
|
envToken,
|
|
36375
|
-
|
|
36679
|
+
source: tokenSourceFor({
|
|
36680
|
+
explicitToken,
|
|
36681
|
+
envToken,
|
|
36682
|
+
input: options.input,
|
|
36683
|
+
isTty: process.stdin.isTTY === true
|
|
36684
|
+
})
|
|
36376
36685
|
});
|
|
36377
36686
|
const spinner = ora("Verifying token...").start();
|
|
36378
36687
|
const client = new ApiClient({ token, apiBaseUrl }, { useEnvOverrides: false });
|
|
@@ -36380,8 +36689,9 @@ var loginCommand = new Command("login").description("Save a personal access toke
|
|
|
36380
36689
|
try {
|
|
36381
36690
|
verified = await client.verifyAuth();
|
|
36382
36691
|
} catch (err) {
|
|
36383
|
-
|
|
36384
|
-
|
|
36692
|
+
const rejected = err instanceof ApiRequestError && err.status === 401;
|
|
36693
|
+
spinner.fail(rejected ? "Token rejected" : "Could not check the token");
|
|
36694
|
+
if (rejected) {
|
|
36385
36695
|
dim(` ${WHERE_TO_GET_A_TOKEN}`);
|
|
36386
36696
|
}
|
|
36387
36697
|
failCommand(err);
|
|
@@ -36416,13 +36726,22 @@ var loginCommand = new Command("login").description("Save a personal access toke
|
|
|
36416
36726
|
failCommand(err);
|
|
36417
36727
|
}
|
|
36418
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
|
+
];
|
|
36419
36738
|
async function resolveToken(input) {
|
|
36420
|
-
if (input.explicitToken) return input.explicitToken;
|
|
36421
|
-
if (input.envToken) {
|
|
36739
|
+
if (input.source === "flag" && input.explicitToken) return input.explicitToken;
|
|
36740
|
+
if (input.source === "env" && input.envToken) {
|
|
36422
36741
|
info(`Using the token from ${TOKEN_ENV_VAR}.`);
|
|
36423
36742
|
return input.envToken;
|
|
36424
36743
|
}
|
|
36425
|
-
if (
|
|
36744
|
+
if (input.source === "refuse") {
|
|
36426
36745
|
failCommand(
|
|
36427
36746
|
new CliError(
|
|
36428
36747
|
"cli_usage",
|
|
@@ -36430,6 +36749,13 @@ async function resolveToken(input) {
|
|
|
36430
36749
|
)
|
|
36431
36750
|
);
|
|
36432
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
|
+
}
|
|
36433
36759
|
const { token } = await esm_default12.prompt([
|
|
36434
36760
|
{
|
|
36435
36761
|
type: "password",
|
|
@@ -36466,12 +36792,19 @@ var logoutCommand = new Command("logout").description("Delete the saved logins o
|
|
|
36466
36792
|
});
|
|
36467
36793
|
|
|
36468
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
|
+
}
|
|
36469
36802
|
function buildWhoamiRows(verified, session, configPath) {
|
|
36470
36803
|
return [
|
|
36471
36804
|
{ label: "Email", value: verified.email },
|
|
36472
36805
|
{ label: "Organization", value: verified.orgName },
|
|
36473
36806
|
{ label: "Org ID", value: verified.orgId },
|
|
36474
|
-
{ label: "Role", value: verified.role },
|
|
36807
|
+
{ label: "Role", value: roleName(verified.role) },
|
|
36475
36808
|
{ label: "API URL", value: session.apiBaseUrl },
|
|
36476
36809
|
{ label: "Token from", value: describeTokenSource(session, configPath) }
|
|
36477
36810
|
];
|
|
@@ -36497,7 +36830,7 @@ var whoamiCommand = withOutputOptions(
|
|
|
36497
36830
|
email: verified.data.email,
|
|
36498
36831
|
orgId: verified.data.orgId,
|
|
36499
36832
|
orgName: verified.data.orgName,
|
|
36500
|
-
role: verified.data.role,
|
|
36833
|
+
role: roleName(verified.data.role),
|
|
36501
36834
|
apiBaseUrl: session.apiBaseUrl,
|
|
36502
36835
|
tokenSource: session.tokenSource,
|
|
36503
36836
|
configPath: getConfigPath()
|
|
@@ -36507,7 +36840,7 @@ var whoamiCommand = withOutputOptions(
|
|
|
36507
36840
|
line();
|
|
36508
36841
|
heading("Signed in");
|
|
36509
36842
|
for (const row of buildWhoamiRows(verified.data, session, getConfigPath())) {
|
|
36510
|
-
|
|
36843
|
+
field(row.label, row.value);
|
|
36511
36844
|
}
|
|
36512
36845
|
line();
|
|
36513
36846
|
});
|
|
@@ -36524,12 +36857,14 @@ function configForSession(session) {
|
|
|
36524
36857
|
apiBaseUrl: session.apiBaseUrl
|
|
36525
36858
|
};
|
|
36526
36859
|
}
|
|
36860
|
+
function describeCandidates(candidates) {
|
|
36861
|
+
return candidates.map((c) => c.shortId != null ? `${c.shortId} (${c.name})` : c.name).join(", ");
|
|
36862
|
+
}
|
|
36527
36863
|
function describeSwitchFailure(candidates, needle) {
|
|
36528
36864
|
if (candidates.length === 0) {
|
|
36529
36865
|
return `No organization matches "${needle}". This token cannot reach any organization.`;
|
|
36530
36866
|
}
|
|
36531
|
-
|
|
36532
|
-
return `No organization matches "${needle}". Available: ${known}.`;
|
|
36867
|
+
return `No organization matches "${needle}". Available: ${describeCandidates(candidates)}.`;
|
|
36533
36868
|
}
|
|
36534
36869
|
var orgCommand = withOutputOptions(
|
|
36535
36870
|
new Command("org").description("Show, list, and switch the active organization")
|
|
@@ -36547,13 +36882,13 @@ var orgCommand = withOutputOptions(
|
|
|
36547
36882
|
}
|
|
36548
36883
|
heading("Current organization");
|
|
36549
36884
|
if (session.orgName) {
|
|
36550
|
-
|
|
36551
|
-
|
|
36885
|
+
field("Name", session.orgName);
|
|
36886
|
+
field("ID", session.orgId);
|
|
36552
36887
|
} else {
|
|
36553
|
-
|
|
36888
|
+
field("Name", "not saved on this machine");
|
|
36554
36889
|
}
|
|
36555
|
-
|
|
36556
|
-
|
|
36890
|
+
field("API URL", session.apiBaseUrl);
|
|
36891
|
+
field("Token from", describeTokenSource(session, getConfigPath()));
|
|
36557
36892
|
if (session.tokenSource === "env") {
|
|
36558
36893
|
dim(
|
|
36559
36894
|
` ${TOKEN_ENV_VAR} is set, so commands use that token. Run \`avsb whoami\` to see which account and organization it reaches.`
|
|
@@ -36563,8 +36898,8 @@ var orgCommand = withOutputOptions(
|
|
|
36563
36898
|
orgCommand.addCommand(
|
|
36564
36899
|
withOutputOptions(
|
|
36565
36900
|
new Command("list").description("List the organizations this token can reach")
|
|
36566
|
-
).action(async (
|
|
36567
|
-
beginCommand("org list",
|
|
36901
|
+
).action(async (_opts, command) => {
|
|
36902
|
+
beginCommand("org list", command.optsWithGlobals());
|
|
36568
36903
|
try {
|
|
36569
36904
|
const session = ensureLoggedIn();
|
|
36570
36905
|
const config = configForSession(session);
|
|
@@ -36628,12 +36963,16 @@ orgCommand.command("switch").description("Choose which organization commands use
|
|
|
36628
36963
|
failCommand(err);
|
|
36629
36964
|
}
|
|
36630
36965
|
});
|
|
36631
|
-
async function promptForOrg(candidates,
|
|
36632
|
-
|
|
36633
|
-
|
|
36634
|
-
|
|
36635
|
-
|
|
36636
|
-
|
|
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);
|
|
36637
36976
|
const { chosen } = await esm_default12.prompt([
|
|
36638
36977
|
{
|
|
36639
36978
|
type: "list",
|
|
@@ -36749,6 +37088,15 @@ var pullCommand = new Command("pull").description("Pull latest experiment data f
|
|
|
36749
37088
|
dim(` ${file}`);
|
|
36750
37089
|
}
|
|
36751
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);
|
|
36752
37100
|
const { proceed } = await esm_default12.prompt([
|
|
36753
37101
|
{
|
|
36754
37102
|
type: "confirm",
|
|
@@ -36784,14 +37132,16 @@ var pullCommand = new Command("pull").description("Pull latest experiment data f
|
|
|
36784
37132
|
|
|
36785
37133
|
// src/commands/push.ts
|
|
36786
37134
|
import { createHash as createHash3 } from "node:crypto";
|
|
36787
|
-
|
|
36788
|
-
|
|
37135
|
+
|
|
37136
|
+
// src/commands/push.payload.ts
|
|
37137
|
+
import fs11 from "node:fs";
|
|
37138
|
+
import path13 from "node:path";
|
|
36789
37139
|
|
|
36790
37140
|
// src/lib/compiler.ts
|
|
36791
37141
|
import { transform } from "esbuild";
|
|
36792
37142
|
import * as sass from "sass";
|
|
36793
|
-
import
|
|
36794
|
-
import
|
|
37143
|
+
import fs10 from "node:fs";
|
|
37144
|
+
import path12 from "node:path";
|
|
36795
37145
|
async function compileTypeScript(source) {
|
|
36796
37146
|
const result = await transform(source, {
|
|
36797
37147
|
loader: "ts",
|
|
@@ -36802,7 +37152,7 @@ async function compileTypeScript(source) {
|
|
|
36802
37152
|
function compileScss(filePath) {
|
|
36803
37153
|
const result = sass.compile(filePath, {
|
|
36804
37154
|
style: "expanded",
|
|
36805
|
-
loadPaths: [
|
|
37155
|
+
loadPaths: [path12.dirname(filePath)]
|
|
36806
37156
|
});
|
|
36807
37157
|
return result.css;
|
|
36808
37158
|
}
|
|
@@ -36878,8 +37228,8 @@ function parseEsbuildErrors(err, fallbackFile) {
|
|
|
36878
37228
|
];
|
|
36879
37229
|
}
|
|
36880
37230
|
async function compileFile(filePath) {
|
|
36881
|
-
const ext =
|
|
36882
|
-
const source =
|
|
37231
|
+
const ext = path12.extname(filePath);
|
|
37232
|
+
const source = fs10.readFileSync(filePath, "utf-8");
|
|
36883
37233
|
switch (ext) {
|
|
36884
37234
|
case ".ts":
|
|
36885
37235
|
return compileTypeScript(source);
|
|
@@ -36894,21 +37244,21 @@ async function compileFile(filePath) {
|
|
|
36894
37244
|
}
|
|
36895
37245
|
async function compileExperiment(dir, manifest) {
|
|
36896
37246
|
const { script } = manifest.languages;
|
|
36897
|
-
const triggerPath =
|
|
37247
|
+
const triggerPath = path12.join(dir, `trigger.${script}`);
|
|
36898
37248
|
let triggersJs = "";
|
|
36899
|
-
if (
|
|
37249
|
+
if (fs10.existsSync(triggerPath)) {
|
|
36900
37250
|
triggersJs = await compileFile(triggerPath);
|
|
36901
37251
|
}
|
|
36902
37252
|
const variations = [];
|
|
36903
37253
|
for (const v of manifest.variations) {
|
|
36904
|
-
const jsPath =
|
|
36905
|
-
const cssPath =
|
|
37254
|
+
const jsPath = path12.join(dir, v.folder, v.scriptFile);
|
|
37255
|
+
const cssPath = path12.join(dir, v.folder, v.styleFile);
|
|
36906
37256
|
let js = "";
|
|
36907
37257
|
let css = "";
|
|
36908
|
-
if (
|
|
37258
|
+
if (fs10.existsSync(jsPath)) {
|
|
36909
37259
|
js = await compileFile(jsPath);
|
|
36910
37260
|
}
|
|
36911
|
-
if (
|
|
37261
|
+
if (fs10.existsSync(cssPath)) {
|
|
36912
37262
|
css = await compileFile(cssPath);
|
|
36913
37263
|
}
|
|
36914
37264
|
variations.push({
|
|
@@ -36927,14 +37277,91 @@ async function compileExperiment(dir, manifest) {
|
|
|
36927
37277
|
};
|
|
36928
37278
|
}
|
|
36929
37279
|
function detectChangeType(filePath) {
|
|
36930
|
-
const basename3 =
|
|
36931
|
-
const ext =
|
|
37280
|
+
const basename3 = path12.basename(filePath, path12.extname(filePath));
|
|
37281
|
+
const ext = path12.extname(filePath);
|
|
36932
37282
|
if (basename3 === "trigger") return "trigger";
|
|
36933
37283
|
if (ext === ".scss" || ext === ".css") return "css-only";
|
|
36934
37284
|
if (ext === ".ts" || ext === ".js") return "js-only";
|
|
36935
37285
|
return "both";
|
|
36936
37286
|
}
|
|
36937
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
|
+
|
|
36938
37365
|
// src/commands/push.ts
|
|
36939
37366
|
function sha2563(content) {
|
|
36940
37367
|
return createHash3("sha256").update(content).digest("hex");
|
|
@@ -36959,35 +37386,60 @@ function detectRemoteChanges(remoteExp, syncedHashes) {
|
|
|
36959
37386
|
}
|
|
36960
37387
|
return changed;
|
|
36961
37388
|
}
|
|
36962
|
-
|
|
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;
|
|
36963
37417
|
try {
|
|
36964
37418
|
const config = ensureLoggedIn();
|
|
36965
|
-
const
|
|
37419
|
+
const read = readManifestOrExit();
|
|
37420
|
+
manifest = read.manifest;
|
|
37421
|
+
const { dir } = read;
|
|
36966
37422
|
let spinner = ora("Compiling experiment...").start();
|
|
36967
|
-
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
|
+
}
|
|
36968
37431
|
const client = new ApiClient(config);
|
|
36969
37432
|
if (!options.force && manifest.syncedHashes) {
|
|
36970
37433
|
spinner.text = "Checking for remote changes...";
|
|
36971
37434
|
const remote = await client.getExperiment(manifest.experimentShortId);
|
|
36972
|
-
const
|
|
36973
|
-
const remoteChanges = detectRemoteChanges(remoteExp, manifest.syncedHashes);
|
|
37435
|
+
const remoteChanges = detectRemoteChanges(remote.data.experiment, manifest.syncedHashes);
|
|
36974
37436
|
if (remoteChanges.length > 0) {
|
|
36975
37437
|
spinner.warn("Remote has changes you haven't pulled");
|
|
36976
37438
|
line();
|
|
36977
37439
|
heading("Files changed on remote:");
|
|
36978
|
-
for (const file of remoteChanges) {
|
|
36979
|
-
dim(` ${file}`);
|
|
36980
|
-
}
|
|
37440
|
+
for (const file of remoteChanges) dim(` ${file}`);
|
|
36981
37441
|
line();
|
|
36982
|
-
|
|
36983
|
-
{
|
|
36984
|
-
type: "confirm",
|
|
36985
|
-
name: "proceed",
|
|
36986
|
-
message: "Pushing will overwrite remote changes. Continue?",
|
|
36987
|
-
default: false
|
|
36988
|
-
}
|
|
36989
|
-
]);
|
|
36990
|
-
if (!proceed) {
|
|
37442
|
+
if (!await confirmOverwrite()) {
|
|
36991
37443
|
info("Push aborted. Run `avsb pull` to fetch remote changes first.");
|
|
36992
37444
|
warn("Note: pulling will overwrite your local files.");
|
|
36993
37445
|
process.exit(EXIT.success);
|
|
@@ -36999,78 +37451,37 @@ var pushCommand = new Command("push").description("Compile and push local change
|
|
|
36999
37451
|
} else {
|
|
37000
37452
|
spinner.text = "Pushing to platform...";
|
|
37001
37453
|
}
|
|
37002
|
-
|
|
37003
|
-
|
|
37004
|
-
|
|
37005
|
-
|
|
37006
|
-
"
|
|
37007
|
-
|
|
37008
|
-
const triggerCompile = await compileScript(rawTrigger, scriptLang, `trigger.${manifest.languages.script}`);
|
|
37009
|
-
if (!triggerCompile.success) {
|
|
37010
|
-
spinner.fail("Trigger compilation failed");
|
|
37011
|
-
for (const e of triggerCompile.errors) {
|
|
37012
|
-
error(` trigger.${manifest.languages.script}:${e.line}:${e.column}: ${e.message}`);
|
|
37013
|
-
}
|
|
37014
|
-
process.exit(EXIT.failure);
|
|
37015
|
-
}
|
|
37016
|
-
const rawVariations = [];
|
|
37017
|
-
const compiledVariations = [];
|
|
37018
|
-
for (const v of manifest.variations) {
|
|
37019
|
-
const rawJs = fs10.readFileSync(path12.join(dir, v.folder, v.scriptFile), "utf-8");
|
|
37020
|
-
const rawCss = fs10.readFileSync(path12.join(dir, v.folder, v.styleFile), "utf-8");
|
|
37021
|
-
const jsCompile = await compileScript(rawJs, scriptLang, `${v.name}.${manifest.languages.script}`);
|
|
37022
|
-
const cssCompile = compileStyle(rawCss, styleLang, `${v.name}.${manifest.languages.style}`);
|
|
37023
|
-
if (!jsCompile.success) {
|
|
37024
|
-
spinner.fail(`Variation "${v.name}" JS compilation failed`);
|
|
37025
|
-
for (const e of jsCompile.errors) error(` ${e.file}:${e.line}:${e.column}: ${e.message}`);
|
|
37026
|
-
process.exit(EXIT.failure);
|
|
37027
|
-
}
|
|
37028
|
-
if (!cssCompile.success) {
|
|
37029
|
-
spinner.fail(`Variation "${v.name}" CSS compilation failed`);
|
|
37030
|
-
for (const e of cssCompile.errors) error(` ${e.file}:${e.line}:${e.column}: ${e.message}`);
|
|
37031
|
-
process.exit(EXIT.failure);
|
|
37032
|
-
}
|
|
37033
|
-
rawVariations.push({ id: v.id, controlJs: rawJs, controlCss: rawCss });
|
|
37034
|
-
compiledVariations.push({
|
|
37035
|
-
id: v.id,
|
|
37036
|
-
controlJs: { source: rawJs, compiled: jsCompile.code },
|
|
37037
|
-
controlCss: { source: rawCss, compiled: cssCompile.code }
|
|
37038
|
-
});
|
|
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;
|
|
37039
37460
|
}
|
|
37040
|
-
|
|
37041
|
-
triggersJs: { source: rawTrigger, compiled: triggerCompile.code },
|
|
37042
|
-
variations: compiledVariations
|
|
37043
|
-
});
|
|
37044
|
-
manifest.syncedHashes = computeSyncHashes(rawTrigger, rawVariations);
|
|
37461
|
+
manifest.syncedHashes = computeSyncHashes(built.sources.triggersJs, built.sources.variations);
|
|
37045
37462
|
writeManifest(dir, manifest);
|
|
37046
37463
|
spinner.succeed("Pushed successfully");
|
|
37047
37464
|
line();
|
|
37048
37465
|
if (response.data.hasPendingChanges) {
|
|
37049
|
-
|
|
37050
|
-
{
|
|
37051
|
-
type: "confirm",
|
|
37052
|
-
name: "publish",
|
|
37053
|
-
message: "Experiment is running. Publish changes now?",
|
|
37054
|
-
default: false
|
|
37055
|
-
}
|
|
37056
|
-
]);
|
|
37057
|
-
if (publish) {
|
|
37466
|
+
if (await shouldPublish(options, response.data.status)) {
|
|
37058
37467
|
const publishSpinner = ora("Publishing changes...").start();
|
|
37059
|
-
await client.publishChanges(manifest.experimentShortId);
|
|
37468
|
+
const published = await client.publishChanges(manifest.experimentShortId);
|
|
37060
37469
|
publishSpinner.succeed("Changes published");
|
|
37470
|
+
if (published.data.warning) warn(published.data.warning.message);
|
|
37061
37471
|
} else {
|
|
37062
|
-
info("Changes saved as pending. Publish from the
|
|
37472
|
+
info("Changes saved as pending. Publish them with `avsb push --publish`, or from the dashboard.");
|
|
37063
37473
|
}
|
|
37064
37474
|
}
|
|
37065
37475
|
line();
|
|
37066
37476
|
success("Push complete");
|
|
37067
37477
|
} catch (err) {
|
|
37068
|
-
|
|
37478
|
+
const current = manifest;
|
|
37479
|
+
failCommand(err, current ? { labelForPath: (bodyPath) => labelForPushPath(current, bodyPath) } : {});
|
|
37069
37480
|
}
|
|
37070
37481
|
});
|
|
37071
37482
|
|
|
37072
37483
|
// src/commands/dev.ts
|
|
37073
|
-
import
|
|
37484
|
+
import path15 from "node:path";
|
|
37074
37485
|
|
|
37075
37486
|
// node_modules/chokidar/esm/index.js
|
|
37076
37487
|
import { stat as statcb } from "fs";
|
|
@@ -37148,7 +37559,7 @@ var ReaddirpStream = class extends Readable {
|
|
|
37148
37559
|
this._directoryFilter = normalizeFilter(opts.directoryFilter);
|
|
37149
37560
|
const statMethod = opts.lstat ? lstat : stat;
|
|
37150
37561
|
if (wantBigintFsStats) {
|
|
37151
|
-
this._stat = (
|
|
37562
|
+
this._stat = (path23) => statMethod(path23, { bigint: true });
|
|
37152
37563
|
} else {
|
|
37153
37564
|
this._stat = statMethod;
|
|
37154
37565
|
}
|
|
@@ -37173,8 +37584,8 @@ var ReaddirpStream = class extends Readable {
|
|
|
37173
37584
|
const par = this.parent;
|
|
37174
37585
|
const fil = par && par.files;
|
|
37175
37586
|
if (fil && fil.length > 0) {
|
|
37176
|
-
const { path:
|
|
37177
|
-
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));
|
|
37178
37589
|
const awaited = await Promise.all(slice);
|
|
37179
37590
|
for (const entry of awaited) {
|
|
37180
37591
|
if (!entry)
|
|
@@ -37214,20 +37625,20 @@ var ReaddirpStream = class extends Readable {
|
|
|
37214
37625
|
this.reading = false;
|
|
37215
37626
|
}
|
|
37216
37627
|
}
|
|
37217
|
-
async _exploreDir(
|
|
37628
|
+
async _exploreDir(path23, depth) {
|
|
37218
37629
|
let files;
|
|
37219
37630
|
try {
|
|
37220
|
-
files = await readdir(
|
|
37631
|
+
files = await readdir(path23, this._rdOptions);
|
|
37221
37632
|
} catch (error2) {
|
|
37222
37633
|
this._onError(error2);
|
|
37223
37634
|
}
|
|
37224
|
-
return { files, depth, path:
|
|
37635
|
+
return { files, depth, path: path23 };
|
|
37225
37636
|
}
|
|
37226
|
-
async _formatEntry(dirent,
|
|
37637
|
+
async _formatEntry(dirent, path23) {
|
|
37227
37638
|
let entry;
|
|
37228
37639
|
const basename3 = this._isDirent ? dirent.name : dirent;
|
|
37229
37640
|
try {
|
|
37230
|
-
const fullPath = presolve(pjoin(
|
|
37641
|
+
const fullPath = presolve(pjoin(path23, basename3));
|
|
37231
37642
|
entry = { path: prelative(this._root, fullPath), fullPath, basename: basename3 };
|
|
37232
37643
|
entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
|
|
37233
37644
|
} catch (err) {
|
|
@@ -37627,16 +38038,16 @@ var delFromSet = (main3, prop, item) => {
|
|
|
37627
38038
|
};
|
|
37628
38039
|
var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
|
|
37629
38040
|
var FsWatchInstances = /* @__PURE__ */ new Map();
|
|
37630
|
-
function createFsWatchInstance(
|
|
38041
|
+
function createFsWatchInstance(path23, options, listener, errHandler, emitRaw) {
|
|
37631
38042
|
const handleEvent = (rawEvent, evPath) => {
|
|
37632
|
-
listener(
|
|
37633
|
-
emitRaw(rawEvent, evPath, { watchedPath:
|
|
37634
|
-
if (evPath &&
|
|
37635
|
-
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));
|
|
37636
38047
|
}
|
|
37637
38048
|
};
|
|
37638
38049
|
try {
|
|
37639
|
-
return fs_watch(
|
|
38050
|
+
return fs_watch(path23, {
|
|
37640
38051
|
persistent: options.persistent
|
|
37641
38052
|
}, handleEvent);
|
|
37642
38053
|
} catch (error2) {
|
|
@@ -37652,12 +38063,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
|
|
|
37652
38063
|
listener(val1, val2, val3);
|
|
37653
38064
|
});
|
|
37654
38065
|
};
|
|
37655
|
-
var setFsWatchListener = (
|
|
38066
|
+
var setFsWatchListener = (path23, fullPath, options, handlers) => {
|
|
37656
38067
|
const { listener, errHandler, rawEmitter } = handlers;
|
|
37657
38068
|
let cont = FsWatchInstances.get(fullPath);
|
|
37658
38069
|
let watcher;
|
|
37659
38070
|
if (!options.persistent) {
|
|
37660
|
-
watcher = createFsWatchInstance(
|
|
38071
|
+
watcher = createFsWatchInstance(path23, options, listener, errHandler, rawEmitter);
|
|
37661
38072
|
if (!watcher)
|
|
37662
38073
|
return;
|
|
37663
38074
|
return watcher.close.bind(watcher);
|
|
@@ -37668,7 +38079,7 @@ var setFsWatchListener = (path22, fullPath, options, handlers) => {
|
|
|
37668
38079
|
addAndConvert(cont, KEY_RAW, rawEmitter);
|
|
37669
38080
|
} else {
|
|
37670
38081
|
watcher = createFsWatchInstance(
|
|
37671
|
-
|
|
38082
|
+
path23,
|
|
37672
38083
|
options,
|
|
37673
38084
|
fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
|
|
37674
38085
|
errHandler,
|
|
@@ -37683,7 +38094,7 @@ var setFsWatchListener = (path22, fullPath, options, handlers) => {
|
|
|
37683
38094
|
cont.watcherUnusable = true;
|
|
37684
38095
|
if (isWindows && error2.code === "EPERM") {
|
|
37685
38096
|
try {
|
|
37686
|
-
const fd = await open(
|
|
38097
|
+
const fd = await open(path23, "r");
|
|
37687
38098
|
await fd.close();
|
|
37688
38099
|
broadcastErr(error2);
|
|
37689
38100
|
} catch (err) {
|
|
@@ -37714,7 +38125,7 @@ var setFsWatchListener = (path22, fullPath, options, handlers) => {
|
|
|
37714
38125
|
};
|
|
37715
38126
|
};
|
|
37716
38127
|
var FsWatchFileInstances = /* @__PURE__ */ new Map();
|
|
37717
|
-
var setFsWatchFileListener = (
|
|
38128
|
+
var setFsWatchFileListener = (path23, fullPath, options, handlers) => {
|
|
37718
38129
|
const { listener, rawEmitter } = handlers;
|
|
37719
38130
|
let cont = FsWatchFileInstances.get(fullPath);
|
|
37720
38131
|
const copts = cont && cont.options;
|
|
@@ -37736,7 +38147,7 @@ var setFsWatchFileListener = (path22, fullPath, options, handlers) => {
|
|
|
37736
38147
|
});
|
|
37737
38148
|
const currmtime = curr.mtimeMs;
|
|
37738
38149
|
if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
|
|
37739
|
-
foreach(cont.listeners, (listener2) => listener2(
|
|
38150
|
+
foreach(cont.listeners, (listener2) => listener2(path23, curr));
|
|
37740
38151
|
}
|
|
37741
38152
|
})
|
|
37742
38153
|
};
|
|
@@ -37764,13 +38175,13 @@ var NodeFsHandler = class {
|
|
|
37764
38175
|
* @param listener on fs change
|
|
37765
38176
|
* @returns closer for the watcher instance
|
|
37766
38177
|
*/
|
|
37767
|
-
_watchWithNodeFs(
|
|
38178
|
+
_watchWithNodeFs(path23, listener) {
|
|
37768
38179
|
const opts = this.fsw.options;
|
|
37769
|
-
const directory = sysPath.dirname(
|
|
37770
|
-
const basename3 = sysPath.basename(
|
|
38180
|
+
const directory = sysPath.dirname(path23);
|
|
38181
|
+
const basename3 = sysPath.basename(path23);
|
|
37771
38182
|
const parent = this.fsw._getWatchedDir(directory);
|
|
37772
38183
|
parent.add(basename3);
|
|
37773
|
-
const absolutePath = sysPath.resolve(
|
|
38184
|
+
const absolutePath = sysPath.resolve(path23);
|
|
37774
38185
|
const options = {
|
|
37775
38186
|
persistent: opts.persistent
|
|
37776
38187
|
};
|
|
@@ -37780,12 +38191,12 @@ var NodeFsHandler = class {
|
|
|
37780
38191
|
if (opts.usePolling) {
|
|
37781
38192
|
const enableBin = opts.interval !== opts.binaryInterval;
|
|
37782
38193
|
options.interval = enableBin && isBinaryPath(basename3) ? opts.binaryInterval : opts.interval;
|
|
37783
|
-
closer = setFsWatchFileListener(
|
|
38194
|
+
closer = setFsWatchFileListener(path23, absolutePath, options, {
|
|
37784
38195
|
listener,
|
|
37785
38196
|
rawEmitter: this.fsw._emitRaw
|
|
37786
38197
|
});
|
|
37787
38198
|
} else {
|
|
37788
|
-
closer = setFsWatchListener(
|
|
38199
|
+
closer = setFsWatchListener(path23, absolutePath, options, {
|
|
37789
38200
|
listener,
|
|
37790
38201
|
errHandler: this._boundHandleError,
|
|
37791
38202
|
rawEmitter: this.fsw._emitRaw
|
|
@@ -37807,7 +38218,7 @@ var NodeFsHandler = class {
|
|
|
37807
38218
|
let prevStats = stats;
|
|
37808
38219
|
if (parent.has(basename3))
|
|
37809
38220
|
return;
|
|
37810
|
-
const listener = async (
|
|
38221
|
+
const listener = async (path23, newStats) => {
|
|
37811
38222
|
if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
|
|
37812
38223
|
return;
|
|
37813
38224
|
if (!newStats || newStats.mtimeMs === 0) {
|
|
@@ -37821,11 +38232,11 @@ var NodeFsHandler = class {
|
|
|
37821
38232
|
this.fsw._emit(EV.CHANGE, file, newStats2);
|
|
37822
38233
|
}
|
|
37823
38234
|
if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
|
|
37824
|
-
this.fsw._closeFile(
|
|
38235
|
+
this.fsw._closeFile(path23);
|
|
37825
38236
|
prevStats = newStats2;
|
|
37826
38237
|
const closer2 = this._watchWithNodeFs(file, listener);
|
|
37827
38238
|
if (closer2)
|
|
37828
|
-
this.fsw._addPathCloser(
|
|
38239
|
+
this.fsw._addPathCloser(path23, closer2);
|
|
37829
38240
|
} else {
|
|
37830
38241
|
prevStats = newStats2;
|
|
37831
38242
|
}
|
|
@@ -37857,7 +38268,7 @@ var NodeFsHandler = class {
|
|
|
37857
38268
|
* @param item basename of this item
|
|
37858
38269
|
* @returns true if no more processing is needed for this entry.
|
|
37859
38270
|
*/
|
|
37860
|
-
async _handleSymlink(entry, directory,
|
|
38271
|
+
async _handleSymlink(entry, directory, path23, item) {
|
|
37861
38272
|
if (this.fsw.closed) {
|
|
37862
38273
|
return;
|
|
37863
38274
|
}
|
|
@@ -37867,7 +38278,7 @@ var NodeFsHandler = class {
|
|
|
37867
38278
|
this.fsw._incrReadyCount();
|
|
37868
38279
|
let linkPath;
|
|
37869
38280
|
try {
|
|
37870
|
-
linkPath = await fsrealpath(
|
|
38281
|
+
linkPath = await fsrealpath(path23);
|
|
37871
38282
|
} catch (e) {
|
|
37872
38283
|
this.fsw._emitReady();
|
|
37873
38284
|
return true;
|
|
@@ -37877,12 +38288,12 @@ var NodeFsHandler = class {
|
|
|
37877
38288
|
if (dir.has(item)) {
|
|
37878
38289
|
if (this.fsw._symlinkPaths.get(full) !== linkPath) {
|
|
37879
38290
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
37880
|
-
this.fsw._emit(EV.CHANGE,
|
|
38291
|
+
this.fsw._emit(EV.CHANGE, path23, entry.stats);
|
|
37881
38292
|
}
|
|
37882
38293
|
} else {
|
|
37883
38294
|
dir.add(item);
|
|
37884
38295
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
37885
|
-
this.fsw._emit(EV.ADD,
|
|
38296
|
+
this.fsw._emit(EV.ADD, path23, entry.stats);
|
|
37886
38297
|
}
|
|
37887
38298
|
this.fsw._emitReady();
|
|
37888
38299
|
return true;
|
|
@@ -37911,9 +38322,9 @@ var NodeFsHandler = class {
|
|
|
37911
38322
|
return;
|
|
37912
38323
|
}
|
|
37913
38324
|
const item = entry.path;
|
|
37914
|
-
let
|
|
38325
|
+
let path23 = sysPath.join(directory, item);
|
|
37915
38326
|
current.add(item);
|
|
37916
|
-
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory,
|
|
38327
|
+
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path23, item)) {
|
|
37917
38328
|
return;
|
|
37918
38329
|
}
|
|
37919
38330
|
if (this.fsw.closed) {
|
|
@@ -37922,8 +38333,8 @@ var NodeFsHandler = class {
|
|
|
37922
38333
|
}
|
|
37923
38334
|
if (item === target || !target && !previous.has(item)) {
|
|
37924
38335
|
this.fsw._incrReadyCount();
|
|
37925
|
-
|
|
37926
|
-
this._addToNodeFs(
|
|
38336
|
+
path23 = sysPath.join(dir, sysPath.relative(dir, path23));
|
|
38337
|
+
this._addToNodeFs(path23, initialAdd, wh, depth + 1);
|
|
37927
38338
|
}
|
|
37928
38339
|
}).on(EV.ERROR, this._boundHandleError);
|
|
37929
38340
|
return new Promise((resolve3, reject) => {
|
|
@@ -37992,13 +38403,13 @@ var NodeFsHandler = class {
|
|
|
37992
38403
|
* @param depth Child path actually targeted for watch
|
|
37993
38404
|
* @param target Child path actually targeted for watch
|
|
37994
38405
|
*/
|
|
37995
|
-
async _addToNodeFs(
|
|
38406
|
+
async _addToNodeFs(path23, initialAdd, priorWh, depth, target) {
|
|
37996
38407
|
const ready = this.fsw._emitReady;
|
|
37997
|
-
if (this.fsw._isIgnored(
|
|
38408
|
+
if (this.fsw._isIgnored(path23) || this.fsw.closed) {
|
|
37998
38409
|
ready();
|
|
37999
38410
|
return false;
|
|
38000
38411
|
}
|
|
38001
|
-
const wh = this.fsw._getWatchHelpers(
|
|
38412
|
+
const wh = this.fsw._getWatchHelpers(path23);
|
|
38002
38413
|
if (priorWh) {
|
|
38003
38414
|
wh.filterPath = (entry) => priorWh.filterPath(entry);
|
|
38004
38415
|
wh.filterDir = (entry) => priorWh.filterDir(entry);
|
|
@@ -38014,8 +38425,8 @@ var NodeFsHandler = class {
|
|
|
38014
38425
|
const follow = this.fsw.options.followSymlinks;
|
|
38015
38426
|
let closer;
|
|
38016
38427
|
if (stats.isDirectory()) {
|
|
38017
|
-
const absPath = sysPath.resolve(
|
|
38018
|
-
const targetPath = follow ? await fsrealpath(
|
|
38428
|
+
const absPath = sysPath.resolve(path23);
|
|
38429
|
+
const targetPath = follow ? await fsrealpath(path23) : path23;
|
|
38019
38430
|
if (this.fsw.closed)
|
|
38020
38431
|
return;
|
|
38021
38432
|
closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
|
|
@@ -38025,29 +38436,29 @@ var NodeFsHandler = class {
|
|
|
38025
38436
|
this.fsw._symlinkPaths.set(absPath, targetPath);
|
|
38026
38437
|
}
|
|
38027
38438
|
} else if (stats.isSymbolicLink()) {
|
|
38028
|
-
const targetPath = follow ? await fsrealpath(
|
|
38439
|
+
const targetPath = follow ? await fsrealpath(path23) : path23;
|
|
38029
38440
|
if (this.fsw.closed)
|
|
38030
38441
|
return;
|
|
38031
38442
|
const parent = sysPath.dirname(wh.watchPath);
|
|
38032
38443
|
this.fsw._getWatchedDir(parent).add(wh.watchPath);
|
|
38033
38444
|
this.fsw._emit(EV.ADD, wh.watchPath, stats);
|
|
38034
|
-
closer = await this._handleDir(parent, stats, initialAdd, depth,
|
|
38445
|
+
closer = await this._handleDir(parent, stats, initialAdd, depth, path23, wh, targetPath);
|
|
38035
38446
|
if (this.fsw.closed)
|
|
38036
38447
|
return;
|
|
38037
38448
|
if (targetPath !== void 0) {
|
|
38038
|
-
this.fsw._symlinkPaths.set(sysPath.resolve(
|
|
38449
|
+
this.fsw._symlinkPaths.set(sysPath.resolve(path23), targetPath);
|
|
38039
38450
|
}
|
|
38040
38451
|
} else {
|
|
38041
38452
|
closer = this._handleFile(wh.watchPath, stats, initialAdd);
|
|
38042
38453
|
}
|
|
38043
38454
|
ready();
|
|
38044
38455
|
if (closer)
|
|
38045
|
-
this.fsw._addPathCloser(
|
|
38456
|
+
this.fsw._addPathCloser(path23, closer);
|
|
38046
38457
|
return false;
|
|
38047
38458
|
} catch (error2) {
|
|
38048
38459
|
if (this.fsw._handleError(error2)) {
|
|
38049
38460
|
ready();
|
|
38050
|
-
return
|
|
38461
|
+
return path23;
|
|
38051
38462
|
}
|
|
38052
38463
|
}
|
|
38053
38464
|
}
|
|
@@ -38090,26 +38501,26 @@ function createPattern(matcher) {
|
|
|
38090
38501
|
}
|
|
38091
38502
|
return () => false;
|
|
38092
38503
|
}
|
|
38093
|
-
function normalizePath(
|
|
38094
|
-
if (typeof
|
|
38504
|
+
function normalizePath(path23) {
|
|
38505
|
+
if (typeof path23 !== "string")
|
|
38095
38506
|
throw new Error("string expected");
|
|
38096
|
-
|
|
38097
|
-
|
|
38507
|
+
path23 = sysPath2.normalize(path23);
|
|
38508
|
+
path23 = path23.replace(/\\/g, "/");
|
|
38098
38509
|
let prepend = false;
|
|
38099
|
-
if (
|
|
38510
|
+
if (path23.startsWith("//"))
|
|
38100
38511
|
prepend = true;
|
|
38101
38512
|
const DOUBLE_SLASH_RE2 = /\/\//;
|
|
38102
|
-
while (
|
|
38103
|
-
|
|
38513
|
+
while (path23.match(DOUBLE_SLASH_RE2))
|
|
38514
|
+
path23 = path23.replace(DOUBLE_SLASH_RE2, "/");
|
|
38104
38515
|
if (prepend)
|
|
38105
|
-
|
|
38106
|
-
return
|
|
38516
|
+
path23 = "/" + path23;
|
|
38517
|
+
return path23;
|
|
38107
38518
|
}
|
|
38108
38519
|
function matchPatterns(patterns, testString, stats) {
|
|
38109
|
-
const
|
|
38520
|
+
const path23 = normalizePath(testString);
|
|
38110
38521
|
for (let index = 0; index < patterns.length; index++) {
|
|
38111
38522
|
const pattern = patterns[index];
|
|
38112
|
-
if (pattern(
|
|
38523
|
+
if (pattern(path23, stats)) {
|
|
38113
38524
|
return true;
|
|
38114
38525
|
}
|
|
38115
38526
|
}
|
|
@@ -38149,19 +38560,19 @@ var toUnix = (string) => {
|
|
|
38149
38560
|
}
|
|
38150
38561
|
return str;
|
|
38151
38562
|
};
|
|
38152
|
-
var normalizePathToUnix = (
|
|
38153
|
-
var normalizeIgnored = (cwd = "") => (
|
|
38154
|
-
if (typeof
|
|
38155
|
-
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));
|
|
38156
38567
|
} else {
|
|
38157
|
-
return
|
|
38568
|
+
return path23;
|
|
38158
38569
|
}
|
|
38159
38570
|
};
|
|
38160
|
-
var getAbsolutePath = (
|
|
38161
|
-
if (sysPath2.isAbsolute(
|
|
38162
|
-
return
|
|
38571
|
+
var getAbsolutePath = (path23, cwd) => {
|
|
38572
|
+
if (sysPath2.isAbsolute(path23)) {
|
|
38573
|
+
return path23;
|
|
38163
38574
|
}
|
|
38164
|
-
return sysPath2.join(cwd,
|
|
38575
|
+
return sysPath2.join(cwd, path23);
|
|
38165
38576
|
};
|
|
38166
38577
|
var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
|
|
38167
38578
|
var DirEntry = class {
|
|
@@ -38216,10 +38627,10 @@ var DirEntry = class {
|
|
|
38216
38627
|
var STAT_METHOD_F = "stat";
|
|
38217
38628
|
var STAT_METHOD_L = "lstat";
|
|
38218
38629
|
var WatchHelper = class {
|
|
38219
|
-
constructor(
|
|
38630
|
+
constructor(path23, follow, fsw) {
|
|
38220
38631
|
this.fsw = fsw;
|
|
38221
|
-
const watchPath =
|
|
38222
|
-
this.path =
|
|
38632
|
+
const watchPath = path23;
|
|
38633
|
+
this.path = path23 = path23.replace(REPLACER_RE, "");
|
|
38223
38634
|
this.watchPath = watchPath;
|
|
38224
38635
|
this.fullWatchPath = sysPath2.resolve(watchPath);
|
|
38225
38636
|
this.dirParts = [];
|
|
@@ -38341,20 +38752,20 @@ var FSWatcher = class extends EventEmitter {
|
|
|
38341
38752
|
this._closePromise = void 0;
|
|
38342
38753
|
let paths = unifyPaths(paths_);
|
|
38343
38754
|
if (cwd) {
|
|
38344
|
-
paths = paths.map((
|
|
38345
|
-
const absPath = getAbsolutePath(
|
|
38755
|
+
paths = paths.map((path23) => {
|
|
38756
|
+
const absPath = getAbsolutePath(path23, cwd);
|
|
38346
38757
|
return absPath;
|
|
38347
38758
|
});
|
|
38348
38759
|
}
|
|
38349
|
-
paths.forEach((
|
|
38350
|
-
this._removeIgnoredPath(
|
|
38760
|
+
paths.forEach((path23) => {
|
|
38761
|
+
this._removeIgnoredPath(path23);
|
|
38351
38762
|
});
|
|
38352
38763
|
this._userIgnored = void 0;
|
|
38353
38764
|
if (!this._readyCount)
|
|
38354
38765
|
this._readyCount = 0;
|
|
38355
38766
|
this._readyCount += paths.length;
|
|
38356
|
-
Promise.all(paths.map(async (
|
|
38357
|
-
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);
|
|
38358
38769
|
if (res)
|
|
38359
38770
|
this._emitReady();
|
|
38360
38771
|
return res;
|
|
@@ -38376,17 +38787,17 @@ var FSWatcher = class extends EventEmitter {
|
|
|
38376
38787
|
return this;
|
|
38377
38788
|
const paths = unifyPaths(paths_);
|
|
38378
38789
|
const { cwd } = this.options;
|
|
38379
|
-
paths.forEach((
|
|
38380
|
-
if (!sysPath2.isAbsolute(
|
|
38790
|
+
paths.forEach((path23) => {
|
|
38791
|
+
if (!sysPath2.isAbsolute(path23) && !this._closers.has(path23)) {
|
|
38381
38792
|
if (cwd)
|
|
38382
|
-
|
|
38383
|
-
|
|
38793
|
+
path23 = sysPath2.join(cwd, path23);
|
|
38794
|
+
path23 = sysPath2.resolve(path23);
|
|
38384
38795
|
}
|
|
38385
|
-
this._closePath(
|
|
38386
|
-
this._addIgnoredPath(
|
|
38387
|
-
if (this._watched.has(
|
|
38796
|
+
this._closePath(path23);
|
|
38797
|
+
this._addIgnoredPath(path23);
|
|
38798
|
+
if (this._watched.has(path23)) {
|
|
38388
38799
|
this._addIgnoredPath({
|
|
38389
|
-
path:
|
|
38800
|
+
path: path23,
|
|
38390
38801
|
recursive: true
|
|
38391
38802
|
});
|
|
38392
38803
|
}
|
|
@@ -38450,38 +38861,38 @@ var FSWatcher = class extends EventEmitter {
|
|
|
38450
38861
|
* @param stats arguments to be passed with event
|
|
38451
38862
|
* @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
|
|
38452
38863
|
*/
|
|
38453
|
-
async _emit(event,
|
|
38864
|
+
async _emit(event, path23, stats) {
|
|
38454
38865
|
if (this.closed)
|
|
38455
38866
|
return;
|
|
38456
38867
|
const opts = this.options;
|
|
38457
38868
|
if (isWindows)
|
|
38458
|
-
|
|
38869
|
+
path23 = sysPath2.normalize(path23);
|
|
38459
38870
|
if (opts.cwd)
|
|
38460
|
-
|
|
38461
|
-
const args = [
|
|
38871
|
+
path23 = sysPath2.relative(opts.cwd, path23);
|
|
38872
|
+
const args = [path23];
|
|
38462
38873
|
if (stats != null)
|
|
38463
38874
|
args.push(stats);
|
|
38464
38875
|
const awf = opts.awaitWriteFinish;
|
|
38465
38876
|
let pw;
|
|
38466
|
-
if (awf && (pw = this._pendingWrites.get(
|
|
38877
|
+
if (awf && (pw = this._pendingWrites.get(path23))) {
|
|
38467
38878
|
pw.lastChange = /* @__PURE__ */ new Date();
|
|
38468
38879
|
return this;
|
|
38469
38880
|
}
|
|
38470
38881
|
if (opts.atomic) {
|
|
38471
38882
|
if (event === EVENTS.UNLINK) {
|
|
38472
|
-
this._pendingUnlinks.set(
|
|
38883
|
+
this._pendingUnlinks.set(path23, [event, ...args]);
|
|
38473
38884
|
setTimeout(() => {
|
|
38474
|
-
this._pendingUnlinks.forEach((entry,
|
|
38885
|
+
this._pendingUnlinks.forEach((entry, path24) => {
|
|
38475
38886
|
this.emit(...entry);
|
|
38476
38887
|
this.emit(EVENTS.ALL, ...entry);
|
|
38477
|
-
this._pendingUnlinks.delete(
|
|
38888
|
+
this._pendingUnlinks.delete(path24);
|
|
38478
38889
|
});
|
|
38479
38890
|
}, typeof opts.atomic === "number" ? opts.atomic : 100);
|
|
38480
38891
|
return this;
|
|
38481
38892
|
}
|
|
38482
|
-
if (event === EVENTS.ADD && this._pendingUnlinks.has(
|
|
38893
|
+
if (event === EVENTS.ADD && this._pendingUnlinks.has(path23)) {
|
|
38483
38894
|
event = EVENTS.CHANGE;
|
|
38484
|
-
this._pendingUnlinks.delete(
|
|
38895
|
+
this._pendingUnlinks.delete(path23);
|
|
38485
38896
|
}
|
|
38486
38897
|
}
|
|
38487
38898
|
if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
|
|
@@ -38499,16 +38910,16 @@ var FSWatcher = class extends EventEmitter {
|
|
|
38499
38910
|
this.emitWithAll(event, args);
|
|
38500
38911
|
}
|
|
38501
38912
|
};
|
|
38502
|
-
this._awaitWriteFinish(
|
|
38913
|
+
this._awaitWriteFinish(path23, awf.stabilityThreshold, event, awfEmit);
|
|
38503
38914
|
return this;
|
|
38504
38915
|
}
|
|
38505
38916
|
if (event === EVENTS.CHANGE) {
|
|
38506
|
-
const isThrottled = !this._throttle(EVENTS.CHANGE,
|
|
38917
|
+
const isThrottled = !this._throttle(EVENTS.CHANGE, path23, 50);
|
|
38507
38918
|
if (isThrottled)
|
|
38508
38919
|
return this;
|
|
38509
38920
|
}
|
|
38510
38921
|
if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
|
|
38511
|
-
const fullPath = opts.cwd ? sysPath2.join(opts.cwd,
|
|
38922
|
+
const fullPath = opts.cwd ? sysPath2.join(opts.cwd, path23) : path23;
|
|
38512
38923
|
let stats2;
|
|
38513
38924
|
try {
|
|
38514
38925
|
stats2 = await stat3(fullPath);
|
|
@@ -38539,23 +38950,23 @@ var FSWatcher = class extends EventEmitter {
|
|
|
38539
38950
|
* @param timeout duration of time to suppress duplicate actions
|
|
38540
38951
|
* @returns tracking object or false if action should be suppressed
|
|
38541
38952
|
*/
|
|
38542
|
-
_throttle(actionType,
|
|
38953
|
+
_throttle(actionType, path23, timeout) {
|
|
38543
38954
|
if (!this._throttled.has(actionType)) {
|
|
38544
38955
|
this._throttled.set(actionType, /* @__PURE__ */ new Map());
|
|
38545
38956
|
}
|
|
38546
38957
|
const action = this._throttled.get(actionType);
|
|
38547
38958
|
if (!action)
|
|
38548
38959
|
throw new Error("invalid throttle");
|
|
38549
|
-
const actionPath = action.get(
|
|
38960
|
+
const actionPath = action.get(path23);
|
|
38550
38961
|
if (actionPath) {
|
|
38551
38962
|
actionPath.count++;
|
|
38552
38963
|
return false;
|
|
38553
38964
|
}
|
|
38554
38965
|
let timeoutObject;
|
|
38555
38966
|
const clear = () => {
|
|
38556
|
-
const item = action.get(
|
|
38967
|
+
const item = action.get(path23);
|
|
38557
38968
|
const count = item ? item.count : 0;
|
|
38558
|
-
action.delete(
|
|
38969
|
+
action.delete(path23);
|
|
38559
38970
|
clearTimeout(timeoutObject);
|
|
38560
38971
|
if (item)
|
|
38561
38972
|
clearTimeout(item.timeoutObject);
|
|
@@ -38563,7 +38974,7 @@ var FSWatcher = class extends EventEmitter {
|
|
|
38563
38974
|
};
|
|
38564
38975
|
timeoutObject = setTimeout(clear, timeout);
|
|
38565
38976
|
const thr = { timeoutObject, clear, count: 0 };
|
|
38566
|
-
action.set(
|
|
38977
|
+
action.set(path23, thr);
|
|
38567
38978
|
return thr;
|
|
38568
38979
|
}
|
|
38569
38980
|
_incrReadyCount() {
|
|
@@ -38577,44 +38988,44 @@ var FSWatcher = class extends EventEmitter {
|
|
|
38577
38988
|
* @param event
|
|
38578
38989
|
* @param awfEmit Callback to be called when ready for event to be emitted.
|
|
38579
38990
|
*/
|
|
38580
|
-
_awaitWriteFinish(
|
|
38991
|
+
_awaitWriteFinish(path23, threshold, event, awfEmit) {
|
|
38581
38992
|
const awf = this.options.awaitWriteFinish;
|
|
38582
38993
|
if (typeof awf !== "object")
|
|
38583
38994
|
return;
|
|
38584
38995
|
const pollInterval = awf.pollInterval;
|
|
38585
38996
|
let timeoutHandler;
|
|
38586
|
-
let fullPath =
|
|
38587
|
-
if (this.options.cwd && !sysPath2.isAbsolute(
|
|
38588
|
-
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);
|
|
38589
39000
|
}
|
|
38590
39001
|
const now = /* @__PURE__ */ new Date();
|
|
38591
39002
|
const writes = this._pendingWrites;
|
|
38592
39003
|
function awaitWriteFinishFn(prevStat) {
|
|
38593
39004
|
statcb(fullPath, (err, curStat) => {
|
|
38594
|
-
if (err || !writes.has(
|
|
39005
|
+
if (err || !writes.has(path23)) {
|
|
38595
39006
|
if (err && err.code !== "ENOENT")
|
|
38596
39007
|
awfEmit(err);
|
|
38597
39008
|
return;
|
|
38598
39009
|
}
|
|
38599
39010
|
const now2 = Number(/* @__PURE__ */ new Date());
|
|
38600
39011
|
if (prevStat && curStat.size !== prevStat.size) {
|
|
38601
|
-
writes.get(
|
|
39012
|
+
writes.get(path23).lastChange = now2;
|
|
38602
39013
|
}
|
|
38603
|
-
const pw = writes.get(
|
|
39014
|
+
const pw = writes.get(path23);
|
|
38604
39015
|
const df = now2 - pw.lastChange;
|
|
38605
39016
|
if (df >= threshold) {
|
|
38606
|
-
writes.delete(
|
|
39017
|
+
writes.delete(path23);
|
|
38607
39018
|
awfEmit(void 0, curStat);
|
|
38608
39019
|
} else {
|
|
38609
39020
|
timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
|
|
38610
39021
|
}
|
|
38611
39022
|
});
|
|
38612
39023
|
}
|
|
38613
|
-
if (!writes.has(
|
|
38614
|
-
writes.set(
|
|
39024
|
+
if (!writes.has(path23)) {
|
|
39025
|
+
writes.set(path23, {
|
|
38615
39026
|
lastChange: now,
|
|
38616
39027
|
cancelWait: () => {
|
|
38617
|
-
writes.delete(
|
|
39028
|
+
writes.delete(path23);
|
|
38618
39029
|
clearTimeout(timeoutHandler);
|
|
38619
39030
|
return event;
|
|
38620
39031
|
}
|
|
@@ -38625,8 +39036,8 @@ var FSWatcher = class extends EventEmitter {
|
|
|
38625
39036
|
/**
|
|
38626
39037
|
* Determines whether user has asked to ignore this path.
|
|
38627
39038
|
*/
|
|
38628
|
-
_isIgnored(
|
|
38629
|
-
if (this.options.atomic && DOT_RE.test(
|
|
39039
|
+
_isIgnored(path23, stats) {
|
|
39040
|
+
if (this.options.atomic && DOT_RE.test(path23))
|
|
38630
39041
|
return true;
|
|
38631
39042
|
if (!this._userIgnored) {
|
|
38632
39043
|
const { cwd } = this.options;
|
|
@@ -38636,17 +39047,17 @@ var FSWatcher = class extends EventEmitter {
|
|
|
38636
39047
|
const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
|
|
38637
39048
|
this._userIgnored = anymatch(list, void 0);
|
|
38638
39049
|
}
|
|
38639
|
-
return this._userIgnored(
|
|
39050
|
+
return this._userIgnored(path23, stats);
|
|
38640
39051
|
}
|
|
38641
|
-
_isntIgnored(
|
|
38642
|
-
return !this._isIgnored(
|
|
39052
|
+
_isntIgnored(path23, stat4) {
|
|
39053
|
+
return !this._isIgnored(path23, stat4);
|
|
38643
39054
|
}
|
|
38644
39055
|
/**
|
|
38645
39056
|
* Provides a set of common helpers and properties relating to symlink handling.
|
|
38646
39057
|
* @param path file or directory pattern being watched
|
|
38647
39058
|
*/
|
|
38648
|
-
_getWatchHelpers(
|
|
38649
|
-
return new WatchHelper(
|
|
39059
|
+
_getWatchHelpers(path23) {
|
|
39060
|
+
return new WatchHelper(path23, this.options.followSymlinks, this);
|
|
38650
39061
|
}
|
|
38651
39062
|
// Directory helpers
|
|
38652
39063
|
// -----------------
|
|
@@ -38678,63 +39089,63 @@ var FSWatcher = class extends EventEmitter {
|
|
|
38678
39089
|
* @param item base path of item/directory
|
|
38679
39090
|
*/
|
|
38680
39091
|
_remove(directory, item, isDirectory) {
|
|
38681
|
-
const
|
|
38682
|
-
const fullPath = sysPath2.resolve(
|
|
38683
|
-
isDirectory = isDirectory != null ? isDirectory : this._watched.has(
|
|
38684
|
-
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))
|
|
38685
39096
|
return;
|
|
38686
39097
|
if (!isDirectory && this._watched.size === 1) {
|
|
38687
39098
|
this.add(directory, item, true);
|
|
38688
39099
|
}
|
|
38689
|
-
const wp = this._getWatchedDir(
|
|
39100
|
+
const wp = this._getWatchedDir(path23);
|
|
38690
39101
|
const nestedDirectoryChildren = wp.getChildren();
|
|
38691
|
-
nestedDirectoryChildren.forEach((nested) => this._remove(
|
|
39102
|
+
nestedDirectoryChildren.forEach((nested) => this._remove(path23, nested));
|
|
38692
39103
|
const parent = this._getWatchedDir(directory);
|
|
38693
39104
|
const wasTracked = parent.has(item);
|
|
38694
39105
|
parent.remove(item);
|
|
38695
39106
|
if (this._symlinkPaths.has(fullPath)) {
|
|
38696
39107
|
this._symlinkPaths.delete(fullPath);
|
|
38697
39108
|
}
|
|
38698
|
-
let relPath =
|
|
39109
|
+
let relPath = path23;
|
|
38699
39110
|
if (this.options.cwd)
|
|
38700
|
-
relPath = sysPath2.relative(this.options.cwd,
|
|
39111
|
+
relPath = sysPath2.relative(this.options.cwd, path23);
|
|
38701
39112
|
if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
|
|
38702
39113
|
const event = this._pendingWrites.get(relPath).cancelWait();
|
|
38703
39114
|
if (event === EVENTS.ADD)
|
|
38704
39115
|
return;
|
|
38705
39116
|
}
|
|
38706
|
-
this._watched.delete(
|
|
39117
|
+
this._watched.delete(path23);
|
|
38707
39118
|
this._watched.delete(fullPath);
|
|
38708
39119
|
const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
|
|
38709
|
-
if (wasTracked && !this._isIgnored(
|
|
38710
|
-
this._emit(eventName,
|
|
38711
|
-
this._closePath(
|
|
39120
|
+
if (wasTracked && !this._isIgnored(path23))
|
|
39121
|
+
this._emit(eventName, path23);
|
|
39122
|
+
this._closePath(path23);
|
|
38712
39123
|
}
|
|
38713
39124
|
/**
|
|
38714
39125
|
* Closes all watchers for a path
|
|
38715
39126
|
*/
|
|
38716
|
-
_closePath(
|
|
38717
|
-
this._closeFile(
|
|
38718
|
-
const dir = sysPath2.dirname(
|
|
38719
|
-
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));
|
|
38720
39131
|
}
|
|
38721
39132
|
/**
|
|
38722
39133
|
* Closes only file-specific watchers
|
|
38723
39134
|
*/
|
|
38724
|
-
_closeFile(
|
|
38725
|
-
const closers = this._closers.get(
|
|
39135
|
+
_closeFile(path23) {
|
|
39136
|
+
const closers = this._closers.get(path23);
|
|
38726
39137
|
if (!closers)
|
|
38727
39138
|
return;
|
|
38728
39139
|
closers.forEach((closer) => closer());
|
|
38729
|
-
this._closers.delete(
|
|
39140
|
+
this._closers.delete(path23);
|
|
38730
39141
|
}
|
|
38731
|
-
_addPathCloser(
|
|
39142
|
+
_addPathCloser(path23, closer) {
|
|
38732
39143
|
if (!closer)
|
|
38733
39144
|
return;
|
|
38734
|
-
let list = this._closers.get(
|
|
39145
|
+
let list = this._closers.get(path23);
|
|
38735
39146
|
if (!list) {
|
|
38736
39147
|
list = [];
|
|
38737
|
-
this._closers.set(
|
|
39148
|
+
this._closers.set(path23, list);
|
|
38738
39149
|
}
|
|
38739
39150
|
list.push(closer);
|
|
38740
39151
|
}
|
|
@@ -38764,7 +39175,7 @@ function watch(paths, options = {}) {
|
|
|
38764
39175
|
var esm_default13 = { watch, FSWatcher };
|
|
38765
39176
|
|
|
38766
39177
|
// src/lib/watcher.ts
|
|
38767
|
-
import
|
|
39178
|
+
import path14 from "node:path";
|
|
38768
39179
|
function startWatcher(options) {
|
|
38769
39180
|
const { dir, extensions, onChange } = options;
|
|
38770
39181
|
const extSet = new Set(extensions);
|
|
@@ -38778,7 +39189,7 @@ function startWatcher(options) {
|
|
|
38778
39189
|
});
|
|
38779
39190
|
let debounceTimer = null;
|
|
38780
39191
|
const handleChange2 = (filePath) => {
|
|
38781
|
-
const ext =
|
|
39192
|
+
const ext = path14.extname(filePath);
|
|
38782
39193
|
if (!extSet.has(ext)) return;
|
|
38783
39194
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
38784
39195
|
debounceTimer = setTimeout(() => {
|
|
@@ -38804,29 +39215,30 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
|
38804
39215
|
import http from "node:http";
|
|
38805
39216
|
var PORT_START = 4400;
|
|
38806
39217
|
var PORT_END = 4409;
|
|
39218
|
+
var PING_INTERVAL_MS = 3e4;
|
|
39219
|
+
function portsBusyError(start, end) {
|
|
39220
|
+
return new CliError("cli_failed", `Ports ${start} to ${end} are all busy.`, [
|
|
39221
|
+
"Another `avsb dev` (or another program) is holding them. Stop it, then run this again.",
|
|
39222
|
+
`\`lsof -i :${start}\` shows what is listening on a port.`
|
|
39223
|
+
]);
|
|
39224
|
+
}
|
|
39225
|
+
function isAddressInUse(err) {
|
|
39226
|
+
return typeof err === "object" && err !== null && "code" in err && err.code === "EADDRINUSE";
|
|
39227
|
+
}
|
|
38807
39228
|
async function startDevServer(options) {
|
|
38808
39229
|
const { experimentName, experimentId, onExtensionMessage } = options;
|
|
38809
|
-
|
|
38810
|
-
|
|
38811
|
-
let
|
|
38812
|
-
for (let
|
|
39230
|
+
const start = options.portRange?.start ?? PORT_START;
|
|
39231
|
+
const end = options.portRange?.end ?? PORT_END;
|
|
39232
|
+
let bound = null;
|
|
39233
|
+
for (let port2 = start; port2 <= end && !bound; port2++) {
|
|
38813
39234
|
try {
|
|
38814
|
-
|
|
38815
|
-
|
|
38816
|
-
|
|
38817
|
-
resolvedPort = port;
|
|
38818
|
-
break;
|
|
38819
|
-
} catch {
|
|
38820
|
-
continue;
|
|
39235
|
+
bound = { ...await tryPort(port2, experimentName, experimentId, onExtensionMessage), port: port2 };
|
|
39236
|
+
} catch (err) {
|
|
39237
|
+
if (!isAddressInUse(err)) throw err;
|
|
38821
39238
|
}
|
|
38822
39239
|
}
|
|
38823
|
-
if (!
|
|
38824
|
-
|
|
38825
|
-
}
|
|
38826
|
-
const pingInterval = setInterval(() => {
|
|
38827
|
-
const msg = { type: "ping", timestamp: Date.now() };
|
|
38828
|
-
broadcast(JSON.stringify(msg));
|
|
38829
|
-
}, 3e4);
|
|
39240
|
+
if (!bound) throw portsBusyError(start, end);
|
|
39241
|
+
const { httpServer, wss, port } = bound;
|
|
38830
39242
|
function broadcast(data) {
|
|
38831
39243
|
wss.clients.forEach((client) => {
|
|
38832
39244
|
if (client.readyState === import_websocket.default.OPEN) {
|
|
@@ -38834,8 +39246,12 @@ async function startDevServer(options) {
|
|
|
38834
39246
|
}
|
|
38835
39247
|
});
|
|
38836
39248
|
}
|
|
39249
|
+
const pingInterval = setInterval(() => {
|
|
39250
|
+
const msg = { type: "ping", timestamp: Date.now() };
|
|
39251
|
+
broadcast(JSON.stringify(msg));
|
|
39252
|
+
}, PING_INTERVAL_MS);
|
|
38837
39253
|
return {
|
|
38838
|
-
port
|
|
39254
|
+
port,
|
|
38839
39255
|
broadcast(msg) {
|
|
38840
39256
|
broadcast(JSON.stringify(msg));
|
|
38841
39257
|
},
|
|
@@ -38878,6 +39294,16 @@ function tryPort(port, experimentName, experimentId, onExtensionMessage) {
|
|
|
38878
39294
|
);
|
|
38879
39295
|
});
|
|
38880
39296
|
const wss = new import_websocket_server.default({ server: httpServer });
|
|
39297
|
+
let settled = false;
|
|
39298
|
+
const fail2 = (err) => {
|
|
39299
|
+
if (settled) return;
|
|
39300
|
+
settled = true;
|
|
39301
|
+
wss.close();
|
|
39302
|
+
httpServer.close();
|
|
39303
|
+
reject(err);
|
|
39304
|
+
};
|
|
39305
|
+
httpServer.on("error", fail2);
|
|
39306
|
+
wss.on("error", fail2);
|
|
38881
39307
|
wss.on("connection", (ws) => {
|
|
38882
39308
|
ws.on("message", (raw) => {
|
|
38883
39309
|
try {
|
|
@@ -38887,8 +39313,11 @@ function tryPort(port, experimentName, experimentId, onExtensionMessage) {
|
|
|
38887
39313
|
}
|
|
38888
39314
|
});
|
|
38889
39315
|
});
|
|
38890
|
-
httpServer.
|
|
38891
|
-
|
|
39316
|
+
httpServer.listen(port, () => {
|
|
39317
|
+
if (settled) return;
|
|
39318
|
+
settled = true;
|
|
39319
|
+
resolve3({ httpServer, wss });
|
|
39320
|
+
});
|
|
38892
39321
|
});
|
|
38893
39322
|
}
|
|
38894
39323
|
|
|
@@ -38944,7 +39373,7 @@ var devCommand = new Command("dev").description("Start dev server with live relo
|
|
|
38944
39373
|
dir,
|
|
38945
39374
|
extensions: [".ts", ".js", ".scss", ".css"],
|
|
38946
39375
|
onChange: async (filePath) => {
|
|
38947
|
-
const relativePath =
|
|
39376
|
+
const relativePath = path15.relative(dir, filePath);
|
|
38948
39377
|
info(`File changed: ${relativePath}`);
|
|
38949
39378
|
try {
|
|
38950
39379
|
compiled = await compileExperiment(dir, manifest);
|
|
@@ -39060,15 +39489,15 @@ var statusCommand = withOutputOptions(
|
|
|
39060
39489
|
heading(manifest.experimentName);
|
|
39061
39490
|
line();
|
|
39062
39491
|
for (const row of buildStatusRows(manifest)) {
|
|
39063
|
-
|
|
39492
|
+
field(row.label, row.value);
|
|
39064
39493
|
}
|
|
39065
39494
|
line();
|
|
39066
39495
|
});
|
|
39067
39496
|
|
|
39068
39497
|
// src/commands/project.ts
|
|
39069
39498
|
import { createHash as createHash4 } from "node:crypto";
|
|
39070
|
-
import
|
|
39071
|
-
import
|
|
39499
|
+
import fs12 from "node:fs";
|
|
39500
|
+
import path16 from "node:path";
|
|
39072
39501
|
function sha2564(content) {
|
|
39073
39502
|
return createHash4("sha256").update(content).digest("hex");
|
|
39074
39503
|
}
|
|
@@ -39099,16 +39528,29 @@ var projectPullCommand = new Command("pull").description("Pull project script fr
|
|
|
39099
39528
|
label("ID", `PRJ-${project.shortId}`);
|
|
39100
39529
|
label("Language", project.projectJsLang === "TYPESCRIPT" ? "TypeScript" : "JavaScript");
|
|
39101
39530
|
line();
|
|
39102
|
-
const
|
|
39103
|
-
const dir =
|
|
39104
|
-
if (
|
|
39531
|
+
const target = resolveProjectPullTarget(process.cwd(), project.shortId);
|
|
39532
|
+
const dir = target.dir;
|
|
39533
|
+
if (target.kind === "blocked") {
|
|
39534
|
+
failCommand(
|
|
39535
|
+
new CliError("cli_usage", `${dir} already exists and is not a pulled copy of PRJ-${project.shortId}.`, [
|
|
39536
|
+
"Move or rename it, or run `avsb project pull` from another folder."
|
|
39537
|
+
])
|
|
39538
|
+
);
|
|
39539
|
+
}
|
|
39540
|
+
if (target.kind === "existing") {
|
|
39105
39541
|
const { manifest } = readProjectManifestOrExit(dir);
|
|
39106
39542
|
if (!options.force) {
|
|
39107
39543
|
const scriptFile = `project.${manifest.language}`;
|
|
39108
|
-
const scriptPath =
|
|
39109
|
-
const localContent =
|
|
39544
|
+
const scriptPath = path16.join(dir, scriptFile);
|
|
39545
|
+
const localContent = fs12.existsSync(scriptPath) ? fs12.readFileSync(scriptPath, "utf-8") : "";
|
|
39110
39546
|
const remoteContent = manifest.language === "ts" ? project.editorProjectTs ?? project.editorProjectJs ?? "" : project.editorProjectJs ?? "";
|
|
39111
39547
|
if (localContent !== remoteContent && localContent !== "") {
|
|
39548
|
+
const refusal = refusalWithoutPrompt({
|
|
39549
|
+
code: "cli_conflict",
|
|
39550
|
+
message: `Pulling would overwrite your local ${scriptFile}, and this run cannot ask first.`,
|
|
39551
|
+
hints: [`Run \`avsb project pull ${projectInput} --force\` to overwrite it.`]
|
|
39552
|
+
});
|
|
39553
|
+
if (refusal) failCommand(refusal);
|
|
39112
39554
|
const { proceed } = await esm_default12.prompt([
|
|
39113
39555
|
{
|
|
39114
39556
|
type: "confirm",
|
|
@@ -39126,7 +39568,7 @@ var projectPullCommand = new Command("pull").description("Pull project script fr
|
|
|
39126
39568
|
const updateSpinner = ora("Updating files...").start();
|
|
39127
39569
|
const updated = updateProjectFile(dir, manifest, response.data);
|
|
39128
39570
|
if (updated.length > 0) {
|
|
39129
|
-
updateSpinner.succeed(
|
|
39571
|
+
updateSpinner.succeed(`Files updated in ${path16.relative(process.cwd(), dir) || "this folder"}`);
|
|
39130
39572
|
fileTree(updated);
|
|
39131
39573
|
} else {
|
|
39132
39574
|
updateSpinner.succeed("Already up to date");
|
|
@@ -39134,12 +39576,12 @@ var projectPullCommand = new Command("pull").description("Pull project script fr
|
|
|
39134
39576
|
} else {
|
|
39135
39577
|
const scaffoldSpinner = ora("Scaffolding project files...").start();
|
|
39136
39578
|
try {
|
|
39137
|
-
const result = scaffoldProject({ data: response.data, apiBaseUrl: config.apiBaseUrl });
|
|
39579
|
+
const result = scaffoldProject({ data: response.data, apiBaseUrl: config.apiBaseUrl, targetDir: dir });
|
|
39138
39580
|
scaffoldSpinner.succeed("Files created");
|
|
39139
39581
|
line();
|
|
39140
39582
|
fileTree(result.files);
|
|
39141
39583
|
line();
|
|
39142
|
-
success(`Project cloned to ${
|
|
39584
|
+
success(`Project cloned to ${path16.relative(process.cwd(), dir) || "."}/`);
|
|
39143
39585
|
} catch (err) {
|
|
39144
39586
|
scaffoldSpinner.fail("Scaffolding failed");
|
|
39145
39587
|
failCommand(err);
|
|
@@ -39155,11 +39597,11 @@ var projectPushCommand = new Command("push").description("Compile and push proje
|
|
|
39155
39597
|
const { manifest, dir } = readProjectManifestOrExit();
|
|
39156
39598
|
const api = new ApiClient(config);
|
|
39157
39599
|
const scriptFile = `project.${manifest.language}`;
|
|
39158
|
-
const scriptPath =
|
|
39159
|
-
if (!
|
|
39600
|
+
const scriptPath = path16.join(dir, scriptFile);
|
|
39601
|
+
if (!fs12.existsSync(scriptPath)) {
|
|
39160
39602
|
failCommand(new CliError("cli_no_manifest", `File not found: ${scriptFile}`));
|
|
39161
39603
|
}
|
|
39162
|
-
const source =
|
|
39604
|
+
const source = fs12.readFileSync(scriptPath, "utf-8");
|
|
39163
39605
|
const projLang = manifest.language === "ts" ? "TYPESCRIPT" : "JAVASCRIPT";
|
|
39164
39606
|
const compileSpinner = ora(`Compiling ${scriptFile}...`).start();
|
|
39165
39607
|
const compileResult = await compileScript(source, projLang, scriptFile, { minify: true });
|
|
@@ -39181,6 +39623,18 @@ var projectPushCommand = new Command("push").description("Compile and push proje
|
|
|
39181
39623
|
if (remoteHash !== manifest.syncedHash) {
|
|
39182
39624
|
spinner.warn("Remote has changes you haven't pulled");
|
|
39183
39625
|
line();
|
|
39626
|
+
if (!isInteractiveRun()) {
|
|
39627
|
+
failCommand(
|
|
39628
|
+
new CliError(
|
|
39629
|
+
"cli_conflict",
|
|
39630
|
+
"The platform copy changed since your last pull, and this run cannot ask before overwriting it.",
|
|
39631
|
+
[
|
|
39632
|
+
"Run `avsb project pull --force` to fetch the remote script first (it overwrites your local file),",
|
|
39633
|
+
"or `avsb project push --force` to overwrite the remote changes."
|
|
39634
|
+
]
|
|
39635
|
+
)
|
|
39636
|
+
);
|
|
39637
|
+
}
|
|
39184
39638
|
const { proceed } = await esm_default12.prompt([
|
|
39185
39639
|
{
|
|
39186
39640
|
type: "confirm",
|
|
@@ -39199,12 +39653,18 @@ var projectPushCommand = new Command("push").description("Compile and push proje
|
|
|
39199
39653
|
}
|
|
39200
39654
|
}
|
|
39201
39655
|
const payload = manifest.language === "ts" ? { editorProjectTs: { source, compiled: compiledSource } } : { editorProjectJs: { source, compiled: compiledSource } };
|
|
39202
|
-
|
|
39656
|
+
try {
|
|
39657
|
+
await api.pushProject(manifest.projectShortId, payload);
|
|
39658
|
+
} catch (err) {
|
|
39659
|
+
spinner.fail("Push refused");
|
|
39660
|
+
failCommand(err, { labelForPath: (bodyPath) => bodyPath.length > 0 ? scriptFile : void 0 });
|
|
39661
|
+
}
|
|
39203
39662
|
manifest.syncedHash = sha2564(source);
|
|
39204
39663
|
manifest.lastSyncedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
39205
39664
|
writeProjectManifest(dir, manifest);
|
|
39206
39665
|
spinner.succeed("Pushed successfully");
|
|
39207
39666
|
line();
|
|
39667
|
+
info("Saved as unpublished changes. Publish the script from the project's Snippet settings to put it live.");
|
|
39208
39668
|
success("Push complete");
|
|
39209
39669
|
} catch (err) {
|
|
39210
39670
|
failCommand(err);
|
|
@@ -39215,11 +39675,11 @@ var projectDevCommand = new Command("dev").description("Watch project script and
|
|
|
39215
39675
|
ensureLoggedIn();
|
|
39216
39676
|
const { manifest, dir } = readProjectManifestOrExit();
|
|
39217
39677
|
const scriptFile = `project.${manifest.language}`;
|
|
39218
|
-
const scriptPath =
|
|
39678
|
+
const scriptPath = path16.join(dir, scriptFile);
|
|
39219
39679
|
heading(`Dev mode: ${manifest.projectName} (project script)`);
|
|
39220
39680
|
line();
|
|
39221
39681
|
let compiledJs;
|
|
39222
|
-
const source =
|
|
39682
|
+
const source = fs12.readFileSync(scriptPath, "utf-8");
|
|
39223
39683
|
if (manifest.language === "ts") {
|
|
39224
39684
|
const spinner = ora("Compiling...").start();
|
|
39225
39685
|
try {
|
|
@@ -39258,10 +39718,10 @@ var projectDevCommand = new Command("dev").description("Watch project script and
|
|
|
39258
39718
|
dir,
|
|
39259
39719
|
extensions: [".ts", ".js"],
|
|
39260
39720
|
onChange: async (filePath) => {
|
|
39261
|
-
const relativePath =
|
|
39721
|
+
const relativePath = path16.relative(dir, filePath);
|
|
39262
39722
|
info(`File changed: ${relativePath}`);
|
|
39263
39723
|
try {
|
|
39264
|
-
const newSource =
|
|
39724
|
+
const newSource = fs12.readFileSync(filePath, "utf-8");
|
|
39265
39725
|
compiledJs = manifest.language === "ts" ? await compileTypeScript(newSource) : newSource;
|
|
39266
39726
|
const updateMsg = {
|
|
39267
39727
|
type: "project:update",
|
|
@@ -39507,6 +39967,57 @@ function isConflictError(err) {
|
|
|
39507
39967
|
if (!(err instanceof Error)) return false;
|
|
39508
39968
|
return /409|conflict|referenc/i.test(err.message);
|
|
39509
39969
|
}
|
|
39970
|
+
function isRecord5(value) {
|
|
39971
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
39972
|
+
}
|
|
39973
|
+
function isCliMetric(value) {
|
|
39974
|
+
return isRecord5(value) && typeof value.id === "string" && typeof value.shortId === "number" && typeof value.name === "string" && typeof value.type === "string";
|
|
39975
|
+
}
|
|
39976
|
+
function readMetricList(data) {
|
|
39977
|
+
if (!isRecord5(data) || !Array.isArray(data.metrics)) return null;
|
|
39978
|
+
const metrics = data.metrics.filter(isCliMetric);
|
|
39979
|
+
const usage = isRecord5(data.usage) ? data.usage : {};
|
|
39980
|
+
return { metrics, usage };
|
|
39981
|
+
}
|
|
39982
|
+
function readMetric(data) {
|
|
39983
|
+
return isRecord5(data) && isCliMetric(data.metric) ? data.metric : null;
|
|
39984
|
+
}
|
|
39985
|
+
function formatMetricTable(metrics, usage) {
|
|
39986
|
+
const rows = metrics.map((metric) => ({
|
|
39987
|
+
id: metricIdLabel(metric),
|
|
39988
|
+
type: metric.type,
|
|
39989
|
+
name: truncate(metric.name, 32),
|
|
39990
|
+
definition: truncate(describeMetric(metric), 48),
|
|
39991
|
+
usage: formatUsage(usage[metric.id]),
|
|
39992
|
+
archived: metric.archivedAt != null
|
|
39993
|
+
}));
|
|
39994
|
+
const widths = {
|
|
39995
|
+
id: Math.max(2, ...rows.map((r) => r.id.length)),
|
|
39996
|
+
type: Math.max(4, ...rows.map((r) => r.type.length)),
|
|
39997
|
+
name: Math.max(4, ...rows.map((r) => r.name.length)),
|
|
39998
|
+
definition: Math.max(10, ...rows.map((r) => r.definition.length))
|
|
39999
|
+
};
|
|
40000
|
+
const heading2 = [
|
|
40001
|
+
"ID".padEnd(widths.id),
|
|
40002
|
+
"Type".padEnd(widths.type),
|
|
40003
|
+
"Name".padEnd(widths.name),
|
|
40004
|
+
"Definition".padEnd(widths.definition),
|
|
40005
|
+
"Usage"
|
|
40006
|
+
].join(" ");
|
|
40007
|
+
return {
|
|
40008
|
+
heading: heading2,
|
|
40009
|
+
rows: rows.map((row) => {
|
|
40010
|
+
const line2 = [
|
|
40011
|
+
row.id.padEnd(widths.id),
|
|
40012
|
+
row.type.padEnd(widths.type),
|
|
40013
|
+
row.name.padEnd(widths.name),
|
|
40014
|
+
row.definition.padEnd(widths.definition),
|
|
40015
|
+
row.usage
|
|
40016
|
+
].join(" ");
|
|
40017
|
+
return row.archived ? `${line2} (archived)` : line2;
|
|
40018
|
+
})
|
|
40019
|
+
};
|
|
40020
|
+
}
|
|
39510
40021
|
|
|
39511
40022
|
// src/commands/metrics.ts
|
|
39512
40023
|
var metricsListCommand = withOutputOptions(
|
|
@@ -39526,49 +40037,26 @@ var metricsListCommand = withOutputOptions(
|
|
|
39526
40037
|
spinner.fail("Failed to fetch metrics");
|
|
39527
40038
|
failCommand(err);
|
|
39528
40039
|
}
|
|
39529
|
-
const { metrics, usage } = response.data;
|
|
39530
40040
|
if (emitJsonResult(response.data)) return;
|
|
39531
|
-
|
|
40041
|
+
const list = readMetricList(response.data);
|
|
40042
|
+
if (!list) {
|
|
40043
|
+
failCommand(
|
|
40044
|
+
new CliError("cli_failed", "The platform answered in a shape this CLI does not read.", [
|
|
40045
|
+
"Upgrade the CLI with `npm install -g @avsbhq/cli`, or pass --json to see the raw answer."
|
|
40046
|
+
])
|
|
40047
|
+
);
|
|
40048
|
+
}
|
|
40049
|
+
if (list.metrics.length === 0) {
|
|
39532
40050
|
line();
|
|
39533
40051
|
info(
|
|
39534
40052
|
'No metrics yet. Create one with `avsb metrics create --type click --name "..." --selector ".cta"`'
|
|
39535
40053
|
);
|
|
39536
40054
|
return;
|
|
39537
40055
|
}
|
|
39538
|
-
const
|
|
39539
|
-
id: metricIdLabel(metric),
|
|
39540
|
-
type: metric.type,
|
|
39541
|
-
name: truncate(metric.name, 32),
|
|
39542
|
-
definition: truncate(describeMetric(metric), 48),
|
|
39543
|
-
usage: formatUsage(usage[metric.id]),
|
|
39544
|
-
archived: metric.archivedAt != null
|
|
39545
|
-
}));
|
|
39546
|
-
const widths = {
|
|
39547
|
-
id: Math.max(2, ...rows.map((r) => r.id.length)),
|
|
39548
|
-
type: Math.max(4, ...rows.map((r) => r.type.length)),
|
|
39549
|
-
name: Math.max(4, ...rows.map((r) => r.name.length)),
|
|
39550
|
-
definition: Math.max(10, ...rows.map((r) => r.definition.length))
|
|
39551
|
-
};
|
|
40056
|
+
const table = formatMetricTable(list.metrics, list.usage);
|
|
39552
40057
|
line();
|
|
39553
|
-
heading(
|
|
39554
|
-
|
|
39555
|
-
"ID".padEnd(widths.id),
|
|
39556
|
-
"Type".padEnd(widths.type),
|
|
39557
|
-
"Name".padEnd(widths.name),
|
|
39558
|
-
"Definition".padEnd(widths.definition),
|
|
39559
|
-
"Usage"
|
|
39560
|
-
].join(" ")
|
|
39561
|
-
);
|
|
39562
|
-
for (const row of rows) {
|
|
39563
|
-
const line2 = [
|
|
39564
|
-
row.id.padEnd(widths.id),
|
|
39565
|
-
row.type.padEnd(widths.type),
|
|
39566
|
-
row.name.padEnd(widths.name),
|
|
39567
|
-
row.definition.padEnd(widths.definition),
|
|
39568
|
-
row.usage
|
|
39569
|
-
].join(" ");
|
|
39570
|
-
line(row.archived ? `${line2} (archived)` : line2);
|
|
39571
|
-
}
|
|
40058
|
+
heading(table.heading);
|
|
40059
|
+
for (const row of table.rows) line(row);
|
|
39572
40060
|
line();
|
|
39573
40061
|
});
|
|
39574
40062
|
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) => {
|
|
@@ -39584,21 +40072,20 @@ var metricsCreateCommand = new Command("create").description("Create a new metri
|
|
|
39584
40072
|
const body = buildCreateBody(type, opts, opts.name);
|
|
39585
40073
|
const api = new ApiClient(config);
|
|
39586
40074
|
const spinner = ora("Creating metric...").start();
|
|
40075
|
+
let response;
|
|
39587
40076
|
try {
|
|
39588
|
-
|
|
39589
|
-
const { metric } = response.data;
|
|
39590
|
-
spinner.succeed(`Metric created: ${metric.name} (${metricIdLabel(metric)})`);
|
|
39591
|
-
printMetricDetails(metric);
|
|
40077
|
+
response = await api.createMetric(shortId, body);
|
|
39592
40078
|
} catch (err) {
|
|
39593
40079
|
spinner.fail("Failed to create metric");
|
|
39594
|
-
|
|
39595
|
-
if (/already exists|conflict|409|unique/i.test(message)) {
|
|
39596
|
-
error(`Conflict: a metric with that name or key already exists. ${message}`);
|
|
39597
|
-
} else {
|
|
39598
|
-
error(message);
|
|
39599
|
-
}
|
|
39600
|
-
process.exit(EXIT.failure);
|
|
40080
|
+
failCommand(err);
|
|
39601
40081
|
}
|
|
40082
|
+
const metric = readMetric(response.data);
|
|
40083
|
+
if (!metric) {
|
|
40084
|
+
spinner.warn("Metric created. Run `avsb metrics list` to see it.");
|
|
40085
|
+
return;
|
|
40086
|
+
}
|
|
40087
|
+
spinner.succeed(`Metric created: ${metric.name} (${metricIdLabel(metric)})`);
|
|
40088
|
+
printMetricDetails(metric);
|
|
39602
40089
|
});
|
|
39603
40090
|
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) => {
|
|
39604
40091
|
const config = ensureLoggedIn();
|
|
@@ -39614,49 +40101,65 @@ var metricsUpdateCommand = new Command("update").argument("<metricId>", "Metric
|
|
|
39614
40101
|
}
|
|
39615
40102
|
const api = new ApiClient(config);
|
|
39616
40103
|
const spinner = ora("Updating metric...").start();
|
|
40104
|
+
let response;
|
|
39617
40105
|
try {
|
|
39618
|
-
|
|
39619
|
-
const { metric } = response.data;
|
|
39620
|
-
spinner.succeed(`Metric updated: ${metric.name} (${metricIdLabel(metric)})`);
|
|
39621
|
-
printMetricDetails(metric);
|
|
40106
|
+
response = await api.updateMetric(shortId, metricId, patch);
|
|
39622
40107
|
} catch (err) {
|
|
39623
40108
|
spinner.fail("Failed to update metric");
|
|
39624
40109
|
failCommand(err);
|
|
39625
40110
|
}
|
|
40111
|
+
const metric = readMetric(response.data);
|
|
40112
|
+
if (!metric) {
|
|
40113
|
+
spinner.warn(`Metric ${metricId} updated.`);
|
|
40114
|
+
return;
|
|
40115
|
+
}
|
|
40116
|
+
spinner.succeed(`Metric updated: ${metric.name} (${metricIdLabel(metric)})`);
|
|
40117
|
+
printMetricDetails(metric);
|
|
39626
40118
|
});
|
|
39627
40119
|
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) => {
|
|
39628
40120
|
const config = ensureLoggedIn();
|
|
39629
40121
|
const shortId = resolveProjectShortId(opts);
|
|
39630
40122
|
const api = new ApiClient(config);
|
|
39631
40123
|
const spinner = ora("Archiving metric...").start();
|
|
40124
|
+
let response;
|
|
39632
40125
|
try {
|
|
39633
|
-
|
|
39634
|
-
const { metric } = response.data;
|
|
39635
|
-
spinner.succeed(`Metric archived: ${metric.name} (${metricIdLabel(metric)})`);
|
|
40126
|
+
response = await api.archiveMetric(shortId, metricId);
|
|
39636
40127
|
} catch (err) {
|
|
39637
40128
|
spinner.fail("Failed to archive metric");
|
|
39638
40129
|
failCommand(err);
|
|
39639
40130
|
}
|
|
40131
|
+
const metric = readMetric(response.data);
|
|
40132
|
+
spinner.succeed(metric ? `Metric archived: ${metric.name} (${metricIdLabel(metric)})` : `Metric ${metricId} archived`);
|
|
39640
40133
|
});
|
|
39641
40134
|
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) => {
|
|
39642
40135
|
const config = ensureLoggedIn();
|
|
39643
40136
|
const shortId = resolveProjectShortId(opts);
|
|
39644
40137
|
const api = new ApiClient(config);
|
|
39645
40138
|
const spinner = ora("Restoring metric...").start();
|
|
40139
|
+
let response;
|
|
39646
40140
|
try {
|
|
39647
|
-
|
|
39648
|
-
const { metric } = response.data;
|
|
39649
|
-
spinner.succeed(`Metric restored from archive: ${metric.name} (${metricIdLabel(metric)})`);
|
|
40141
|
+
response = await api.unarchiveMetric(shortId, metricId);
|
|
39650
40142
|
} catch (err) {
|
|
39651
40143
|
spinner.fail("Failed to unarchive metric");
|
|
39652
40144
|
failCommand(err);
|
|
39653
40145
|
}
|
|
40146
|
+
const metric = readMetric(response.data);
|
|
40147
|
+
spinner.succeed(
|
|
40148
|
+
metric ? `Metric restored from archive: ${metric.name} (${metricIdLabel(metric)})` : `Metric ${metricId} restored from archive`
|
|
40149
|
+
);
|
|
39654
40150
|
});
|
|
39655
40151
|
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) => {
|
|
39656
40152
|
const config = ensureLoggedIn();
|
|
39657
40153
|
const shortId = resolveProjectShortId(opts);
|
|
39658
40154
|
const api = new ApiClient(config);
|
|
39659
40155
|
if (!opts.force) {
|
|
40156
|
+
if (!isInteractiveRun()) {
|
|
40157
|
+
failCommand(
|
|
40158
|
+
new CliError("cli_usage", `Deleting metric ${metricId} needs a confirmation this run cannot ask for.`, [
|
|
40159
|
+
`Pass -f to delete without asking: \`avsb metrics delete ${metricId} -f\``
|
|
40160
|
+
])
|
|
40161
|
+
);
|
|
40162
|
+
}
|
|
39660
40163
|
const { proceed } = await esm_default12.prompt([
|
|
39661
40164
|
{
|
|
39662
40165
|
type: "confirm",
|
|
@@ -39676,19 +40179,12 @@ var metricsDeleteCommand = new Command("delete").argument("<metricId>", "Metric
|
|
|
39676
40179
|
spinner.succeed(`Metric deleted: ${metricId}`);
|
|
39677
40180
|
} catch (err) {
|
|
39678
40181
|
spinner.fail("Failed to delete metric");
|
|
39679
|
-
const message = err instanceof Error ? err.message : "Unknown error";
|
|
39680
40182
|
if (isConflictError(err)) {
|
|
39681
40183
|
const refs = extractBindingsReferencing(err);
|
|
39682
|
-
if (refs.length > 0) {
|
|
39683
|
-
error(`Cannot delete: referenced by ${refs.join(", ")}.`);
|
|
39684
|
-
} else {
|
|
39685
|
-
error(`Cannot delete: metric bindings still reference this metric. ${message}`);
|
|
39686
|
-
}
|
|
40184
|
+
if (refs.length > 0) error(`Referenced by ${refs.join(", ")}.`);
|
|
39687
40185
|
info(`Tip: run \`avsb metrics archive ${metricId}\` instead to hide it without losing history.`);
|
|
39688
|
-
} else {
|
|
39689
|
-
error(message);
|
|
39690
40186
|
}
|
|
39691
|
-
|
|
40187
|
+
failCommand(err);
|
|
39692
40188
|
}
|
|
39693
40189
|
});
|
|
39694
40190
|
var metricsCommand = new Command("metrics").description("Manage metric tracking definitions").addCommand(metricsListCommand).addCommand(metricsCreateCommand).addCommand(metricsUpdateCommand).addCommand(metricsArchiveCommand).addCommand(metricsUnarchiveCommand).addCommand(metricsDeleteCommand);
|
|
@@ -39903,6 +40399,40 @@ function isConflictError2(err) {
|
|
|
39903
40399
|
if (!(err instanceof Error)) return false;
|
|
39904
40400
|
return /409|conflict|attached|referenc/i.test(err.message);
|
|
39905
40401
|
}
|
|
40402
|
+
function isRecord6(value) {
|
|
40403
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
40404
|
+
}
|
|
40405
|
+
function isCliMetricBinding(value) {
|
|
40406
|
+
return isRecord6(value) && typeof value.id === "string" && typeof value.shortId === "number" && typeof value.name === "string" && typeof value.measure === "string";
|
|
40407
|
+
}
|
|
40408
|
+
function readMetricBindingList(data) {
|
|
40409
|
+
if (!isRecord6(data) || !Array.isArray(data.metricBindings)) return null;
|
|
40410
|
+
return data.metricBindings.filter(isCliMetricBinding);
|
|
40411
|
+
}
|
|
40412
|
+
function readMetricBinding(data) {
|
|
40413
|
+
return isRecord6(data) && isCliMetricBinding(data.metricBinding) ? data.metricBinding : null;
|
|
40414
|
+
}
|
|
40415
|
+
function formatMetricBindingTable(bindings) {
|
|
40416
|
+
const rows = bindings.map((binding) => ({
|
|
40417
|
+
id: metricBindingIdLabel(binding),
|
|
40418
|
+
measure: measureBadge(binding.measure),
|
|
40419
|
+
name: truncate2(binding.name, 28),
|
|
40420
|
+
sentence: truncate2(renderMetricBindingSentence(binding), 64),
|
|
40421
|
+
archived: binding.archivedAt != null
|
|
40422
|
+
}));
|
|
40423
|
+
const widths = {
|
|
40424
|
+
id: Math.max(2, ...rows.map((r) => r.id.length)),
|
|
40425
|
+
measure: Math.max(7, ...rows.map((r) => r.measure.length)),
|
|
40426
|
+
name: Math.max(4, ...rows.map((r) => r.name.length))
|
|
40427
|
+
};
|
|
40428
|
+
return {
|
|
40429
|
+
heading: ["ID".padEnd(widths.id), "Measure".padEnd(widths.measure), "Name".padEnd(widths.name), "Sentence"].join(" "),
|
|
40430
|
+
rows: rows.map((row) => {
|
|
40431
|
+
const line2 = [row.id.padEnd(widths.id), row.measure.padEnd(widths.measure), row.name.padEnd(widths.name), row.sentence].join(" ");
|
|
40432
|
+
return row.archived ? `${line2} (archived)` : line2;
|
|
40433
|
+
})
|
|
40434
|
+
};
|
|
40435
|
+
}
|
|
39906
40436
|
|
|
39907
40437
|
// src/commands/metric-bindings.body.ts
|
|
39908
40438
|
function parseComposition(opts) {
|
|
@@ -39964,12 +40494,11 @@ function parseWinsorization(opts) {
|
|
|
39964
40494
|
}
|
|
39965
40495
|
return { enabled: true, upperPercentile: upper, lowerPercentile: lower };
|
|
39966
40496
|
}
|
|
39967
|
-
function buildCreateMetricBindingBody(
|
|
40497
|
+
function buildCreateMetricBindingBody(measure, opts, name) {
|
|
39968
40498
|
const direction = opts.direction ? normalizeDirection(opts.direction) : "INCREASE";
|
|
39969
40499
|
const body = {
|
|
39970
40500
|
name,
|
|
39971
40501
|
description: opts.description ?? void 0,
|
|
39972
|
-
projectId,
|
|
39973
40502
|
measure,
|
|
39974
40503
|
direction,
|
|
39975
40504
|
isLibrary: true
|
|
@@ -40040,8 +40569,10 @@ function buildUpdateMetricBindingPatch(opts) {
|
|
|
40040
40569
|
if (opts.denominatorMetric !== void 0) patch.denominatorMetricId = opts.denominatorMetric;
|
|
40041
40570
|
if (opts.percentile !== void 0) {
|
|
40042
40571
|
const p = Number(opts.percentile);
|
|
40043
|
-
if (!Number.isFinite(p)) {
|
|
40044
|
-
failCommand(
|
|
40572
|
+
if (!Number.isFinite(p) || p <= 0 || p >= 100) {
|
|
40573
|
+
failCommand(
|
|
40574
|
+
new CliError("cli_usage", "--percentile must be a number strictly between 0 and 100.")
|
|
40575
|
+
);
|
|
40045
40576
|
}
|
|
40046
40577
|
patch.percentile = p;
|
|
40047
40578
|
}
|
|
@@ -40078,8 +40609,15 @@ var metricBindingsListCommand = withOutputOptions(
|
|
|
40078
40609
|
spinner.fail("Failed to fetch metric bindings");
|
|
40079
40610
|
failCommand(err);
|
|
40080
40611
|
}
|
|
40081
|
-
const { metricBindings } = response.data;
|
|
40082
40612
|
if (emitJsonResult(response.data)) return;
|
|
40613
|
+
const metricBindings = readMetricBindingList(response.data);
|
|
40614
|
+
if (!metricBindings) {
|
|
40615
|
+
failCommand(
|
|
40616
|
+
new CliError("cli_failed", "The platform answered in a shape this CLI does not read.", [
|
|
40617
|
+
"Upgrade the CLI with `npm install -g @avsbhq/cli`, or pass --json to see the raw answer."
|
|
40618
|
+
])
|
|
40619
|
+
);
|
|
40620
|
+
}
|
|
40083
40621
|
if (metricBindings.length === 0) {
|
|
40084
40622
|
line();
|
|
40085
40623
|
info(
|
|
@@ -40087,45 +40625,19 @@ var metricBindingsListCommand = withOutputOptions(
|
|
|
40087
40625
|
);
|
|
40088
40626
|
return;
|
|
40089
40627
|
}
|
|
40090
|
-
const
|
|
40091
|
-
id: metricBindingIdLabel(binding),
|
|
40092
|
-
measure: measureBadge(binding.measure),
|
|
40093
|
-
name: truncate2(binding.name, 28),
|
|
40094
|
-
sentence: truncate2(renderMetricBindingSentence(binding), 64),
|
|
40095
|
-
archived: binding.archivedAt != null
|
|
40096
|
-
}));
|
|
40097
|
-
const widths = {
|
|
40098
|
-
id: Math.max(2, ...rows.map((r) => r.id.length)),
|
|
40099
|
-
measure: Math.max(7, ...rows.map((r) => r.measure.length)),
|
|
40100
|
-
name: Math.max(4, ...rows.map((r) => r.name.length))
|
|
40101
|
-
};
|
|
40628
|
+
const table = formatMetricBindingTable(metricBindings);
|
|
40102
40629
|
line();
|
|
40103
|
-
heading(
|
|
40104
|
-
|
|
40105
|
-
"ID".padEnd(widths.id),
|
|
40106
|
-
"Measure".padEnd(widths.measure),
|
|
40107
|
-
"Name".padEnd(widths.name),
|
|
40108
|
-
"Sentence"
|
|
40109
|
-
].join(" ")
|
|
40110
|
-
);
|
|
40111
|
-
for (const row of rows) {
|
|
40112
|
-
const line2 = [
|
|
40113
|
-
row.id.padEnd(widths.id),
|
|
40114
|
-
row.measure.padEnd(widths.measure),
|
|
40115
|
-
row.name.padEnd(widths.name),
|
|
40116
|
-
row.sentence
|
|
40117
|
-
].join(" ");
|
|
40118
|
-
line(row.archived ? `${line2} (archived)` : line2);
|
|
40119
|
-
}
|
|
40630
|
+
heading(table.heading);
|
|
40631
|
+
for (const row of table.rows) line(row);
|
|
40120
40632
|
line();
|
|
40121
40633
|
}
|
|
40122
40634
|
);
|
|
40123
40635
|
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(
|
|
40124
40636
|
"--measure <measure>",
|
|
40125
40637
|
"Measure (UNIQUE_CONVERSIONS_PER_VISITOR | TOTAL_EVENTS | UNIQUE_VISITORS_WHO_FIRED | TOTAL_VALUE_PER_VISITOR | TOTAL_VALUE | PERCENTILE | RATE | COMPOSITE)"
|
|
40126
|
-
).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
|
|
40127
|
-
"--component <
|
|
40128
|
-
"Repeatable component
|
|
40638
|
+
).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(
|
|
40639
|
+
"--component <bindingId:weight>",
|
|
40640
|
+
"Repeatable COMPOSITE component, a metric binding and its weight (e.g. --component MBD-8:0.5 --component MBD-9:0.5)",
|
|
40129
40641
|
(value, prev = []) => prev.concat(value),
|
|
40130
40642
|
[]
|
|
40131
40643
|
).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) => {
|
|
@@ -40139,29 +40651,26 @@ var metricBindingsCreateCommand = new Command("create").description("Create a ne
|
|
|
40139
40651
|
const shortId = resolveProjectShortId2(opts);
|
|
40140
40652
|
const measure = normalizeMeasure(opts.measure);
|
|
40141
40653
|
const api = new ApiClient(config);
|
|
40142
|
-
const
|
|
40143
|
-
const projectId = projectResponse.data.project.id;
|
|
40144
|
-
const body = buildCreateMetricBindingBody(projectId, measure, opts, opts.name);
|
|
40654
|
+
const body = buildCreateMetricBindingBody(measure, opts, opts.name);
|
|
40145
40655
|
const spinner = ora("Creating metric binding...").start();
|
|
40656
|
+
let response;
|
|
40146
40657
|
try {
|
|
40147
|
-
|
|
40148
|
-
const { metricBinding } = response.data;
|
|
40149
|
-
spinner.succeed(`Metric binding created: ${metricBinding.name} (${metricBindingIdLabel(metricBinding)})`);
|
|
40150
|
-
printMetricBindingDetails(metricBinding);
|
|
40658
|
+
response = await api.createMetricBinding(shortId, body);
|
|
40151
40659
|
} catch (err) {
|
|
40152
40660
|
spinner.fail("Failed to create metric binding");
|
|
40153
|
-
|
|
40154
|
-
|
|
40155
|
-
|
|
40156
|
-
|
|
40157
|
-
|
|
40158
|
-
|
|
40159
|
-
process.exit(EXIT.failure);
|
|
40661
|
+
failCommand(err);
|
|
40662
|
+
}
|
|
40663
|
+
const metricBinding = readMetricBinding(response.data);
|
|
40664
|
+
if (!metricBinding) {
|
|
40665
|
+
spinner.warn("Metric binding created. Run `avsb metric-bindings list` to see it.");
|
|
40666
|
+
return;
|
|
40160
40667
|
}
|
|
40668
|
+
spinner.succeed(`Metric binding created: ${metricBinding.name} (${metricBindingIdLabel(metricBinding)})`);
|
|
40669
|
+
printMetricBindingDetails(metricBinding);
|
|
40161
40670
|
});
|
|
40162
40671
|
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(
|
|
40163
|
-
"--component <
|
|
40164
|
-
"Repeatable component (replaces composition)",
|
|
40672
|
+
"--component <bindingId:weight>",
|
|
40673
|
+
"Repeatable COMPOSITE component, a metric binding and its weight (replaces the composition)",
|
|
40165
40674
|
(value, prev = []) => prev.concat(value),
|
|
40166
40675
|
[]
|
|
40167
40676
|
).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) => {
|
|
@@ -40169,59 +40678,73 @@ var metricBindingsUpdateCommand = new Command("update").argument("<metricBinding
|
|
|
40169
40678
|
const shortId = resolveProjectShortId2(opts);
|
|
40170
40679
|
const patch = buildUpdateMetricBindingPatch(opts);
|
|
40171
40680
|
if (Object.keys(patch).length === 0) {
|
|
40172
|
-
|
|
40173
|
-
|
|
40681
|
+
failCommand(
|
|
40682
|
+
new CliError("cli_usage", "No update fields provided. Pass at least one of --name, --description, --direction, etc.")
|
|
40683
|
+
);
|
|
40174
40684
|
}
|
|
40175
40685
|
const api = new ApiClient(config);
|
|
40176
40686
|
const spinner = ora("Updating metric binding...").start();
|
|
40687
|
+
let response;
|
|
40177
40688
|
try {
|
|
40178
|
-
|
|
40179
|
-
const { metricBinding, amendment } = response.data;
|
|
40180
|
-
spinner.succeed(`Metric binding updated: ${metricBinding.name} (${metricBindingIdLabel(metricBinding)})`);
|
|
40181
|
-
if (amendment?.required) {
|
|
40182
|
-
warn(
|
|
40183
|
-
`Change requires an amendment on sealed analysis plan(s). Fields: ${amendment.requiredFields.join(", ")}`
|
|
40184
|
-
);
|
|
40185
|
-
}
|
|
40186
|
-
printMetricBindingDetails(metricBinding);
|
|
40689
|
+
response = await api.updateMetricBinding(shortId, metricBindingId, patch);
|
|
40187
40690
|
} catch (err) {
|
|
40188
40691
|
spinner.fail("Failed to update metric binding");
|
|
40189
40692
|
failCommand(err);
|
|
40190
40693
|
}
|
|
40694
|
+
const metricBinding = readMetricBinding(response.data);
|
|
40695
|
+
if (!metricBinding) {
|
|
40696
|
+
spinner.warn(`Metric binding ${metricBindingId} updated.`);
|
|
40697
|
+
return;
|
|
40698
|
+
}
|
|
40699
|
+
spinner.succeed(`Metric binding updated: ${metricBinding.name} (${metricBindingIdLabel(metricBinding)})`);
|
|
40700
|
+
printMetricBindingDetails(metricBinding);
|
|
40191
40701
|
});
|
|
40192
40702
|
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) => {
|
|
40193
40703
|
const config = ensureLoggedIn();
|
|
40194
40704
|
const shortId = resolveProjectShortId2(opts);
|
|
40195
40705
|
const api = new ApiClient(config);
|
|
40196
40706
|
const spinner = ora("Archiving metric binding...").start();
|
|
40707
|
+
let response;
|
|
40197
40708
|
try {
|
|
40198
|
-
|
|
40199
|
-
const { metricBinding } = response.data;
|
|
40200
|
-
spinner.succeed(`Metric binding archived: ${metricBinding.name} (${metricBindingIdLabel(metricBinding)})`);
|
|
40709
|
+
response = await api.archiveMetricBinding(shortId, metricBindingId);
|
|
40201
40710
|
} catch (err) {
|
|
40202
40711
|
spinner.fail("Failed to archive metric binding");
|
|
40203
40712
|
failCommand(err);
|
|
40204
40713
|
}
|
|
40714
|
+
const metricBinding = readMetricBinding(response.data);
|
|
40715
|
+
spinner.succeed(
|
|
40716
|
+
metricBinding ? `Metric binding archived: ${metricBinding.name} (${metricBindingIdLabel(metricBinding)})` : `Metric binding ${metricBindingId} archived`
|
|
40717
|
+
);
|
|
40205
40718
|
});
|
|
40206
40719
|
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) => {
|
|
40207
40720
|
const config = ensureLoggedIn();
|
|
40208
40721
|
const shortId = resolveProjectShortId2(opts);
|
|
40209
40722
|
const api = new ApiClient(config);
|
|
40210
40723
|
const spinner = ora("Restoring metric binding...").start();
|
|
40724
|
+
let response;
|
|
40211
40725
|
try {
|
|
40212
|
-
|
|
40213
|
-
const { metricBinding } = response.data;
|
|
40214
|
-
spinner.succeed(`Metric binding restored from archive: ${metricBinding.name} (${metricBindingIdLabel(metricBinding)})`);
|
|
40726
|
+
response = await api.unarchiveMetricBinding(shortId, metricBindingId);
|
|
40215
40727
|
} catch (err) {
|
|
40216
40728
|
spinner.fail("Failed to unarchive metric binding");
|
|
40217
40729
|
failCommand(err);
|
|
40218
40730
|
}
|
|
40731
|
+
const metricBinding = readMetricBinding(response.data);
|
|
40732
|
+
spinner.succeed(
|
|
40733
|
+
metricBinding ? `Metric binding restored from archive: ${metricBinding.name} (${metricBindingIdLabel(metricBinding)})` : `Metric binding ${metricBindingId} restored from archive`
|
|
40734
|
+
);
|
|
40219
40735
|
});
|
|
40220
40736
|
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) => {
|
|
40221
40737
|
const config = ensureLoggedIn();
|
|
40222
40738
|
const shortId = resolveProjectShortId2(opts);
|
|
40223
40739
|
const api = new ApiClient(config);
|
|
40224
40740
|
if (!opts.force) {
|
|
40741
|
+
if (!isInteractiveRun()) {
|
|
40742
|
+
failCommand(
|
|
40743
|
+
new CliError("cli_usage", `Deleting metric binding ${metricBindingId} needs a confirmation this run cannot ask for.`, [
|
|
40744
|
+
`Pass -f to delete without asking: \`avsb metric-bindings delete ${metricBindingId} -f\``
|
|
40745
|
+
])
|
|
40746
|
+
);
|
|
40747
|
+
}
|
|
40225
40748
|
const { proceed } = await esm_default12.prompt([
|
|
40226
40749
|
{
|
|
40227
40750
|
type: "confirm",
|
|
@@ -40241,28 +40764,21 @@ var metricBindingsDeleteCommand = new Command("delete").argument("<metricBinding
|
|
|
40241
40764
|
spinner.succeed(`Metric binding deleted: ${metricBindingId}`);
|
|
40242
40765
|
} catch (err) {
|
|
40243
40766
|
spinner.fail("Failed to delete metric binding");
|
|
40244
|
-
const message = err instanceof Error ? err.message : "Unknown error";
|
|
40245
40767
|
if (isConflictError2(err)) {
|
|
40246
40768
|
const refs = extractExperimentsReferencing(err);
|
|
40247
|
-
if (refs.length > 0) {
|
|
40248
|
-
error(`Cannot delete: referenced by ${refs.join(", ")}.`);
|
|
40249
|
-
} else {
|
|
40250
|
-
error(`Cannot delete: experiments still attach this metric binding. ${message}`);
|
|
40251
|
-
}
|
|
40769
|
+
if (refs.length > 0) error(`Attached to ${refs.join(", ")}.`);
|
|
40252
40770
|
info(
|
|
40253
40771
|
`Tip: run \`avsb metric-bindings archive ${metricBindingId}\` instead to hide it without losing history.`
|
|
40254
40772
|
);
|
|
40255
|
-
} else {
|
|
40256
|
-
error(message);
|
|
40257
40773
|
}
|
|
40258
|
-
|
|
40774
|
+
failCommand(err);
|
|
40259
40775
|
}
|
|
40260
40776
|
});
|
|
40261
40777
|
var metricBindingsCommand = new Command("metric-bindings").description("Manage saved/library metric bindings").addCommand(metricBindingsListCommand).addCommand(metricBindingsCreateCommand).addCommand(metricBindingsUpdateCommand).addCommand(metricBindingsArchiveCommand).addCommand(metricBindingsUnarchiveCommand).addCommand(metricBindingsDeleteCommand);
|
|
40262
40778
|
|
|
40263
40779
|
// src/commands/codegen.ts
|
|
40264
|
-
import
|
|
40265
|
-
import
|
|
40780
|
+
import path17 from "node:path";
|
|
40781
|
+
import fs13 from "node:fs/promises";
|
|
40266
40782
|
|
|
40267
40783
|
// src/lib/codegenApi.ts
|
|
40268
40784
|
var CodegenApiClient = class extends ApiClient {
|
|
@@ -40319,6 +40835,9 @@ function serialiseManifest(manifest) {
|
|
|
40319
40835
|
}
|
|
40320
40836
|
|
|
40321
40837
|
// src/lib/codegenCheck.ts
|
|
40838
|
+
function withoutPublishStamp(text) {
|
|
40839
|
+
return text.replace(/^(\/\/ Source: .*\bpublishedAt=)\S+/m, "$1*").replace(/^(\s*"publishedAt": )(?:"[^"\n]*"|null)/gm, "$1*");
|
|
40840
|
+
}
|
|
40322
40841
|
function diffArtifact(artifact) {
|
|
40323
40842
|
const expectedLines = artifact.expected.split("\n");
|
|
40324
40843
|
if (artifact.actual === null) {
|
|
@@ -40333,11 +40852,13 @@ function diffArtifact(artifact) {
|
|
|
40333
40852
|
};
|
|
40334
40853
|
}
|
|
40335
40854
|
const actual = artifact.actual.replace(/\r\n/g, "\n");
|
|
40336
|
-
|
|
40855
|
+
const expectedComparable = withoutPublishStamp(artifact.expected).split("\n");
|
|
40856
|
+
const actualComparable = withoutPublishStamp(actual).split("\n");
|
|
40857
|
+
if (expectedComparable.join("\n") === actualComparable.join("\n")) return null;
|
|
40337
40858
|
const actualLines = actual.split("\n");
|
|
40338
|
-
const shared = Math.min(
|
|
40859
|
+
const shared = Math.min(expectedComparable.length, actualComparable.length);
|
|
40339
40860
|
let index = 0;
|
|
40340
|
-
while (index < shared &&
|
|
40861
|
+
while (index < shared && expectedComparable[index] === actualComparable[index]) index += 1;
|
|
40341
40862
|
return {
|
|
40342
40863
|
path: artifact.path,
|
|
40343
40864
|
reason: "different",
|
|
@@ -40491,7 +41012,7 @@ function planArtifacts(datafile, targets) {
|
|
|
40491
41012
|
}
|
|
40492
41013
|
async function readIfPresent(filePath) {
|
|
40493
41014
|
try {
|
|
40494
|
-
return await
|
|
41015
|
+
return await fs13.readFile(filePath, "utf-8");
|
|
40495
41016
|
} catch (err) {
|
|
40496
41017
|
if (isMissingFile(err)) return null;
|
|
40497
41018
|
throw err;
|
|
@@ -40505,8 +41026,8 @@ async function writeArtifacts(artifacts) {
|
|
|
40505
41026
|
for (const artifact of artifacts) {
|
|
40506
41027
|
const existing = await readIfPresent(artifact.path);
|
|
40507
41028
|
if (existing === artifact.contents) continue;
|
|
40508
|
-
await
|
|
40509
|
-
await
|
|
41029
|
+
await fs13.mkdir(path17.dirname(artifact.path), { recursive: true });
|
|
41030
|
+
await fs13.writeFile(artifact.path, artifact.contents, "utf-8");
|
|
40510
41031
|
written.push(artifact.path);
|
|
40511
41032
|
}
|
|
40512
41033
|
return written;
|
|
@@ -40528,6 +41049,7 @@ function flagCountLine(datafile) {
|
|
|
40528
41049
|
}
|
|
40529
41050
|
async function runCheck(artifacts, datafile, targets) {
|
|
40530
41051
|
const drifts = diffArtifacts(await toComparisons(artifacts));
|
|
41052
|
+
const exitCode = drifts.length === 0 ? EXIT.success : EXIT.failure;
|
|
40531
41053
|
if (emitJsonResult({
|
|
40532
41054
|
upToDate: drifts.length === 0,
|
|
40533
41055
|
checked: artifacts.map((a) => a.path),
|
|
@@ -40536,25 +41058,25 @@ async function runCheck(artifacts, datafile, targets) {
|
|
|
40536
41058
|
flagCount: datafile.flags.length,
|
|
40537
41059
|
publishedAt: datafile.publishedAt ?? null
|
|
40538
41060
|
})) {
|
|
40539
|
-
|
|
41061
|
+
return exitCode;
|
|
40540
41062
|
}
|
|
40541
41063
|
if (drifts.length === 0) {
|
|
40542
41064
|
success(describeDriftSummary(drifts));
|
|
40543
41065
|
dim(` Checked ${artifacts.length === 1 ? "1 file" : `${artifacts.length} files`} against ${flagCountLine(datafile)} in ${targets.env}.`);
|
|
40544
|
-
|
|
41066
|
+
return exitCode;
|
|
40545
41067
|
}
|
|
40546
41068
|
error(describeDriftSummary(drifts));
|
|
40547
41069
|
for (const drift of drifts) {
|
|
40548
41070
|
for (const line2 of describeDrift(drift)) errorDetail(line2);
|
|
40549
41071
|
}
|
|
40550
41072
|
errorDetail("Run the same command without --check, then commit the result.");
|
|
40551
|
-
|
|
41073
|
+
return exitCode;
|
|
40552
41074
|
}
|
|
40553
41075
|
function reportWatchEvent(event, targets) {
|
|
40554
41076
|
switch (event.kind) {
|
|
40555
41077
|
case "polled":
|
|
40556
41078
|
if (event.changed) {
|
|
40557
|
-
success(`Updated ${event.written.map((p) =>
|
|
41079
|
+
success(`Updated ${event.written.map((p) => path17.relative(process.cwd(), p)).join(", ")}`);
|
|
40558
41080
|
}
|
|
40559
41081
|
return;
|
|
40560
41082
|
case "failed":
|
|
@@ -40645,6 +41167,7 @@ var codegenCommand = withOutputOptions(
|
|
|
40645
41167
|
const project = resolveProjectRefOrExit(opts);
|
|
40646
41168
|
const config = ensureLoggedIn();
|
|
40647
41169
|
const spinner = startSpinner("Fetching datafile...");
|
|
41170
|
+
let checkExitCode = null;
|
|
40648
41171
|
try {
|
|
40649
41172
|
const client = new CodegenApiClient(config);
|
|
40650
41173
|
const first = await client.getDatafileConditional(project.shortId, opts.env, null);
|
|
@@ -40657,15 +41180,15 @@ var codegenCommand = withOutputOptions(
|
|
|
40657
41180
|
const datafile = first.body;
|
|
40658
41181
|
spinner.succeed("Datafile fetched");
|
|
40659
41182
|
const targets = {
|
|
40660
|
-
typesPath:
|
|
40661
|
-
manifestPath: opts.manifest ?
|
|
41183
|
+
typesPath: path17.resolve(opts.output),
|
|
41184
|
+
manifestPath: opts.manifest ? path17.resolve(opts.manifest) : null,
|
|
40662
41185
|
projectShortId: project.shortId,
|
|
40663
41186
|
env: opts.env,
|
|
40664
41187
|
augment: opts.augment !== false
|
|
40665
41188
|
};
|
|
40666
41189
|
const artifacts = planArtifacts(datafile, targets);
|
|
40667
41190
|
if (opts.check) {
|
|
40668
|
-
await runCheck(artifacts, datafile, targets);
|
|
41191
|
+
checkExitCode = await runCheck(artifacts, datafile, targets);
|
|
40669
41192
|
return;
|
|
40670
41193
|
}
|
|
40671
41194
|
const written = await writeArtifacts(artifacts);
|
|
@@ -40695,12 +41218,14 @@ var codegenCommand = withOutputOptions(
|
|
|
40695
41218
|
} catch (err) {
|
|
40696
41219
|
spinner.fail("Codegen failed");
|
|
40697
41220
|
failCommand(err);
|
|
41221
|
+
} finally {
|
|
41222
|
+
if (checkExitCode !== null) process.exit(checkExitCode);
|
|
40698
41223
|
}
|
|
40699
41224
|
});
|
|
40700
41225
|
|
|
40701
41226
|
// src/commands/find-refs.ts
|
|
40702
|
-
import
|
|
40703
|
-
import
|
|
41227
|
+
import fs14 from "node:fs/promises";
|
|
41228
|
+
import path19 from "node:path";
|
|
40704
41229
|
|
|
40705
41230
|
// src/lib/findRefs.ts
|
|
40706
41231
|
var FLAG_GETTERS = /* @__PURE__ */ new Map([
|
|
@@ -40863,7 +41388,7 @@ function byKeyName(a, b) {
|
|
|
40863
41388
|
}
|
|
40864
41389
|
|
|
40865
41390
|
// src/lib/ignoreRules.ts
|
|
40866
|
-
import
|
|
41391
|
+
import path18 from "node:path";
|
|
40867
41392
|
var ALWAYS_SKIPPED_DIRECTORIES = [
|
|
40868
41393
|
".git",
|
|
40869
41394
|
"node_modules",
|
|
@@ -40967,7 +41492,7 @@ function relativeToBase(relPath, base) {
|
|
|
40967
41492
|
return relPath.slice(base.length + 1);
|
|
40968
41493
|
}
|
|
40969
41494
|
function toRelativePosix(root, target) {
|
|
40970
|
-
return
|
|
41495
|
+
return path18.relative(root, target).split(path18.sep).join("/");
|
|
40971
41496
|
}
|
|
40972
41497
|
|
|
40973
41498
|
// src/commands/find-refs.ts
|
|
@@ -41023,13 +41548,13 @@ async function scanTree(root, extraRules, knownKeys, maxFiles) {
|
|
|
41023
41548
|
if (truncated) return;
|
|
41024
41549
|
let entries;
|
|
41025
41550
|
try {
|
|
41026
|
-
entries = await
|
|
41551
|
+
entries = await fs14.readdir(dir, { withFileTypes: true });
|
|
41027
41552
|
} catch {
|
|
41028
41553
|
return;
|
|
41029
41554
|
}
|
|
41030
41555
|
let rules = inherited;
|
|
41031
41556
|
if (entries.some((e) => e.isFile() && e.name === ".gitignore")) {
|
|
41032
|
-
const contents = await readTextFile(
|
|
41557
|
+
const contents = await readTextFile(path19.join(dir, ".gitignore"));
|
|
41033
41558
|
if (contents !== null) {
|
|
41034
41559
|
rules = [...inherited, ...parseIgnoreFile(contents, toRelativePosix(root, dir))];
|
|
41035
41560
|
}
|
|
@@ -41037,7 +41562,7 @@ async function scanTree(root, extraRules, knownKeys, maxFiles) {
|
|
|
41037
41562
|
for (const entry of entries) {
|
|
41038
41563
|
if (truncated) return;
|
|
41039
41564
|
if (entry.isSymbolicLink()) continue;
|
|
41040
|
-
const full =
|
|
41565
|
+
const full = path19.join(dir, entry.name);
|
|
41041
41566
|
const rel = toRelativePosix(root, full);
|
|
41042
41567
|
if (entry.isDirectory()) {
|
|
41043
41568
|
if (ALWAYS_SKIPPED_DIRECTORIES.includes(entry.name)) continue;
|
|
@@ -41046,7 +41571,7 @@ async function scanTree(root, extraRules, knownKeys, maxFiles) {
|
|
|
41046
41571
|
continue;
|
|
41047
41572
|
}
|
|
41048
41573
|
if (!entry.isFile()) continue;
|
|
41049
|
-
if (!SOURCE_EXTENSIONS.has(
|
|
41574
|
+
if (!SOURCE_EXTENSIONS.has(path19.extname(entry.name))) continue;
|
|
41050
41575
|
if (isIgnored(rel, false, rules)) continue;
|
|
41051
41576
|
if (filesScanned >= maxFiles) {
|
|
41052
41577
|
truncated = true;
|
|
@@ -41063,9 +41588,9 @@ async function scanTree(root, extraRules, knownKeys, maxFiles) {
|
|
|
41063
41588
|
}
|
|
41064
41589
|
async function readTextFile(filePath) {
|
|
41065
41590
|
try {
|
|
41066
|
-
const stat4 = await
|
|
41591
|
+
const stat4 = await fs14.stat(filePath);
|
|
41067
41592
|
if (stat4.size > MAX_FILE_BYTES) return null;
|
|
41068
|
-
const contents = await
|
|
41593
|
+
const contents = await fs14.readFile(filePath, "utf-8");
|
|
41069
41594
|
return contents.includes("\0") ? null : contents;
|
|
41070
41595
|
} catch {
|
|
41071
41596
|
return null;
|
|
@@ -41131,7 +41656,7 @@ var findRefsCommand = withOutputOptions(
|
|
|
41131
41656
|
const api = new FlagsApiClient(config);
|
|
41132
41657
|
const registry = await readRegistry(api, project.shortId);
|
|
41133
41658
|
spinner.succeed(`Read ${registry.length} flag${registry.length === 1 ? "" : "s"}`);
|
|
41134
|
-
const root =
|
|
41659
|
+
const root = path19.resolve(opts.dir);
|
|
41135
41660
|
await assertDirectory(root, opts.dir);
|
|
41136
41661
|
const extraRules = (opts.ignore ?? []).flatMap((pattern) => parseIgnoreFile(pattern));
|
|
41137
41662
|
const knownKeys = new Set(registry.map((f) => f.key));
|
|
@@ -41166,7 +41691,7 @@ function parseMaxFiles(raw) {
|
|
|
41166
41691
|
}
|
|
41167
41692
|
async function assertDirectory(root, asTyped) {
|
|
41168
41693
|
try {
|
|
41169
|
-
const stat4 = await
|
|
41694
|
+
const stat4 = await fs14.stat(root);
|
|
41170
41695
|
if (stat4.isDirectory()) return;
|
|
41171
41696
|
} catch {
|
|
41172
41697
|
}
|
|
@@ -41174,11 +41699,11 @@ async function assertDirectory(root, asTyped) {
|
|
|
41174
41699
|
}
|
|
41175
41700
|
|
|
41176
41701
|
// src/commands/datasets.ts
|
|
41177
|
-
import
|
|
41702
|
+
import fs16 from "node:fs";
|
|
41178
41703
|
|
|
41179
41704
|
// src/commands/datasets.helpers.ts
|
|
41180
|
-
import
|
|
41181
|
-
import
|
|
41705
|
+
import fs15 from "node:fs";
|
|
41706
|
+
import path20 from "node:path";
|
|
41182
41707
|
function parseVersionArg(raw) {
|
|
41183
41708
|
const normalized = raw.trim().replace(/^[vV]/, "");
|
|
41184
41709
|
if (!/^\d+$/.test(normalized)) {
|
|
@@ -41193,13 +41718,13 @@ function parseVersionArg(raw) {
|
|
|
41193
41718
|
return shortId;
|
|
41194
41719
|
}
|
|
41195
41720
|
function validateNdjsonPath(filePath) {
|
|
41196
|
-
const resolved =
|
|
41197
|
-
if (!
|
|
41721
|
+
const resolved = path20.resolve(filePath);
|
|
41722
|
+
if (!fs15.existsSync(resolved) || !fs15.statSync(resolved).isFile()) {
|
|
41198
41723
|
throw new Error(`File not found: ${resolved}`);
|
|
41199
41724
|
}
|
|
41200
|
-
if (
|
|
41725
|
+
if (path20.extname(resolved).toLowerCase() !== ".ndjson") {
|
|
41201
41726
|
throw new Error(
|
|
41202
|
-
`Expected an .ndjson file, got "${
|
|
41727
|
+
`Expected an .ndjson file, got "${path20.basename(resolved)}". Convert CSV files in the dashboard upload modal, or export NDJSON directly.`
|
|
41203
41728
|
);
|
|
41204
41729
|
}
|
|
41205
41730
|
return resolved;
|
|
@@ -41388,7 +41913,7 @@ var datasetsPushCommand = new Command("push").argument("<slug>", "Dataset slug")
|
|
|
41388
41913
|
try {
|
|
41389
41914
|
const result = await runPushFlow(api, projectShortId, {
|
|
41390
41915
|
slug,
|
|
41391
|
-
fileBody:
|
|
41916
|
+
fileBody: fs16.readFileSync(resolvedPath),
|
|
41392
41917
|
label: opts.label,
|
|
41393
41918
|
activate: opts.activate,
|
|
41394
41919
|
poll: { timeoutMs: timeoutSeconds * 1e3 },
|
|
@@ -41482,6 +42007,12 @@ var datasetsRollbackCommand = new Command("rollback").argument("<slug>", "Datase
|
|
|
41482
42007
|
fail(spinner, "Failed to resolve rollback target", err);
|
|
41483
42008
|
}
|
|
41484
42009
|
if (!opts.force) {
|
|
42010
|
+
const refusal = refusalWithoutPrompt({
|
|
42011
|
+
code: "cli_usage",
|
|
42012
|
+
message: `Rolling back "${slug}" (${summary}) needs a confirmation this run cannot ask for.`,
|
|
42013
|
+
hints: [`Pass -f to roll back without asking: \`avsb datasets rollback ${slug} -f\``]
|
|
42014
|
+
});
|
|
42015
|
+
if (refusal) failCommand(refusal);
|
|
41485
42016
|
const { proceed } = await esm_default12.prompt([
|
|
41486
42017
|
{
|
|
41487
42018
|
type: "confirm",
|
|
@@ -41757,7 +42288,7 @@ function printSecretHint() {
|
|
|
41757
42288
|
" A vs B shows a webhook signing secret only when the webhook is created and when its"
|
|
41758
42289
|
);
|
|
41759
42290
|
dim(" secret is rotated, so copy it then. If you no longer have it, rotate it:");
|
|
41760
|
-
dim(" Dashboard:
|
|
42291
|
+
dim(" Dashboard: Organization Settings > Integrations > Webhooks");
|
|
41761
42292
|
dim(" API: POST /api/v1/projects/{projectId}/webhooks/{webhookId}/secret");
|
|
41762
42293
|
dim(
|
|
41763
42294
|
" Put it in AVSB_WEBHOOK_SECRET and verify with verifyWebhookSignature from @avsbhq/node."
|
|
@@ -41779,8 +42310,7 @@ var listenCommand = new Command("listen").description("Watch this project's webh
|
|
|
41779
42310
|
if (opts.forward) {
|
|
41780
42311
|
const validated = validateForwardUrl(opts.forward);
|
|
41781
42312
|
if (!validated.ok) {
|
|
41782
|
-
|
|
41783
|
-
process.exit(EXIT.failure);
|
|
42313
|
+
failCommand(new CliError("cli_usage", validated.reason));
|
|
41784
42314
|
}
|
|
41785
42315
|
forwardUrl = validated.url;
|
|
41786
42316
|
}
|
|
@@ -42306,7 +42836,7 @@ var flagsCreateCommand = withOutputOptions(
|
|
|
42306
42836
|
var flagsUpdateCommand = withOutputOptions(
|
|
42307
42837
|
withProjectOption(
|
|
42308
42838
|
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(
|
|
42309
|
-
"--if-match <
|
|
42839
|
+
"--if-match <version>",
|
|
42310
42840
|
"Only write if the flag still matches this version (from `avsb flags get`)"
|
|
42311
42841
|
).option("--force", "Write without checking the version first")
|
|
42312
42842
|
)
|
|
@@ -42610,46 +43140,127 @@ var openCommand = withOutputOptions(
|
|
|
42610
43140
|
|
|
42611
43141
|
// src/commands/completion.ts
|
|
42612
43142
|
var COMPLETION_SHELLS = ["bash", "zsh", "fish"];
|
|
42613
|
-
|
|
42614
|
-
|
|
43143
|
+
var ARGUMENT_CHOICES = {
|
|
43144
|
+
completion: COMPLETION_SHELLS,
|
|
43145
|
+
open: OPEN_TARGETS
|
|
43146
|
+
};
|
|
43147
|
+
function flagsOf(command, isRoot) {
|
|
43148
|
+
const flags = command.options.map((option) => option.long).filter((long) => typeof long === "string");
|
|
43149
|
+
flags.push("--help");
|
|
43150
|
+
if (isRoot) flags.push("--version");
|
|
43151
|
+
return [...new Set(flags)].sort();
|
|
43152
|
+
}
|
|
43153
|
+
function completionEntries(program3) {
|
|
43154
|
+
const entries = [];
|
|
43155
|
+
function visit(command, path23) {
|
|
43156
|
+
const subcommands = command.commands.map((child) => child.name()).sort();
|
|
43157
|
+
entries.push({
|
|
43158
|
+
path: path23,
|
|
43159
|
+
words: [...subcommands, ...ARGUMENT_CHOICES[path23] ?? []],
|
|
43160
|
+
flags: flagsOf(command, path23 === "")
|
|
43161
|
+
});
|
|
43162
|
+
for (const child of command.commands) {
|
|
43163
|
+
visit(child, path23 === "" ? child.name() : `${path23} ${child.name()}`);
|
|
43164
|
+
}
|
|
43165
|
+
}
|
|
43166
|
+
visit(program3, "");
|
|
43167
|
+
return entries;
|
|
42615
43168
|
}
|
|
42616
|
-
|
|
43169
|
+
var quoteBash = (value) => `"${value}"`;
|
|
43170
|
+
function bashCompletion(entries) {
|
|
43171
|
+
const known = entries.filter((entry) => entry.path !== "").map((entry) => quoteBash(entry.path));
|
|
43172
|
+
const cases = entries.map((entry) => {
|
|
43173
|
+
const offer = [...entry.words, ...entry.flags].join(" ");
|
|
43174
|
+
return ` ${quoteBash(entry.path)}) offer="${offer}" ;;`;
|
|
43175
|
+
});
|
|
42617
43176
|
return `# avsb completion for bash. Add to ~/.bashrc:
|
|
42618
43177
|
# eval "$(avsb completion bash)"
|
|
42619
43178
|
_avsb_completions() {
|
|
42620
|
-
local
|
|
42621
|
-
|
|
43179
|
+
local cur="\${COMP_WORDS[COMP_CWORD]}" cmdpath="" word candidate i offer=""
|
|
43180
|
+
for ((i = 1; i < COMP_CWORD; i++)); do
|
|
43181
|
+
word="\${COMP_WORDS[i]}"
|
|
43182
|
+
[[ "$word" == -* ]] && continue
|
|
43183
|
+
candidate="\${cmdpath:+$cmdpath }$word"
|
|
43184
|
+
case "$candidate" in
|
|
43185
|
+
${known.length > 0 ? known.join("|") : '"__none__"'}) cmdpath="$candidate" ;;
|
|
43186
|
+
esac
|
|
43187
|
+
done
|
|
43188
|
+
case "$cmdpath" in
|
|
43189
|
+
${cases.join("\n")}
|
|
43190
|
+
esac
|
|
43191
|
+
COMPREPLY=($(compgen -W "$offer" -- "$cur"))
|
|
42622
43192
|
}
|
|
42623
43193
|
complete -F _avsb_completions avsb`;
|
|
42624
43194
|
}
|
|
42625
|
-
|
|
43195
|
+
var quoteZsh = (value) => `'${value}'`;
|
|
43196
|
+
function zshCompletion(entries) {
|
|
43197
|
+
const known = entries.filter((entry) => entry.path !== "").map((entry) => quoteZsh(entry.path));
|
|
43198
|
+
const cases = entries.map((entry) => {
|
|
43199
|
+
const offer = [...entry.words, ...entry.flags].map(quoteZsh).join(" ");
|
|
43200
|
+
return ` (${quoteZsh(entry.path)}) offer=(${offer}) ;;`;
|
|
43201
|
+
});
|
|
42626
43202
|
return `# avsb completion for zsh. Add to ~/.zshrc:
|
|
42627
43203
|
# eval "$(avsb completion zsh)"
|
|
42628
43204
|
_avsb() {
|
|
42629
|
-
local
|
|
42630
|
-
|
|
42631
|
-
|
|
43205
|
+
local cmdpath="" word candidate i
|
|
43206
|
+
local -a offer
|
|
43207
|
+
for ((i = 2; i < CURRENT; i++)); do
|
|
43208
|
+
word="\${words[i]}"
|
|
43209
|
+
[[ "$word" == -* ]] && continue
|
|
43210
|
+
candidate="\${cmdpath:+$cmdpath }$word"
|
|
43211
|
+
case "$candidate" in
|
|
43212
|
+
(${known.length > 0 ? known.join("|") : "'__none__'"}) cmdpath="$candidate" ;;
|
|
43213
|
+
esac
|
|
43214
|
+
done
|
|
43215
|
+
case "$cmdpath" in
|
|
43216
|
+
${cases.join("\n")}
|
|
43217
|
+
esac
|
|
43218
|
+
compadd -- $offer
|
|
42632
43219
|
}
|
|
42633
43220
|
compdef _avsb avsb`;
|
|
42634
43221
|
}
|
|
42635
|
-
|
|
42636
|
-
|
|
42637
|
-
|
|
42638
|
-
|
|
43222
|
+
var quoteFish = (value) => `'${value}'`;
|
|
43223
|
+
function fishCompletion(entries) {
|
|
43224
|
+
const known = entries.filter((entry) => entry.path !== "").map((entry) => quoteFish(entry.path));
|
|
43225
|
+
const lines = [];
|
|
43226
|
+
for (const entry of entries) {
|
|
43227
|
+
const condition = `'test (__avsb_path) = "${entry.path}"'`;
|
|
43228
|
+
if (entry.words.length > 0) {
|
|
43229
|
+
lines.push(`complete -c avsb -n ${condition} -a ${quoteFish(entry.words.join(" "))}`);
|
|
43230
|
+
}
|
|
43231
|
+
for (const flag of entry.flags) {
|
|
43232
|
+
lines.push(`complete -c avsb -n ${condition} -l ${flag.replace(/^--/, "")}`);
|
|
43233
|
+
}
|
|
43234
|
+
}
|
|
42639
43235
|
return [
|
|
42640
43236
|
"# avsb completion for fish. Save as:",
|
|
42641
43237
|
"# avsb completion fish > ~/.config/fish/completions/avsb.fish",
|
|
43238
|
+
`set -g __avsb_known_paths ${known.join(" ")}`,
|
|
43239
|
+
// Drops the first token (`avsb`) with `set -e` rather than a [2..-1]
|
|
43240
|
+
// slice, and always prints exactly one line, so `test (__avsb_path) = ""`
|
|
43241
|
+
// compares one value even at the top level.
|
|
43242
|
+
"function __avsb_path",
|
|
43243
|
+
" set -l tokens (commandline -opc)",
|
|
43244
|
+
" set -e tokens[1]",
|
|
43245
|
+
" set -l cmdpath",
|
|
43246
|
+
" for word in $tokens",
|
|
43247
|
+
" string match -q -- '-*' $word; and continue",
|
|
43248
|
+
" set -l candidate (string join ' ' $cmdpath $word)",
|
|
43249
|
+
" contains -- $candidate $__avsb_known_paths; and set cmdpath $cmdpath $word",
|
|
43250
|
+
" end",
|
|
43251
|
+
" echo (string join ' ' $cmdpath)",
|
|
43252
|
+
"end",
|
|
42642
43253
|
...lines
|
|
42643
43254
|
].join("\n");
|
|
42644
43255
|
}
|
|
42645
|
-
function completionScript(shell,
|
|
43256
|
+
function completionScript(shell, entries) {
|
|
42646
43257
|
switch (shell) {
|
|
42647
43258
|
case "bash":
|
|
42648
|
-
return bashCompletion(
|
|
43259
|
+
return bashCompletion(entries);
|
|
42649
43260
|
case "zsh":
|
|
42650
|
-
return zshCompletion(
|
|
43261
|
+
return zshCompletion(entries);
|
|
42651
43262
|
case "fish":
|
|
42652
|
-
return fishCompletion(
|
|
43263
|
+
return fishCompletion(entries);
|
|
42653
43264
|
}
|
|
42654
43265
|
}
|
|
42655
43266
|
function buildCompletionCommand(program3) {
|
|
@@ -42663,16 +43274,16 @@ function buildCompletionCommand(program3) {
|
|
|
42663
43274
|
])
|
|
42664
43275
|
);
|
|
42665
43276
|
}
|
|
42666
|
-
line(completionScript(requested,
|
|
43277
|
+
line(completionScript(requested, completionEntries(program3)));
|
|
42667
43278
|
});
|
|
42668
43279
|
}
|
|
42669
43280
|
|
|
42670
43281
|
// src/lib/updateNotifier.ts
|
|
42671
|
-
import
|
|
43282
|
+
import fs17 from "node:fs";
|
|
42672
43283
|
import os4 from "node:os";
|
|
42673
|
-
import
|
|
43284
|
+
import path21 from "node:path";
|
|
42674
43285
|
var REGISTRY_URL = "https://registry.npmjs.org/@avsbhq/cli/latest";
|
|
42675
|
-
var CACHE_FILE =
|
|
43286
|
+
var CACHE_FILE = path21.join(os4.homedir(), ".avsb", "update-check.json");
|
|
42676
43287
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
42677
43288
|
var FETCH_TIMEOUT_MS = 1500;
|
|
42678
43289
|
function isNewerVersion(latest, current) {
|
|
@@ -42712,8 +43323,8 @@ function updateMessage(latest, current) {
|
|
|
42712
43323
|
}
|
|
42713
43324
|
function writeCache(cache) {
|
|
42714
43325
|
try {
|
|
42715
|
-
|
|
42716
|
-
|
|
43326
|
+
fs17.mkdirSync(path21.dirname(CACHE_FILE), { recursive: true });
|
|
43327
|
+
fs17.writeFileSync(CACHE_FILE, JSON.stringify(cache));
|
|
42717
43328
|
} catch {
|
|
42718
43329
|
}
|
|
42719
43330
|
}
|
|
@@ -42737,7 +43348,7 @@ async function checkForUpdate(currentVersion, notify) {
|
|
|
42737
43348
|
}
|
|
42738
43349
|
let cache = null;
|
|
42739
43350
|
try {
|
|
42740
|
-
cache = readCache(() =>
|
|
43351
|
+
cache = readCache(() => fs17.readFileSync(CACHE_FILE, "utf-8"));
|
|
42741
43352
|
} catch {
|
|
42742
43353
|
cache = null;
|
|
42743
43354
|
}
|
|
@@ -42750,8 +43361,8 @@ async function checkForUpdate(currentVersion, notify) {
|
|
|
42750
43361
|
}
|
|
42751
43362
|
|
|
42752
43363
|
// src/cli.ts
|
|
42753
|
-
var __dirname =
|
|
42754
|
-
var pkg = JSON.parse(
|
|
43364
|
+
var __dirname = path22.dirname(fileURLToPath2(import.meta.url));
|
|
43365
|
+
var pkg = JSON.parse(fs18.readFileSync(path22.resolve(__dirname, "..", "package.json"), "utf-8"));
|
|
42755
43366
|
var program2 = new Command();
|
|
42756
43367
|
program2.name("avsb").description(
|
|
42757
43368
|
"A vs B command line tool: experiments, flags, project scripts, metrics, datasets, webhooks, and typed flags"
|