@drip-apex/apex-cli 0.13.0 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +45 -1
- package/README.md +71 -0
- package/dist/cli.js +1995 -659
- package/package.json +1 -1
- package/skills/apex-cli/SKILL.md +13 -0
package/dist/cli.js
CHANGED
|
@@ -1159,8 +1159,8 @@ var require_command = __commonJS({
|
|
|
1159
1159
|
"node_modules/commander/lib/command.js"(exports2) {
|
|
1160
1160
|
var EventEmitter = require("node:events").EventEmitter;
|
|
1161
1161
|
var childProcess2 = require("node:child_process");
|
|
1162
|
-
var
|
|
1163
|
-
var
|
|
1162
|
+
var path25 = require("node:path");
|
|
1163
|
+
var fs29 = require("node:fs");
|
|
1164
1164
|
var process16 = require("node:process");
|
|
1165
1165
|
var { Argument: Argument2, humanReadableArgName } = require_argument();
|
|
1166
1166
|
var { CommanderError: CommanderError2 } = require_error();
|
|
@@ -2141,7 +2141,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2141
2141
|
* @param {string} subcommandName
|
|
2142
2142
|
*/
|
|
2143
2143
|
_checkForMissingExecutable(executableFile, executableDir, subcommandName) {
|
|
2144
|
-
if (
|
|
2144
|
+
if (fs29.existsSync(executableFile)) return;
|
|
2145
2145
|
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";
|
|
2146
2146
|
const executableMissing = `'${executableFile}' does not exist
|
|
2147
2147
|
- if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
|
|
@@ -2159,11 +2159,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2159
2159
|
let launchWithNode = false;
|
|
2160
2160
|
const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
|
|
2161
2161
|
function findFile(baseDir, baseName) {
|
|
2162
|
-
const localBin =
|
|
2163
|
-
if (
|
|
2164
|
-
if (sourceExt.includes(
|
|
2162
|
+
const localBin = path25.resolve(baseDir, baseName);
|
|
2163
|
+
if (fs29.existsSync(localBin)) return localBin;
|
|
2164
|
+
if (sourceExt.includes(path25.extname(baseName))) return void 0;
|
|
2165
2165
|
const foundExt = sourceExt.find(
|
|
2166
|
-
(ext) =>
|
|
2166
|
+
(ext) => fs29.existsSync(`${localBin}${ext}`)
|
|
2167
2167
|
);
|
|
2168
2168
|
if (foundExt) return `${localBin}${foundExt}`;
|
|
2169
2169
|
return void 0;
|
|
@@ -2175,21 +2175,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2175
2175
|
if (this._scriptPath) {
|
|
2176
2176
|
let resolvedScriptPath;
|
|
2177
2177
|
try {
|
|
2178
|
-
resolvedScriptPath =
|
|
2178
|
+
resolvedScriptPath = fs29.realpathSync(this._scriptPath);
|
|
2179
2179
|
} catch {
|
|
2180
2180
|
resolvedScriptPath = this._scriptPath;
|
|
2181
2181
|
}
|
|
2182
|
-
executableDir =
|
|
2183
|
-
|
|
2182
|
+
executableDir = path25.resolve(
|
|
2183
|
+
path25.dirname(resolvedScriptPath),
|
|
2184
2184
|
executableDir
|
|
2185
2185
|
);
|
|
2186
2186
|
}
|
|
2187
2187
|
if (executableDir) {
|
|
2188
2188
|
let localFile = findFile(executableDir, executableFile);
|
|
2189
2189
|
if (!localFile && !subcommand._executableFile && this._scriptPath) {
|
|
2190
|
-
const legacyName =
|
|
2190
|
+
const legacyName = path25.basename(
|
|
2191
2191
|
this._scriptPath,
|
|
2192
|
-
|
|
2192
|
+
path25.extname(this._scriptPath)
|
|
2193
2193
|
);
|
|
2194
2194
|
if (legacyName !== this._name) {
|
|
2195
2195
|
localFile = findFile(
|
|
@@ -2200,7 +2200,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2200
2200
|
}
|
|
2201
2201
|
executableFile = localFile || executableFile;
|
|
2202
2202
|
}
|
|
2203
|
-
launchWithNode = sourceExt.includes(
|
|
2203
|
+
launchWithNode = sourceExt.includes(path25.extname(executableFile));
|
|
2204
2204
|
let proc;
|
|
2205
2205
|
if (process16.platform !== "win32") {
|
|
2206
2206
|
if (launchWithNode) {
|
|
@@ -3047,7 +3047,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3047
3047
|
* @return {Command}
|
|
3048
3048
|
*/
|
|
3049
3049
|
nameFromFilename(filename) {
|
|
3050
|
-
this._name =
|
|
3050
|
+
this._name = path25.basename(filename, path25.extname(filename));
|
|
3051
3051
|
return this;
|
|
3052
3052
|
}
|
|
3053
3053
|
/**
|
|
@@ -3061,9 +3061,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3061
3061
|
* @param {string} [path]
|
|
3062
3062
|
* @return {(string|null|Command)}
|
|
3063
3063
|
*/
|
|
3064
|
-
executableDir(
|
|
3065
|
-
if (
|
|
3066
|
-
this._executableDir =
|
|
3064
|
+
executableDir(path26) {
|
|
3065
|
+
if (path26 === void 0) return this._executableDir;
|
|
3066
|
+
this._executableDir = path26;
|
|
3067
3067
|
return this;
|
|
3068
3068
|
}
|
|
3069
3069
|
/**
|
|
@@ -3080,9 +3080,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3080
3080
|
helpWidth: context.helpWidth,
|
|
3081
3081
|
outputHasColors: context.hasColors
|
|
3082
3082
|
});
|
|
3083
|
-
const
|
|
3084
|
-
if (context.hasColors) return
|
|
3085
|
-
return this._outputConfiguration.stripColor(
|
|
3083
|
+
const text2 = helper.formatHelp(this, helper);
|
|
3084
|
+
if (context.hasColors) return text2;
|
|
3085
|
+
return this._outputConfiguration.stripColor(text2);
|
|
3086
3086
|
}
|
|
3087
3087
|
/**
|
|
3088
3088
|
* @typedef HelpContext
|
|
@@ -3236,7 +3236,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3236
3236
|
* @param {(string | Function)} text - string to add, or a function returning a string
|
|
3237
3237
|
* @return {Command} `this` command for chaining
|
|
3238
3238
|
*/
|
|
3239
|
-
addHelpText(position,
|
|
3239
|
+
addHelpText(position, text2) {
|
|
3240
3240
|
const allowedValues = ["beforeAll", "before", "after", "afterAll"];
|
|
3241
3241
|
if (!allowedValues.includes(position)) {
|
|
3242
3242
|
throw new Error(`Unexpected value for position to addHelpText.
|
|
@@ -3245,10 +3245,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3245
3245
|
const helpEvent = `${position}Help`;
|
|
3246
3246
|
this.on(helpEvent, (context) => {
|
|
3247
3247
|
let helpStr;
|
|
3248
|
-
if (typeof
|
|
3249
|
-
helpStr =
|
|
3248
|
+
if (typeof text2 === "function") {
|
|
3249
|
+
helpStr = text2({ error: context.error, command: context.command });
|
|
3250
3250
|
} else {
|
|
3251
|
-
helpStr =
|
|
3251
|
+
helpStr = text2;
|
|
3252
3252
|
}
|
|
3253
3253
|
if (helpStr) {
|
|
3254
3254
|
context.write(`${helpStr}
|
|
@@ -3583,7 +3583,7 @@ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
|
|
|
3583
3583
|
return min;
|
|
3584
3584
|
}
|
|
3585
3585
|
if (import_node_process.default.platform === "win32") {
|
|
3586
|
-
const osRelease =
|
|
3586
|
+
const osRelease = import_node_os3.default.release().split(".");
|
|
3587
3587
|
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
|
|
3588
3588
|
return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
3589
3589
|
}
|
|
@@ -3642,11 +3642,11 @@ function createSupportsColor(stream, options = {}) {
|
|
|
3642
3642
|
});
|
|
3643
3643
|
return translateLevel(level);
|
|
3644
3644
|
}
|
|
3645
|
-
var import_node_process,
|
|
3645
|
+
var import_node_process, import_node_os3, import_node_tty, env, flagForceColor, supportsColor, supports_color_default;
|
|
3646
3646
|
var init_supports_color = __esm({
|
|
3647
3647
|
"node_modules/chalk/source/vendor/supports-color/index.js"() {
|
|
3648
3648
|
import_node_process = __toESM(require("node:process"), 1);
|
|
3649
|
-
|
|
3649
|
+
import_node_os3 = __toESM(require("node:os"), 1);
|
|
3650
3650
|
import_node_tty = __toESM(require("node:tty"), 1);
|
|
3651
3651
|
({ env } = import_node_process.default);
|
|
3652
3652
|
if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
|
|
@@ -4073,12 +4073,12 @@ var init_mjs = __esm({
|
|
|
4073
4073
|
for (const sig of signals) {
|
|
4074
4074
|
this.#sigListeners[sig] = () => {
|
|
4075
4075
|
const listeners = this.#process.listeners(sig);
|
|
4076
|
-
let { count } = this.#emitter;
|
|
4076
|
+
let { count: count2 } = this.#emitter;
|
|
4077
4077
|
const p = process16;
|
|
4078
4078
|
if (typeof p.__signal_exit_emitter__ === "object" && typeof p.__signal_exit_emitter__.count === "number") {
|
|
4079
|
-
|
|
4079
|
+
count2 += p.__signal_exit_emitter__.count;
|
|
4080
4080
|
}
|
|
4081
|
-
if (listeners.length ===
|
|
4081
|
+
if (listeners.length === count2) {
|
|
4082
4082
|
this.unload();
|
|
4083
4083
|
const ret = this.#emitter.emit("exit", null, sig);
|
|
4084
4084
|
const s = sig === "SIGHUP" ? this.#hupSig : sig;
|
|
@@ -6405,9 +6405,9 @@ var init_ora = __esm({
|
|
|
6405
6405
|
this.#linesToClear = this.#lineCount;
|
|
6406
6406
|
return this;
|
|
6407
6407
|
}
|
|
6408
|
-
start(
|
|
6409
|
-
if (
|
|
6410
|
-
this.text =
|
|
6408
|
+
start(text2) {
|
|
6409
|
+
if (text2) {
|
|
6410
|
+
this.text = text2;
|
|
6411
6411
|
}
|
|
6412
6412
|
if (this.#isSilent) {
|
|
6413
6413
|
return this;
|
|
@@ -6450,17 +6450,17 @@ var init_ora = __esm({
|
|
|
6450
6450
|
}
|
|
6451
6451
|
return this;
|
|
6452
6452
|
}
|
|
6453
|
-
succeed(
|
|
6454
|
-
return this.stopAndPersist({ symbol: log_symbols_default.success, text });
|
|
6453
|
+
succeed(text2) {
|
|
6454
|
+
return this.stopAndPersist({ symbol: log_symbols_default.success, text: text2 });
|
|
6455
6455
|
}
|
|
6456
|
-
fail(
|
|
6457
|
-
return this.stopAndPersist({ symbol: log_symbols_default.error, text });
|
|
6456
|
+
fail(text2) {
|
|
6457
|
+
return this.stopAndPersist({ symbol: log_symbols_default.error, text: text2 });
|
|
6458
6458
|
}
|
|
6459
|
-
warn(
|
|
6460
|
-
return this.stopAndPersist({ symbol: log_symbols_default.warning, text });
|
|
6459
|
+
warn(text2) {
|
|
6460
|
+
return this.stopAndPersist({ symbol: log_symbols_default.warning, text: text2 });
|
|
6461
6461
|
}
|
|
6462
|
-
info(
|
|
6463
|
-
return this.stopAndPersist({ symbol: log_symbols_default.info, text });
|
|
6462
|
+
info(text2) {
|
|
6463
|
+
return this.stopAndPersist({ symbol: log_symbols_default.info, text: text2 });
|
|
6464
6464
|
}
|
|
6465
6465
|
stopAndPersist(options = {}) {
|
|
6466
6466
|
if (this.#isSilent) {
|
|
@@ -6469,9 +6469,9 @@ var init_ora = __esm({
|
|
|
6469
6469
|
const prefixText = options.prefixText ?? this.#prefixText;
|
|
6470
6470
|
const fullPrefixText = this.#getFullPrefixText(prefixText, " ");
|
|
6471
6471
|
const symbolText = options.symbol ?? " ";
|
|
6472
|
-
const
|
|
6472
|
+
const text2 = options.text ?? this.text;
|
|
6473
6473
|
const separatorText = symbolText ? " " : "";
|
|
6474
|
-
const fullText = typeof
|
|
6474
|
+
const fullText = typeof text2 === "string" ? separatorText + text2 : "";
|
|
6475
6475
|
const suffixText = options.suffixText ?? this.#suffixText;
|
|
6476
6476
|
const fullSuffixText = this.#getFullSuffixText(suffixText, " ");
|
|
6477
6477
|
const textToWrite = fullPrefixText + symbolText + fullText + fullSuffixText + "\n";
|
|
@@ -6486,7 +6486,7 @@ var init_ora = __esm({
|
|
|
6486
6486
|
// node_modules/is-docker/index.js
|
|
6487
6487
|
function hasDockerEnv() {
|
|
6488
6488
|
try {
|
|
6489
|
-
|
|
6489
|
+
import_node_fs4.default.statSync("/.dockerenv");
|
|
6490
6490
|
return true;
|
|
6491
6491
|
} catch {
|
|
6492
6492
|
return false;
|
|
@@ -6494,7 +6494,7 @@ function hasDockerEnv() {
|
|
|
6494
6494
|
}
|
|
6495
6495
|
function hasDockerCGroup() {
|
|
6496
6496
|
try {
|
|
6497
|
-
return
|
|
6497
|
+
return import_node_fs4.default.readFileSync("/proc/self/cgroup", "utf8").includes("docker");
|
|
6498
6498
|
} catch {
|
|
6499
6499
|
return false;
|
|
6500
6500
|
}
|
|
@@ -6505,10 +6505,10 @@ function isDocker() {
|
|
|
6505
6505
|
}
|
|
6506
6506
|
return isDockerCached;
|
|
6507
6507
|
}
|
|
6508
|
-
var
|
|
6508
|
+
var import_node_fs4, isDockerCached;
|
|
6509
6509
|
var init_is_docker = __esm({
|
|
6510
6510
|
"node_modules/is-docker/index.js"() {
|
|
6511
|
-
|
|
6511
|
+
import_node_fs4 = __toESM(require("node:fs"), 1);
|
|
6512
6512
|
}
|
|
6513
6513
|
});
|
|
6514
6514
|
|
|
@@ -6519,14 +6519,14 @@ function isInsideContainer() {
|
|
|
6519
6519
|
}
|
|
6520
6520
|
return cachedResult;
|
|
6521
6521
|
}
|
|
6522
|
-
var
|
|
6522
|
+
var import_node_fs5, cachedResult, hasContainerEnv;
|
|
6523
6523
|
var init_is_inside_container = __esm({
|
|
6524
6524
|
"node_modules/is-inside-container/index.js"() {
|
|
6525
|
-
|
|
6525
|
+
import_node_fs5 = __toESM(require("node:fs"), 1);
|
|
6526
6526
|
init_is_docker();
|
|
6527
6527
|
hasContainerEnv = () => {
|
|
6528
6528
|
try {
|
|
6529
|
-
|
|
6529
|
+
import_node_fs5.default.statSync("/run/.containerenv");
|
|
6530
6530
|
return true;
|
|
6531
6531
|
} catch {
|
|
6532
6532
|
return false;
|
|
@@ -6536,25 +6536,25 @@ var init_is_inside_container = __esm({
|
|
|
6536
6536
|
});
|
|
6537
6537
|
|
|
6538
6538
|
// node_modules/is-wsl/index.js
|
|
6539
|
-
var import_node_process8,
|
|
6539
|
+
var import_node_process8, import_node_os5, import_node_fs6, isWsl, is_wsl_default;
|
|
6540
6540
|
var init_is_wsl = __esm({
|
|
6541
6541
|
"node_modules/is-wsl/index.js"() {
|
|
6542
6542
|
import_node_process8 = __toESM(require("node:process"), 1);
|
|
6543
|
-
|
|
6544
|
-
|
|
6543
|
+
import_node_os5 = __toESM(require("node:os"), 1);
|
|
6544
|
+
import_node_fs6 = __toESM(require("node:fs"), 1);
|
|
6545
6545
|
init_is_inside_container();
|
|
6546
6546
|
isWsl = () => {
|
|
6547
6547
|
if (import_node_process8.default.platform !== "linux") {
|
|
6548
6548
|
return false;
|
|
6549
6549
|
}
|
|
6550
|
-
if (
|
|
6550
|
+
if (import_node_os5.default.release().toLowerCase().includes("microsoft")) {
|
|
6551
6551
|
if (isInsideContainer()) {
|
|
6552
6552
|
return false;
|
|
6553
6553
|
}
|
|
6554
6554
|
return true;
|
|
6555
6555
|
}
|
|
6556
6556
|
try {
|
|
6557
|
-
return
|
|
6557
|
+
return import_node_fs6.default.readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft") ? !isInsideContainer() : false;
|
|
6558
6558
|
} catch {
|
|
6559
6559
|
return false;
|
|
6560
6560
|
}
|
|
@@ -6564,11 +6564,11 @@ var init_is_wsl = __esm({
|
|
|
6564
6564
|
});
|
|
6565
6565
|
|
|
6566
6566
|
// node_modules/wsl-utils/index.js
|
|
6567
|
-
var import_node_process9,
|
|
6567
|
+
var import_node_process9, import_promises2, wslDrivesMountPoint, powerShellPathFromWsl, powerShellPath;
|
|
6568
6568
|
var init_wsl_utils = __esm({
|
|
6569
6569
|
"node_modules/wsl-utils/index.js"() {
|
|
6570
6570
|
import_node_process9 = __toESM(require("node:process"), 1);
|
|
6571
|
-
|
|
6571
|
+
import_promises2 = __toESM(require("node:fs/promises"), 1);
|
|
6572
6572
|
init_is_wsl();
|
|
6573
6573
|
init_is_wsl();
|
|
6574
6574
|
wslDrivesMountPoint = /* @__PURE__ */ (() => {
|
|
@@ -6581,14 +6581,14 @@ var init_wsl_utils = __esm({
|
|
|
6581
6581
|
const configFilePath = "/etc/wsl.conf";
|
|
6582
6582
|
let isConfigFileExists = false;
|
|
6583
6583
|
try {
|
|
6584
|
-
await
|
|
6584
|
+
await import_promises2.default.access(configFilePath, import_promises2.constants.F_OK);
|
|
6585
6585
|
isConfigFileExists = true;
|
|
6586
6586
|
} catch {
|
|
6587
6587
|
}
|
|
6588
6588
|
if (!isConfigFileExists) {
|
|
6589
6589
|
return defaultMountPoint;
|
|
6590
6590
|
}
|
|
6591
|
-
const configContent = await
|
|
6591
|
+
const configContent = await import_promises2.default.readFile(configFilePath, { encoding: "utf8" });
|
|
6592
6592
|
const configMountPoint = /(?<!#.*)root\s*=\s*(?<mountPoint>.*)/g.exec(configContent);
|
|
6593
6593
|
if (!configMountPoint) {
|
|
6594
6594
|
return defaultMountPoint;
|
|
@@ -6823,24 +6823,24 @@ function detectPlatformBinary({ [platform]: platformBinary }, { wsl }) {
|
|
|
6823
6823
|
}
|
|
6824
6824
|
return detectArchBinary(platformBinary);
|
|
6825
6825
|
}
|
|
6826
|
-
var import_node_process13, import_node_buffer,
|
|
6826
|
+
var import_node_process13, import_node_buffer, import_node_path4, import_node_url, import_node_util5, import_node_child_process5, import_promises3, import_meta, execFile5, __dirname2, localXdgOpenPath, platform, arch, pTryEach, baseOpen, open, openApp, apps, open_default;
|
|
6827
6827
|
var init_open = __esm({
|
|
6828
6828
|
"node_modules/open/index.js"() {
|
|
6829
6829
|
import_node_process13 = __toESM(require("node:process"), 1);
|
|
6830
6830
|
import_node_buffer = require("node:buffer");
|
|
6831
|
-
|
|
6831
|
+
import_node_path4 = __toESM(require("node:path"), 1);
|
|
6832
6832
|
import_node_url = require("node:url");
|
|
6833
6833
|
import_node_util5 = require("node:util");
|
|
6834
6834
|
import_node_child_process5 = __toESM(require("node:child_process"), 1);
|
|
6835
|
-
|
|
6835
|
+
import_promises3 = __toESM(require("node:fs/promises"), 1);
|
|
6836
6836
|
init_wsl_utils();
|
|
6837
6837
|
init_define_lazy_prop();
|
|
6838
6838
|
init_default_browser();
|
|
6839
6839
|
init_is_inside_container();
|
|
6840
6840
|
import_meta = {};
|
|
6841
6841
|
execFile5 = (0, import_node_util5.promisify)(import_node_child_process5.default.execFile);
|
|
6842
|
-
__dirname2 =
|
|
6843
|
-
localXdgOpenPath =
|
|
6842
|
+
__dirname2 = import_node_path4.default.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
|
|
6843
|
+
localXdgOpenPath = import_node_path4.default.join(__dirname2, "xdg-open");
|
|
6844
6844
|
({ platform, arch } = import_node_process13.default);
|
|
6845
6845
|
pTryEach = async (array, mapper) => {
|
|
6846
6846
|
let latestError;
|
|
@@ -6965,7 +6965,7 @@ var init_open = __esm({
|
|
|
6965
6965
|
const isBundled = !__dirname2 || __dirname2 === "/";
|
|
6966
6966
|
let exeLocalXdgOpen = false;
|
|
6967
6967
|
try {
|
|
6968
|
-
await
|
|
6968
|
+
await import_promises3.default.access(localXdgOpenPath, import_promises3.constants.X_OK);
|
|
6969
6969
|
exeLocalXdgOpen = true;
|
|
6970
6970
|
} catch {
|
|
6971
6971
|
}
|
|
@@ -7085,34 +7085,247 @@ var {
|
|
|
7085
7085
|
Help
|
|
7086
7086
|
} = import_index.default;
|
|
7087
7087
|
|
|
7088
|
-
// src/
|
|
7089
|
-
var
|
|
7090
|
-
|
|
7091
|
-
|
|
7088
|
+
// ../api-client/src/client-identity.ts
|
|
7089
|
+
var import_node_crypto = require("node:crypto");
|
|
7090
|
+
var invocationId = (0, import_node_crypto.randomUUID)();
|
|
7091
|
+
var identity;
|
|
7092
|
+
function setClientIdentity(value) {
|
|
7093
|
+
identity = { ...value, invocationId };
|
|
7092
7094
|
}
|
|
7093
|
-
function
|
|
7094
|
-
return
|
|
7095
|
+
function getClientIdentity() {
|
|
7096
|
+
return identity ? { ...identity } : void 0;
|
|
7097
|
+
}
|
|
7098
|
+
function clientIdentityHeaders() {
|
|
7099
|
+
if (!identity) return {};
|
|
7100
|
+
return {
|
|
7101
|
+
"User-Agent": `${identity.client}/${identity.version} (${process.platform}; node/${process.versions.node.split(".")[0]})`,
|
|
7102
|
+
"X-Apex-Invocation-Id": invocationId,
|
|
7103
|
+
...identity.command ? { "X-Apex-Client-Command": identity.command } : {}
|
|
7104
|
+
};
|
|
7095
7105
|
}
|
|
7096
7106
|
|
|
7097
|
-
// src/
|
|
7098
|
-
|
|
7107
|
+
// ../api-client/src/http.ts
|
|
7108
|
+
var APEX_API_VERSION_HEADER = "x-apex-api-version";
|
|
7109
|
+
var SUPPORTED_APEX_API_VERSION = "1";
|
|
7110
|
+
function extractErrorMessage(json, status3) {
|
|
7111
|
+
if (!json) return `HTTP ${status3}`;
|
|
7112
|
+
const error = json.error;
|
|
7113
|
+
if (typeof error === "string" && error.trim()) return error;
|
|
7114
|
+
if (error && typeof error === "object") {
|
|
7115
|
+
const message = error.message;
|
|
7116
|
+
if (typeof message === "string" && message.trim()) return message;
|
|
7117
|
+
}
|
|
7118
|
+
return typeof json.message === "string" && json.message.trim() ? json.message : `HTTP ${status3}`;
|
|
7119
|
+
}
|
|
7120
|
+
function extractErrorDetails(json) {
|
|
7121
|
+
if (!json) return void 0;
|
|
7122
|
+
const nested = json.error && typeof json.error === "object" && !Array.isArray(json.error) ? json.error : null;
|
|
7123
|
+
const code = typeof json.code === "string" ? json.code : typeof nested?.code === "string" ? nested.code : void 0;
|
|
7124
|
+
for (const value of [json.details, json.meta, nested?.details]) if (value && typeof value === "object" && !Array.isArray(value)) return { ...value, ...code ? { code } : {} };
|
|
7125
|
+
const details = Object.fromEntries(["limit", "current", "max", "actual", "field", "code"].filter((key) => key in json).map((key) => [key, json[key]]));
|
|
7126
|
+
if (code) details.code = code;
|
|
7127
|
+
return Object.keys(details).length ? details : void 0;
|
|
7128
|
+
}
|
|
7129
|
+
var warnedAboutVersionSkew = false;
|
|
7130
|
+
function createHttpClient(creds, options) {
|
|
7131
|
+
const warn3 = options.warn ?? ((message) => process.stderr.write(message));
|
|
7132
|
+
function inspectVersion(headers) {
|
|
7133
|
+
const value = headers.get(APEX_API_VERSION_HEADER);
|
|
7134
|
+
if (!value || warnedAboutVersionSkew) return;
|
|
7135
|
+
const version = Number(value);
|
|
7136
|
+
if (!Number.isFinite(version) || version === Number(SUPPORTED_APEX_API_VERSION)) return;
|
|
7137
|
+
warnedAboutVersionSkew = true;
|
|
7138
|
+
warn3(version > 1 ? "CLI is behind the server \u2014 update.\n" : "Server API is older than this CLI supports.\n");
|
|
7139
|
+
}
|
|
7140
|
+
async function request2(method, path25, body5, requestTimeoutMs, form = false) {
|
|
7141
|
+
const started2 = performance.now();
|
|
7142
|
+
const observe = (status3, requestId2, errorCode) => {
|
|
7143
|
+
try {
|
|
7144
|
+
options.onResponse?.({ method, path: path25, status: status3, requestId: requestId2, errorCode: typeof errorCode === "string" ? errorCode : void 0, durationMs: Math.max(0, Math.round(performance.now() - started2)) });
|
|
7145
|
+
} catch {
|
|
7146
|
+
}
|
|
7147
|
+
};
|
|
7148
|
+
const fallback2 = form ? 12e4 : 3e4;
|
|
7149
|
+
const configured = Number(process.env.APEX_TIMEOUT_MS ?? fallback2);
|
|
7150
|
+
const defaultMs = Number.isFinite(configured) && configured > 0 ? configured : fallback2;
|
|
7151
|
+
const timeoutMs = requestTimeoutMs === void 0 ? defaultMs : Math.max(1, Math.min(defaultMs, requestTimeoutMs));
|
|
7152
|
+
const headers = { ...clientIdentityHeaders(), Authorization: `Bearer ${creds.apiKey}` };
|
|
7153
|
+
if (!form) headers["Content-Type"] = "application/json";
|
|
7154
|
+
if (creds.credentialKind === "user" || creds.apiKey.startsWith("apx_uk_")) {
|
|
7155
|
+
if (creds.resolvedContext?.orgId) headers["X-Apex-Org"] = creds.resolvedContext.orgId;
|
|
7156
|
+
if (creds.resolvedContext?.shopId) headers["X-Apex-Shop"] = creds.resolvedContext.shopId;
|
|
7157
|
+
}
|
|
7158
|
+
const mock = options.transport?.(method, path25, body5, headers);
|
|
7159
|
+
if (mock !== void 0) {
|
|
7160
|
+
const version = options.testTransportVersion?.();
|
|
7161
|
+
if (version) inspectVersion(new Headers({ [APEX_API_VERSION_HEADER]: version }));
|
|
7162
|
+
observe(mock.status, mock.requestId, mock.errorDetails?.code);
|
|
7163
|
+
return mock;
|
|
7164
|
+
}
|
|
7165
|
+
let response;
|
|
7166
|
+
try {
|
|
7167
|
+
response = await fetch(`${creds.baseUrl}${path25}`, { method, headers, body: form ? body5 : body5 ? JSON.stringify(body5) : void 0, signal: AbortSignal.timeout(timeoutMs) });
|
|
7168
|
+
} catch (error) {
|
|
7169
|
+
const outer = error && typeof error === "object" ? error : void 0;
|
|
7170
|
+
const nested = outer?.cause && typeof outer.cause === "object" ? outer.cause : void 0;
|
|
7171
|
+
return options.onNetworkError({
|
|
7172
|
+
baseUrl: creds.baseUrl,
|
|
7173
|
+
timeoutMs,
|
|
7174
|
+
cause: error instanceof Error ? error.message : String(error),
|
|
7175
|
+
code: typeof nested?.code === "string" ? nested.code : typeof outer?.code === "string" ? outer.code : void 0,
|
|
7176
|
+
name: typeof outer?.name === "string" ? outer.name : void 0,
|
|
7177
|
+
causeMessage: typeof nested?.message === "string" ? nested.message : void 0
|
|
7178
|
+
});
|
|
7179
|
+
}
|
|
7180
|
+
inspectVersion(response.headers);
|
|
7181
|
+
const json = await response.json().catch(() => null);
|
|
7182
|
+
const requestId = response.headers.get("x-request-id") ?? response.headers.get("x-vercel-id") ?? response.headers.get("cf-ray");
|
|
7183
|
+
observe(response.status, requestId, extractErrorDetails(json)?.code);
|
|
7184
|
+
if (!response.ok) return { ok: false, status: response.status, data: json, error: extractErrorMessage(json, response.status), errorDetails: extractErrorDetails(json), requestId, retryAfter: response.headers.get("retry-after"), rateLimit: { limit: response.headers.get("x-ratelimit-limit"), remaining: response.headers.get("x-ratelimit-remaining"), reset: response.headers.get("x-ratelimit-reset") } };
|
|
7185
|
+
return { ok: true, status: response.status, data: json };
|
|
7186
|
+
}
|
|
7187
|
+
return { request: (method, path25, body5, timeoutMs) => request2(method, path25, body5, timeoutMs), requestForm: (method, path25, body5) => request2(method, path25, body5, void 0, true) };
|
|
7188
|
+
}
|
|
7099
7189
|
|
|
7100
|
-
// src/
|
|
7101
|
-
|
|
7102
|
-
var
|
|
7103
|
-
var
|
|
7104
|
-
|
|
7105
|
-
|
|
7190
|
+
// ../api-client/src/credentials.ts
|
|
7191
|
+
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
7192
|
+
var import_node_os = __toESM(require("node:os"), 1);
|
|
7193
|
+
var import_node_path = __toESM(require("node:path"), 1);
|
|
7194
|
+
function readStoredCredentials(file = import_node_path.default.join(import_node_os.default.homedir(), ".drip", "credentials.json")) {
|
|
7195
|
+
try {
|
|
7196
|
+
return JSON.parse(import_node_fs.default.readFileSync(file, "utf8"));
|
|
7197
|
+
} catch {
|
|
7198
|
+
return null;
|
|
7199
|
+
}
|
|
7200
|
+
}
|
|
7201
|
+
function resolveCredentials(env2 = process.env, stored = readStoredCredentials()) {
|
|
7202
|
+
const context = {
|
|
7203
|
+
orgId: env2.DRIP_ORG || stored?.context?.orgId,
|
|
7204
|
+
shopId: env2.DRIP_SHOP || stored?.context?.shopId
|
|
7205
|
+
};
|
|
7206
|
+
const environmentApiKey = env2.APEX_API_KEY || env2.DRIP_API_KEY;
|
|
7207
|
+
const environmentBaseUrl = env2.APEX_BASE_URL || env2.DRIP_BASE_URL;
|
|
7208
|
+
if (environmentApiKey) {
|
|
7209
|
+
const user = environmentApiKey.startsWith("apx_uk_");
|
|
7210
|
+
return {
|
|
7211
|
+
...stored ?? { apiKey: "", baseUrl: "https://app.drip-apex.com" },
|
|
7212
|
+
apiKey: environmentApiKey,
|
|
7213
|
+
baseUrl: environmentBaseUrl ?? "https://app.drip-apex.com",
|
|
7214
|
+
credentialKind: user ? "user" : "shop",
|
|
7215
|
+
resolvedContext: context
|
|
7216
|
+
};
|
|
7217
|
+
}
|
|
7218
|
+
if (stored?.userApiKey) return {
|
|
7219
|
+
...stored,
|
|
7220
|
+
apiKey: stored.userApiKey,
|
|
7221
|
+
baseUrl: stored.userBaseUrl ?? stored.baseUrl,
|
|
7222
|
+
credentialKind: "user",
|
|
7223
|
+
resolvedContext: context
|
|
7224
|
+
};
|
|
7225
|
+
if (stored?.apiKey) return { ...stored, credentialKind: "shop", resolvedContext: context };
|
|
7226
|
+
return null;
|
|
7227
|
+
}
|
|
7106
7228
|
|
|
7107
7229
|
// src/config.ts
|
|
7108
|
-
var
|
|
7230
|
+
var import_node_fs3 = __toESM(require("node:fs"));
|
|
7231
|
+
var import_node_path3 = __toESM(require("node:path"));
|
|
7232
|
+
var import_node_os4 = __toESM(require("node:os"));
|
|
7233
|
+
|
|
7234
|
+
// src/update.ts
|
|
7235
|
+
var import_node_fs2 = require("node:fs");
|
|
7236
|
+
var import_promises = require("node:fs/promises");
|
|
7237
|
+
var import_node_os2 = require("node:os");
|
|
7109
7238
|
var import_node_path2 = __toESM(require("node:path"));
|
|
7110
|
-
var
|
|
7239
|
+
var TTL = 24 * 60 * 60 * 1e3;
|
|
7240
|
+
var FAILURE_TTL = 60 * 60 * 1e3;
|
|
7241
|
+
function versionParts(version) {
|
|
7242
|
+
return /^\d+\.\d+\.\d+$/.test(version) ? version.split(".").map(Number) : void 0;
|
|
7243
|
+
}
|
|
7244
|
+
async function checkForUpdate(current) {
|
|
7245
|
+
if (process.env.APEX_NO_UPDATE_CHECK === "1") return;
|
|
7246
|
+
if (process.env[["APEX", "CLI", "MOCK", "API"].join("_")] === "1" || process.env.CI) return;
|
|
7247
|
+
const installed = versionParts(current);
|
|
7248
|
+
if (!installed) return;
|
|
7249
|
+
const controller = new AbortController();
|
|
7250
|
+
let timer;
|
|
7251
|
+
const cachePath = import_node_path2.default.join((0, import_node_os2.homedir)(), ".drip", "update-check.json");
|
|
7252
|
+
const lookup2 = async () => {
|
|
7253
|
+
let latest;
|
|
7254
|
+
const remember = async (value) => {
|
|
7255
|
+
try {
|
|
7256
|
+
await (0, import_promises.mkdir)(import_node_path2.default.dirname(cachePath), { recursive: true });
|
|
7257
|
+
await (0, import_promises.writeFile)(cachePath, JSON.stringify({ latest: value, checked_at: Date.now() }), {
|
|
7258
|
+
mode: 384
|
|
7259
|
+
});
|
|
7260
|
+
} catch {
|
|
7261
|
+
}
|
|
7262
|
+
};
|
|
7263
|
+
try {
|
|
7264
|
+
const cache = JSON.parse(await (0, import_promises.readFile)(cachePath, "utf8"));
|
|
7265
|
+
const age = Date.now() - cache.checked_at;
|
|
7266
|
+
if (cache.latest === null && typeof cache.checked_at === "number" && age >= 0 && age < FAILURE_TTL)
|
|
7267
|
+
return;
|
|
7268
|
+
if (typeof cache.checked_at === "number" && age >= 0 && age < TTL && typeof cache.latest === "string" && versionParts(cache.latest))
|
|
7269
|
+
latest = cache.latest;
|
|
7270
|
+
} catch {
|
|
7271
|
+
}
|
|
7272
|
+
if (!latest && !controller.signal.aborted) {
|
|
7273
|
+
let body5;
|
|
7274
|
+
try {
|
|
7275
|
+
const response = await fetch("https://registry.npmjs.org/@drip-apex%2Fapex-cli/latest", {
|
|
7276
|
+
headers: { Accept: "application/json" },
|
|
7277
|
+
signal: controller.signal
|
|
7278
|
+
});
|
|
7279
|
+
body5 = response.ok ? await response.json() : void 0;
|
|
7280
|
+
} catch {
|
|
7281
|
+
body5 = void 0;
|
|
7282
|
+
}
|
|
7283
|
+
if (typeof body5?.version !== "string" || !versionParts(body5.version)) {
|
|
7284
|
+
await remember(null);
|
|
7285
|
+
return;
|
|
7286
|
+
}
|
|
7287
|
+
latest = body5.version;
|
|
7288
|
+
await remember(latest);
|
|
7289
|
+
}
|
|
7290
|
+
if (!latest) return;
|
|
7291
|
+
const available = versionParts(latest);
|
|
7292
|
+
const difference = available.findIndex((part, index) => part !== installed[index]);
|
|
7293
|
+
if (difference < 0 || available[difference] < installed[difference]) return;
|
|
7294
|
+
return {
|
|
7295
|
+
current,
|
|
7296
|
+
latest,
|
|
7297
|
+
breaking: available[0] !== installed[0] || installed[0] === 0 && available[1] !== installed[1],
|
|
7298
|
+
notes_url: "https://docs.drip-apex.com/docs/cli/commands-output"
|
|
7299
|
+
};
|
|
7300
|
+
};
|
|
7301
|
+
try {
|
|
7302
|
+
return await Promise.race([
|
|
7303
|
+
lookup2(),
|
|
7304
|
+
new Promise((resolve2) => {
|
|
7305
|
+
timer = setTimeout(() => {
|
|
7306
|
+
controller.abort();
|
|
7307
|
+
try {
|
|
7308
|
+
(0, import_node_fs2.mkdirSync)(import_node_path2.default.dirname(cachePath), { recursive: true });
|
|
7309
|
+
(0, import_node_fs2.writeFileSync)(cachePath, JSON.stringify({ latest: null, checked_at: Date.now() }), {
|
|
7310
|
+
mode: 384
|
|
7311
|
+
});
|
|
7312
|
+
} catch {
|
|
7313
|
+
}
|
|
7314
|
+
resolve2(void 0);
|
|
7315
|
+
}, 900);
|
|
7316
|
+
})
|
|
7317
|
+
]);
|
|
7318
|
+
} catch {
|
|
7319
|
+
return;
|
|
7320
|
+
} finally {
|
|
7321
|
+
if (timer) clearTimeout(timer);
|
|
7322
|
+
}
|
|
7323
|
+
}
|
|
7111
7324
|
|
|
7112
7325
|
// package.json
|
|
7113
7326
|
var package_default = {
|
|
7114
7327
|
name: "@drip-apex/apex-cli",
|
|
7115
|
-
version: "0.
|
|
7328
|
+
version: "0.14.1",
|
|
7116
7329
|
license: "MIT",
|
|
7117
7330
|
description: "Apex CLI for managing experiments, approved roadmap ideas, and draft workspaces.",
|
|
7118
7331
|
private: false,
|
|
@@ -7180,18 +7393,20 @@ function recordApiResponse(response) {
|
|
|
7180
7393
|
status: response.status,
|
|
7181
7394
|
body: response.data ?? null,
|
|
7182
7395
|
message: response.error,
|
|
7183
|
-
details: response.errorDetails
|
|
7396
|
+
details: response.errorDetails,
|
|
7397
|
+
requestId: response.requestId,
|
|
7398
|
+
retryAfter: response.retryAfter
|
|
7184
7399
|
});
|
|
7185
7400
|
if (recentApiFailures.length > MAX_RECORDED_API_FAILURES) recentApiFailures.shift();
|
|
7186
7401
|
}
|
|
7187
7402
|
function matchApiFailure(message, details) {
|
|
7188
7403
|
const byDetails = details !== void 0 ? recentApiFailures.find((entry) => entry.details === details) : void 0;
|
|
7189
|
-
if (byDetails) return
|
|
7404
|
+
if (byDetails) return byDetails;
|
|
7190
7405
|
const byMessage = recentApiFailures.filter(
|
|
7191
7406
|
(entry) => Boolean(entry.message) && message.includes(entry.message)
|
|
7192
7407
|
);
|
|
7193
7408
|
if (byMessage.length !== 1) return void 0;
|
|
7194
|
-
return
|
|
7409
|
+
return byMessage[0];
|
|
7195
7410
|
}
|
|
7196
7411
|
var CliError = class extends Error {
|
|
7197
7412
|
exitCode;
|
|
@@ -7206,7 +7421,12 @@ var CliError = class extends Error {
|
|
|
7206
7421
|
}
|
|
7207
7422
|
};
|
|
7208
7423
|
function apiFailureOf(response) {
|
|
7209
|
-
return response.ok ? void 0 : {
|
|
7424
|
+
return response.ok ? void 0 : {
|
|
7425
|
+
status: response.status,
|
|
7426
|
+
body: response.data ?? null,
|
|
7427
|
+
requestId: response.requestId,
|
|
7428
|
+
retryAfter: response.retryAfter
|
|
7429
|
+
};
|
|
7210
7430
|
}
|
|
7211
7431
|
var initialEnvironmentApiKey = process.env.APEX_API_KEY || process.env.DRIP_API_KEY;
|
|
7212
7432
|
var envelopeContext = {
|
|
@@ -7253,7 +7473,7 @@ function installJsonStdoutGuard() {
|
|
|
7253
7473
|
if (rawStdoutWrite) return;
|
|
7254
7474
|
const stdout = process.stdout;
|
|
7255
7475
|
const original = stdout.write.bind(stdout);
|
|
7256
|
-
rawStdoutWrite = (
|
|
7476
|
+
rawStdoutWrite = (text2) => original(text2);
|
|
7257
7477
|
stdout.write = ((chunk, ...args) => process.stderr.write(
|
|
7258
7478
|
chunk,
|
|
7259
7479
|
...args
|
|
@@ -7263,6 +7483,7 @@ function isJsonMode() {
|
|
|
7263
7483
|
return jsonMode;
|
|
7264
7484
|
}
|
|
7265
7485
|
function configureRuntime(command) {
|
|
7486
|
+
setClientIdentity({ client: "apex-cli", version: CLI_VERSION, command: commandPath(command) });
|
|
7266
7487
|
commandName = commandPath(command) || commandName;
|
|
7267
7488
|
jsonMode = shouldUseJson(command);
|
|
7268
7489
|
if (!jsonMode) return;
|
|
@@ -7274,10 +7495,10 @@ function configureRuntime(command) {
|
|
|
7274
7495
|
};
|
|
7275
7496
|
}
|
|
7276
7497
|
function writeJsonDocument(envelope) {
|
|
7277
|
-
const
|
|
7498
|
+
const text2 = `${JSON.stringify({ ...envelope, ...updateNotice ? { update: updateNotice } : {} }, null, 2)}
|
|
7278
7499
|
`;
|
|
7279
7500
|
if (wroteEnvelope) {
|
|
7280
|
-
process.stderr.write(
|
|
7501
|
+
process.stderr.write(text2);
|
|
7281
7502
|
return;
|
|
7282
7503
|
}
|
|
7283
7504
|
wroteEnvelope = true;
|
|
@@ -7286,8 +7507,8 @@ function writeJsonDocument(envelope) {
|
|
|
7286
7507
|
restoreConsoleLog();
|
|
7287
7508
|
if (pendingHumanLines.length > 0) process.stderr.write(`${pendingHumanLines.join("\n")}
|
|
7288
7509
|
`);
|
|
7289
|
-
if (rawStdoutWrite) rawStdoutWrite(
|
|
7290
|
-
else process.stdout.write(
|
|
7510
|
+
if (rawStdoutWrite) rawStdoutWrite(text2);
|
|
7511
|
+
else process.stdout.write(text2);
|
|
7291
7512
|
}
|
|
7292
7513
|
function flushFallbackEnvelope() {
|
|
7293
7514
|
if (!jsonMode || wroteEnvelope) return;
|
|
@@ -7315,6 +7536,11 @@ function writeEnvelope(data, command = commandName) {
|
|
|
7315
7536
|
};
|
|
7316
7537
|
writeJsonDocument(envelope);
|
|
7317
7538
|
}
|
|
7539
|
+
function writeRawCommandOutput(text2) {
|
|
7540
|
+
wroteEnvelope = true;
|
|
7541
|
+
if (rawStdoutWrite) return rawStdoutWrite(text2);
|
|
7542
|
+
return process.stdout.write(text2);
|
|
7543
|
+
}
|
|
7318
7544
|
function writeErrorEnvelope(error, command = commandName) {
|
|
7319
7545
|
const envelope = {
|
|
7320
7546
|
ok: false,
|
|
@@ -7323,8 +7549,11 @@ function writeErrorEnvelope(error, command = commandName) {
|
|
|
7323
7549
|
error: {
|
|
7324
7550
|
message: error.message,
|
|
7325
7551
|
code: error.exitCode,
|
|
7552
|
+
reason: errorReason(error),
|
|
7553
|
+
next_steps: errorNextSteps(error),
|
|
7554
|
+
...error.api?.requestId ? { request_id: error.api.requestId } : {},
|
|
7326
7555
|
...error.details ? { details: error.details } : {},
|
|
7327
|
-
...error.api ? { api: error.api } : {}
|
|
7556
|
+
...error.api ? { api: { status: error.api.status, body: error.api.body } } : {}
|
|
7328
7557
|
},
|
|
7329
7558
|
context: envelopeContext
|
|
7330
7559
|
};
|
|
@@ -7350,18 +7579,18 @@ function commandPath(command) {
|
|
|
7350
7579
|
function fail(command, message, exitCode = EXIT_CODES.general, details, api) {
|
|
7351
7580
|
throw new CliError(message, exitCode, details, api);
|
|
7352
7581
|
}
|
|
7353
|
-
function classifyStatus(
|
|
7354
|
-
if (
|
|
7355
|
-
if (
|
|
7356
|
-
if (
|
|
7582
|
+
function classifyStatus(status3) {
|
|
7583
|
+
if (status3 === 401 || status3 === 403) return EXIT_CODES.auth;
|
|
7584
|
+
if (status3 === 409) return EXIT_CODES.conflict;
|
|
7585
|
+
if (status3 === 400 || status3 === 413 || status3 === 422) return EXIT_CODES.validation;
|
|
7357
7586
|
return EXIT_CODES.general;
|
|
7358
7587
|
}
|
|
7359
|
-
function classifyApiError(
|
|
7588
|
+
function classifyApiError(status3, details) {
|
|
7360
7589
|
const code = details?.code;
|
|
7361
7590
|
if (code === "shop_context_required" || code === "org_context_required")
|
|
7362
7591
|
return EXIT_CODES.validation;
|
|
7363
7592
|
if (code === "shop_access_denied" || code === "org_access_denied") return EXIT_CODES.auth;
|
|
7364
|
-
return classifyStatus(
|
|
7593
|
+
return classifyStatus(status3);
|
|
7365
7594
|
}
|
|
7366
7595
|
function confirmationRequired(command, message, retry) {
|
|
7367
7596
|
return fail(command, message, EXIT_CODES.confirmationRequired, { retry });
|
|
@@ -7408,33 +7637,25 @@ async function createSpinner(message) {
|
|
|
7408
7637
|
}
|
|
7409
7638
|
return spinner.start();
|
|
7410
7639
|
}
|
|
7640
|
+
var updateNotice;
|
|
7411
7641
|
async function maybeCheckForUpdate() {
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
|
|
7415
|
-
const res = await fetch("https://registry.npmjs.org/@drip-apex%2Fapex-cli/latest", {
|
|
7416
|
-
headers: { Accept: "application/json" },
|
|
7417
|
-
signal: AbortSignal.timeout(900)
|
|
7418
|
-
});
|
|
7419
|
-
if (!res.ok) return;
|
|
7420
|
-
const latest = await res.json();
|
|
7421
|
-
if (latest.version && latest.version !== CLI_VERSION) {
|
|
7422
|
-
process.stderr.write(`Apex CLI ${latest.version} is available; current ${CLI_VERSION}.
|
|
7642
|
+
updateNotice = await checkForUpdate(CLI_VERSION);
|
|
7643
|
+
if (updateNotice)
|
|
7644
|
+
process.stderr.write(`Apex CLI ${updateNotice.latest} is available; current ${CLI_VERSION}.
|
|
7423
7645
|
`);
|
|
7424
|
-
}
|
|
7425
|
-
} catch {
|
|
7426
|
-
}
|
|
7427
7646
|
}
|
|
7428
7647
|
async function handleCliError(err, command) {
|
|
7429
7648
|
if (err && typeof err === "object" && "exitCode" in err && err.exitCode === 0) {
|
|
7430
7649
|
flushFallbackEnvelope();
|
|
7650
|
+
await finishInvocation({ exitCode: 0, error: err });
|
|
7431
7651
|
drainAndExit(0);
|
|
7432
7652
|
return;
|
|
7433
7653
|
}
|
|
7434
7654
|
const commanderExitCode = err && typeof err === "object" && "exitCode" in err && typeof err.exitCode === "number" ? err.exitCode : null;
|
|
7435
7655
|
const cliError = err instanceof CliError ? err : new CliError(
|
|
7436
7656
|
err instanceof Error ? err.message : String(err),
|
|
7437
|
-
commanderExitCode === EXIT_CODES.general ? EXIT_CODES.validation : commanderExitCode ?? EXIT_CODES.general
|
|
7657
|
+
commanderExitCode === EXIT_CODES.general ? EXIT_CODES.validation : commanderExitCode ?? EXIT_CODES.general,
|
|
7658
|
+
err && typeof err === "object" && "code" in err && ["ENOENT", "EACCES", "EPERM", "EISDIR", "ENOTDIR", "ENOSPC"].includes(String(err.code)) ? { code: "local_file_error" } : void 0
|
|
7438
7659
|
);
|
|
7439
7660
|
const interceptedCommand = err && typeof err === "object" ? interceptedCommands.get(err) : void 0;
|
|
7440
7661
|
if (shouldUseJson(command)) {
|
|
@@ -7443,26 +7664,94 @@ async function handleCliError(err, command) {
|
|
|
7443
7664
|
interceptedCommand ? commandPath(interceptedCommand) : commandName
|
|
7444
7665
|
);
|
|
7445
7666
|
} else {
|
|
7446
|
-
process.stderr.write(
|
|
7667
|
+
process.stderr.write(
|
|
7668
|
+
`Error: ${cliError.message}${cliError.api?.requestId ? ` (request ${cliError.api.requestId})` : ""}
|
|
7669
|
+
`
|
|
7670
|
+
);
|
|
7671
|
+
for (const step of errorNextSteps(cliError))
|
|
7672
|
+
process.stderr.write(`Next: ${step.command} - ${step.why}
|
|
7447
7673
|
`);
|
|
7448
7674
|
if (cliError.details?.retry) {
|
|
7449
7675
|
process.stderr.write(`Retry: ${String(cliError.details.retry)}
|
|
7450
7676
|
`);
|
|
7451
7677
|
}
|
|
7452
7678
|
}
|
|
7679
|
+
await finishInvocation({ exitCode: cliError.exitCode, error: err });
|
|
7453
7680
|
drainAndExit(cliError.exitCode);
|
|
7454
7681
|
}
|
|
7455
|
-
|
|
7456
|
-
|
|
7457
|
-
|
|
7458
|
-
|
|
7459
|
-
|
|
7460
|
-
|
|
7461
|
-
|
|
7462
|
-
|
|
7463
|
-
|
|
7464
|
-
|
|
7682
|
+
function errorReason(error) {
|
|
7683
|
+
const details = error.details;
|
|
7684
|
+
if (!error.api && typeof details?.baseUrl === "string" && typeof details.reason === "string")
|
|
7685
|
+
return `network_${details.reason}`;
|
|
7686
|
+
const body5 = error.api?.body;
|
|
7687
|
+
const code = body5?.code ?? body5?.error?.code ?? (error.api ? details?.code : void 0);
|
|
7688
|
+
if (typeof code === "string" && code) return code;
|
|
7689
|
+
if (["shop_context_required", "local_file_error", "validation_failed"].includes(
|
|
7690
|
+
String(details?.code)
|
|
7691
|
+
))
|
|
7692
|
+
return String(details?.code);
|
|
7693
|
+
const status3 = error.api?.status;
|
|
7694
|
+
if (status3 === 401) return "auth_required";
|
|
7695
|
+
if (status3 === 403) return "auth_forbidden";
|
|
7696
|
+
if (status3 === 404) return "not_found";
|
|
7697
|
+
if (status3 === 429) return "rate_limited";
|
|
7698
|
+
if (status3 === 409 || error.exitCode === EXIT_CODES.conflict) return "conflict";
|
|
7699
|
+
if (error.exitCode === EXIT_CODES.auth) return "auth_required";
|
|
7700
|
+
if (error.exitCode === EXIT_CODES.confirmationRequired) return "confirmation_required";
|
|
7701
|
+
if (error.exitCode === EXIT_CODES.validation) return "validation_failed";
|
|
7702
|
+
return "internal_error";
|
|
7703
|
+
}
|
|
7704
|
+
function errorNextSteps(error) {
|
|
7705
|
+
const reason2 = errorReason(error);
|
|
7706
|
+
const steps = [];
|
|
7707
|
+
if (reason2 === "auth_required" || error.api?.status === 401) {
|
|
7708
|
+
steps.push({ command: "apex login", why: "Authenticate with a valid API key." });
|
|
7709
|
+
if (!error.api)
|
|
7710
|
+
steps.push({
|
|
7711
|
+
command: "set APEX_API_KEY",
|
|
7712
|
+
why: "Provide credentials for a headless invocation."
|
|
7713
|
+
});
|
|
7465
7714
|
}
|
|
7715
|
+
if (reason2 === "auth_forbidden" || error.api?.status === 403)
|
|
7716
|
+
steps.push({ command: "apex whoami", why: "Check the API key scope and permissions." });
|
|
7717
|
+
if (reason2 === "shop_context_required")
|
|
7718
|
+
steps.push({
|
|
7719
|
+
command: typeof error.details?.retry === "string" ? error.details.retry : "apex use <org>/<shop>",
|
|
7720
|
+
why: "Select the shop for this command, or pass --shop <id>."
|
|
7721
|
+
});
|
|
7722
|
+
if (reason2.startsWith("network_")) {
|
|
7723
|
+
steps.push(
|
|
7724
|
+
{ command: "check APEX_BASE_URL", why: "Verify the API host and network access." },
|
|
7725
|
+
{ command: "retry", why: "Retry the command after connectivity is restored." }
|
|
7726
|
+
);
|
|
7727
|
+
if (reason2.includes("timeout"))
|
|
7728
|
+
steps.push({
|
|
7729
|
+
command: "set APEX_TIMEOUT_MS",
|
|
7730
|
+
why: "Increase the request timeout if needed."
|
|
7731
|
+
});
|
|
7732
|
+
}
|
|
7733
|
+
if (reason2 === "rate_limited" || error.api?.status === 429)
|
|
7734
|
+
steps.push({
|
|
7735
|
+
command: "wait",
|
|
7736
|
+
why: error.api?.retryAfter ? `Wait for Retry-After: ${error.api.retryAfter} before retrying.` : "Wait before retrying the request."
|
|
7737
|
+
});
|
|
7738
|
+
if (typeof error.details?.retry === "string" && reason2 !== "shop_context_required")
|
|
7739
|
+
steps.push({
|
|
7740
|
+
command: error.details.retry,
|
|
7741
|
+
why: "Review the requested action before retrying."
|
|
7742
|
+
});
|
|
7743
|
+
const hints = error.details?.next_steps;
|
|
7744
|
+
if (Array.isArray(hints))
|
|
7745
|
+
for (const hint of hints) {
|
|
7746
|
+
if (hint && typeof hint.command === "string" && typeof hint.why === "string")
|
|
7747
|
+
steps.push({ command: hint.command, why: hint.why });
|
|
7748
|
+
}
|
|
7749
|
+
if (reason2 === "validation_failed" && error.details?.plan)
|
|
7750
|
+
steps.push({
|
|
7751
|
+
command: "apex experiments plan",
|
|
7752
|
+
why: "Correct the reported blocking issues, then validate the folder again."
|
|
7753
|
+
});
|
|
7754
|
+
return steps;
|
|
7466
7755
|
}
|
|
7467
7756
|
|
|
7468
7757
|
// src/config.ts
|
|
@@ -7534,8 +7823,8 @@ function configureInvocationContext() {
|
|
|
7534
7823
|
shop_id: context.shopId ?? null
|
|
7535
7824
|
});
|
|
7536
7825
|
}
|
|
7537
|
-
var DRIP_DIR =
|
|
7538
|
-
var CREDS_FILE =
|
|
7826
|
+
var DRIP_DIR = import_node_path3.default.join(import_node_os4.default.homedir(), ".drip");
|
|
7827
|
+
var CREDS_FILE = import_node_path3.default.join(DRIP_DIR, "credentials.json");
|
|
7539
7828
|
function getStoredCredentials() {
|
|
7540
7829
|
return readStoredCredentials(CREDS_FILE);
|
|
7541
7830
|
}
|
|
@@ -7561,14 +7850,14 @@ function getCredentials() {
|
|
|
7561
7850
|
}
|
|
7562
7851
|
function saveCredentials(creds) {
|
|
7563
7852
|
const { credentialKind: _kind, resolvedContext: _resolved, ...persisted } = creds;
|
|
7564
|
-
|
|
7565
|
-
|
|
7853
|
+
import_node_fs3.default.mkdirSync(DRIP_DIR, { recursive: true });
|
|
7854
|
+
import_node_fs3.default.writeFileSync(CREDS_FILE, JSON.stringify(persisted, null, 2) + "\n", {
|
|
7566
7855
|
mode: 384
|
|
7567
7856
|
});
|
|
7568
7857
|
}
|
|
7569
7858
|
function clearCredentials() {
|
|
7570
7859
|
try {
|
|
7571
|
-
|
|
7860
|
+
import_node_fs3.default.unlinkSync(CREDS_FILE);
|
|
7572
7861
|
} catch {
|
|
7573
7862
|
}
|
|
7574
7863
|
}
|
|
@@ -7659,89 +7948,264 @@ var CONFIG_NAMES = [".drip.config.ts", ".drip.config.js", "drip.config.ts", "dri
|
|
|
7659
7948
|
function findConfigPath() {
|
|
7660
7949
|
const cwd = process.cwd();
|
|
7661
7950
|
for (const name of CONFIG_NAMES) {
|
|
7662
|
-
const p =
|
|
7663
|
-
if (
|
|
7951
|
+
const p = import_node_path3.default.join(cwd, name);
|
|
7952
|
+
if (import_node_fs3.default.existsSync(p)) return p;
|
|
7664
7953
|
}
|
|
7665
7954
|
return null;
|
|
7666
7955
|
}
|
|
7667
7956
|
|
|
7668
|
-
// src/
|
|
7669
|
-
var
|
|
7670
|
-
|
|
7671
|
-
|
|
7672
|
-
var APEX_API_VERSION_HEADER = "x-apex-api-version";
|
|
7673
|
-
var SUPPORTED_APEX_API_VERSION = "1";
|
|
7674
|
-
function extractErrorMessage(json, status2) {
|
|
7675
|
-
if (!json) return `HTTP ${status2}`;
|
|
7676
|
-
const error = json.error;
|
|
7677
|
-
if (typeof error === "string" && error.trim()) return error;
|
|
7678
|
-
if (error && typeof error === "object") {
|
|
7679
|
-
const message = error.message;
|
|
7680
|
-
if (typeof message === "string" && message.trim()) return message;
|
|
7681
|
-
}
|
|
7682
|
-
return typeof json.message === "string" && json.message.trim() ? json.message : `HTTP ${status2}`;
|
|
7957
|
+
// src/test-harness.ts
|
|
7958
|
+
var readSetting;
|
|
7959
|
+
function cliTestSetting(name) {
|
|
7960
|
+
return readSetting?.(name);
|
|
7683
7961
|
}
|
|
7684
|
-
function
|
|
7685
|
-
|
|
7686
|
-
const nested = json.error && typeof json.error === "object" && !Array.isArray(json.error) ? json.error : null;
|
|
7687
|
-
const code = typeof json.code === "string" ? json.code : typeof nested?.code === "string" ? nested.code : void 0;
|
|
7688
|
-
for (const value of [json.details, json.meta, nested?.details]) if (value && typeof value === "object" && !Array.isArray(value)) return { ...value, ...code ? { code } : {} };
|
|
7689
|
-
const details = Object.fromEntries(["limit", "current", "max", "actual", "field", "code"].filter((key) => key in json).map((key) => [key, json[key]]));
|
|
7690
|
-
if (code) details.code = code;
|
|
7691
|
-
return Object.keys(details).length ? details : void 0;
|
|
7962
|
+
function cliTestHarnessActive() {
|
|
7963
|
+
return readSetting !== void 0;
|
|
7692
7964
|
}
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
|
|
7696
|
-
|
|
7697
|
-
|
|
7698
|
-
|
|
7699
|
-
|
|
7700
|
-
|
|
7701
|
-
|
|
7702
|
-
|
|
7703
|
-
|
|
7704
|
-
|
|
7705
|
-
|
|
7706
|
-
|
|
7707
|
-
const version = options.testTransportVersion?.();
|
|
7708
|
-
if (version) inspectVersion(new Headers({ [APEX_API_VERSION_HEADER]: version }));
|
|
7709
|
-
return mock;
|
|
7710
|
-
}
|
|
7711
|
-
const fallback2 = form ? 12e4 : 3e4;
|
|
7712
|
-
const configured = Number(process.env.APEX_TIMEOUT_MS ?? fallback2);
|
|
7713
|
-
const defaultMs = Number.isFinite(configured) && configured > 0 ? configured : fallback2;
|
|
7714
|
-
const timeoutMs = requestTimeoutMs === void 0 ? defaultMs : Math.max(1, Math.min(defaultMs, requestTimeoutMs));
|
|
7715
|
-
const headers = { Authorization: `Bearer ${creds.apiKey}` };
|
|
7716
|
-
if (!form) headers["Content-Type"] = "application/json";
|
|
7717
|
-
if (creds.credentialKind === "user" || creds.apiKey.startsWith("apx_uk_")) {
|
|
7718
|
-
if (creds.resolvedContext?.orgId) headers["X-Apex-Org"] = creds.resolvedContext.orgId;
|
|
7719
|
-
if (creds.resolvedContext?.shopId) headers["X-Apex-Shop"] = creds.resolvedContext.shopId;
|
|
7720
|
-
}
|
|
7721
|
-
let response;
|
|
7722
|
-
try {
|
|
7723
|
-
response = await fetch(`${creds.baseUrl}${path24}`, { method, headers, body: form ? body5 : body5 ? JSON.stringify(body5) : void 0, signal: AbortSignal.timeout(timeoutMs) });
|
|
7724
|
-
} catch (error) {
|
|
7725
|
-
const outer = error && typeof error === "object" ? error : void 0;
|
|
7726
|
-
const nested = outer?.cause && typeof outer.cause === "object" ? outer.cause : void 0;
|
|
7727
|
-
return options.onNetworkError({
|
|
7728
|
-
baseUrl: creds.baseUrl,
|
|
7729
|
-
timeoutMs,
|
|
7730
|
-
cause: error instanceof Error ? error.message : String(error),
|
|
7731
|
-
code: typeof nested?.code === "string" ? nested.code : typeof outer?.code === "string" ? outer.code : void 0,
|
|
7732
|
-
name: typeof outer?.name === "string" ? outer.name : void 0,
|
|
7733
|
-
causeMessage: typeof nested?.message === "string" ? nested.message : void 0
|
|
7734
|
-
});
|
|
7965
|
+
|
|
7966
|
+
// src/telemetry.ts
|
|
7967
|
+
var started = performance.now();
|
|
7968
|
+
var invocation;
|
|
7969
|
+
var firstError;
|
|
7970
|
+
var apiCalls = 0;
|
|
7971
|
+
var finished = false;
|
|
7972
|
+
function beginInvocation(command, context = {}) {
|
|
7973
|
+
const names = /* @__PURE__ */ new Set();
|
|
7974
|
+
for (let current = command; current; current = current.parent) {
|
|
7975
|
+
for (const option of current.options) {
|
|
7976
|
+
const name = option.attributeName();
|
|
7977
|
+
const source = current.getOptionValueSource(name);
|
|
7978
|
+
if (source && source !== "default") names.add(name);
|
|
7735
7979
|
}
|
|
7736
|
-
inspectVersion(response.headers);
|
|
7737
|
-
const json = await response.json().catch(() => null);
|
|
7738
|
-
if (!response.ok) return { ok: false, status: response.status, data: json, error: extractErrorMessage(json, response.status), errorDetails: extractErrorDetails(json), requestId: response.headers.get("x-request-id"), rateLimit: { limit: response.headers.get("x-ratelimit-limit"), remaining: response.headers.get("x-ratelimit-remaining"), reset: response.headers.get("x-ratelimit-reset") } };
|
|
7739
|
-
return { ok: true, status: response.status, data: json };
|
|
7740
7980
|
}
|
|
7741
|
-
|
|
7981
|
+
invocation = {
|
|
7982
|
+
command: commandPath(command).replace(/^apex\s+/, ""),
|
|
7983
|
+
option_names: [...names].sort(),
|
|
7984
|
+
json_mode: shouldUseJson(command),
|
|
7985
|
+
tty: process.stdout.isTTY === true,
|
|
7986
|
+
credential_kind: process.env.APEX_API_KEY || process.env.DRIP_API_KEY ? "env" : defaultCredentialKind(getCredentials()),
|
|
7987
|
+
org_id: context.orgId,
|
|
7988
|
+
shop_id: context.shopId
|
|
7989
|
+
};
|
|
7990
|
+
}
|
|
7991
|
+
var staticRouteSegments = /* @__PURE__ */ new Set([
|
|
7992
|
+
"account",
|
|
7993
|
+
"activate",
|
|
7994
|
+
"admin",
|
|
7995
|
+
"analytics",
|
|
7996
|
+
"api",
|
|
7997
|
+
"apply",
|
|
7998
|
+
"approve",
|
|
7999
|
+
"approved",
|
|
8000
|
+
"archive",
|
|
8001
|
+
"assets",
|
|
8002
|
+
"audit-logs",
|
|
8003
|
+
"backlog",
|
|
8004
|
+
"brand-context",
|
|
8005
|
+
"build",
|
|
8006
|
+
"bulk",
|
|
8007
|
+
"by-slug",
|
|
8008
|
+
"cli",
|
|
8009
|
+
"complete",
|
|
8010
|
+
"conflicts",
|
|
8011
|
+
"consent",
|
|
8012
|
+
"context",
|
|
8013
|
+
"count",
|
|
8014
|
+
"create",
|
|
8015
|
+
"current",
|
|
8016
|
+
"data-deletion",
|
|
8017
|
+
"data-export",
|
|
8018
|
+
"deliveries",
|
|
8019
|
+
"design",
|
|
8020
|
+
"discovery",
|
|
8021
|
+
"domains",
|
|
8022
|
+
"duplicate",
|
|
8023
|
+
"entry-exit",
|
|
8024
|
+
"environments",
|
|
8025
|
+
"evidence",
|
|
8026
|
+
"exclusion-groups",
|
|
8027
|
+
"experiments",
|
|
8028
|
+
"external-pixels",
|
|
8029
|
+
"external-tracking",
|
|
8030
|
+
"fields",
|
|
8031
|
+
"files",
|
|
8032
|
+
"filesystem",
|
|
8033
|
+
"flags",
|
|
8034
|
+
"funnels",
|
|
8035
|
+
"generate",
|
|
8036
|
+
"geo",
|
|
8037
|
+
"goals",
|
|
8038
|
+
"handoff",
|
|
8039
|
+
"harness",
|
|
8040
|
+
"idea",
|
|
8041
|
+
"ideas",
|
|
8042
|
+
"inbox",
|
|
8043
|
+
"install-domains",
|
|
8044
|
+
"invitations",
|
|
8045
|
+
"items",
|
|
8046
|
+
"keys",
|
|
8047
|
+
"landing-pages",
|
|
8048
|
+
"latency",
|
|
8049
|
+
"list",
|
|
8050
|
+
"manifest",
|
|
8051
|
+
"me",
|
|
8052
|
+
"members",
|
|
8053
|
+
"messages",
|
|
8054
|
+
"mine",
|
|
8055
|
+
"org",
|
|
8056
|
+
"organizations",
|
|
8057
|
+
"orgs",
|
|
8058
|
+
"pages",
|
|
8059
|
+
"pause",
|
|
8060
|
+
"personalizations",
|
|
8061
|
+
"plan",
|
|
8062
|
+
"preview-sessions",
|
|
8063
|
+
"profile",
|
|
8064
|
+
"publish",
|
|
8065
|
+
"qa",
|
|
8066
|
+
"quarantine",
|
|
8067
|
+
"read-state",
|
|
8068
|
+
"realtime",
|
|
8069
|
+
"recipes",
|
|
8070
|
+
"reject",
|
|
8071
|
+
"restore",
|
|
8072
|
+
"results",
|
|
8073
|
+
"revisions",
|
|
8074
|
+
"roadmap",
|
|
8075
|
+
"run",
|
|
8076
|
+
"runs",
|
|
8077
|
+
"runtime-settings",
|
|
8078
|
+
"screenshots",
|
|
8079
|
+
"search",
|
|
8080
|
+
"segments",
|
|
8081
|
+
"sessions",
|
|
8082
|
+
"settings",
|
|
8083
|
+
"shops",
|
|
8084
|
+
"signals",
|
|
8085
|
+
"simulate-trigger",
|
|
8086
|
+
"snippet",
|
|
8087
|
+
"sos",
|
|
8088
|
+
"start",
|
|
8089
|
+
"stats",
|
|
8090
|
+
"status",
|
|
8091
|
+
"suggest-goals",
|
|
8092
|
+
"summary",
|
|
8093
|
+
"targeting",
|
|
8094
|
+
"team",
|
|
8095
|
+
"telemetry",
|
|
8096
|
+
"test",
|
|
8097
|
+
"timeseries",
|
|
8098
|
+
"tracking",
|
|
8099
|
+
"traffic",
|
|
8100
|
+
"triggers",
|
|
8101
|
+
"url-inventory",
|
|
8102
|
+
"v1",
|
|
8103
|
+
"variations",
|
|
8104
|
+
"verify",
|
|
8105
|
+
"versions",
|
|
8106
|
+
"visitor-series",
|
|
8107
|
+
"webhooks",
|
|
8108
|
+
"workspace"
|
|
8109
|
+
]);
|
|
8110
|
+
function normalizeRoute(path25) {
|
|
8111
|
+
if (!path25.startsWith("/api/")) return ":route";
|
|
8112
|
+
const parts = path25.split(/[?#]/, 1)[0].split("/");
|
|
8113
|
+
const files = parts.indexOf("files");
|
|
8114
|
+
if (files !== -1 && parts.length > files + 1) parts.splice(files + 1, parts.length, ":id");
|
|
8115
|
+
return parts.map((part) => !part || staticRouteSegments.has(part) ? part : ":id").join("/");
|
|
8116
|
+
}
|
|
8117
|
+
function recordApiResponse2(response) {
|
|
8118
|
+
apiCalls++;
|
|
8119
|
+
if (response.status >= 400 && !firstError)
|
|
8120
|
+
firstError = { ...response, path: normalizeRoute(response.path) };
|
|
8121
|
+
}
|
|
8122
|
+
function classifyError(exitCode, error, status3) {
|
|
8123
|
+
const detail5 = error && typeof error === "object" ? error : {};
|
|
8124
|
+
if (exitCode === 0) return "ok";
|
|
8125
|
+
if (detail5.code === "commander.unknownCommand") return "unknown_command";
|
|
8126
|
+
if (detail5.code?.startsWith("commander.")) return "validation";
|
|
8127
|
+
if (exitCode === 4) return "confirmation_required";
|
|
8128
|
+
if (status3 === 401 || status3 === 403 || exitCode === 2) return "auth";
|
|
8129
|
+
if (status3 === 404) return "not_found";
|
|
8130
|
+
if (status3 === 429) return "rate_limited";
|
|
8131
|
+
if (status3 === 409 || exitCode === 5) return "conflict";
|
|
8132
|
+
if (status3 && status3 >= 500) return "server";
|
|
8133
|
+
if (status3 === 400 || status3 === 422 || exitCode === 3) return "validation";
|
|
8134
|
+
if (Boolean(
|
|
8135
|
+
detail5.details?.reason && ["network", "dns", "timeout", "tls", "refused", "reset", "unknown"].includes(
|
|
8136
|
+
detail5.details.reason
|
|
8137
|
+
)
|
|
8138
|
+
) || detail5.details?.code === "network_error" || detail5.name === "TimeoutError")
|
|
8139
|
+
return "network";
|
|
8140
|
+
return "internal";
|
|
8141
|
+
}
|
|
8142
|
+
async function finishInvocation({
|
|
8143
|
+
exitCode: rawExitCode,
|
|
8144
|
+
error
|
|
8145
|
+
}) {
|
|
8146
|
+
const exitCode = Number(rawExitCode);
|
|
8147
|
+
if (finished) return;
|
|
8148
|
+
finished = true;
|
|
8149
|
+
await (async () => {
|
|
8150
|
+
if (process.env.APEX_TELEMETRY === "0" || process.env.NODE_ENV === "test" || cliTestHarnessActive())
|
|
8151
|
+
return;
|
|
8152
|
+
const stored = getStoredCredentials();
|
|
8153
|
+
const creds = resolveCredentials(void 0, stored);
|
|
8154
|
+
if (!creds) return;
|
|
8155
|
+
if (invocation)
|
|
8156
|
+
creds.resolvedContext = {
|
|
8157
|
+
orgId: typeof invocation.org_id === "string" ? invocation.org_id : void 0,
|
|
8158
|
+
shopId: typeof invocation.shop_id === "string" ? invocation.shop_id : void 0
|
|
8159
|
+
};
|
|
8160
|
+
const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
|
|
8161
|
+
const commanderExit = code.startsWith("commander.");
|
|
8162
|
+
const parse3 = commanderExit && exitCode !== 0;
|
|
8163
|
+
const token = commanderExit ? process.argv.slice(2).find((token2) => !token2.startsWith("-")) : void 0;
|
|
8164
|
+
const attempted = token && (/^(?:apx_|sk_|pk_|rk_|ghp_|gho_|xox|Bearer)/.test(token) || /^[A-Za-z0-9_-]{24,}$/.test(token) || /:\/\/|[/\\@=]/.test(token) || token.length > 40) ? "(redacted)" : token;
|
|
8165
|
+
if (attempted === "login" || attempted === "logout") return;
|
|
8166
|
+
const command = parse3 ? "(parse)" : invocation?.command ?? (commanderExit && exitCode === 0 ? attempted ?? (code === "commander.version" ? "(version)" : "(help)") : void 0);
|
|
8167
|
+
if (!command || command === "login" || command === "logout") return;
|
|
8168
|
+
if (!getClientIdentity()) setClientIdentity({ client: "apex-cli", version: CLI_VERSION });
|
|
8169
|
+
const payload = {
|
|
8170
|
+
...invocation,
|
|
8171
|
+
command,
|
|
8172
|
+
...parse3 ? { attempted } : {},
|
|
8173
|
+
invocation_id: getClientIdentity().invocationId,
|
|
8174
|
+
client_version: CLI_VERSION,
|
|
8175
|
+
node_version: process.versions.node,
|
|
8176
|
+
platform: process.platform,
|
|
8177
|
+
exit_code: exitCode,
|
|
8178
|
+
error_class: classifyError(exitCode, error, firstError?.status),
|
|
8179
|
+
api_calls: apiCalls,
|
|
8180
|
+
first_error_status: firstError?.status,
|
|
8181
|
+
first_error_code: firstError?.errorCode,
|
|
8182
|
+
first_error_request_id: firstError?.requestId,
|
|
8183
|
+
first_error_route: firstError?.path,
|
|
8184
|
+
duration_ms: Math.max(0, Math.round(performance.now() - started))
|
|
8185
|
+
};
|
|
8186
|
+
const client = createHttpClient(creds, {
|
|
8187
|
+
onNetworkError() {
|
|
8188
|
+
throw new Error("telemetry unavailable");
|
|
8189
|
+
},
|
|
8190
|
+
warn() {
|
|
8191
|
+
}
|
|
8192
|
+
});
|
|
8193
|
+
await client.request("POST", "/api/v1/telemetry/cli", payload, 800);
|
|
8194
|
+
})().catch(() => void 0);
|
|
7742
8195
|
}
|
|
7743
8196
|
|
|
8197
|
+
// src/cli.ts
|
|
8198
|
+
init_source();
|
|
8199
|
+
|
|
8200
|
+
// src/commands/login.ts
|
|
8201
|
+
init_source();
|
|
8202
|
+
var import_node_crypto3 = __toESM(require("node:crypto"));
|
|
8203
|
+
var import_node_http = __toESM(require("node:http"));
|
|
8204
|
+
var import_node_os6 = __toESM(require("node:os"));
|
|
8205
|
+
var import_node_readline2 = __toESM(require("node:readline"));
|
|
8206
|
+
|
|
7744
8207
|
// src/api.ts
|
|
8208
|
+
var import_node_crypto2 = require("node:crypto");
|
|
7745
8209
|
function simulateTriggers(creds, body5) {
|
|
7746
8210
|
return request(creds, "POST", "/api/v1/tracking/simulate-trigger", body5);
|
|
7747
8211
|
}
|
|
@@ -7765,11 +8229,11 @@ function getUserKeyIdentity(creds) {
|
|
|
7765
8229
|
function getAccountContext(creds) {
|
|
7766
8230
|
return userRequest(creds, "GET", "/api/v1/me/context");
|
|
7767
8231
|
}
|
|
7768
|
-
function userRequest(creds, method,
|
|
8232
|
+
function userRequest(creds, method, path25, body5) {
|
|
7769
8233
|
return request(
|
|
7770
8234
|
{ ...creds, apiKey: creds.userApiKey ?? "", baseUrl: creds.userBaseUrl ?? creds.baseUrl },
|
|
7771
8235
|
method,
|
|
7772
|
-
|
|
8236
|
+
path25,
|
|
7773
8237
|
body5
|
|
7774
8238
|
);
|
|
7775
8239
|
}
|
|
@@ -7801,6 +8265,7 @@ function sharedClient(creds) {
|
|
|
7801
8265
|
let client = sharedClients.get(creds);
|
|
7802
8266
|
if (!client) {
|
|
7803
8267
|
client = createHttpClient(creds, {
|
|
8268
|
+
onResponse: recordApiResponse2,
|
|
7804
8269
|
transport: testTransport,
|
|
7805
8270
|
testTransportVersion: () => testTransport ? process.env[["APEX", "CLI", "MOCK", "API", "VERSION"].join("_")] : void 0,
|
|
7806
8271
|
onNetworkError: (info) => {
|
|
@@ -7811,19 +8276,22 @@ function sharedClient(creds) {
|
|
|
7811
8276
|
}
|
|
7812
8277
|
const host = url?.hostname ?? info.baseUrl;
|
|
7813
8278
|
const code = info.code;
|
|
7814
|
-
let
|
|
8279
|
+
let reason2 = "unknown";
|
|
7815
8280
|
let message = info.causeMessage ?? info.cause;
|
|
7816
|
-
if (
|
|
7817
|
-
|
|
8281
|
+
if (code === "UND_ERR_CONNECT_TIMEOUT") {
|
|
8282
|
+
reason2 = "connect_timeout";
|
|
8283
|
+
message = `could not open a connection to ${host} within ${info.timeoutMs / 1e3}s`;
|
|
8284
|
+
} else if (info.name === "TimeoutError" || info.name === "AbortError") {
|
|
8285
|
+
reason2 = "timeout";
|
|
7818
8286
|
message = `server did not respond within ${info.timeoutMs / 1e3}s`;
|
|
7819
8287
|
} else if (code === "ENOTFOUND" || code === "EAI_AGAIN") {
|
|
7820
|
-
|
|
8288
|
+
reason2 = "dns";
|
|
7821
8289
|
message = `could not resolve host ${host}`;
|
|
7822
8290
|
} else if (code === "ECONNREFUSED") {
|
|
7823
|
-
|
|
8291
|
+
reason2 = "refused";
|
|
7824
8292
|
message = `connection refused by ${host}:${url?.port || (url?.protocol === "https:" ? "443" : "80")}`;
|
|
7825
8293
|
} else if (code === "ECONNRESET" || code === "EPIPE") {
|
|
7826
|
-
|
|
8294
|
+
reason2 = "reset";
|
|
7827
8295
|
message = `connection reset by ${host}`;
|
|
7828
8296
|
} else if (code?.startsWith("CERT_") || code?.startsWith("ERR_TLS_") || [
|
|
7829
8297
|
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
|
@@ -7833,13 +8301,13 @@ function sharedClient(creds) {
|
|
|
7833
8301
|
"UNABLE_TO_GET_ISSUER_CERT",
|
|
7834
8302
|
"HOSTNAME_MISMATCH"
|
|
7835
8303
|
].includes(code ?? "")) {
|
|
7836
|
-
|
|
8304
|
+
reason2 = "tls";
|
|
7837
8305
|
message = `TLS error (${code})`;
|
|
7838
8306
|
}
|
|
7839
8307
|
throw new CliError(
|
|
7840
8308
|
`Could not reach ${info.baseUrl}: check APEX_BASE_URL (or DRIP_BASE_URL) / network; ${message}.`,
|
|
7841
8309
|
EXIT_CODES.general,
|
|
7842
|
-
{ reason, code, name: info.name, baseUrl: info.baseUrl, timeoutMs: info.timeoutMs }
|
|
8310
|
+
{ reason: reason2, code, name: info.name, baseUrl: info.baseUrl, timeoutMs: info.timeoutMs }
|
|
7843
8311
|
);
|
|
7844
8312
|
}
|
|
7845
8313
|
});
|
|
@@ -7847,13 +8315,13 @@ function sharedClient(creds) {
|
|
|
7847
8315
|
}
|
|
7848
8316
|
return client;
|
|
7849
8317
|
}
|
|
7850
|
-
async function request(creds, method,
|
|
7851
|
-
const response = await sharedClient(creds).request(method,
|
|
8318
|
+
async function request(creds, method, path25, body5, requestTimeoutMs) {
|
|
8319
|
+
const response = await sharedClient(creds).request(method, path25, body5, requestTimeoutMs);
|
|
7852
8320
|
recordApiResponse(response);
|
|
7853
8321
|
return response;
|
|
7854
8322
|
}
|
|
7855
|
-
async function requestForm(creds, method,
|
|
7856
|
-
const response = await sharedClient(creds).requestForm(method,
|
|
8323
|
+
async function requestForm(creds, method, path25, body5) {
|
|
8324
|
+
const response = await sharedClient(creds).requestForm(method, path25, body5);
|
|
7857
8325
|
recordApiResponse(response);
|
|
7858
8326
|
return response;
|
|
7859
8327
|
}
|
|
@@ -7868,11 +8336,19 @@ function listExperiments(creds, opts) {
|
|
|
7868
8336
|
const qs = params.toString();
|
|
7869
8337
|
return request(creds, "GET", `/api/v1/experiments${qs ? `?${qs}` : ""}`);
|
|
7870
8338
|
}
|
|
7871
|
-
function getExperiment(creds, id) {
|
|
7872
|
-
return request(
|
|
8339
|
+
function getExperiment(creds, id, includePreviewTarget = false) {
|
|
8340
|
+
return request(
|
|
8341
|
+
creds,
|
|
8342
|
+
"GET",
|
|
8343
|
+
`/api/v1/experiments/${id}${includePreviewTarget ? "?include=preview_target" : ""}`
|
|
8344
|
+
);
|
|
7873
8345
|
}
|
|
7874
|
-
function getExperimentBySlug(creds, slug) {
|
|
7875
|
-
return request(
|
|
8346
|
+
function getExperimentBySlug(creds, slug, includePreviewTarget = false) {
|
|
8347
|
+
return request(
|
|
8348
|
+
creds,
|
|
8349
|
+
"GET",
|
|
8350
|
+
`/api/v1/experiments/by-slug/${slug}${includePreviewTarget ? "?include=preview_target" : ""}`
|
|
8351
|
+
);
|
|
7876
8352
|
}
|
|
7877
8353
|
function upsertExperimentBySlug(creds, slug, body5) {
|
|
7878
8354
|
return request(creds, "PUT", `/api/v1/experiments/by-slug/${slug}`, body5);
|
|
@@ -7911,6 +8387,16 @@ function getExperimentResults(creds, id, opts) {
|
|
|
7911
8387
|
`/api/v1/experiments/${id}/results${qs ? `?${qs}` : ""}`
|
|
7912
8388
|
);
|
|
7913
8389
|
}
|
|
8390
|
+
function getExperimentResultsExplain(creds, id, opts) {
|
|
8391
|
+
const params = new URLSearchParams();
|
|
8392
|
+
for (const key of ["currency", "goal", "order"])
|
|
8393
|
+
if (opts[key]) params.set(key, opts[key]);
|
|
8394
|
+
return request(
|
|
8395
|
+
creds,
|
|
8396
|
+
"GET",
|
|
8397
|
+
`/api/v1/experiments/${id}/results/explain?${params}`
|
|
8398
|
+
);
|
|
8399
|
+
}
|
|
7914
8400
|
function createExperimentScreenshots(creds, id, body5) {
|
|
7915
8401
|
return request(creds, "POST", `/api/v1/experiments/${id}/screenshots`, body5);
|
|
7916
8402
|
}
|
|
@@ -7937,7 +8423,10 @@ function applyExperimentFilesystem(creds, id, body5) {
|
|
|
7937
8423
|
creds,
|
|
7938
8424
|
"POST",
|
|
7939
8425
|
`/api/v1/experiments/${id}/filesystem/apply`,
|
|
7940
|
-
|
|
8426
|
+
{
|
|
8427
|
+
files: body5.files,
|
|
8428
|
+
...body5.confirmTargetingReplace === true ? { confirmTargetingReplace: true } : {}
|
|
8429
|
+
}
|
|
7941
8430
|
);
|
|
7942
8431
|
}
|
|
7943
8432
|
function getExperimentQaRun(creds, id) {
|
|
@@ -8104,6 +8593,15 @@ function verifyTracking(creds, opts) {
|
|
|
8104
8593
|
`/api/v1/tracking/verify${qs ? `?${qs}` : ""}`
|
|
8105
8594
|
);
|
|
8106
8595
|
}
|
|
8596
|
+
function exportEventsPage(creds, opts) {
|
|
8597
|
+
const params = new URLSearchParams({ from: opts.from, to: opts.to });
|
|
8598
|
+
if (opts.experiment) params.set("experiment", opts.experiment);
|
|
8599
|
+
if (opts.eventTypes?.length) params.set("event_type", opts.eventTypes.join(","));
|
|
8600
|
+
if (opts.qa) params.set("qa", opts.qa);
|
|
8601
|
+
if (opts.limit) params.set("limit", String(opts.limit));
|
|
8602
|
+
if (opts.cursor) params.set("cursor", opts.cursor);
|
|
8603
|
+
return request(creds, "GET", `/api/v1/events/export?${params}`);
|
|
8604
|
+
}
|
|
8107
8605
|
function discoverTrackingSchema(creds, minutes) {
|
|
8108
8606
|
const query = minutes ? `?minutes=${encodeURIComponent(String(minutes))}` : "";
|
|
8109
8607
|
return request(creds, "GET", `/api/v1/tracking/discovery${query}`);
|
|
@@ -8114,7 +8612,7 @@ function testTargeting(creds, url, opts) {
|
|
|
8114
8612
|
return request(creds, "GET", `/api/v1/targeting/test?${params.toString()}`);
|
|
8115
8613
|
}
|
|
8116
8614
|
function createSos(creds, body5) {
|
|
8117
|
-
const idempotency_key = body5.idempotency_key ?? (0,
|
|
8615
|
+
const idempotency_key = body5.idempotency_key ?? (0, import_node_crypto2.createHash)("sha256").update(
|
|
8118
8616
|
[
|
|
8119
8617
|
body5.type,
|
|
8120
8618
|
body5.experiment_id ?? "",
|
|
@@ -8265,8 +8763,8 @@ function requestTargetingResource(creds, method, kind5, id, body5, limit, cursor
|
|
|
8265
8763
|
if (limit) params.set("limit", String(limit));
|
|
8266
8764
|
if (cursor) params.set("cursor", cursor);
|
|
8267
8765
|
const query = params.toString();
|
|
8268
|
-
const
|
|
8269
|
-
return request(creds, method,
|
|
8766
|
+
const path25 = `/api/v1/${kind5}${id ? `/${id.split("/").map(encodeURIComponent).join("/")}` : ""}${query ? `?${query}` : ""}`;
|
|
8767
|
+
return request(creds, method, path25, body5);
|
|
8270
8768
|
}
|
|
8271
8769
|
function managedPath(kind5, id, action74) {
|
|
8272
8770
|
return `/api/v1/${kind5}${id ? `/${encodeURIComponent(id)}` : ""}${action74 ? `/${action74}` : ""}`;
|
|
@@ -8502,6 +9000,24 @@ function getWebhookDeliveries(creds, id, limit) {
|
|
|
8502
9000
|
function testWebhook(creds, id) {
|
|
8503
9001
|
return request(creds, "POST", `/api/v1/webhooks/${encodeURIComponent(id)}/test`);
|
|
8504
9002
|
}
|
|
9003
|
+
function getOrderJourney(creds, opts) {
|
|
9004
|
+
const params = new URLSearchParams({
|
|
9005
|
+
platform: opts.platform,
|
|
9006
|
+
environment: opts.environment,
|
|
9007
|
+
from: opts.from,
|
|
9008
|
+
to: opts.to
|
|
9009
|
+
});
|
|
9010
|
+
if (opts.orderId !== void 0) params.set("platform_order_id", opts.orderId);
|
|
9011
|
+
if (opts.orderNumber !== void 0) params.set("order_number", opts.orderNumber);
|
|
9012
|
+
if (opts.includeQa) params.set("include_qa", "true");
|
|
9013
|
+
return request(
|
|
9014
|
+
creds,
|
|
9015
|
+
"GET",
|
|
9016
|
+
`/api/v1/analytics/order-journey?${params}`,
|
|
9017
|
+
void 0,
|
|
9018
|
+
45e3
|
|
9019
|
+
);
|
|
9020
|
+
}
|
|
8505
9021
|
|
|
8506
9022
|
// src/context.ts
|
|
8507
9023
|
var import_node_readline = __toESM(require("node:readline"));
|
|
@@ -8587,9 +9103,9 @@ var STATUS_COLORS = {
|
|
|
8587
9103
|
paused: source_default.yellow,
|
|
8588
9104
|
completed: source_default.blue
|
|
8589
9105
|
};
|
|
8590
|
-
function statusBadge(
|
|
8591
|
-
const colorize = STATUS_COLORS[
|
|
8592
|
-
return colorize(
|
|
9106
|
+
function statusBadge(status3) {
|
|
9107
|
+
const colorize = STATUS_COLORS[status3] ?? source_default.white;
|
|
9108
|
+
return colorize(status3.toUpperCase());
|
|
8593
9109
|
}
|
|
8594
9110
|
function printTable(headers, rows, widths) {
|
|
8595
9111
|
const colWidths = widths ?? headers.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length)));
|
|
@@ -8667,8 +9183,8 @@ function parseFormBody(body5) {
|
|
|
8667
9183
|
return new URLSearchParams(body5);
|
|
8668
9184
|
}
|
|
8669
9185
|
async function waitForBrowserLogin(baseUrl, userMode = false) {
|
|
8670
|
-
const state =
|
|
8671
|
-
const clientName =
|
|
9186
|
+
const state = import_node_crypto3.default.randomBytes(24).toString("base64url");
|
|
9187
|
+
const clientName = import_node_os6.default.hostname() || "Local machine";
|
|
8672
9188
|
return new Promise((resolve2, reject) => {
|
|
8673
9189
|
let settled = false;
|
|
8674
9190
|
let timeout = null;
|
|
@@ -8999,9 +9515,9 @@ var useCommand = new Command("use").description("Set the current Apex organizati
|
|
|
8999
9515
|
|
|
9000
9516
|
// src/commands/experiments.ts
|
|
9001
9517
|
init_source();
|
|
9002
|
-
var
|
|
9003
|
-
var
|
|
9004
|
-
var
|
|
9518
|
+
var import_node_crypto5 = __toESM(require("node:crypto"));
|
|
9519
|
+
var import_node_fs8 = __toESM(require("node:fs"));
|
|
9520
|
+
var import_node_path6 = __toESM(require("node:path"));
|
|
9005
9521
|
|
|
9006
9522
|
// src/lib/qa-vocabulary.ts
|
|
9007
9523
|
var checklistStatuses = [
|
|
@@ -9031,6 +9547,30 @@ var evidenceKinds = [
|
|
|
9031
9547
|
"readiness"
|
|
9032
9548
|
];
|
|
9033
9549
|
|
|
9550
|
+
// src/lib/experiment-handover.ts
|
|
9551
|
+
function summarizeHandoverQa(value) {
|
|
9552
|
+
const record3 = (value2) => value2 !== null && typeof value2 === "object" && !Array.isArray(value2) ? value2 : {};
|
|
9553
|
+
const qa3 = record3(value);
|
|
9554
|
+
const run3 = record3(qa3.latestRun);
|
|
9555
|
+
const summary = record3(run3.summary);
|
|
9556
|
+
const count2 = (value2) => typeof value2 === "number" && Number.isInteger(value2) && value2 >= 0 ? value2 : null;
|
|
9557
|
+
let requiredDone = count2(summary.completedRequired);
|
|
9558
|
+
let requiredTotal = count2(summary.required);
|
|
9559
|
+
if (requiredDone !== null && requiredTotal !== null && requiredDone > requiredTotal) {
|
|
9560
|
+
requiredDone = null;
|
|
9561
|
+
requiredTotal = null;
|
|
9562
|
+
}
|
|
9563
|
+
const blockers = Array.isArray(summary.blockers) && summary.blockers.every((item) => typeof item === "string") ? summary.blockers : null;
|
|
9564
|
+
const approvedAt = typeof qa3.qaApprovedAt === "string" ? Date.parse(qa3.qaApprovedAt) : Number.NaN;
|
|
9565
|
+
return {
|
|
9566
|
+
run_status: typeof run3.status === "string" ? run3.status : null,
|
|
9567
|
+
required_done: requiredDone,
|
|
9568
|
+
required_total: requiredTotal,
|
|
9569
|
+
blockers,
|
|
9570
|
+
approval_present: Number.isFinite(approvedAt)
|
|
9571
|
+
};
|
|
9572
|
+
}
|
|
9573
|
+
|
|
9034
9574
|
// src/lib/dashboard-url.ts
|
|
9035
9575
|
function buildDashboardUrl(baseUrl, shopId, experimentId) {
|
|
9036
9576
|
if (!shopId) return null;
|
|
@@ -9148,8 +9688,8 @@ function pageEntries(raw) {
|
|
|
9148
9688
|
}
|
|
9149
9689
|
return [...byId.values()];
|
|
9150
9690
|
}
|
|
9151
|
-
function urlRuleClause(
|
|
9152
|
-
return
|
|
9691
|
+
function urlRuleClause(count2) {
|
|
9692
|
+
return count2 === 1 ? "1 URL rule" : `${count2} URL rules`;
|
|
9153
9693
|
}
|
|
9154
9694
|
function effectivePageTargetingLabel(targeting) {
|
|
9155
9695
|
const raw = isRecord(targeting) ? targeting : {};
|
|
@@ -9194,9 +9734,9 @@ function deriveStoreUrl(patterns) {
|
|
|
9194
9734
|
}
|
|
9195
9735
|
|
|
9196
9736
|
// src/lib/asset-upload.ts
|
|
9197
|
-
var
|
|
9198
|
-
var
|
|
9199
|
-
var
|
|
9737
|
+
var import_node_crypto4 = __toESM(require("node:crypto"));
|
|
9738
|
+
var import_node_fs7 = __toESM(require("node:fs"));
|
|
9739
|
+
var import_node_path5 = __toESM(require("node:path"));
|
|
9200
9740
|
var INLINE_DATA_URI_MAX_CHARS = 4096;
|
|
9201
9741
|
var DATA_IMAGE_URI_PATTERN = /data:image\/[a-z0-9.+-]+(?:(?:;[^;,\s"'`]*)*;base64,[A-Za-z0-9+/=\s]+|(?:;[^,\s"'`]*)?,[^\s"'`]+)/gi;
|
|
9202
9742
|
var QUOTED_LOCAL_IMAGE_PATTERN = /(["'])([^"'\n]+?\.(?:png|jpe?g|gif|webp|svg|avif))\1/gi;
|
|
@@ -9219,7 +9759,7 @@ var EXTENSION_BY_MIME = {
|
|
|
9219
9759
|
"image/avif": ".avif"
|
|
9220
9760
|
};
|
|
9221
9761
|
function contentHash(bytes) {
|
|
9222
|
-
return
|
|
9762
|
+
return import_node_crypto4.default.createHash("sha256").update(bytes).digest("hex");
|
|
9223
9763
|
}
|
|
9224
9764
|
function deliveryUrl(baseUrl, asset) {
|
|
9225
9765
|
const returnedUrl = asset.deliveryUrl ?? asset.delivery_url ?? asset.url;
|
|
@@ -9307,23 +9847,23 @@ function createAssetReferenceRewriter(options) {
|
|
|
9307
9847
|
if (reference.startsWith("/") || reference.startsWith("data:") || reference.includes("://")) {
|
|
9308
9848
|
return null;
|
|
9309
9849
|
}
|
|
9310
|
-
const absolutePath =
|
|
9850
|
+
const absolutePath = import_node_path5.default.resolve(baseDir, reference);
|
|
9311
9851
|
let realBaseDir;
|
|
9312
9852
|
let realPath;
|
|
9313
9853
|
try {
|
|
9314
|
-
realBaseDir =
|
|
9315
|
-
realPath =
|
|
9854
|
+
realBaseDir = import_node_fs7.default.realpathSync(baseDir);
|
|
9855
|
+
realPath = import_node_fs7.default.realpathSync(absolutePath);
|
|
9316
9856
|
} catch {
|
|
9317
9857
|
return null;
|
|
9318
9858
|
}
|
|
9319
|
-
const relativeRealPath =
|
|
9320
|
-
if (relativeRealPath.startsWith("..") ||
|
|
9321
|
-
if (!
|
|
9322
|
-
const bytes =
|
|
9323
|
-
const extension =
|
|
9859
|
+
const relativeRealPath = import_node_path5.default.relative(realBaseDir, realPath);
|
|
9860
|
+
if (relativeRealPath.startsWith("..") || import_node_path5.default.isAbsolute(relativeRealPath)) return null;
|
|
9861
|
+
if (!import_node_fs7.default.statSync(realPath).isFile()) return null;
|
|
9862
|
+
const bytes = import_node_fs7.default.readFileSync(realPath);
|
|
9863
|
+
const extension = import_node_path5.default.extname(realPath).toLowerCase();
|
|
9324
9864
|
return uploadOnce(
|
|
9325
9865
|
{
|
|
9326
|
-
name:
|
|
9866
|
+
name: import_node_path5.default.basename(absolutePath),
|
|
9327
9867
|
bytes,
|
|
9328
9868
|
type: MIME_BY_EXTENSION[extension]
|
|
9329
9869
|
},
|
|
@@ -9500,6 +10040,16 @@ function printExperimentResults(results2, currency) {
|
|
|
9500
10040
|
[32, 8, 6, 8, 10]
|
|
9501
10041
|
);
|
|
9502
10042
|
}
|
|
10043
|
+
const metrics = Array.isArray(results2.metrics) ? results2.metrics : [];
|
|
10044
|
+
if (metrics.length > 0) {
|
|
10045
|
+
console.log();
|
|
10046
|
+
console.log(source_default.bold("Goal counting"));
|
|
10047
|
+
metrics.forEach((metric) => {
|
|
10048
|
+
const basis = metric.counting === "once" ? "unique visitors" : metric.counting === "every" ? "every event" : "unknown basis";
|
|
10049
|
+
console.log(` - ${String(metric.name ?? metric.goalId ?? "Goal")}: ${basis}`);
|
|
10050
|
+
});
|
|
10051
|
+
}
|
|
10052
|
+
console.log(" Definitions: https://docs.drip-apex.com/docs/experiments/metric-definitions");
|
|
9503
10053
|
const warnings = Array.isArray(results2.warnings) ? results2.warnings : [];
|
|
9504
10054
|
if (warnings.length > 0) {
|
|
9505
10055
|
console.log();
|
|
@@ -9528,39 +10078,59 @@ var FILESYSTEM_FILE_RE = new RegExp(
|
|
|
9528
10078
|
`^(?:${pathAlternation(EXPERIMENT_FILESYSTEM_PATH_SCHEMA.topLevel)}|experiment/(?:${pathAlternation(EXPERIMENT_FILESYSTEM_PATH_SCHEMA.experiment)})|variations/[^/]+/(?:${pathAlternation(EXPERIMENT_FILESYSTEM_PATH_SCHEMA.variation)}))$`
|
|
9529
10079
|
);
|
|
9530
10080
|
function sha256Text(value) {
|
|
9531
|
-
return
|
|
10081
|
+
return import_node_crypto5.default.createHash("sha256").update(value).digest("hex");
|
|
9532
10082
|
}
|
|
9533
10083
|
function folderPath(folder) {
|
|
9534
|
-
return
|
|
10084
|
+
return import_node_path6.default.resolve(process.cwd(), folder);
|
|
9535
10085
|
}
|
|
9536
10086
|
function filesystemApexDir(folder) {
|
|
9537
|
-
return
|
|
10087
|
+
return import_node_path6.default.join(folder, ".apex");
|
|
9538
10088
|
}
|
|
9539
10089
|
function filesystemSessionPath(folder) {
|
|
9540
|
-
return
|
|
10090
|
+
return import_node_path6.default.join(filesystemApexDir(folder), "experiment-filesystem.json");
|
|
9541
10091
|
}
|
|
9542
10092
|
function writeFilesystemSession(folder, session) {
|
|
9543
|
-
|
|
9544
|
-
|
|
9545
|
-
|
|
10093
|
+
import_node_fs8.default.mkdirSync(filesystemApexDir(folder), { recursive: true });
|
|
10094
|
+
import_node_fs8.default.writeFileSync(
|
|
10095
|
+
import_node_path6.default.join(filesystemApexDir(folder), ".gitignore"),
|
|
9546
10096
|
"experiment-filesystem.json\n",
|
|
9547
10097
|
"utf8"
|
|
9548
10098
|
);
|
|
9549
|
-
|
|
10099
|
+
import_node_fs8.default.writeFileSync(filesystemSessionPath(folder), `${JSON.stringify(session, null, 2)}
|
|
9550
10100
|
`, "utf8");
|
|
9551
10101
|
}
|
|
9552
10102
|
function readFilesystemSession(folder) {
|
|
9553
10103
|
const filePath = filesystemSessionPath(folder);
|
|
9554
|
-
if (!
|
|
9555
|
-
const workspaceMarker =
|
|
9556
|
-
if (
|
|
9557
|
-
throw new
|
|
9558
|
-
"This folder is an operator workspace (apex workspace \u2026), not an experiment filesystem \u2014 run `apex workspace status` here, or `apex experiments pull` into a different folder."
|
|
10104
|
+
if (!import_node_fs8.default.existsSync(filePath)) {
|
|
10105
|
+
const workspaceMarker = import_node_path6.default.join(filesystemApexDir(folder), "session.json");
|
|
10106
|
+
if (import_node_fs8.default.existsSync(workspaceMarker)) {
|
|
10107
|
+
throw new CliError(
|
|
10108
|
+
"This folder is an operator workspace (apex workspace \u2026), not an experiment filesystem \u2014 run `apex workspace status` here, or `apex experiments pull` into a different folder.",
|
|
10109
|
+
EXIT_CODES.general,
|
|
10110
|
+
{
|
|
10111
|
+
code: "validation_failed",
|
|
10112
|
+
next_steps: [
|
|
10113
|
+
{ command: "apex workspace status", why: "Use the command for this folder format." },
|
|
10114
|
+
{
|
|
10115
|
+
command: "apex experiments pull",
|
|
10116
|
+
why: "Create the required folder in a different directory."
|
|
10117
|
+
}
|
|
10118
|
+
]
|
|
10119
|
+
}
|
|
9559
10120
|
);
|
|
9560
10121
|
}
|
|
9561
|
-
throw new
|
|
10122
|
+
throw new CliError(
|
|
10123
|
+
`Missing ${filePath}. Run apex experiments pull first.`,
|
|
10124
|
+
EXIT_CODES.general,
|
|
10125
|
+
{
|
|
10126
|
+
code: "local_file_error",
|
|
10127
|
+
next_steps: [
|
|
10128
|
+
{ command: "apex experiments pull", why: "Create the missing local folder metadata." }
|
|
10129
|
+
]
|
|
10130
|
+
}
|
|
10131
|
+
);
|
|
9562
10132
|
}
|
|
9563
|
-
return JSON.parse(
|
|
10133
|
+
return JSON.parse(import_node_fs8.default.readFileSync(filePath, "utf8"));
|
|
9564
10134
|
}
|
|
9565
10135
|
function validateFilesystemPath(relativePath) {
|
|
9566
10136
|
const normalized = relativePath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
@@ -9576,17 +10146,17 @@ function writeExperimentFilesystemFiles(folder, files) {
|
|
|
9576
10146
|
}));
|
|
9577
10147
|
for (const file of validatedFiles) {
|
|
9578
10148
|
const relativePath = file.relativePath;
|
|
9579
|
-
const absPath =
|
|
9580
|
-
|
|
9581
|
-
|
|
10149
|
+
const absPath = import_node_path6.default.join(folder, relativePath);
|
|
10150
|
+
import_node_fs8.default.mkdirSync(import_node_path6.default.dirname(absPath), { recursive: true });
|
|
10151
|
+
import_node_fs8.default.writeFileSync(absPath, file.content, "utf8");
|
|
9582
10152
|
}
|
|
9583
10153
|
}
|
|
9584
10154
|
function listExperimentFilesystemFiles(folder, session) {
|
|
9585
10155
|
const files = [];
|
|
9586
10156
|
for (const fileName of EXPERIMENT_FILESYSTEM_PATH_SCHEMA.topLevel) {
|
|
9587
|
-
const absPath =
|
|
9588
|
-
if (!
|
|
9589
|
-
const content =
|
|
10157
|
+
const absPath = import_node_path6.default.join(folder, fileName);
|
|
10158
|
+
if (!import_node_fs8.default.existsSync(absPath)) continue;
|
|
10159
|
+
const content = import_node_fs8.default.readFileSync(absPath, "utf8");
|
|
9590
10160
|
const sha256 = sha256Text(content);
|
|
9591
10161
|
if (SERVER_OWNED_EXPERIMENT_FILES.has(fileName)) {
|
|
9592
10162
|
if (session.files[fileName]?.sha256 !== sha256) {
|
|
@@ -9600,21 +10170,21 @@ function listExperimentFilesystemFiles(folder, session) {
|
|
|
9600
10170
|
}
|
|
9601
10171
|
for (const fileName of EXPERIMENT_FILESYSTEM_PATH_SCHEMA.experiment) {
|
|
9602
10172
|
const relativePath = `experiment/${fileName}`;
|
|
9603
|
-
const absPath =
|
|
9604
|
-
if (!
|
|
9605
|
-
const content =
|
|
10173
|
+
const absPath = import_node_path6.default.join(folder, relativePath);
|
|
10174
|
+
if (!import_node_fs8.default.existsSync(absPath)) continue;
|
|
10175
|
+
const content = import_node_fs8.default.readFileSync(absPath, "utf8");
|
|
9606
10176
|
files.push({ path: relativePath, content, sha256: sha256Text(content) });
|
|
9607
10177
|
}
|
|
9608
|
-
const variationsRoot =
|
|
9609
|
-
if (
|
|
9610
|
-
for (const variationDir of
|
|
10178
|
+
const variationsRoot = import_node_path6.default.join(folder, "variations");
|
|
10179
|
+
if (import_node_fs8.default.existsSync(variationsRoot)) {
|
|
10180
|
+
for (const variationDir of import_node_fs8.default.readdirSync(variationsRoot, { withFileTypes: true })) {
|
|
9611
10181
|
if (!variationDir.isDirectory()) continue;
|
|
9612
|
-
const base =
|
|
10182
|
+
const base = import_node_path6.default.join(variationsRoot, variationDir.name);
|
|
9613
10183
|
for (const fileName of EXPERIMENT_FILESYSTEM_PATH_SCHEMA.variation) {
|
|
9614
|
-
const absPath =
|
|
9615
|
-
if (!
|
|
9616
|
-
const relativePath = validateFilesystemPath(
|
|
9617
|
-
const content =
|
|
10184
|
+
const absPath = import_node_path6.default.join(base, fileName);
|
|
10185
|
+
if (!import_node_fs8.default.existsSync(absPath)) continue;
|
|
10186
|
+
const relativePath = validateFilesystemPath(import_node_path6.default.relative(folder, absPath));
|
|
10187
|
+
const content = import_node_fs8.default.readFileSync(absPath, "utf8");
|
|
9618
10188
|
files.push({ path: relativePath, content, sha256: sha256Text(content) });
|
|
9619
10189
|
}
|
|
9620
10190
|
}
|
|
@@ -9627,7 +10197,7 @@ async function rewriteExperimentFilesystemAssets(input) {
|
|
|
9627
10197
|
baseUrl: input.baseUrl,
|
|
9628
10198
|
enabled: input.enabled,
|
|
9629
10199
|
upload: async (file) => {
|
|
9630
|
-
const hash =
|
|
10200
|
+
const hash = import_node_crypto5.default.createHash("sha256").update(file.bytes).digest("hex");
|
|
9631
10201
|
const cached = input.assets[hash];
|
|
9632
10202
|
if (cached) return cached;
|
|
9633
10203
|
if (input.upload === null) return null;
|
|
@@ -9656,7 +10226,7 @@ async function rewriteExperimentFilesystemAssets(input) {
|
|
|
9656
10226
|
}
|
|
9657
10227
|
const content = await rewriter.rewrite(
|
|
9658
10228
|
file.content,
|
|
9659
|
-
|
|
10229
|
+
import_node_path6.default.dirname(import_node_path6.default.join(input.folder, file.path))
|
|
9660
10230
|
);
|
|
9661
10231
|
return { ...file, content, sha256: sha256Text(content) };
|
|
9662
10232
|
})
|
|
@@ -9667,9 +10237,17 @@ function printFilesystemPlan(plan) {
|
|
|
9667
10237
|
console.log(source_default.red("\nErrors"));
|
|
9668
10238
|
plan.errors.forEach((error) => console.log(` - ${error}`));
|
|
9669
10239
|
}
|
|
9670
|
-
|
|
10240
|
+
const compatibilityWarnings = (plan.previewCompatibility ?? []).map(
|
|
10241
|
+
(finding) => `${finding.path}:${finding.line}:${finding.column} ${finding.rule}: ${finding.message}. Try: ${finding.alternative}`
|
|
10242
|
+
);
|
|
10243
|
+
if (compatibilityWarnings.length > 0) {
|
|
10244
|
+
console.log(source_default.yellow("\nPreview compatibility"));
|
|
10245
|
+
compatibilityWarnings.forEach((warning) => console.log(` - ${warning}`));
|
|
10246
|
+
}
|
|
10247
|
+
const otherWarnings = plan.warnings.filter((warning) => !compatibilityWarnings.includes(warning));
|
|
10248
|
+
if (otherWarnings.length > 0) {
|
|
9671
10249
|
console.log(source_default.yellow("\nWarnings"));
|
|
9672
|
-
|
|
10250
|
+
otherWarnings.forEach((warning) => console.log(` - ${warning}`));
|
|
9673
10251
|
}
|
|
9674
10252
|
if ((plan.blockingIssues?.length ?? 0) > 0) {
|
|
9675
10253
|
console.log(source_default.red("\nBlocking issues"));
|
|
@@ -9744,6 +10322,10 @@ function printQaPreviewUrls(experimentId, result) {
|
|
|
9744
10322
|
);
|
|
9745
10323
|
}
|
|
9746
10324
|
}
|
|
10325
|
+
function qaVersionMarker(value) {
|
|
10326
|
+
const version = value;
|
|
10327
|
+
return version.stale ? " [stale: checked against an earlier version]" : version.unversioned ? " [unversioned]" : "";
|
|
10328
|
+
}
|
|
9747
10329
|
function printQaRun(run3) {
|
|
9748
10330
|
console.log();
|
|
9749
10331
|
console.log(source_default.bold(`QA run ${run3.id}`));
|
|
@@ -9760,7 +10342,7 @@ function printQaRun(run3) {
|
|
|
9760
10342
|
for (const item of run3.checklist) {
|
|
9761
10343
|
const required = item.required ? source_default.yellow(" required") : "";
|
|
9762
10344
|
const notes = item.notes ? source_default.dim(` \u2014 ${item.notes}`) : "";
|
|
9763
|
-
console.log(` ${statusBadge(item.status)} ${item.id}${required}`);
|
|
10345
|
+
console.log(` ${statusBadge(item.status)} ${item.id}${required}${qaVersionMarker(item)}`);
|
|
9764
10346
|
console.log(` ${item.title}${notes}`);
|
|
9765
10347
|
}
|
|
9766
10348
|
if (run3.urls.length > 0) {
|
|
@@ -9771,10 +10353,10 @@ function printQaRun(run3) {
|
|
|
9771
10353
|
}
|
|
9772
10354
|
if ((run3.evidenceRecords ?? []).length > 0) {
|
|
9773
10355
|
console.log(source_default.bold("\nEvidence records"));
|
|
9774
|
-
for (const
|
|
9775
|
-
const device =
|
|
10356
|
+
for (const record3 of run3.evidenceRecords ?? []) {
|
|
10357
|
+
const device = record3.device ? ` \xB7 ${record3.device}` : "";
|
|
9776
10358
|
console.log(
|
|
9777
|
-
` - ${
|
|
10359
|
+
` - ${record3.kind} \xB7 ${record3.label} \xB7 ${record3.status}${device} \xB7 ${record3.capturedAt}${qaVersionMarker(record3)}`
|
|
9778
10360
|
);
|
|
9779
10361
|
}
|
|
9780
10362
|
}
|
|
@@ -9795,8 +10377,8 @@ async function confirmApply(message) {
|
|
|
9795
10377
|
function parseEvidenceInput(value) {
|
|
9796
10378
|
if (!value) return void 0;
|
|
9797
10379
|
const source = value.startsWith("@") ? value.slice(1) : value;
|
|
9798
|
-
const
|
|
9799
|
-
const parsed = JSON.parse(
|
|
10380
|
+
const text2 = value.startsWith("@") || import_node_fs8.default.existsSync(source) ? import_node_fs8.default.readFileSync(import_node_path6.default.resolve(process.cwd(), source), "utf8") : value;
|
|
10381
|
+
const parsed = JSON.parse(text2);
|
|
9800
10382
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
9801
10383
|
throw new Error("QA evidence must be a JSON object");
|
|
9802
10384
|
}
|
|
@@ -9840,7 +10422,7 @@ async function resolveExperimentForFilesystemOrId(value) {
|
|
|
9840
10422
|
return {
|
|
9841
10423
|
experimentId: session.experimentId,
|
|
9842
10424
|
folder: candidate,
|
|
9843
|
-
label:
|
|
10425
|
+
label: import_node_path6.default.basename(candidate)
|
|
9844
10426
|
};
|
|
9845
10427
|
}
|
|
9846
10428
|
const exp = await resolveExperiment(value);
|
|
@@ -10026,7 +10608,53 @@ var detachIdea = new Command("detach-idea").description("Detach an experiment fr
|
|
|
10026
10608
|
);
|
|
10027
10609
|
output(this, res.data, () => printSuccess("Idea detached."));
|
|
10028
10610
|
});
|
|
10029
|
-
|
|
10611
|
+
function printResultsExplain(body5, samples) {
|
|
10612
|
+
const data = body5.data;
|
|
10613
|
+
console.log("Diagnostic results explanation");
|
|
10614
|
+
for (const arm of data.arms) {
|
|
10615
|
+
console.log(`
|
|
10616
|
+
${arm.name} (${arm.variation_id})`);
|
|
10617
|
+
for (const name of ["visitors", "conversions", "orders", "revenue"]) {
|
|
10618
|
+
const metric = arm[name];
|
|
10619
|
+
if (!metric) continue;
|
|
10620
|
+
console.log(` ${name}`);
|
|
10621
|
+
for (const [label, count2] of Object.entries(metric.counts ?? {}))
|
|
10622
|
+
console.log(` ${label.padEnd(28)} ${count2}`);
|
|
10623
|
+
console.log(
|
|
10624
|
+
` Counted: ${metric.classified_counted ?? "unavailable"} Reported: ${metric.reported ?? "unavailable"} Residual: ${metric.residual ?? "unavailable"}`
|
|
10625
|
+
);
|
|
10626
|
+
if (metric.explanation) console.log(` ${metric.explanation}`);
|
|
10627
|
+
if (samples && metric.samples) {
|
|
10628
|
+
for (const [label, entries] of Object.entries(
|
|
10629
|
+
metric.samples
|
|
10630
|
+
)) {
|
|
10631
|
+
for (const entry of entries)
|
|
10632
|
+
console.log(
|
|
10633
|
+
` ${label}: ${entry.order_key || entry.event_id} ${entry.amount ?? ""} ${entry.currency ?? ""} ${entry.timestamp ?? ""} arm_only=${entry.arm_only ?? false}`
|
|
10634
|
+
);
|
|
10635
|
+
}
|
|
10636
|
+
}
|
|
10637
|
+
}
|
|
10638
|
+
}
|
|
10639
|
+
if (data.order) console.log(`
|
|
10640
|
+
Order trail
|
|
10641
|
+
${JSON.stringify(data.order, null, 2)}`);
|
|
10642
|
+
const others = data.other_variations ?? [];
|
|
10643
|
+
if (others.length > 0) {
|
|
10644
|
+
console.log("\nOther variations (not part of this test)");
|
|
10645
|
+
for (const other of others) console.log(` ${other.variation_id}: ${other.row_count} rows`);
|
|
10646
|
+
}
|
|
10647
|
+
const limitations = data.limitations ?? [];
|
|
10648
|
+
if (limitations.length > 0) {
|
|
10649
|
+
console.log("\nLimitations");
|
|
10650
|
+
for (const limitation of limitations) console.log(` - ${limitation}`);
|
|
10651
|
+
}
|
|
10652
|
+
console.log(`
|
|
10653
|
+
Definitions: ${data.definitions_url}`);
|
|
10654
|
+
}
|
|
10655
|
+
var results = new Command("results").description("Read experiment results and timeseries by ID or slug").argument("<id-or-slug>", EXPERIMENT_REFERENCE_DESCRIPTION).option("--currency <code>", "Currency code for revenue formatting").option("--explain", "Explain raw-event exclusions and residuals").option("--goal <id>", "Goal ID or event key for --explain").option("--order <key>", "Order label trail for --explain").option("--samples", "Show sample identifiers with --explain").action(async function action7(idOrSlug, opts) {
|
|
10656
|
+
if (!opts.explain && (opts.goal || opts.order || opts.samples))
|
|
10657
|
+
fail(this, "--goal, --order and --samples require --explain", EXIT_CODES.validation);
|
|
10030
10658
|
const creds = requireCredentials();
|
|
10031
10659
|
const spinner = await createSpinner("Fetching experiment results...");
|
|
10032
10660
|
try {
|
|
@@ -10035,7 +10663,7 @@ var results = new Command("results").description("Read experiment results and ti
|
|
|
10035
10663
|
spinner?.fail(`Experiment "${idOrSlug}" not found`);
|
|
10036
10664
|
fail(this, `Experiment "${idOrSlug}" not found.`, EXIT_CODES.validation);
|
|
10037
10665
|
}
|
|
10038
|
-
const res = await getExperimentResults(creds, exp.id, { currency: opts.currency });
|
|
10666
|
+
const res = opts.explain ? await getExperimentResultsExplain(creds, exp.id, opts) : await getExperimentResults(creds, exp.id, { currency: opts.currency });
|
|
10039
10667
|
if (!res.ok || !res.data) {
|
|
10040
10668
|
spinner?.fail("Failed to fetch experiment results");
|
|
10041
10669
|
fail(
|
|
@@ -10049,7 +10677,7 @@ var results = new Command("results").description("Read experiment results and ti
|
|
|
10049
10677
|
output(
|
|
10050
10678
|
this,
|
|
10051
10679
|
res.data,
|
|
10052
|
-
() => printExperimentResults(res.data, opts.currency)
|
|
10680
|
+
() => opts.explain ? printResultsExplain(res.data, Boolean(opts.samples)) : printExperimentResults(res.data, opts.currency)
|
|
10053
10681
|
);
|
|
10054
10682
|
} catch (error) {
|
|
10055
10683
|
spinner?.fail("Failed to fetch experiment results");
|
|
@@ -10132,7 +10760,7 @@ function parseHttpDestination(value) {
|
|
|
10132
10760
|
function readCreateJson(command, reference) {
|
|
10133
10761
|
const file = reference.startsWith("@") ? reference.slice(1) : reference;
|
|
10134
10762
|
try {
|
|
10135
|
-
const parsed = JSON.parse(
|
|
10763
|
+
const parsed = JSON.parse(import_node_fs8.default.readFileSync(import_node_path6.default.resolve(process.cwd(), file), "utf8"));
|
|
10136
10764
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
10137
10765
|
throw new Error("expected an object");
|
|
10138
10766
|
return parsed;
|
|
@@ -10168,8 +10796,8 @@ function normalizeNewGoalKey(key) {
|
|
|
10168
10796
|
return normalized === "page" ? "page_id" : normalized;
|
|
10169
10797
|
}
|
|
10170
10798
|
function validateNewGoal(command, value, body5) {
|
|
10171
|
-
const invalid = (
|
|
10172
|
-
const
|
|
10799
|
+
const invalid = (reason2) => fail(command, `Invalid --goal "new:${value}"; ${reason2}`, EXIT_CODES.validation);
|
|
10800
|
+
const text2 = (field) => {
|
|
10173
10801
|
const raw = body5[field];
|
|
10174
10802
|
return typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
10175
10803
|
};
|
|
@@ -10179,12 +10807,12 @@ function validateNewGoal(command, value, body5) {
|
|
|
10179
10807
|
key === "capture" || key === "filter" ? `${key} is not expressible inline; create the goal with \`apex goals create\` and pass its id` : `unknown field "${key}"; inline fields are ${INLINE_GOAL_FIELDS.join(", ")}`
|
|
10180
10808
|
);
|
|
10181
10809
|
}
|
|
10182
|
-
const type =
|
|
10183
|
-
const name =
|
|
10810
|
+
const type = text2("type");
|
|
10811
|
+
const name = text2("name");
|
|
10184
10812
|
if (!type || !name) invalid("type and name are required");
|
|
10185
10813
|
if (!GOAL_TYPES.includes(type)) invalid(`type must be one of ${GOAL_TYPES.join(", ")}`);
|
|
10186
10814
|
if (name.length > 255) invalid("name must be 255 characters or fewer");
|
|
10187
|
-
const measurement =
|
|
10815
|
+
const measurement = text2("measurement") ?? "conversion";
|
|
10188
10816
|
if (!GOAL_MEASUREMENTS.includes(measurement))
|
|
10189
10817
|
invalid(`measurement must be one of ${GOAL_MEASUREMENTS.join(", ")}`);
|
|
10190
10818
|
if (body5.count_once !== void 0) {
|
|
@@ -10192,21 +10820,21 @@ function validateNewGoal(command, value, body5) {
|
|
|
10192
10820
|
if (flag !== "true" && flag !== "false") invalid("count-once must be true or false");
|
|
10193
10821
|
body5.count_once = flag === "true";
|
|
10194
10822
|
}
|
|
10195
|
-
if (type === "click" && !
|
|
10823
|
+
if (type === "click" && !text2("selector") && !text2("text_match"))
|
|
10196
10824
|
invalid("click goals need selector=<css> or text-match=<text>");
|
|
10197
|
-
if (type === "pageview" && !
|
|
10825
|
+
if (type === "pageview" && !text2("page_id") && !text2("url_pattern"))
|
|
10198
10826
|
invalid("pageview goals need page=<page-id> or url-pattern=<pattern>");
|
|
10199
|
-
if (type === "custom" && !
|
|
10200
|
-
const eventName =
|
|
10827
|
+
if (type === "custom" && !text2("event_name")) invalid("custom goals need event-name=<name>");
|
|
10828
|
+
const eventName = text2("event_name");
|
|
10201
10829
|
if (eventName && !GOAL_EVENT_NAME.test(eventName))
|
|
10202
10830
|
invalid("event-name must be 1-120 letters, numbers, dots, colons, underscores, or hyphens");
|
|
10203
|
-
if ((type === "custom" || type === "revenue") &&
|
|
10831
|
+
if ((type === "custom" || type === "revenue") && text2("page_id"))
|
|
10204
10832
|
invalid(`page is not supported for ${type} goals`);
|
|
10205
|
-
const pageId =
|
|
10833
|
+
const pageId = text2("page_id");
|
|
10206
10834
|
if (pageId && !GOAL_UUID.test(pageId))
|
|
10207
10835
|
invalid("page must be a page UUID (list them with `apex pages list`)");
|
|
10208
|
-
const valueProperty =
|
|
10209
|
-
const valueUnit =
|
|
10836
|
+
const valueProperty = text2("value_property");
|
|
10837
|
+
const valueUnit = text2("value_unit");
|
|
10210
10838
|
if (measurement !== "value" && (valueProperty || valueUnit))
|
|
10211
10839
|
invalid("value-property and value-unit need measurement=value");
|
|
10212
10840
|
if (type === "revenue" && valueProperty)
|
|
@@ -10730,7 +11358,7 @@ var pullFilesystem = new Command("pull").description("Pull an experiment as an e
|
|
|
10730
11358
|
{
|
|
10731
11359
|
folder,
|
|
10732
11360
|
experimentId: exp.id,
|
|
10733
|
-
files: res.data.files.map((file) =>
|
|
11361
|
+
files: res.data.files.map((file) => import_node_path6.default.join(folder, file.path))
|
|
10734
11362
|
},
|
|
10735
11363
|
() => {
|
|
10736
11364
|
spinner.succeed(`Pulled ${exp.name}`);
|
|
@@ -10742,6 +11370,252 @@ var pullFilesystem = new Command("pull").description("Pull an experiment as an e
|
|
|
10742
11370
|
throw error;
|
|
10743
11371
|
}
|
|
10744
11372
|
});
|
|
11373
|
+
var experimentStatus = new Command("status").description(
|
|
11374
|
+
"Read experiment context, local changes, QA, and handover next steps without writing"
|
|
11375
|
+
).argument("[folder-or-id]", "Pulled folder, experiment ID, slug, or test key", ".").action(async function(target) {
|
|
11376
|
+
const creds = requireCredentials();
|
|
11377
|
+
const candidate = folderPath(target);
|
|
11378
|
+
const isFolder = import_node_fs8.default.existsSync(candidate) && import_node_fs8.default.statSync(candidate).isDirectory();
|
|
11379
|
+
const hasApexDir = isFolder && import_node_fs8.default.existsSync(import_node_path6.default.join(candidate, ".apex"));
|
|
11380
|
+
if (isFolder && !hasApexDir && target === ".") {
|
|
11381
|
+
fail(this, "This folder is not a pulled experiment.", EXIT_CODES.validation, {
|
|
11382
|
+
code: "validation_failed",
|
|
11383
|
+
next_steps: [
|
|
11384
|
+
{
|
|
11385
|
+
command: "apex experiments status <experiment-id-or-test-key>",
|
|
11386
|
+
why: "Read the saved experiment without a local folder."
|
|
11387
|
+
},
|
|
11388
|
+
{
|
|
11389
|
+
command: "apex experiments pull <experiment-id-or-test-key> --out <folder>",
|
|
11390
|
+
why: "Pull the experiment to compare local files before handover."
|
|
11391
|
+
}
|
|
11392
|
+
]
|
|
11393
|
+
});
|
|
11394
|
+
}
|
|
11395
|
+
const session = hasApexDir ? readFilesystemSession(candidate) : null;
|
|
11396
|
+
if (session && new URL(session.baseUrl).origin !== new URL(creds.baseUrl).origin) {
|
|
11397
|
+
fail(
|
|
11398
|
+
this,
|
|
11399
|
+
"This folder was pulled from a different base URL. Select that base URL before inspecting it.",
|
|
11400
|
+
EXIT_CODES.validation
|
|
11401
|
+
);
|
|
11402
|
+
}
|
|
11403
|
+
const response = session ? await getExperiment(creds, session.experimentId) : null;
|
|
11404
|
+
if (response && (!response.ok || !response.data)) {
|
|
11405
|
+
throw new CliError(
|
|
11406
|
+
response.error ?? "Failed to read experiment",
|
|
11407
|
+
classifyStatus(response.status),
|
|
11408
|
+
response.errorDetails,
|
|
11409
|
+
apiFailureOf(response)
|
|
11410
|
+
);
|
|
11411
|
+
}
|
|
11412
|
+
const experiment = response?.data ?? await resolveExperiment(target);
|
|
11413
|
+
if (!experiment) fail(this, "Experiment not found.");
|
|
11414
|
+
const snapshot = await getExperimentFilesystem(creds, experiment.id);
|
|
11415
|
+
if (!snapshot.ok || !snapshot.data) {
|
|
11416
|
+
throw new CliError(
|
|
11417
|
+
snapshot.error ?? "Failed to read experiment filesystem",
|
|
11418
|
+
classifyStatus(snapshot.status),
|
|
11419
|
+
snapshot.errorDetails,
|
|
11420
|
+
apiFailureOf(snapshot)
|
|
11421
|
+
);
|
|
11422
|
+
}
|
|
11423
|
+
let versions = null;
|
|
11424
|
+
if (session) {
|
|
11425
|
+
const localFiles = listExperimentFilesystemFiles(candidate, session);
|
|
11426
|
+
const localHashes = new Map(localFiles.map((file) => [file.path, file.sha256]));
|
|
11427
|
+
for (const name of SERVER_OWNED_EXPERIMENT_FILES) {
|
|
11428
|
+
const file = import_node_path6.default.join(candidate, name);
|
|
11429
|
+
if (import_node_fs8.default.existsSync(file)) localHashes.set(name, sha256Text(import_node_fs8.default.readFileSync(file, "utf8")));
|
|
11430
|
+
}
|
|
11431
|
+
const paths = [.../* @__PURE__ */ new Set([...Object.keys(session.files), ...localHashes.keys()])].sort();
|
|
11432
|
+
const files = paths.map((file) => {
|
|
11433
|
+
const local = localHashes.get(file) ?? null;
|
|
11434
|
+
const pulled = session.files[file]?.sha256 ?? null;
|
|
11435
|
+
return {
|
|
11436
|
+
path: file,
|
|
11437
|
+
state: !local ? "deleted" : !pulled ? "added" : local === pulled ? "unchanged" : "modified",
|
|
11438
|
+
local_sha256: local,
|
|
11439
|
+
pulled_sha256: pulled
|
|
11440
|
+
};
|
|
11441
|
+
});
|
|
11442
|
+
const plannedFiles = await rewriteExperimentFilesystemAssets({
|
|
11443
|
+
files: localFiles,
|
|
11444
|
+
folder: candidate,
|
|
11445
|
+
baseUrl: creds.baseUrl,
|
|
11446
|
+
enabled: true,
|
|
11447
|
+
upload: null,
|
|
11448
|
+
assets: session.assets ?? {},
|
|
11449
|
+
credentials: creds
|
|
11450
|
+
});
|
|
11451
|
+
const plan = await planExperimentFilesystem(creds, experiment.id, { files: plannedFiles });
|
|
11452
|
+
if (!plan.ok || !plan.data) {
|
|
11453
|
+
throw new CliError(
|
|
11454
|
+
plan.error ?? "Failed to read saved state",
|
|
11455
|
+
classifyStatus(plan.status),
|
|
11456
|
+
plan.errorDetails,
|
|
11457
|
+
apiFailureOf(plan)
|
|
11458
|
+
);
|
|
11459
|
+
}
|
|
11460
|
+
versions = {
|
|
11461
|
+
files,
|
|
11462
|
+
saved: {
|
|
11463
|
+
would_change: plan.data.changes.length > 0,
|
|
11464
|
+
invalidates_qa: plan.data.invalidatesQa,
|
|
11465
|
+
blocking_issue_count: plan.data.blockingIssues?.filter((issue) => issue.severity === "blocking").length ?? 0,
|
|
11466
|
+
valid: plan.data.ok,
|
|
11467
|
+
errors: plan.data.errors,
|
|
11468
|
+
warnings: plan.data.warnings
|
|
11469
|
+
}
|
|
11470
|
+
};
|
|
11471
|
+
}
|
|
11472
|
+
const qaFile = snapshot.data.files.find((file) => file.path === "qa.json");
|
|
11473
|
+
let qaSnapshot = null;
|
|
11474
|
+
let qaUnreadable = false;
|
|
11475
|
+
if (qaFile) {
|
|
11476
|
+
try {
|
|
11477
|
+
qaSnapshot = JSON.parse(qaFile.content);
|
|
11478
|
+
} catch {
|
|
11479
|
+
qaUnreadable = true;
|
|
11480
|
+
}
|
|
11481
|
+
}
|
|
11482
|
+
const qa3 = {
|
|
11483
|
+
...summarizeHandoverQa(qaSnapshot),
|
|
11484
|
+
ready_version_id: experiment.ready_version_id ?? null
|
|
11485
|
+
};
|
|
11486
|
+
const shopId = experiment.shop_id ?? creds.resolvedContext?.shopId ?? null;
|
|
11487
|
+
let org = {
|
|
11488
|
+
id: creds.resolvedContext?.orgId ?? null,
|
|
11489
|
+
name: null
|
|
11490
|
+
};
|
|
11491
|
+
let shop = { id: shopId, name: null };
|
|
11492
|
+
const notes = [];
|
|
11493
|
+
if (qaUnreadable) notes.push("The saved qa.json could not be parsed; QA counts are unknown.");
|
|
11494
|
+
if (!session)
|
|
11495
|
+
notes.push(
|
|
11496
|
+
"Versions are unavailable without a pulled folder. Pull the experiment to compare local files."
|
|
11497
|
+
);
|
|
11498
|
+
if (creds.credentialKind === "user") {
|
|
11499
|
+
const context = await getAccountContext(creds);
|
|
11500
|
+
const owner = context.data?.data?.orgs?.find(
|
|
11501
|
+
(item) => item.shops?.some((item2) => item2.id === shopId)
|
|
11502
|
+
);
|
|
11503
|
+
if (owner) {
|
|
11504
|
+
org = { id: owner.id, name: owner.name };
|
|
11505
|
+
shop = { id: shopId, name: owner.shops.find((item) => item.id === shopId)?.name ?? null };
|
|
11506
|
+
} else notes.push("Organization and shop names are unavailable from account context.");
|
|
11507
|
+
} else
|
|
11508
|
+
notes.push(
|
|
11509
|
+
"Shop credentials do not expose account context; organization and shop names are unknown."
|
|
11510
|
+
);
|
|
11511
|
+
const nextSteps = [];
|
|
11512
|
+
const ref = shellQuote(session ? target : experiment.id);
|
|
11513
|
+
if (!session)
|
|
11514
|
+
nextSteps.push({
|
|
11515
|
+
command: `apex experiments pull ${ref} --out <folder>`,
|
|
11516
|
+
why: "Pull files to verify the local version."
|
|
11517
|
+
});
|
|
11518
|
+
if (versions && (versions.files.some((file) => file.state !== "unchanged") || versions.saved.would_change || !versions.saved.valid || versions.saved.blocking_issue_count > 0)) {
|
|
11519
|
+
nextSteps.push(
|
|
11520
|
+
{
|
|
11521
|
+
command: `apex experiments plan ${ref} --json`,
|
|
11522
|
+
why: "Review local changes, saved differences, and blocking issues."
|
|
11523
|
+
},
|
|
11524
|
+
{
|
|
11525
|
+
command: `apex experiments apply ${ref} --json`,
|
|
11526
|
+
why: "Save the reviewed changes after resolving blockers and any QA invalidation acknowledgment."
|
|
11527
|
+
}
|
|
11528
|
+
);
|
|
11529
|
+
}
|
|
11530
|
+
if (qa3.required_done === null || qa3.required_total === null || qa3.required_done < qa3.required_total || qa3.blockers === null || qa3.blockers.length > 0) {
|
|
11531
|
+
nextSteps.push(
|
|
11532
|
+
{
|
|
11533
|
+
command: `apex experiments qa status ${ref} --json`,
|
|
11534
|
+
why: "Inspect missing required checks and evidence before recording QA results. This separate command can create a QA run."
|
|
11535
|
+
},
|
|
11536
|
+
{
|
|
11537
|
+
command: `apex experiments qa check ${ref} --item <item-id> --status pass`,
|
|
11538
|
+
why: "Record each missing required check; attach supporting evidence with apex experiments qa evidence."
|
|
11539
|
+
}
|
|
11540
|
+
);
|
|
11541
|
+
} else if (!qa3.approval_present) {
|
|
11542
|
+
nextSteps.push({
|
|
11543
|
+
command: `apex experiments qa approve ${ref}`,
|
|
11544
|
+
why: "Required QA checks are complete; approve after verifying the saved version."
|
|
11545
|
+
});
|
|
11546
|
+
}
|
|
11547
|
+
const locallyChanged = versions !== null && (versions.files.some((file) => file.state !== "unchanged") || versions.saved.would_change || !versions.saved.valid || versions.saved.blocking_issue_count > 0);
|
|
11548
|
+
const qaComplete = qa3.required_done !== null && qa3.required_total !== null && qa3.required_done === qa3.required_total && qa3.blockers !== null && qa3.blockers.length === 0;
|
|
11549
|
+
const readyVersionKnown = typeof qa3.ready_version_id === "string" && qa3.ready_version_id.length > 0;
|
|
11550
|
+
const approvalCurrent = qa3.approval_present && readyVersionKnown && !locallyChanged;
|
|
11551
|
+
const lifecycleReady = experiment.status === "ready_to_launch";
|
|
11552
|
+
const readyForHandover = lifecycleReady && approvalCurrent && qaComplete;
|
|
11553
|
+
if (approvalCurrent && qaComplete && !lifecycleReady)
|
|
11554
|
+
notes.push(`The experiment status is ${experiment.status}; handover needs ready_to_launch.`);
|
|
11555
|
+
if (qa3.approval_present && !readyVersionKnown && qaComplete && !locallyChanged)
|
|
11556
|
+
nextSteps.push({
|
|
11557
|
+
command: `apex experiments qa approve ${ref}`,
|
|
11558
|
+
why: "The approval is not bound to a saved version; approve again to bind it before handover."
|
|
11559
|
+
});
|
|
11560
|
+
if (readyForHandover)
|
|
11561
|
+
nextSteps.push({
|
|
11562
|
+
command: "launch in the Apex dashboard",
|
|
11563
|
+
why: "QA is approved for the saved version. Starting a test is a human decision."
|
|
11564
|
+
});
|
|
11565
|
+
if (!versions)
|
|
11566
|
+
notes.push(
|
|
11567
|
+
"Without a pulled folder, readiness reflects the saved version only; local edits elsewhere are not visible."
|
|
11568
|
+
);
|
|
11569
|
+
const data = {
|
|
11570
|
+
context: {
|
|
11571
|
+
org,
|
|
11572
|
+
shop,
|
|
11573
|
+
credential_kind: creds.credentialKind ?? "unknown",
|
|
11574
|
+
base_url: creds.baseUrl
|
|
11575
|
+
},
|
|
11576
|
+
experiment: {
|
|
11577
|
+
id: experiment.id,
|
|
11578
|
+
test_key: experiment.test_key ?? null,
|
|
11579
|
+
name: experiment.name,
|
|
11580
|
+
status: experiment.status,
|
|
11581
|
+
dashboard_url: buildDashboardUrl(creds.baseUrl, shopId, experiment.id)
|
|
11582
|
+
},
|
|
11583
|
+
versions,
|
|
11584
|
+
qa: { ...qa3, approval_current: approvalCurrent },
|
|
11585
|
+
next_steps: nextSteps,
|
|
11586
|
+
ready_for_handover: readyForHandover,
|
|
11587
|
+
notes
|
|
11588
|
+
};
|
|
11589
|
+
output(this, data, () => {
|
|
11590
|
+
console.log(
|
|
11591
|
+
`Context: ${org.name ?? org.id ?? "unknown organization"} / ${shop.name ?? shop.id ?? "unknown shop"}
|
|
11592
|
+
Credential: ${data.context.credential_kind}
|
|
11593
|
+
Base URL: ${creds.baseUrl}`
|
|
11594
|
+
);
|
|
11595
|
+
console.log(
|
|
11596
|
+
`Experiment: ${experiment.test_key ?? experiment.id} / ${experiment.name} (${experiment.status})
|
|
11597
|
+
Dashboard: ${data.experiment.dashboard_url ?? "unknown"}`
|
|
11598
|
+
);
|
|
11599
|
+
console.log("Versions:");
|
|
11600
|
+
if (versions) {
|
|
11601
|
+
for (const file of versions.files) console.log(` ${file.state}: ${file.path}`);
|
|
11602
|
+
console.log(
|
|
11603
|
+
` Saved: would change ${versions.saved.would_change}; invalidates QA ${versions.saved.invalidates_qa}; blocking issues ${versions.saved.blocking_issue_count}`
|
|
11604
|
+
);
|
|
11605
|
+
for (const message of [...versions.saved.errors, ...versions.saved.warnings])
|
|
11606
|
+
console.log(` ${message}`);
|
|
11607
|
+
} else console.log(" unknown (no pulled folder)");
|
|
11608
|
+
console.log(
|
|
11609
|
+
`QA: ${qa3.run_status ?? "unknown"}; required ${qa3.required_done ?? "unknown"}/${qa3.required_total ?? "unknown"}; approval ${qa3.approval_present}; current for this version: ${approvalCurrent}`
|
|
11610
|
+
);
|
|
11611
|
+
for (const blocker of qa3.blockers ?? []) console.log(` Blocker: ${blocker}`);
|
|
11612
|
+
console.log(`Ready for handover: ${readyForHandover ? "yes" : "no"}
|
|
11613
|
+
Next steps:`);
|
|
11614
|
+
for (const step of nextSteps) console.log(` ${step.command}
|
|
11615
|
+
${step.why}`);
|
|
11616
|
+
for (const note of notes) console.log(` ${note}`);
|
|
11617
|
+
});
|
|
11618
|
+
});
|
|
10745
11619
|
var planFilesystem = new Command("plan").description("Plan experiment filesystem changes").argument("[folder]", "Experiment filesystem folder", ".").action(async function action14(folderArg) {
|
|
10746
11620
|
const creds = requireCredentials();
|
|
10747
11621
|
try {
|
|
@@ -10788,7 +11662,10 @@ var planFilesystem = new Command("plan").description("Plan experiment filesystem
|
|
|
10788
11662
|
fail(this, error instanceof Error ? error.message : String(error));
|
|
10789
11663
|
}
|
|
10790
11664
|
});
|
|
10791
|
-
var applyFilesystem = new Command("apply").option(
|
|
11665
|
+
var applyFilesystem = new Command("apply").option(
|
|
11666
|
+
"--confirm-targeting-replace",
|
|
11667
|
+
"Confirm that attaching a Page may replace URL targeting authored through the API"
|
|
11668
|
+
).option("--sync", "Refresh local authored files with the stored server bytes").description("Apply experiment filesystem changes to Apex").argument("[folder]", "Experiment filesystem folder", ".").option("--accept-qa-invalidation", "Acknowledge that applying changes invalidates QA approval").addOption(new Option("-f, --force", "Deprecated alias for --accept-qa-invalidation").hideHelp()).option("--no-asset-upload", "Keep inline and local image references unchanged").action(async function action15(folderArg, opts) {
|
|
10792
11669
|
const creds = requireCredentials();
|
|
10793
11670
|
try {
|
|
10794
11671
|
const folder = folderPath(folderArg);
|
|
@@ -10822,13 +11699,19 @@ var applyFilesystem = new Command("apply").option("--sync", "Refresh local autho
|
|
|
10822
11699
|
if (!shouldUseJson(this)) printFilesystemPlan(planRes.data);
|
|
10823
11700
|
if (opts.assetUpload === false && planRes.data.blockingIssues?.some((issue) => /inline|hosted asset/i.test(issue.message))) {
|
|
10824
11701
|
process.stderr.write(
|
|
10825
|
-
`Retry with hosted asset upload: apex experiments apply ${folderArg}
|
|
11702
|
+
`Retry with hosted asset upload: apex experiments apply ${shellQuote(folderArg)}
|
|
10826
11703
|
`
|
|
10827
11704
|
);
|
|
10828
11705
|
}
|
|
10829
11706
|
fail(this, blockingIssueSummary(planRes.data.blockingIssues), EXIT_CODES.validation, {
|
|
10830
11707
|
errors: planRes.data.errors,
|
|
10831
11708
|
plan: planRes.data,
|
|
11709
|
+
next_steps: opts.assetUpload === false && planRes.data.blockingIssues?.some((issue) => /inline|hosted asset/i.test(issue.message)) ? [
|
|
11710
|
+
{
|
|
11711
|
+
command: `apex experiments apply ${shellQuote(folderArg)}`,
|
|
11712
|
+
why: "Retry with hosted asset upload."
|
|
11713
|
+
}
|
|
11714
|
+
] : [],
|
|
10832
11715
|
requiredAcknowledgment: planRes.data.invalidatesQa ? "accept-qa-invalidation" : null
|
|
10833
11716
|
});
|
|
10834
11717
|
}
|
|
@@ -10873,7 +11756,31 @@ var applyFilesystem = new Command("apply").option("--sync", "Refresh local autho
|
|
|
10873
11756
|
}
|
|
10874
11757
|
}
|
|
10875
11758
|
const spinner = process.stderr.isTTY ? await createSpinner("Applying experiment filesystem...") : null;
|
|
10876
|
-
|
|
11759
|
+
let res = await applyExperimentFilesystem(creds, session.experimentId, {
|
|
11760
|
+
files,
|
|
11761
|
+
...opts.confirmTargetingReplace ? { confirmTargetingReplace: true } : {}
|
|
11762
|
+
});
|
|
11763
|
+
if (!res.ok && res.status === 409 && res.errorDetails?.code === "confirm_targeting_replace_required" && !opts.confirmTargetingReplace) {
|
|
11764
|
+
spinner?.stop();
|
|
11765
|
+
const ok = await confirmApply(
|
|
11766
|
+
"Attaching this Page replaces URL targeting authored through the API. Continue?"
|
|
11767
|
+
);
|
|
11768
|
+
if (ok === null) {
|
|
11769
|
+
confirmationRequired(
|
|
11770
|
+
this,
|
|
11771
|
+
"Attaching this Page replaces URL targeting authored through the API and requires confirmation.",
|
|
11772
|
+
`apex experiments apply ${folderArg} --confirm-targeting-replace`
|
|
11773
|
+
);
|
|
11774
|
+
}
|
|
11775
|
+
if (!ok) {
|
|
11776
|
+
output(this, { aborted: true }, () => console.log("Aborted."));
|
|
11777
|
+
return;
|
|
11778
|
+
}
|
|
11779
|
+
res = await applyExperimentFilesystem(creds, session.experimentId, {
|
|
11780
|
+
files,
|
|
11781
|
+
confirmTargetingReplace: true
|
|
11782
|
+
});
|
|
11783
|
+
}
|
|
10877
11784
|
if (!res.ok || !res.data?.applied) {
|
|
10878
11785
|
spinner?.fail("Failed to apply experiment filesystem");
|
|
10879
11786
|
fail(this, res.error ?? "Apply failed", classifyStatus(res.status), {
|
|
@@ -11154,8 +12061,8 @@ var qaReopen = new Command("reopen").description("Reopen QA and clear scheduled
|
|
|
11154
12061
|
});
|
|
11155
12062
|
function parseJsonValue(value) {
|
|
11156
12063
|
const source = value.startsWith("@") ? value.slice(1) : value;
|
|
11157
|
-
const
|
|
11158
|
-
return JSON.parse(
|
|
12064
|
+
const text2 = value.startsWith("@") || import_node_fs8.default.existsSync(source) ? import_node_fs8.default.readFileSync(import_node_path6.default.resolve(process.cwd(), source), "utf8") : value;
|
|
12065
|
+
return JSON.parse(text2);
|
|
11159
12066
|
}
|
|
11160
12067
|
function collectFile(value, previous = []) {
|
|
11161
12068
|
return [...previous, value];
|
|
@@ -11207,7 +12114,7 @@ var qaEvidence = new Command("evidence").description("Attach screenshot jobs or
|
|
|
11207
12114
|
classifyStatus(current.status),
|
|
11208
12115
|
current.errorDetails
|
|
11209
12116
|
);
|
|
11210
|
-
for (const
|
|
12117
|
+
for (const record3 of current.data.run.evidenceRecords) previousIds.add(record3.id);
|
|
11211
12118
|
}
|
|
11212
12119
|
let response;
|
|
11213
12120
|
if (opts.job) {
|
|
@@ -11226,12 +12133,12 @@ var qaEvidence = new Command("evidence").description("Attach screenshot jobs or
|
|
|
11226
12133
|
const variationId = resolveEvidenceVariation(experiment, opts.variation);
|
|
11227
12134
|
const form = new FormData();
|
|
11228
12135
|
for (const filePath of opts.file) {
|
|
11229
|
-
const absolutePath =
|
|
11230
|
-
if (!
|
|
11231
|
-
const bytes =
|
|
11232
|
-
const extension =
|
|
12136
|
+
const absolutePath = import_node_path6.default.resolve(process.cwd(), filePath);
|
|
12137
|
+
if (!import_node_fs8.default.existsSync(absolutePath)) throw new Error(`Evidence file not found: ${filePath}`);
|
|
12138
|
+
const bytes = import_node_fs8.default.readFileSync(absolutePath);
|
|
12139
|
+
const extension = import_node_path6.default.extname(absolutePath).toLowerCase();
|
|
11233
12140
|
const mimeType = extension === ".png" ? "image/png" : extension === ".jpg" || extension === ".jpeg" ? "image/jpeg" : extension === ".webp" ? "image/webp" : "application/octet-stream";
|
|
11234
|
-
form.append("files", new File([bytes],
|
|
12141
|
+
form.append("files", new File([bytes], import_node_path6.default.basename(absolutePath), { type: mimeType }));
|
|
11235
12142
|
}
|
|
11236
12143
|
if (opts.label) form.append("label", opts.label);
|
|
11237
12144
|
const device = normalizeEvidenceDevice(opts.device);
|
|
@@ -11252,7 +12159,7 @@ var qaEvidence = new Command("evidence").description("Attach screenshot jobs or
|
|
|
11252
12159
|
const run3 = response.data.run;
|
|
11253
12160
|
const data = opts.summary ? qaSummary(
|
|
11254
12161
|
run3,
|
|
11255
|
-
run3.evidenceRecords.filter((
|
|
12162
|
+
run3.evidenceRecords.filter((record3) => !previousIds.has(record3.id))
|
|
11256
12163
|
) : response.data;
|
|
11257
12164
|
output(this, data, () => {
|
|
11258
12165
|
if (opts.summary) process.stdout.write(`${JSON.stringify(data, null, 2)}
|
|
@@ -11278,8 +12185,8 @@ function shellQuote(value) {
|
|
|
11278
12185
|
if (value.length > 0 && /^[A-Za-z0-9_@%+=:,./-]+$/.test(value)) return value;
|
|
11279
12186
|
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
11280
12187
|
}
|
|
11281
|
-
function isTransientResumeStatus(
|
|
11282
|
-
return
|
|
12188
|
+
function isTransientResumeStatus(status3) {
|
|
12189
|
+
return status3 === 429 || status3 !== void 0 && status3 >= 500;
|
|
11283
12190
|
}
|
|
11284
12191
|
function screenshotTimeoutMessage(job, experimentRef, out, seconds, flags = {}) {
|
|
11285
12192
|
const progress = job.progress ? ` Last progress: ${job.progress.captured}/${job.progress.total} captured.` : "";
|
|
@@ -11563,8 +12470,8 @@ var screenshots = new Command("screenshots").description(
|
|
|
11563
12470
|
}
|
|
11564
12471
|
if (job.status === "failed")
|
|
11565
12472
|
fail(this, `Screenshot job failed: ${job.error ?? "unknown error"}`);
|
|
11566
|
-
const outputDir =
|
|
11567
|
-
|
|
12473
|
+
const outputDir = import_node_path6.default.resolve(process.cwd(), opts.out);
|
|
12474
|
+
import_node_fs8.default.mkdirSync(outputDir, { recursive: true });
|
|
11568
12475
|
const rows = [];
|
|
11569
12476
|
const errors = [];
|
|
11570
12477
|
const usedFilenames = /* @__PURE__ */ new Set();
|
|
@@ -11622,8 +12529,8 @@ var screenshots = new Command("screenshots").description(
|
|
|
11622
12529
|
try {
|
|
11623
12530
|
const response = await fetch(url);
|
|
11624
12531
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
11625
|
-
await
|
|
11626
|
-
|
|
12532
|
+
await import_node_fs8.default.promises.writeFile(
|
|
12533
|
+
import_node_path6.default.join(outputDir, filename),
|
|
11627
12534
|
Buffer.from(await response.arrayBuffer())
|
|
11628
12535
|
);
|
|
11629
12536
|
workerResults.push({ index, row: [variation.name, device, filename] });
|
|
@@ -11650,7 +12557,7 @@ var screenshots = new Command("screenshots").description(
|
|
|
11650
12557
|
printTable(["VARIATION", "DEVICE", "FILE"], rows, [24, 20, 48]);
|
|
11651
12558
|
});
|
|
11652
12559
|
});
|
|
11653
|
-
var experimentsCommand = new Command("experiments").alias("exp").description("Manage experiments").addCommand(list).addCommand(get).addCommand(results).addCommand(update).addCommand(create).addCommand(attachIdea).addCommand(detachIdea).addCommand(pause).addCommand(complete).addCommand(del).addCommand(pullFilesystem).addCommand(planFilesystem).addCommand(applyFilesystem).addCommand(screenshots).addCommand(qa).addHelpText(
|
|
12560
|
+
var experimentsCommand = new Command("experiments").alias("exp").description("Manage experiments").addCommand(list).addCommand(get).addCommand(results).addCommand(update).addCommand(create).addCommand(attachIdea).addCommand(detachIdea).addCommand(pause).addCommand(complete).addCommand(del).addCommand(pullFilesystem).addCommand(experimentStatus).addCommand(planFilesystem).addCommand(applyFilesystem).addCommand(screenshots).addCommand(qa).addHelpText(
|
|
11654
12561
|
"after",
|
|
11655
12562
|
`
|
|
11656
12563
|
Examples:
|
|
@@ -11663,9 +12570,9 @@ Examples:
|
|
|
11663
12570
|
|
|
11664
12571
|
// src/commands/push.ts
|
|
11665
12572
|
init_source();
|
|
11666
|
-
var
|
|
11667
|
-
var
|
|
11668
|
-
var
|
|
12573
|
+
var import_node_fs9 = __toESM(require("node:fs"));
|
|
12574
|
+
var import_node_os7 = __toESM(require("node:os"));
|
|
12575
|
+
var import_node_path7 = __toESM(require("node:path"));
|
|
11669
12576
|
var import_node_url2 = require("node:url");
|
|
11670
12577
|
function toApiBody(exp) {
|
|
11671
12578
|
return {
|
|
@@ -11706,7 +12613,7 @@ async function rewriteExperimentAssets(exp, rewrite, baseDir) {
|
|
|
11706
12613
|
};
|
|
11707
12614
|
}
|
|
11708
12615
|
async function importConfigModule(absPath) {
|
|
11709
|
-
const ext =
|
|
12616
|
+
const ext = import_node_path7.default.extname(absPath).toLowerCase();
|
|
11710
12617
|
if (ext === ".js" || ext === ".mjs" || ext === ".cjs") {
|
|
11711
12618
|
return await import((0, import_node_url2.pathToFileURL)(absPath).href);
|
|
11712
12619
|
}
|
|
@@ -11720,8 +12627,8 @@ async function importConfigModule(absPath) {
|
|
|
11720
12627
|
` + (err instanceof Error ? err.message : String(err))
|
|
11721
12628
|
);
|
|
11722
12629
|
}
|
|
11723
|
-
const tmpDir =
|
|
11724
|
-
const outFile =
|
|
12630
|
+
const tmpDir = import_node_fs9.default.mkdtempSync(import_node_path7.default.join(import_node_os7.default.tmpdir(), "drip-config-"));
|
|
12631
|
+
const outFile = import_node_path7.default.join(tmpDir, "config.mjs");
|
|
11725
12632
|
try {
|
|
11726
12633
|
await esbuild.build({
|
|
11727
12634
|
entryPoints: [absPath],
|
|
@@ -11737,7 +12644,7 @@ async function importConfigModule(absPath) {
|
|
|
11737
12644
|
return await import(href);
|
|
11738
12645
|
} finally {
|
|
11739
12646
|
try {
|
|
11740
|
-
|
|
12647
|
+
import_node_fs9.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
11741
12648
|
} catch {
|
|
11742
12649
|
}
|
|
11743
12650
|
}
|
|
@@ -11758,7 +12665,7 @@ var pushCommand = new Command("push").description("Push experiment definitions f
|
|
|
11758
12665
|
);
|
|
11759
12666
|
}
|
|
11760
12667
|
const spinner = process.stderr.isTTY ? await createSpinner(`Loading config from ${source_default.cyan(configPath)}...`) : null;
|
|
11761
|
-
const absoluteConfigPath =
|
|
12668
|
+
const absoluteConfigPath = import_node_path7.default.resolve(configPath);
|
|
11762
12669
|
let experiments;
|
|
11763
12670
|
try {
|
|
11764
12671
|
const mod = await importConfigModule(absoluteConfigPath);
|
|
@@ -11825,7 +12732,7 @@ var pushCommand = new Command("push").description("Push experiment definitions f
|
|
|
11825
12732
|
return;
|
|
11826
12733
|
}
|
|
11827
12734
|
const assetRewriter = createAssetReferenceRewriter({
|
|
11828
|
-
baseDir:
|
|
12735
|
+
baseDir: import_node_path7.default.dirname(absoluteConfigPath),
|
|
11829
12736
|
baseUrl: creds.baseUrl,
|
|
11830
12737
|
enabled: opts.assetUpload !== false,
|
|
11831
12738
|
upload: async (file) => {
|
|
@@ -11855,7 +12762,7 @@ var pushCommand = new Command("push").description("Push experiment definitions f
|
|
|
11855
12762
|
let body5;
|
|
11856
12763
|
try {
|
|
11857
12764
|
body5 = toApiBody(
|
|
11858
|
-
await rewriteExperimentAssets(exp, assetRewriter,
|
|
12765
|
+
await rewriteExperimentAssets(exp, assetRewriter, import_node_path7.default.dirname(absoluteConfigPath))
|
|
11859
12766
|
);
|
|
11860
12767
|
} catch (error) {
|
|
11861
12768
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -11943,6 +12850,14 @@ var previewCommand = new Command("preview").description(
|
|
|
11943
12850
|
const data = created.data;
|
|
11944
12851
|
output(previewCommand, data, () => {
|
|
11945
12852
|
console.log(source_default.cyan(data.preview_url));
|
|
12853
|
+
if (data.preview_target) {
|
|
12854
|
+
console.log(`Chosen page: ${data.preview_target.url} (${data.preview_target.source})`);
|
|
12855
|
+
if (!data.preview_target.matches_targeting) {
|
|
12856
|
+
console.log(
|
|
12857
|
+
`The test will not show on this page: ${data.preview_target.reason ?? "The page does not match URL targeting."}`
|
|
12858
|
+
);
|
|
12859
|
+
}
|
|
12860
|
+
}
|
|
11946
12861
|
console.log(
|
|
11947
12862
|
data.qa_run.created ? `QA run ${data.qa_run.id} created` : `QA run ${data.qa_run.id} refreshed (${data.qa_run.automated_evidence_count} automated evidence records)`
|
|
11948
12863
|
);
|
|
@@ -11968,7 +12883,7 @@ var previewCommand = new Command("preview").description(
|
|
|
11968
12883
|
const spinner = await createSpinner("Fetching experiment...");
|
|
11969
12884
|
let exp;
|
|
11970
12885
|
if (/^[0-9a-f-]{36}$/i.test(idOrSlug)) {
|
|
11971
|
-
const res = await getExperiment(creds, idOrSlug);
|
|
12886
|
+
const res = await getExperiment(creds, idOrSlug, !opts?.storeUrl);
|
|
11972
12887
|
if (!res.ok) {
|
|
11973
12888
|
spinner.fail(`Experiment "${idOrSlug}" not found`);
|
|
11974
12889
|
fail(
|
|
@@ -11981,7 +12896,7 @@ var previewCommand = new Command("preview").description(
|
|
|
11981
12896
|
exp = res.data;
|
|
11982
12897
|
} else {
|
|
11983
12898
|
const slug = idOrSlug.startsWith("slug:") ? idOrSlug.slice(5) : idOrSlug;
|
|
11984
|
-
const res = await getExperimentBySlug(creds, slug);
|
|
12899
|
+
const res = await getExperimentBySlug(creds, slug, !opts?.storeUrl);
|
|
11985
12900
|
if (!res.ok) {
|
|
11986
12901
|
spinner.fail(`Experiment "${slug}" not found`);
|
|
11987
12902
|
fail(
|
|
@@ -12016,7 +12931,7 @@ var previewCommand = new Command("preview").description(
|
|
|
12016
12931
|
variation = exp.variations.find((v) => !v.is_control) ?? exp.variations[0];
|
|
12017
12932
|
}
|
|
12018
12933
|
spinner.stop();
|
|
12019
|
-
const storeUrl = opts?.storeUrl ?? exp.store_url;
|
|
12934
|
+
const storeUrl = opts?.storeUrl ?? exp.preview_target?.url ?? exp.store_url;
|
|
12020
12935
|
if (!storeUrl) {
|
|
12021
12936
|
fail(
|
|
12022
12937
|
previewCommand,
|
|
@@ -12034,11 +12949,24 @@ var previewCommand = new Command("preview").description(
|
|
|
12034
12949
|
}
|
|
12035
12950
|
output(
|
|
12036
12951
|
previewCommand,
|
|
12037
|
-
{
|
|
12952
|
+
{
|
|
12953
|
+
experimentId: exp.id,
|
|
12954
|
+
variationId: variation.id,
|
|
12955
|
+
previewUrl: previewUrl.toString(),
|
|
12956
|
+
...!opts?.storeUrl && exp.preview_target ? { previewTarget: exp.preview_target } : {}
|
|
12957
|
+
},
|
|
12038
12958
|
() => {
|
|
12039
12959
|
console.log();
|
|
12040
12960
|
console.log(source_default.bold(`Preview: ${exp.name}`));
|
|
12041
12961
|
console.log(`Variation: ${variation.name}${variation.is_control ? " (control)" : ""}`);
|
|
12962
|
+
if (!opts?.storeUrl && exp.preview_target) {
|
|
12963
|
+
console.log(`Chosen page: ${exp.preview_target.url} (${exp.preview_target.source})`);
|
|
12964
|
+
if (!exp.preview_target.matches_targeting) {
|
|
12965
|
+
console.log(
|
|
12966
|
+
`The test will not show on this page: ${exp.preview_target.reason ?? "The page does not match URL targeting."}`
|
|
12967
|
+
);
|
|
12968
|
+
}
|
|
12969
|
+
}
|
|
12042
12970
|
console.log();
|
|
12043
12971
|
console.log(source_default.cyan(previewUrl.toString()));
|
|
12044
12972
|
console.log();
|
|
@@ -12058,25 +12986,25 @@ var previewCommand = new Command("preview").description(
|
|
|
12058
12986
|
|
|
12059
12987
|
// src/commands/harness.ts
|
|
12060
12988
|
init_source();
|
|
12061
|
-
var
|
|
12062
|
-
var
|
|
12989
|
+
var import_node_fs10 = __toESM(require("node:fs"));
|
|
12990
|
+
var import_node_path8 = __toESM(require("node:path"));
|
|
12063
12991
|
var import_node_child_process6 = require("node:child_process");
|
|
12064
12992
|
function slugify(value, fallback2 = "variation") {
|
|
12065
12993
|
const slug = value.normalize("NFKD").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
12066
12994
|
return slug || fallback2;
|
|
12067
12995
|
}
|
|
12068
12996
|
function readJson(filePath) {
|
|
12069
|
-
if (!
|
|
12070
|
-
return JSON.parse(
|
|
12997
|
+
if (!import_node_fs10.default.existsSync(filePath)) return null;
|
|
12998
|
+
return JSON.parse(import_node_fs10.default.readFileSync(filePath, "utf8"));
|
|
12071
12999
|
}
|
|
12072
13000
|
function resolveFolder(folder) {
|
|
12073
|
-
return
|
|
13001
|
+
return import_node_path8.default.resolve(process.cwd(), folder);
|
|
12074
13002
|
}
|
|
12075
13003
|
function experimentConfigPath(folder) {
|
|
12076
|
-
return
|
|
13004
|
+
return import_node_path8.default.join(folder, "experiment.config.json");
|
|
12077
13005
|
}
|
|
12078
13006
|
function apexConfigPath(folder) {
|
|
12079
|
-
return
|
|
13007
|
+
return import_node_path8.default.join(folder, "apex.harness.json");
|
|
12080
13008
|
}
|
|
12081
13009
|
function loadHarnessConfig(folder) {
|
|
12082
13010
|
const experiment = readJson(experimentConfigPath(folder)) ?? {};
|
|
@@ -12085,8 +13013,8 @@ function loadHarnessConfig(folder) {
|
|
|
12085
13013
|
const targetOrigin = experiment.targetOrigin?.replace(/\/+$/, "") ?? "";
|
|
12086
13014
|
const defaultPath = experiment.defaultPath ?? "/";
|
|
12087
13015
|
const targetUrl = targetOrigin ? new URL(defaultPath, targetOrigin).toString() : "";
|
|
12088
|
-
const id = experiment.id ??
|
|
12089
|
-
const variation =
|
|
13016
|
+
const id = experiment.id ?? import_node_path8.default.basename(import_node_path8.default.dirname(folder));
|
|
13017
|
+
const variation = import_node_path8.default.basename(folder);
|
|
12090
13018
|
return {
|
|
12091
13019
|
experiment,
|
|
12092
13020
|
apex: {
|
|
@@ -12121,37 +13049,37 @@ function runAbHarness(args, cwd) {
|
|
|
12121
13049
|
function artifactPaths(folder, experiment) {
|
|
12122
13050
|
const variant = experiment.variant;
|
|
12123
13051
|
if (variant?.endsWith(".bundle.js")) {
|
|
12124
|
-
const jsPath =
|
|
12125
|
-
const cssPath =
|
|
12126
|
-
return { jsPath, cssPath:
|
|
13052
|
+
const jsPath = import_node_path8.default.join(folder, variant.replace(/\.bundle\.js$/, ".js"));
|
|
13053
|
+
const cssPath = import_node_path8.default.join(folder, variant.replace(/\.bundle\.js$/, ".css"));
|
|
13054
|
+
return { jsPath, cssPath: import_node_fs10.default.existsSync(cssPath) ? cssPath : null };
|
|
12127
13055
|
}
|
|
12128
|
-
const buildDir =
|
|
12129
|
-
const files =
|
|
13056
|
+
const buildDir = import_node_path8.default.join(folder, "build");
|
|
13057
|
+
const files = import_node_fs10.default.existsSync(buildDir) ? import_node_fs10.default.readdirSync(buildDir) : [];
|
|
12130
13058
|
const js = files.find(
|
|
12131
13059
|
(file) => file.endsWith(".js") && !file.endsWith(".bundle.js") && !file.endsWith(".min.js")
|
|
12132
13060
|
);
|
|
12133
13061
|
const css = files.find((file) => file.endsWith(".css") && !file.endsWith(".min.css"));
|
|
12134
13062
|
if (!js) throw new Error("Could not find harness JS artifact in build/");
|
|
12135
13063
|
return {
|
|
12136
|
-
jsPath:
|
|
12137
|
-
cssPath: css ?
|
|
13064
|
+
jsPath: import_node_path8.default.join(buildDir, js),
|
|
13065
|
+
cssPath: css ? import_node_path8.default.join(buildDir, css) : null
|
|
12138
13066
|
};
|
|
12139
13067
|
}
|
|
12140
13068
|
async function uploadVersion(params) {
|
|
12141
13069
|
const creds = requireCredentials();
|
|
12142
13070
|
const { jsPath, cssPath } = artifactPaths(params.folder, params.experiment);
|
|
12143
|
-
if (!
|
|
13071
|
+
if (!import_node_fs10.default.existsSync(jsPath)) throw new Error(`Missing JS artifact: ${jsPath}`);
|
|
12144
13072
|
const form = new FormData();
|
|
12145
13073
|
form.append(
|
|
12146
13074
|
"js",
|
|
12147
|
-
new Blob([
|
|
12148
|
-
|
|
13075
|
+
new Blob([import_node_fs10.default.readFileSync(jsPath)], { type: "application/javascript" }),
|
|
13076
|
+
import_node_path8.default.basename(jsPath)
|
|
12149
13077
|
);
|
|
12150
|
-
if (cssPath &&
|
|
13078
|
+
if (cssPath && import_node_fs10.default.existsSync(cssPath) && import_node_fs10.default.statSync(cssPath).size > 0) {
|
|
12151
13079
|
form.append(
|
|
12152
13080
|
"css",
|
|
12153
|
-
new Blob([
|
|
12154
|
-
|
|
13081
|
+
new Blob([import_node_fs10.default.readFileSync(cssPath)], { type: "text/css" }),
|
|
13082
|
+
import_node_path8.default.basename(cssPath)
|
|
12155
13083
|
);
|
|
12156
13084
|
}
|
|
12157
13085
|
form.append(
|
|
@@ -12160,8 +13088,8 @@ async function uploadVersion(params) {
|
|
|
12160
13088
|
...params.apex,
|
|
12161
13089
|
resetJs: cleanupResetJs(params.experiment),
|
|
12162
13090
|
builtAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12163
|
-
jsPath:
|
|
12164
|
-
cssPath: cssPath ?
|
|
13091
|
+
jsPath: import_node_path8.default.relative(params.folder, jsPath),
|
|
13092
|
+
cssPath: cssPath ? import_node_path8.default.relative(params.folder, cssPath) : null
|
|
12165
13093
|
})
|
|
12166
13094
|
);
|
|
12167
13095
|
const res = await uploadHarnessVersion(creds, params.session.sessionId, form);
|
|
@@ -12202,11 +13130,11 @@ var harnessCommand = new Command("harness").description(
|
|
|
12202
13130
|
harnessCommand.command("init").argument("<folder>", "AB Test Harness experiment folder").option("--force", "Overwrite existing apex.harness.json").action(function action24(folderArg, opts) {
|
|
12203
13131
|
const folder = resolveFolder(folderArg);
|
|
12204
13132
|
const targetPath = apexConfigPath(folder);
|
|
12205
|
-
if (
|
|
13133
|
+
if (import_node_fs10.default.existsSync(targetPath) && !opts.force) {
|
|
12206
13134
|
fail(this, `${targetPath} already exists. Use --force to overwrite.`, EXIT_CODES.conflict);
|
|
12207
13135
|
}
|
|
12208
13136
|
const { apex } = loadHarnessConfig(folder);
|
|
12209
|
-
|
|
13137
|
+
import_node_fs10.default.writeFileSync(targetPath, `${JSON.stringify(apex, null, 2)}
|
|
12210
13138
|
`, "utf8");
|
|
12211
13139
|
output(
|
|
12212
13140
|
this,
|
|
@@ -12272,7 +13200,7 @@ harnessCommand.command("dev").argument("<folder>", "AB Test Harness experiment f
|
|
|
12272
13200
|
}
|
|
12273
13201
|
};
|
|
12274
13202
|
let debounce = null;
|
|
12275
|
-
const watcher =
|
|
13203
|
+
const watcher = import_node_fs10.default.watch(folder, { recursive: true }, (_event, filename) => {
|
|
12276
13204
|
const name = String(filename ?? "");
|
|
12277
13205
|
if (!name || name.startsWith("build/") || name.includes("/build/")) return;
|
|
12278
13206
|
if (debounce) clearTimeout(debounce);
|
|
@@ -12314,9 +13242,9 @@ harnessCommand.command("publish").argument("<folder>", "AB Test Harness experime
|
|
|
12314
13242
|
|
|
12315
13243
|
// src/commands/operator.ts
|
|
12316
13244
|
init_source();
|
|
12317
|
-
var
|
|
12318
|
-
var
|
|
12319
|
-
var
|
|
13245
|
+
var import_node_crypto6 = __toESM(require("node:crypto"));
|
|
13246
|
+
var import_node_fs11 = __toESM(require("node:fs"));
|
|
13247
|
+
var import_node_path9 = __toESM(require("node:path"));
|
|
12320
13248
|
var CANONICAL_FILE_RE = /^(src\/(?:index\.js|index\.scss|info\.js|mutations\.json)|experiment\.config\.json|metrics\.json|targeting\.json|dev-doc\.md|brand\.md|goals\/[a-zA-Z0-9_.-]+\.json|segments\/[a-zA-Z0-9_.-]+\.json|network\/[a-zA-Z0-9_.-]+\.recipe\.json)$/;
|
|
12321
13249
|
function workspaceIssueFile(field, evidence) {
|
|
12322
13250
|
if (field === "custom_js" || field === "reset_js") return "src/index.js";
|
|
@@ -12360,7 +13288,7 @@ function formatWorkspaceBlockingIssues(value) {
|
|
|
12360
13288
|
}).join("\n");
|
|
12361
13289
|
}
|
|
12362
13290
|
function sha256Text2(value) {
|
|
12363
|
-
return
|
|
13291
|
+
return import_node_crypto6.default.createHash("sha256").update(value).digest("hex");
|
|
12364
13292
|
}
|
|
12365
13293
|
function byteLength(value) {
|
|
12366
13294
|
return Buffer.byteLength(value, "utf8");
|
|
@@ -12370,40 +13298,60 @@ function slugify2(value, fallback2 = "operator") {
|
|
|
12370
13298
|
return slug || fallback2;
|
|
12371
13299
|
}
|
|
12372
13300
|
function folderPath2(folder) {
|
|
12373
|
-
return
|
|
13301
|
+
return import_node_path9.default.resolve(process.cwd(), folder);
|
|
12374
13302
|
}
|
|
12375
13303
|
function apexDir(folder) {
|
|
12376
|
-
return
|
|
13304
|
+
return import_node_path9.default.join(folder, ".apex");
|
|
12377
13305
|
}
|
|
12378
13306
|
function sessionPath(folder) {
|
|
12379
|
-
return
|
|
13307
|
+
return import_node_path9.default.join(apexDir(folder), "session.json");
|
|
12380
13308
|
}
|
|
12381
13309
|
function readJsonFile(filePath) {
|
|
12382
|
-
if (!
|
|
12383
|
-
return JSON.parse(
|
|
13310
|
+
if (!import_node_fs11.default.existsSync(filePath)) return null;
|
|
13311
|
+
return JSON.parse(import_node_fs11.default.readFileSync(filePath, "utf8"));
|
|
12384
13312
|
}
|
|
12385
13313
|
function writeJsonFile(filePath, value) {
|
|
12386
|
-
|
|
12387
|
-
|
|
13314
|
+
import_node_fs11.default.mkdirSync(import_node_path9.default.dirname(filePath), { recursive: true });
|
|
13315
|
+
import_node_fs11.default.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}
|
|
12388
13316
|
`, "utf8");
|
|
12389
13317
|
}
|
|
12390
13318
|
function readSession(folder) {
|
|
12391
13319
|
const session = readJsonFile(sessionPath(folder));
|
|
12392
13320
|
if (!session) {
|
|
12393
|
-
const filesystemMarker =
|
|
12394
|
-
if (
|
|
12395
|
-
throw new
|
|
12396
|
-
"This folder is an experiment filesystem (apex experiments \u2026), not an operator workspace \u2014 run `apex experiments plan` here, or `apex workspace checkout` into a different folder."
|
|
13321
|
+
const filesystemMarker = import_node_path9.default.join(apexDir(folder), "experiment-filesystem.json");
|
|
13322
|
+
if (import_node_fs11.default.existsSync(filesystemMarker)) {
|
|
13323
|
+
throw new CliError(
|
|
13324
|
+
"This folder is an experiment filesystem (apex experiments \u2026), not an operator workspace \u2014 run `apex experiments plan` here, or `apex workspace checkout` into a different folder.",
|
|
13325
|
+
EXIT_CODES.general,
|
|
13326
|
+
{
|
|
13327
|
+
code: "validation_failed",
|
|
13328
|
+
next_steps: [
|
|
13329
|
+
{ command: "apex experiments plan", why: "Use the command for this folder format." },
|
|
13330
|
+
{
|
|
13331
|
+
command: "apex workspace checkout",
|
|
13332
|
+
why: "Create the required folder in a different directory."
|
|
13333
|
+
}
|
|
13334
|
+
]
|
|
13335
|
+
}
|
|
12397
13336
|
);
|
|
12398
13337
|
}
|
|
12399
|
-
throw new
|
|
13338
|
+
throw new CliError(
|
|
13339
|
+
`Missing ${sessionPath(folder)}. Run apex workspace checkout first.`,
|
|
13340
|
+
EXIT_CODES.general,
|
|
13341
|
+
{
|
|
13342
|
+
code: "local_file_error",
|
|
13343
|
+
next_steps: [
|
|
13344
|
+
{ command: "apex workspace checkout", why: "Create the missing local folder metadata." }
|
|
13345
|
+
]
|
|
13346
|
+
}
|
|
13347
|
+
);
|
|
12400
13348
|
}
|
|
12401
13349
|
return session;
|
|
12402
13350
|
}
|
|
12403
13351
|
function writeSession(folder, session) {
|
|
12404
|
-
|
|
12405
|
-
|
|
12406
|
-
|
|
13352
|
+
import_node_fs11.default.mkdirSync(apexDir(folder), { recursive: true });
|
|
13353
|
+
import_node_fs11.default.writeFileSync(
|
|
13354
|
+
import_node_path9.default.join(apexDir(folder), ".gitignore"),
|
|
12407
13355
|
"session.json\nharness-session.json\n",
|
|
12408
13356
|
"utf8"
|
|
12409
13357
|
);
|
|
@@ -12427,15 +13375,15 @@ function listLocalWorkspaceFiles(folder) {
|
|
|
12427
13375
|
"brand.md"
|
|
12428
13376
|
];
|
|
12429
13377
|
function visit2(dir) {
|
|
12430
|
-
if (!
|
|
12431
|
-
for (const entry of
|
|
12432
|
-
const absPath =
|
|
13378
|
+
if (!import_node_fs11.default.existsSync(dir)) return;
|
|
13379
|
+
for (const entry of import_node_fs11.default.readdirSync(dir, { withFileTypes: true })) {
|
|
13380
|
+
const absPath = import_node_path9.default.join(dir, entry.name);
|
|
12433
13381
|
if (entry.isDirectory()) {
|
|
12434
13382
|
visit2(absPath);
|
|
12435
13383
|
continue;
|
|
12436
13384
|
}
|
|
12437
|
-
const relativePath = validateCanonicalPath(
|
|
12438
|
-
const content =
|
|
13385
|
+
const relativePath = validateCanonicalPath(import_node_path9.default.relative(folder, absPath));
|
|
13386
|
+
const content = import_node_fs11.default.readFileSync(absPath, "utf8");
|
|
12439
13387
|
files.push({
|
|
12440
13388
|
path: relativePath,
|
|
12441
13389
|
absPath,
|
|
@@ -12445,11 +13393,11 @@ function listLocalWorkspaceFiles(folder) {
|
|
|
12445
13393
|
});
|
|
12446
13394
|
}
|
|
12447
13395
|
}
|
|
12448
|
-
for (const root of roots) visit2(
|
|
13396
|
+
for (const root of roots) visit2(import_node_path9.default.join(folder, root));
|
|
12449
13397
|
for (const file of topLevel) {
|
|
12450
|
-
const absPath =
|
|
12451
|
-
if (!
|
|
12452
|
-
const content =
|
|
13398
|
+
const absPath = import_node_path9.default.join(folder, file);
|
|
13399
|
+
if (!import_node_fs11.default.existsSync(absPath)) continue;
|
|
13400
|
+
const content = import_node_fs11.default.readFileSync(absPath, "utf8");
|
|
12453
13401
|
files.push({
|
|
12454
13402
|
path: file,
|
|
12455
13403
|
absPath,
|
|
@@ -12473,7 +13421,7 @@ function validateJsonFiles(files) {
|
|
|
12473
13421
|
}
|
|
12474
13422
|
}
|
|
12475
13423
|
function readExperimentConfig(folder) {
|
|
12476
|
-
return readJsonFile(
|
|
13424
|
+
return readJsonFile(import_node_path9.default.join(folder, "experiment.config.json")) ?? {};
|
|
12477
13425
|
}
|
|
12478
13426
|
function resolveVariation(exp, variationArg) {
|
|
12479
13427
|
const variations = exp.variations ?? [];
|
|
@@ -12490,9 +13438,9 @@ function resolveVariation(exp, variationArg) {
|
|
|
12490
13438
|
function writeWorkspaceFiles(folder, workspace) {
|
|
12491
13439
|
for (const file of workspace.files) {
|
|
12492
13440
|
const relativePath = validateCanonicalPath(file.path);
|
|
12493
|
-
const absPath =
|
|
12494
|
-
|
|
12495
|
-
|
|
13441
|
+
const absPath = import_node_path9.default.join(folder, relativePath);
|
|
13442
|
+
import_node_fs11.default.mkdirSync(import_node_path9.default.dirname(absPath), { recursive: true });
|
|
13443
|
+
import_node_fs11.default.writeFileSync(absPath, file.content, "utf8");
|
|
12496
13444
|
}
|
|
12497
13445
|
}
|
|
12498
13446
|
function sessionFromWorkspace(input) {
|
|
@@ -12561,10 +13509,10 @@ async function uploadCurrentArtifactVersion(folder, sessionId) {
|
|
|
12561
13509
|
form.append(
|
|
12562
13510
|
"js",
|
|
12563
13511
|
new Blob([js.content], { type: "application/javascript" }),
|
|
12564
|
-
|
|
13512
|
+
import_node_path9.default.basename(js.path)
|
|
12565
13513
|
);
|
|
12566
13514
|
if (css && css.content.trim()) {
|
|
12567
|
-
form.append("css", new Blob([css.content], { type: "text/css" }),
|
|
13515
|
+
form.append("css", new Blob([css.content], { type: "text/css" }), import_node_path9.default.basename(css.path));
|
|
12568
13516
|
}
|
|
12569
13517
|
form.append("manifest", JSON.stringify(manifest));
|
|
12570
13518
|
const res = await uploadHarnessVersion(creds, sessionId, form);
|
|
@@ -12683,7 +13631,7 @@ operatorCommand.command("checkout").argument("<experiment-id>", "Apex experiment
|
|
|
12683
13631
|
classifyStatus(workspace.status),
|
|
12684
13632
|
workspace.errorDetails
|
|
12685
13633
|
);
|
|
12686
|
-
|
|
13634
|
+
import_node_fs11.default.mkdirSync(folder, { recursive: true });
|
|
12687
13635
|
writeWorkspaceFiles(folder, workspace.data);
|
|
12688
13636
|
writeSession(
|
|
12689
13637
|
folder,
|
|
@@ -12740,6 +13688,7 @@ operatorCommand.command("status").argument("[folder]", "Operator workspace folde
|
|
|
12740
13688
|
}
|
|
12741
13689
|
);
|
|
12742
13690
|
} catch (error) {
|
|
13691
|
+
if (error instanceof CliError) throw error;
|
|
12743
13692
|
fail(this, error instanceof Error ? error.message : String(error));
|
|
12744
13693
|
}
|
|
12745
13694
|
});
|
|
@@ -12749,8 +13698,8 @@ operatorCommand.command("dev").argument("[folder]", "Operator workspace folder",
|
|
|
12749
13698
|
const session = readSession(folder);
|
|
12750
13699
|
const harnessSession = await createExactHarnessSession(folder, session);
|
|
12751
13700
|
const version = await uploadCurrentArtifactVersion(folder, harnessSession.sessionId);
|
|
12752
|
-
|
|
12753
|
-
writeJsonFile(
|
|
13701
|
+
import_node_fs11.default.mkdirSync(apexDir(folder), { recursive: true });
|
|
13702
|
+
writeJsonFile(import_node_path9.default.join(apexDir(folder), "harness-session.json"), {
|
|
12754
13703
|
sessionId: harnessSession.sessionId,
|
|
12755
13704
|
experimentId: harnessSession.experimentId,
|
|
12756
13705
|
variationId: harnessSession.variationId,
|
|
@@ -12805,7 +13754,7 @@ operatorCommand.command("dev").argument("[folder]", "Operator workspace folder",
|
|
|
12805
13754
|
}
|
|
12806
13755
|
};
|
|
12807
13756
|
let timer = null;
|
|
12808
|
-
const watcher =
|
|
13757
|
+
const watcher = import_node_fs11.default.watch(folder, { recursive: true }, (_event, filename) => {
|
|
12809
13758
|
const name = String(filename ?? "");
|
|
12810
13759
|
if (!name || name.startsWith(".apex/") || name.includes("/.apex/")) return;
|
|
12811
13760
|
if (timer) clearTimeout(timer);
|
|
@@ -12840,13 +13789,13 @@ operatorCommand.command("publish").argument("[folder]", "Operator workspace fold
|
|
|
12840
13789
|
|
|
12841
13790
|
// src/commands/roadmap.ts
|
|
12842
13791
|
init_source();
|
|
12843
|
-
var
|
|
12844
|
-
var
|
|
13792
|
+
var import_node_fs12 = __toESM(require("node:fs"));
|
|
13793
|
+
var import_node_path10 = __toESM(require("node:path"));
|
|
12845
13794
|
var LEGACY_OUTPUT_REMOVAL_VERSION = "0.7.0";
|
|
12846
13795
|
function truncate(value, max) {
|
|
12847
|
-
const
|
|
12848
|
-
if (
|
|
12849
|
-
return `${
|
|
13796
|
+
const text2 = typeof value === "string" ? value : value == null ? "" : String(value);
|
|
13797
|
+
if (text2.length <= max) return text2;
|
|
13798
|
+
return `${text2.slice(0, Math.max(0, max - 3))}...`;
|
|
12850
13799
|
}
|
|
12851
13800
|
function score(value) {
|
|
12852
13801
|
if (typeof value !== "number" || !Number.isFinite(value)) return "-";
|
|
@@ -12951,9 +13900,9 @@ var handoff = new Command("handoff").description(
|
|
|
12951
13900
|
`;
|
|
12952
13901
|
const outputData = opts.instructions ? { roadmapItemId: id, instructions: String(payload.instructions ?? "") } : payload;
|
|
12953
13902
|
if (opts.out || opts.output) {
|
|
12954
|
-
const target =
|
|
12955
|
-
|
|
12956
|
-
|
|
13903
|
+
const target = import_node_path10.default.resolve(process.cwd(), opts.out ?? opts.output);
|
|
13904
|
+
import_node_fs12.default.mkdirSync(import_node_path10.default.dirname(target), { recursive: true });
|
|
13905
|
+
import_node_fs12.default.writeFileSync(target, rendered, "utf8");
|
|
12957
13906
|
output(this, { path: target, roadmapItemId: id }, () => printSuccess(`Wrote ${target}`));
|
|
12958
13907
|
return;
|
|
12959
13908
|
}
|
|
@@ -12967,8 +13916,8 @@ roadmapCommand.addCommand(handoff);
|
|
|
12967
13916
|
|
|
12968
13917
|
// src/commands/tracking.ts
|
|
12969
13918
|
init_source();
|
|
12970
|
-
var
|
|
12971
|
-
var
|
|
13919
|
+
var import_node_fs13 = __toESM(require("node:fs"));
|
|
13920
|
+
var import_node_path11 = __toESM(require("node:path"));
|
|
12972
13921
|
function normalizeUrl(input) {
|
|
12973
13922
|
const value = input.trim();
|
|
12974
13923
|
if (!value) throw new Error("URL cannot be empty");
|
|
@@ -13014,17 +13963,17 @@ function commonCookieDomain(hostnames) {
|
|
|
13014
13963
|
return suffix && suffixes.every((candidate) => candidate === suffix) ? `.${suffix}` : null;
|
|
13015
13964
|
}
|
|
13016
13965
|
function defaultManifestPath() {
|
|
13017
|
-
return
|
|
13966
|
+
return import_node_path11.default.resolve(process.cwd(), ".apex", "tracking-manifest.json");
|
|
13018
13967
|
}
|
|
13019
13968
|
function readManifest(filePath) {
|
|
13020
|
-
if (!
|
|
13021
|
-
const parsed = JSON.parse(
|
|
13969
|
+
if (!import_node_fs13.default.existsSync(filePath)) throw new Error(`Manifest not found: ${filePath}`);
|
|
13970
|
+
const parsed = JSON.parse(import_node_fs13.default.readFileSync(filePath, "utf8"));
|
|
13022
13971
|
if (parsed.schemaVersion !== 1) throw new Error("Unsupported tracking manifest schema");
|
|
13023
13972
|
return parsed;
|
|
13024
13973
|
}
|
|
13025
13974
|
function writeManifest(filePath, manifest) {
|
|
13026
|
-
|
|
13027
|
-
|
|
13975
|
+
import_node_fs13.default.mkdirSync(import_node_path11.default.dirname(filePath), { recursive: true });
|
|
13976
|
+
import_node_fs13.default.writeFileSync(filePath, `${JSON.stringify(manifest, null, 2)}
|
|
13028
13977
|
`, "utf8");
|
|
13029
13978
|
}
|
|
13030
13979
|
function buildSnippet(shopId, endpoint) {
|
|
@@ -13091,7 +14040,7 @@ function buildManifest(urlArgs, opts) {
|
|
|
13091
14040
|
};
|
|
13092
14041
|
}
|
|
13093
14042
|
function manifestPath(input) {
|
|
13094
|
-
return
|
|
14043
|
+
return import_node_path11.default.resolve(process.cwd(), input ?? defaultManifestPath());
|
|
13095
14044
|
}
|
|
13096
14045
|
function printManifestPlan(manifest) {
|
|
13097
14046
|
console.log(source_default.bold("Apex tracking ingestion plan"));
|
|
@@ -13181,12 +14130,15 @@ trackingCommand.command("verify").description("Verify live tracking events for t
|
|
|
13181
14130
|
["Last event", totals.last_event_at ?? "-"]
|
|
13182
14131
|
]
|
|
13183
14132
|
);
|
|
14133
|
+
if (res.data.truncated) {
|
|
14134
|
+
console.log(`Showing the newest ${totals.events} events; totals cover this sample only`);
|
|
14135
|
+
}
|
|
13184
14136
|
const eventTypes = Object.entries(totals.event_types);
|
|
13185
14137
|
if (eventTypes.length > 0) {
|
|
13186
14138
|
console.log();
|
|
13187
14139
|
printTable(
|
|
13188
14140
|
["EVENT TYPE", "COUNT"],
|
|
13189
|
-
eventTypes.map(([eventType,
|
|
14141
|
+
eventTypes.map(([eventType, count2]) => [eventType, String(count2)])
|
|
13190
14142
|
);
|
|
13191
14143
|
}
|
|
13192
14144
|
if (res.data.recent_events.length > 0) {
|
|
@@ -13261,10 +14213,180 @@ trackingCommand.command("latency").description("Measure forced-preview versus na
|
|
|
13261
14213
|
});
|
|
13262
14214
|
});
|
|
13263
14215
|
|
|
14216
|
+
// src/commands/events.ts
|
|
14217
|
+
var import_node_fs14 = __toESM(require("node:fs"));
|
|
14218
|
+
var eventsCommand = new Command("events").description("Export raw tracking events");
|
|
14219
|
+
eventsCommand.command("export").description("Export every raw event in an absolute time window").requiredOption("--from <iso>", "Inclusive ISO 8601 window start").requiredOption("--to <iso>", "Exclusive ISO 8601 window end").option("--experiment <id>", "Filter by experiment ID").option("--event-type <types>", "Comma-separated event types").option("--qa <scope>", "QA scope: exclude, include, or only", "include").option("--limit <count>", "Page size from 1 to 1000", "500").option("--out <path>", "Write event rows to a file instead of stdout").option("--json-summary", "Print a JSON summary on stdout; requires --out for the NDJSON rows").action(async (opts, command) => {
|
|
14220
|
+
if (!Number.isFinite(Date.parse(opts.from)) || !Number.isFinite(Date.parse(opts.to))) {
|
|
14221
|
+
fail(command, "--from and --to must be ISO 8601 timestamps", EXIT_CODES.validation);
|
|
14222
|
+
}
|
|
14223
|
+
if (!["exclude", "include", "only"].includes(opts.qa)) {
|
|
14224
|
+
fail(command, "--qa must be exclude, include, or only", EXIT_CODES.validation);
|
|
14225
|
+
}
|
|
14226
|
+
const limit = Number(opts.limit);
|
|
14227
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 1e3) {
|
|
14228
|
+
fail(command, "--limit must be an integer from 1 to 1000", EXIT_CODES.validation);
|
|
14229
|
+
}
|
|
14230
|
+
const summaryOnStdout = Boolean(opts.jsonSummary) || isJsonMode();
|
|
14231
|
+
if (isJsonMode() && !opts.out) {
|
|
14232
|
+
fail(
|
|
14233
|
+
command,
|
|
14234
|
+
"In JSON mode (--json or piped stdout) pass --out <file> for the NDJSON rows; stdout carries one summary document.",
|
|
14235
|
+
EXIT_CODES.validation,
|
|
14236
|
+
{
|
|
14237
|
+
next_steps: [
|
|
14238
|
+
{
|
|
14239
|
+
command: "apex events export --from <iso> --to <iso> --out events.ndjson",
|
|
14240
|
+
why: "Write rows to a file."
|
|
14241
|
+
}
|
|
14242
|
+
]
|
|
14243
|
+
}
|
|
14244
|
+
);
|
|
14245
|
+
}
|
|
14246
|
+
if (opts.jsonSummary && !opts.out) {
|
|
14247
|
+
fail(command, "--json-summary requires --out for the event rows", EXIT_CODES.validation);
|
|
14248
|
+
}
|
|
14249
|
+
const output2 = opts.out ? import_node_fs14.default.createWriteStream(opts.out, { flags: "wx" }) : null;
|
|
14250
|
+
let outputError = null;
|
|
14251
|
+
output2?.on("error", (error) => {
|
|
14252
|
+
outputError = error;
|
|
14253
|
+
});
|
|
14254
|
+
if (output2) {
|
|
14255
|
+
try {
|
|
14256
|
+
await new Promise((resolve2, reject) => {
|
|
14257
|
+
output2.once("open", () => resolve2());
|
|
14258
|
+
output2.once("error", reject);
|
|
14259
|
+
});
|
|
14260
|
+
} catch (error) {
|
|
14261
|
+
fail(
|
|
14262
|
+
command,
|
|
14263
|
+
`Cannot write ${opts.out}: ${error instanceof Error ? error.message : String(error)}`,
|
|
14264
|
+
EXIT_CODES.general,
|
|
14265
|
+
{ code: "local_file_error" }
|
|
14266
|
+
);
|
|
14267
|
+
}
|
|
14268
|
+
}
|
|
14269
|
+
const writeLine = async (line) => {
|
|
14270
|
+
const stream = output2 ?? process.stdout;
|
|
14271
|
+
const accepted = output2 ? output2.write(line) : writeRawCommandOutput(line);
|
|
14272
|
+
if (accepted) return;
|
|
14273
|
+
await new Promise((resolve2, reject) => {
|
|
14274
|
+
const onDrain = () => {
|
|
14275
|
+
stream.off("error", onError);
|
|
14276
|
+
resolve2();
|
|
14277
|
+
};
|
|
14278
|
+
const onError = (error) => {
|
|
14279
|
+
stream.off("drain", onDrain);
|
|
14280
|
+
reject(error);
|
|
14281
|
+
};
|
|
14282
|
+
stream.once("drain", onDrain);
|
|
14283
|
+
stream.once("error", onError);
|
|
14284
|
+
});
|
|
14285
|
+
};
|
|
14286
|
+
let cursor;
|
|
14287
|
+
let written = 0;
|
|
14288
|
+
let pages = 0;
|
|
14289
|
+
let totals;
|
|
14290
|
+
do {
|
|
14291
|
+
const response = await exportEventsPage(requireCredentials(), {
|
|
14292
|
+
from: opts.from,
|
|
14293
|
+
to: opts.to,
|
|
14294
|
+
experiment: opts.experiment,
|
|
14295
|
+
eventTypes: opts.eventType?.split(",").map((value) => value.trim()).filter(Boolean),
|
|
14296
|
+
qa: opts.qa,
|
|
14297
|
+
limit,
|
|
14298
|
+
cursor
|
|
14299
|
+
});
|
|
14300
|
+
if (!response.ok || !response.data) {
|
|
14301
|
+
output2?.destroy();
|
|
14302
|
+
fail(
|
|
14303
|
+
command,
|
|
14304
|
+
response.error ?? "Event export page failed",
|
|
14305
|
+
classifyStatus(response.status),
|
|
14306
|
+
response.errorDetails
|
|
14307
|
+
);
|
|
14308
|
+
}
|
|
14309
|
+
if (outputError)
|
|
14310
|
+
fail(
|
|
14311
|
+
command,
|
|
14312
|
+
`Cannot write ${opts.out}: ${outputError.message}`,
|
|
14313
|
+
EXIT_CODES.general,
|
|
14314
|
+
{
|
|
14315
|
+
code: "local_file_error"
|
|
14316
|
+
}
|
|
14317
|
+
);
|
|
14318
|
+
const page = response.data.data;
|
|
14319
|
+
totals ??= page.totals;
|
|
14320
|
+
for (const event of page.events) {
|
|
14321
|
+
try {
|
|
14322
|
+
await writeLine(`${JSON.stringify(event)}
|
|
14323
|
+
`);
|
|
14324
|
+
} catch (error) {
|
|
14325
|
+
fail(
|
|
14326
|
+
command,
|
|
14327
|
+
`Cannot write ${opts.out ?? "stdout"}: ${error instanceof Error ? error.message : String(error)}`,
|
|
14328
|
+
EXIT_CODES.general,
|
|
14329
|
+
{ code: "local_file_error" }
|
|
14330
|
+
);
|
|
14331
|
+
}
|
|
14332
|
+
written += 1;
|
|
14333
|
+
}
|
|
14334
|
+
pages += 1;
|
|
14335
|
+
cursor = page.next_cursor ?? void 0;
|
|
14336
|
+
} while (cursor);
|
|
14337
|
+
if (output2) {
|
|
14338
|
+
try {
|
|
14339
|
+
await new Promise((resolve2, reject) => {
|
|
14340
|
+
if (outputError) return reject(outputError);
|
|
14341
|
+
output2.once("error", reject);
|
|
14342
|
+
output2.end(resolve2);
|
|
14343
|
+
});
|
|
14344
|
+
} catch (error) {
|
|
14345
|
+
fail(
|
|
14346
|
+
command,
|
|
14347
|
+
`Cannot write ${opts.out}: ${error instanceof Error ? error.message : String(error)}`,
|
|
14348
|
+
EXIT_CODES.general,
|
|
14349
|
+
{ code: "local_file_error" }
|
|
14350
|
+
);
|
|
14351
|
+
}
|
|
14352
|
+
if (outputError)
|
|
14353
|
+
fail(
|
|
14354
|
+
command,
|
|
14355
|
+
`Cannot write ${opts.out}: ${outputError.message}`,
|
|
14356
|
+
EXIT_CODES.general,
|
|
14357
|
+
{
|
|
14358
|
+
code: "local_file_error"
|
|
14359
|
+
}
|
|
14360
|
+
);
|
|
14361
|
+
}
|
|
14362
|
+
const matches = totals ? written === totals.events : false;
|
|
14363
|
+
const summary = {
|
|
14364
|
+
events_written: written,
|
|
14365
|
+
pages,
|
|
14366
|
+
totals: totals ?? null,
|
|
14367
|
+
count_matches: matches
|
|
14368
|
+
};
|
|
14369
|
+
if (!matches)
|
|
14370
|
+
fail(
|
|
14371
|
+
command,
|
|
14372
|
+
"Exported event count does not match the server total. Totals are counted when the first page is read, so events that arrive late for a window ending in the last few minutes, or rows sharing one timestamp and event ID, can cause a difference. Re-run for a window that ended at least 15 minutes ago.",
|
|
14373
|
+
EXIT_CODES.general,
|
|
14374
|
+
{
|
|
14375
|
+
reason: "export_count_mismatch",
|
|
14376
|
+
...summary
|
|
14377
|
+
}
|
|
14378
|
+
);
|
|
14379
|
+
if (summaryOnStdout) writeEnvelope(summary, "apex events export");
|
|
14380
|
+
process.stderr.write(
|
|
14381
|
+
`Exported ${written} events across ${pages} page(s); total ${totals?.events ?? "unknown"}; count matches: ${matches}.
|
|
14382
|
+
`
|
|
14383
|
+
);
|
|
14384
|
+
});
|
|
14385
|
+
|
|
13264
14386
|
// src/commands/goals.ts
|
|
13265
14387
|
init_source();
|
|
13266
|
-
var
|
|
13267
|
-
var
|
|
14388
|
+
var import_node_fs15 = __toESM(require("node:fs"));
|
|
14389
|
+
var import_node_path12 = __toESM(require("node:path"));
|
|
13268
14390
|
|
|
13269
14391
|
// ../sdk/src/safe-regex.ts
|
|
13270
14392
|
var MAX_PATTERN_LENGTH = 1024;
|
|
@@ -13440,9 +14562,9 @@ function printSuggestions(data) {
|
|
|
13440
14562
|
function parseCapture(command, value) {
|
|
13441
14563
|
if (!value) return void 0;
|
|
13442
14564
|
const source = value.startsWith("@") ? value.slice(1) : value;
|
|
13443
|
-
const
|
|
14565
|
+
const text2 = value.startsWith("@") || import_node_fs15.default.existsSync(source) ? import_node_fs15.default.readFileSync(import_node_path12.default.resolve(process.cwd(), source), "utf8") : value;
|
|
13444
14566
|
try {
|
|
13445
|
-
return JSON.parse(
|
|
14567
|
+
return JSON.parse(text2);
|
|
13446
14568
|
} catch (error) {
|
|
13447
14569
|
const detail5 = error instanceof Error ? error.message : String(error);
|
|
13448
14570
|
fail(command, `Invalid --capture JSON: ${detail5}`, EXIT_CODES.validation);
|
|
@@ -13586,10 +14708,10 @@ var test = new Command("test").description("Dry run a goal URL pattern against t
|
|
|
13586
14708
|
const days = Math.max(1, Number.parseInt(opts.days, 10) || 14);
|
|
13587
14709
|
const dependence = urlPatternDependence(pattern);
|
|
13588
14710
|
if (dependence !== "none") {
|
|
13589
|
-
const
|
|
13590
|
-
output(this, { verdict: "indeterminate", pattern, dependence, reason }, () => {
|
|
14711
|
+
const reason2 = DEPENDENCE_REASON[dependence];
|
|
14712
|
+
output(this, { verdict: "indeterminate", pattern, dependence, reason: reason2 }, () => {
|
|
13591
14713
|
console.log(source_default.yellow("INDETERMINATE"));
|
|
13592
|
-
console.log(
|
|
14714
|
+
console.log(reason2);
|
|
13593
14715
|
});
|
|
13594
14716
|
return;
|
|
13595
14717
|
}
|
|
@@ -13693,12 +14815,12 @@ Examples:
|
|
|
13693
14815
|
|
|
13694
14816
|
// src/commands/funnels.ts
|
|
13695
14817
|
init_source();
|
|
13696
|
-
var
|
|
13697
|
-
var
|
|
14818
|
+
var import_node_fs16 = __toESM(require("node:fs"));
|
|
14819
|
+
var import_node_path13 = __toESM(require("node:path"));
|
|
13698
14820
|
function readSteps(command, file) {
|
|
13699
14821
|
let parsed;
|
|
13700
14822
|
try {
|
|
13701
|
-
parsed = JSON.parse(
|
|
14823
|
+
parsed = JSON.parse(import_node_fs16.default.readFileSync(import_node_path13.default.resolve(process.cwd(), file), "utf8"));
|
|
13702
14824
|
} catch (error) {
|
|
13703
14825
|
fail(
|
|
13704
14826
|
command,
|
|
@@ -14012,8 +15134,8 @@ var off = new Command("off").description("Disable consent gating").action(async
|
|
|
14012
15134
|
var consentCommand = new Command("consent").description("Manage consent gating").addCommand(get2).addCommand(set).addCommand(off);
|
|
14013
15135
|
|
|
14014
15136
|
// src/commands/project-code.ts
|
|
14015
|
-
var
|
|
14016
|
-
var
|
|
15137
|
+
var import_node_fs17 = require("node:fs");
|
|
15138
|
+
var import_node_path14 = require("node:path");
|
|
14017
15139
|
|
|
14018
15140
|
// src/commands/runtime-settings-shop.ts
|
|
14019
15141
|
var uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
@@ -14058,7 +15180,7 @@ var fields = ["enabled", "css", "helperJs", "js"];
|
|
|
14058
15180
|
function readPatch(command, file) {
|
|
14059
15181
|
let input;
|
|
14060
15182
|
try {
|
|
14061
|
-
input = JSON.parse((0,
|
|
15183
|
+
input = JSON.parse((0, import_node_fs17.readFileSync)(file, "utf8"));
|
|
14062
15184
|
} catch (error) {
|
|
14063
15185
|
fail(command, `Cannot read project code JSON: ${String(error)}`, EXIT_CODES.validation);
|
|
14064
15186
|
}
|
|
@@ -14118,15 +15240,15 @@ var projectCodeCommand = new Command("project-code").description(
|
|
|
14118
15240
|
);
|
|
14119
15241
|
projectCodeCommand.addCommand(
|
|
14120
15242
|
new Command("get").description("Read complete project code and its opaque version").requiredOption("--shop <domain-or-id>", "Shop domain or UUID").action(async (options, command) => {
|
|
14121
|
-
const { credentials: _credentials, ...
|
|
15243
|
+
const { credentials: _credentials, ...identity2 } = await identifyRuntimeSettingsShop(
|
|
14122
15244
|
command,
|
|
14123
15245
|
options.shop
|
|
14124
15246
|
);
|
|
14125
|
-
const current = await readCurrent(command, { ...
|
|
15247
|
+
const current = await readCurrent(command, { ...identity2, credentials: _credentials });
|
|
14126
15248
|
output(
|
|
14127
15249
|
command,
|
|
14128
|
-
{ ...
|
|
14129
|
-
() => process.stdout.write(`${JSON.stringify({ ...
|
|
15250
|
+
{ ...identity2, ...current },
|
|
15251
|
+
() => process.stdout.write(`${JSON.stringify({ ...identity2, ...current }, null, 2)}
|
|
14130
15252
|
`)
|
|
14131
15253
|
);
|
|
14132
15254
|
})
|
|
@@ -14144,11 +15266,11 @@ for (const mode of ["diff", "set"]) {
|
|
|
14144
15266
|
const patch = readPatch(actionCommand, options.file);
|
|
14145
15267
|
const shop = await identifyRuntimeSettingsShop(actionCommand, options.shop);
|
|
14146
15268
|
const current = await readCurrent(actionCommand, shop);
|
|
14147
|
-
const { credentials: _credentials, ...
|
|
15269
|
+
const { credentials: _credentials, ...identity2 } = shop;
|
|
14148
15270
|
const diff = effectiveDiff(current.projectCode, patch);
|
|
14149
15271
|
if (mode === "diff" || diff.changedFields.length === 0) {
|
|
14150
15272
|
const result2 = {
|
|
14151
|
-
...
|
|
15273
|
+
...identity2,
|
|
14152
15274
|
status: diff.changedFields.length ? "changed" : "unchanged",
|
|
14153
15275
|
version: current.version,
|
|
14154
15276
|
diff
|
|
@@ -14161,11 +15283,11 @@ for (const mode of ["diff", "set"]) {
|
|
|
14161
15283
|
);
|
|
14162
15284
|
return;
|
|
14163
15285
|
}
|
|
14164
|
-
const backupPath = (0,
|
|
15286
|
+
const backupPath = (0, import_node_path14.resolve)(options.backup);
|
|
14165
15287
|
try {
|
|
14166
|
-
(0,
|
|
15288
|
+
(0, import_node_fs17.writeFileSync)(
|
|
14167
15289
|
backupPath,
|
|
14168
|
-
`${JSON.stringify({ ...
|
|
15290
|
+
`${JSON.stringify({ ...identity2, ...current, capturedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)}
|
|
14169
15291
|
`,
|
|
14170
15292
|
{ flag: "wx", mode: 384 }
|
|
14171
15293
|
);
|
|
@@ -14195,14 +15317,14 @@ for (const mode of ["diff", "set"]) {
|
|
|
14195
15317
|
`Project code saved; publication ${data.precompile.status}: ${data.precompile.summary}`
|
|
14196
15318
|
);
|
|
14197
15319
|
}
|
|
14198
|
-
if (data.shop_id !==
|
|
15320
|
+
if (data.shop_id !== identity2.shop_id)
|
|
14199
15321
|
warnings.push(
|
|
14200
15322
|
"The write response has a different shop identity; inspect the saved state before another write."
|
|
14201
15323
|
);
|
|
14202
15324
|
for (const warning of warnings) process.stderr.write(`WARNING: ${warning}
|
|
14203
15325
|
`);
|
|
14204
15326
|
const result = {
|
|
14205
|
-
...
|
|
15327
|
+
...identity2,
|
|
14206
15328
|
status: "saved",
|
|
14207
15329
|
diff,
|
|
14208
15330
|
backupPath,
|
|
@@ -14222,12 +15344,12 @@ for (const mode of ["diff", "set"]) {
|
|
|
14222
15344
|
}
|
|
14223
15345
|
|
|
14224
15346
|
// src/commands/triggers.ts
|
|
14225
|
-
var
|
|
14226
|
-
var
|
|
15347
|
+
var import_node_fs18 = __toESM(require("node:fs"));
|
|
15348
|
+
var import_node_path15 = __toESM(require("node:path"));
|
|
14227
15349
|
|
|
14228
15350
|
// src/trigger-proposal.ts
|
|
14229
|
-
function runtimePath(
|
|
14230
|
-
return
|
|
15351
|
+
function runtimePath(path25) {
|
|
15352
|
+
return path25.replaceAll("[]", ".0");
|
|
14231
15353
|
}
|
|
14232
15354
|
function fieldsFrom(value, prefix = "", descriptorList = false) {
|
|
14233
15355
|
if (!value || typeof value !== "object") return [];
|
|
@@ -14240,18 +15362,18 @@ function fieldsFrom(value, prefix = "", descriptorList = false) {
|
|
|
14240
15362
|
)
|
|
14241
15363
|
);
|
|
14242
15364
|
}
|
|
14243
|
-
const
|
|
14244
|
-
const explicitPath = typeof
|
|
15365
|
+
const record3 = value;
|
|
15366
|
+
const explicitPath = typeof record3.path === "string" ? record3.path : void 0;
|
|
14245
15367
|
if (descriptorList && explicitPath) {
|
|
14246
|
-
const rawValues =
|
|
15368
|
+
const rawValues = record3.values ?? record3.samples ?? record3.examples ?? record3.example;
|
|
14247
15369
|
const values = Array.isArray(rawValues) ? rawValues : rawValues === void 0 ? [] : [rawValues];
|
|
14248
|
-
const magnitude = [
|
|
15370
|
+
const magnitude = [record3.mean, record3.average, record3.max, record3.magnitude].find(
|
|
14249
15371
|
(candidate) => typeof candidate === "number"
|
|
14250
15372
|
);
|
|
14251
|
-
const numericType = Array.isArray(
|
|
15373
|
+
const numericType = Array.isArray(record3.types) && record3.types.length > 0 && record3.types.every((type) => type === "num" || type === "numstr" || type === "numstr_comma");
|
|
14252
15374
|
return [{ path: runtimePath(explicitPath), values, magnitude, numericType }];
|
|
14253
15375
|
}
|
|
14254
|
-
return Object.entries(
|
|
15376
|
+
return Object.entries(record3).flatMap(([key, child]) => {
|
|
14255
15377
|
if (["eventName", "event_name", "name", "count", "frequency", "evidence"].includes(key))
|
|
14256
15378
|
return [];
|
|
14257
15379
|
const next = prefix ? `${prefix}.${key}` : key;
|
|
@@ -14284,8 +15406,8 @@ function isDescriptorArray(value) {
|
|
|
14284
15406
|
]);
|
|
14285
15407
|
return Array.isArray(value) && value.length > 0 && value.every((item) => {
|
|
14286
15408
|
if (!item || typeof item !== "object" || Array.isArray(item)) return false;
|
|
14287
|
-
const
|
|
14288
|
-
return typeof
|
|
15409
|
+
const record3 = item;
|
|
15410
|
+
return typeof record3.path === "string" && record3.path.length > 0 && Object.keys(record3).every((key) => descriptorKeys.has(key));
|
|
14289
15411
|
});
|
|
14290
15412
|
}
|
|
14291
15413
|
function candidatesFrom(report) {
|
|
@@ -14305,9 +15427,9 @@ function candidatesFrom(report) {
|
|
|
14305
15427
|
}
|
|
14306
15428
|
return [root];
|
|
14307
15429
|
}
|
|
14308
|
-
function score2(
|
|
15430
|
+
function score2(path25, patterns) {
|
|
14309
15431
|
return patterns.reduce(
|
|
14310
|
-
(total, pattern, index) => total + (pattern.test(
|
|
15432
|
+
(total, pattern, index) => total + (pattern.test(path25) ? patterns.length - index : 0),
|
|
14311
15433
|
0
|
|
14312
15434
|
);
|
|
14313
15435
|
}
|
|
@@ -14441,10 +15563,10 @@ var whenOps = new Set(runtime_settings_trigger_constants_default.whenOps);
|
|
|
14441
15563
|
function strictObject(value, location, keys) {
|
|
14442
15564
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
14443
15565
|
throw new Error(`${location} must be an object`);
|
|
14444
|
-
const
|
|
14445
|
-
for (const key of Object.keys(
|
|
15566
|
+
const record3 = value;
|
|
15567
|
+
for (const key of Object.keys(record3))
|
|
14446
15568
|
if (!keys.has(key)) throw new Error(`${location}.${key} is not allowed`);
|
|
14447
|
-
return
|
|
15569
|
+
return record3;
|
|
14448
15570
|
}
|
|
14449
15571
|
function optionalType(value, type, location) {
|
|
14450
15572
|
if (value !== void 0 && typeof value !== type)
|
|
@@ -14471,8 +15593,8 @@ function strictSource(value, location, allowSignal, required = false) {
|
|
|
14471
15593
|
function defaultMatchesValueType(value, valueType) {
|
|
14472
15594
|
if (valueType === "number") {
|
|
14473
15595
|
if (typeof value !== "string") return Number.isFinite(Number(value));
|
|
14474
|
-
const
|
|
14475
|
-
const normalized = /^-?\d{1,3}(\.\d{3})+,\d+$/.test(
|
|
15596
|
+
const text2 = String(value).trim();
|
|
15597
|
+
const normalized = /^-?\d{1,3}(\.\d{3})+,\d+$/.test(text2) ? text2.replace(/\./g, "").replace(",", ".") : /^-?\d+,\d+$/.test(text2) ? text2.replace(",", ".") : text2;
|
|
14476
15598
|
return Number.isFinite(Number(normalized));
|
|
14477
15599
|
}
|
|
14478
15600
|
if (valueType === "boolean") {
|
|
@@ -14580,19 +15702,19 @@ function strictTriggers(value) {
|
|
|
14580
15702
|
return value.map((item, index) => {
|
|
14581
15703
|
if (!item || typeof item !== "object" || Array.isArray(item))
|
|
14582
15704
|
throw new Error(`trigger[${index}] must be an object`);
|
|
14583
|
-
const
|
|
14584
|
-
for (const key of Object.keys(
|
|
15705
|
+
const record3 = item;
|
|
15706
|
+
for (const key of Object.keys(record3))
|
|
14585
15707
|
if (!allowed.has(key)) throw new Error(`trigger[${index}].${key} is not allowed`);
|
|
14586
|
-
if (typeof
|
|
15708
|
+
if (typeof record3.eventName !== "string" || !record3.eventName.trim())
|
|
14587
15709
|
throw new Error(`trigger[${index}].eventName must be a non-empty string`);
|
|
14588
15710
|
for (const key of ["enabled", "reEvaluate", "trackPageview"])
|
|
14589
|
-
optionalType(
|
|
15711
|
+
optionalType(record3[key], "boolean", `trigger[${index}].${key}`);
|
|
14590
15712
|
for (const key of ["dataLayerName", "trackEventName"])
|
|
14591
|
-
optionalType(
|
|
14592
|
-
strictWhen(
|
|
14593
|
-
if (
|
|
15713
|
+
optionalType(record3[key], "string", `trigger[${index}].${key}`);
|
|
15714
|
+
strictWhen(record3.when, `trigger[${index}].when`);
|
|
15715
|
+
if (record3.trackRevenue !== void 0) {
|
|
14594
15716
|
const revenue = strictObject(
|
|
14595
|
-
|
|
15717
|
+
record3.trackRevenue,
|
|
14596
15718
|
`trigger[${index}].trackRevenue`,
|
|
14597
15719
|
/* @__PURE__ */ new Set([
|
|
14598
15720
|
"enabled",
|
|
@@ -14615,21 +15737,21 @@ function strictTriggers(value) {
|
|
|
14615
15737
|
);
|
|
14616
15738
|
}
|
|
14617
15739
|
}
|
|
14618
|
-
if (
|
|
14619
|
-
if (!Array.isArray(
|
|
15740
|
+
if (record3.mappings !== void 0) {
|
|
15741
|
+
if (!Array.isArray(record3.mappings))
|
|
14620
15742
|
throw new Error(`trigger[${index}].mappings must be an array`);
|
|
14621
|
-
|
|
15743
|
+
record3.mappings.forEach(
|
|
14622
15744
|
(mapping, mappingIndex) => strictMapping(mapping, `trigger[${index}].mappings[${mappingIndex}]`, false)
|
|
14623
15745
|
);
|
|
14624
15746
|
}
|
|
14625
|
-
if (
|
|
14626
|
-
if (!Array.isArray(
|
|
15747
|
+
if (record3.properties !== void 0) {
|
|
15748
|
+
if (!Array.isArray(record3.properties))
|
|
14627
15749
|
throw new Error(`trigger[${index}].properties must be an array`);
|
|
14628
|
-
|
|
15750
|
+
record3.properties.forEach(
|
|
14629
15751
|
(mapping, mappingIndex) => strictMapping(mapping, `trigger[${index}].properties[${mappingIndex}]`, true)
|
|
14630
15752
|
);
|
|
14631
15753
|
}
|
|
14632
|
-
return
|
|
15754
|
+
return record3;
|
|
14633
15755
|
});
|
|
14634
15756
|
}
|
|
14635
15757
|
function writeWarnings(result) {
|
|
@@ -14649,9 +15771,22 @@ async function identify(command, requestedShop) {
|
|
|
14649
15771
|
resolvedContext: { ...credentials.resolvedContext, shopId: void 0 }
|
|
14650
15772
|
};
|
|
14651
15773
|
const context = await getAccountContext(discoveryCredentials);
|
|
14652
|
-
|
|
14653
|
-
|
|
14654
|
-
|
|
15774
|
+
if (!context.ok) {
|
|
15775
|
+
fail(
|
|
15776
|
+
command,
|
|
15777
|
+
`${context.status === 401 || context.status === 403 ? "TRIGGERS_AUTH" : "TRIGGERS_CONFIG_ABSENT"}: ${context.error ?? "account context unavailable"}`,
|
|
15778
|
+
classifyStatus(context.status),
|
|
15779
|
+
context.errorDetails,
|
|
15780
|
+
apiFailureOf(context)
|
|
15781
|
+
);
|
|
15782
|
+
}
|
|
15783
|
+
const match2 = context.data?.data?.orgs?.flatMap((org) => org.shops).find((shop) => shop.domain.toLowerCase() === requestedShop.toLowerCase());
|
|
15784
|
+
if (!match2) {
|
|
15785
|
+
fail(
|
|
15786
|
+
command,
|
|
15787
|
+
"TRIGGERS_CONFIG_ABSENT: --shop domain is not available to this user",
|
|
15788
|
+
EXIT_CODES.validation
|
|
15789
|
+
);
|
|
14655
15790
|
}
|
|
14656
15791
|
credentials = {
|
|
14657
15792
|
...credentials,
|
|
@@ -14660,46 +15795,49 @@ async function identify(command, requestedShop) {
|
|
|
14660
15795
|
}
|
|
14661
15796
|
const res = await getInstallDomains(credentials);
|
|
14662
15797
|
if (!res.ok || !res.data) {
|
|
14663
|
-
const exitCode = res.status === 401 || res.status === 403 ? 4 : 2;
|
|
14664
15798
|
fail(
|
|
14665
15799
|
command,
|
|
14666
|
-
`${
|
|
14667
|
-
|
|
15800
|
+
`${res.status === 401 || res.status === 403 ? "TRIGGERS_AUTH" : "TRIGGERS_CONFIG_ABSENT"}: ${res.error ?? "shop identity unavailable"}`,
|
|
15801
|
+
classifyStatus(res.status)
|
|
14668
15802
|
);
|
|
14669
15803
|
}
|
|
14670
|
-
const
|
|
14671
|
-
if (requestedShop !==
|
|
14672
|
-
fail(
|
|
14673
|
-
|
|
15804
|
+
const identity2 = { shop_id: res.data.shop_id, domain: res.data.domain };
|
|
15805
|
+
if (requestedShop !== identity2.shop_id && requestedShop.toLowerCase() !== identity2.domain.toLowerCase())
|
|
15806
|
+
fail(
|
|
15807
|
+
command,
|
|
15808
|
+
"TRIGGERS_CONFIG_ABSENT: --shop does not match the authenticated shop",
|
|
15809
|
+
EXIT_CODES.validation
|
|
15810
|
+
);
|
|
15811
|
+
return { ...identity2, credentials };
|
|
14674
15812
|
}
|
|
14675
15813
|
async function send(command, requestedShop, body5) {
|
|
14676
|
-
const
|
|
14677
|
-
const res = await updateRuntimeSettings(
|
|
15814
|
+
const identity2 = await identify(command, requestedShop);
|
|
15815
|
+
const res = await updateRuntimeSettings(identity2.credentials, body5);
|
|
14678
15816
|
if (!res.ok || !res.data)
|
|
14679
15817
|
fail(
|
|
14680
15818
|
command,
|
|
14681
15819
|
`${res.status === 401 || res.status === 403 ? "TRIGGERS_AUTH" : "TRIGGERS_CONFIG_ABSENT"}: ${res.error ?? "Runtime settings update failed"}`,
|
|
14682
|
-
res.status
|
|
15820
|
+
classifyStatus(res.status),
|
|
14683
15821
|
res.errorDetails
|
|
14684
15822
|
);
|
|
14685
|
-
const { credentials: _credentials, ...publicIdentity } =
|
|
15823
|
+
const { credentials: _credentials, ...publicIdentity } = identity2;
|
|
14686
15824
|
return { data: res.data, identity: publicIdentity };
|
|
14687
15825
|
}
|
|
14688
15826
|
var get3 = new Command("get").description("Get data layer triggers").requiredOption("--shop <domain-or-id>", "Shop domain or Apex shop ID").action(async function(opts) {
|
|
14689
|
-
const
|
|
14690
|
-
const res = await getRuntimeSettings(
|
|
15827
|
+
const identity2 = await identify(this, opts.shop);
|
|
15828
|
+
const res = await getRuntimeSettings(identity2.credentials);
|
|
14691
15829
|
if (!res.ok || !res.data)
|
|
14692
15830
|
fail(
|
|
14693
15831
|
this,
|
|
14694
15832
|
`${res.status === 401 || res.status === 403 ? "TRIGGERS_AUTH" : "TRIGGERS_CONFIG_ABSENT"}: ${res.error ?? "Runtime settings read failed"}`,
|
|
14695
|
-
|
|
15833
|
+
classifyStatus(res.status),
|
|
14696
15834
|
res.errorDetails
|
|
14697
15835
|
);
|
|
14698
15836
|
const triggers = res.data.runtime_settings.dataLayerTriggers ?? [];
|
|
14699
|
-
const { credentials: _credentials, ...publicIdentity } =
|
|
15837
|
+
const { credentials: _credentials, ...publicIdentity } = identity2;
|
|
14700
15838
|
output(this, { verdict: "TRIGGERS_GET", ...publicIdentity, triggers }, () => {
|
|
14701
15839
|
process.stdout.write(
|
|
14702
|
-
`TRIGGERS_GET shop_id=${
|
|
15840
|
+
`TRIGGERS_GET shop_id=${identity2.shop_id} domain=${identity2.domain} count=${triggers.length}
|
|
14703
15841
|
`
|
|
14704
15842
|
);
|
|
14705
15843
|
printTable(
|
|
@@ -14716,24 +15854,24 @@ var get3 = new Command("get").description("Get data layer triggers").requiredOpt
|
|
|
14716
15854
|
var set2 = new Command("set").requiredOption("--file <json>", "JSON trigger array").requiredOption("--shop <domain-or-id>", "Shop domain or Apex shop ID").action(async function(opts) {
|
|
14717
15855
|
let triggers;
|
|
14718
15856
|
try {
|
|
14719
|
-
triggers = strictTriggers(JSON.parse(
|
|
15857
|
+
triggers = strictTriggers(JSON.parse(import_node_fs18.default.readFileSync(import_node_path15.default.resolve(opts.file), "utf8")));
|
|
14720
15858
|
} catch (error) {
|
|
14721
15859
|
fail(
|
|
14722
15860
|
this,
|
|
14723
15861
|
`TRIGGERS_CONFIG_ABSENT: ${error instanceof Error ? error.message : String(error)}`,
|
|
14724
|
-
|
|
15862
|
+
EXIT_CODES.validation
|
|
14725
15863
|
);
|
|
14726
15864
|
}
|
|
14727
|
-
const { data, identity } = await send(this, opts.shop, { dataLayerTriggers: triggers });
|
|
15865
|
+
const { data, identity: identity2 } = await send(this, opts.shop, { dataLayerTriggers: triggers });
|
|
14728
15866
|
const result = {
|
|
14729
15867
|
verdict: "TRIGGERS_SET",
|
|
14730
|
-
...
|
|
15868
|
+
...identity2,
|
|
14731
15869
|
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14732
15870
|
...data
|
|
14733
15871
|
};
|
|
14734
15872
|
output(this, result, () => {
|
|
14735
15873
|
process.stdout.write(
|
|
14736
|
-
`TRIGGERS_SET shop_id=${
|
|
15874
|
+
`TRIGGERS_SET shop_id=${identity2.shop_id} domain=${identity2.domain} updated_at=${result.updated_at} count=${triggers.length}
|
|
14737
15875
|
`
|
|
14738
15876
|
);
|
|
14739
15877
|
writeWarnings(result);
|
|
@@ -14743,17 +15881,21 @@ var propose = new Command("propose").requiredOption("--from-file <u2-report.json
|
|
|
14743
15881
|
let proposals;
|
|
14744
15882
|
let report;
|
|
14745
15883
|
try {
|
|
14746
|
-
report = JSON.parse(
|
|
15884
|
+
report = JSON.parse(import_node_fs18.default.readFileSync(import_node_path15.default.resolve(opts.fromFile), "utf8"));
|
|
14747
15885
|
proposals = proposeTriggers(report);
|
|
14748
15886
|
} catch (error) {
|
|
14749
|
-
fail(this, error instanceof Error ? error.message : String(error),
|
|
15887
|
+
fail(this, error instanceof Error ? error.message : String(error), EXIT_CODES.validation);
|
|
14750
15888
|
}
|
|
14751
15889
|
const reportBody = report.data && typeof report.data === "object" && !Array.isArray(report.data) ? report.data : report;
|
|
14752
15890
|
const reportShop = reportBody.shop ?? reportBody.shop_context ?? {};
|
|
14753
15891
|
const shop_id = String(reportShop.shop_id ?? reportShop.id ?? reportBody.shop_id ?? "");
|
|
14754
15892
|
const domain = String(reportShop.domain ?? reportBody.domain ?? "");
|
|
14755
15893
|
if (!shop_id || !domain || opts.shop !== shop_id && opts.shop.toLowerCase() !== domain.toLowerCase()) {
|
|
14756
|
-
fail(
|
|
15894
|
+
fail(
|
|
15895
|
+
this,
|
|
15896
|
+
"TRIGGERS_CONFIG_ABSENT: --shop must match the report shop_id or domain",
|
|
15897
|
+
EXIT_CODES.validation
|
|
15898
|
+
);
|
|
14757
15899
|
}
|
|
14758
15900
|
if (!proposals.length)
|
|
14759
15901
|
fail(this, "TRIGGERS_NO_DATA: report has no usable purchase candidates", 3);
|
|
@@ -14782,29 +15924,34 @@ var recipe = new Command("apply-recipe").argument("<recipeId>").requiredOption("
|
|
|
14782
15924
|
const params = Object.fromEntries(
|
|
14783
15925
|
opts.param.map((pair) => {
|
|
14784
15926
|
const i = pair.indexOf("=");
|
|
14785
|
-
if (i < 1)
|
|
15927
|
+
if (i < 1)
|
|
15928
|
+
fail(
|
|
15929
|
+
this,
|
|
15930
|
+
`TRIGGERS_CONFIG_ABSENT: invalid --param ${pair}; expected k=v`,
|
|
15931
|
+
EXIT_CODES.validation
|
|
15932
|
+
);
|
|
14786
15933
|
return [pair.slice(0, i), parseParamValue(pair.slice(i + 1))];
|
|
14787
15934
|
})
|
|
14788
15935
|
);
|
|
14789
|
-
const
|
|
14790
|
-
const res = await applyRuntimeSettingsRecipe(
|
|
15936
|
+
const identity2 = await identify(this, opts.shop);
|
|
15937
|
+
const res = await applyRuntimeSettingsRecipe(identity2.credentials, { recipeId, params });
|
|
14791
15938
|
if (!res.ok || !res.data)
|
|
14792
15939
|
fail(
|
|
14793
15940
|
this,
|
|
14794
15941
|
`${res.status === 401 || res.status === 403 ? "TRIGGERS_AUTH" : "TRIGGERS_CONFIG_ABSENT"}: ${res.error ?? "Recipe failed"}`,
|
|
14795
|
-
res.status
|
|
15942
|
+
classifyStatus(res.status),
|
|
14796
15943
|
res.errorDetails
|
|
14797
15944
|
);
|
|
14798
15945
|
const result = {
|
|
14799
15946
|
verdict: "TRIGGERS_APPLIED",
|
|
14800
15947
|
...res.data,
|
|
14801
|
-
shop_id:
|
|
14802
|
-
domain:
|
|
15948
|
+
shop_id: identity2.shop_id,
|
|
15949
|
+
domain: identity2.domain,
|
|
14803
15950
|
applied_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
14804
15951
|
};
|
|
14805
15952
|
output(this, result, () => {
|
|
14806
15953
|
process.stdout.write(
|
|
14807
|
-
`TRIGGERS_APPLIED shop_id=${
|
|
15954
|
+
`TRIGGERS_APPLIED shop_id=${identity2.shop_id} domain=${identity2.domain} applied_at=${result.applied_at} recipe=${recipeId}
|
|
14808
15955
|
`
|
|
14809
15956
|
);
|
|
14810
15957
|
writeWarnings(result);
|
|
@@ -14813,35 +15960,39 @@ var recipe = new Command("apply-recipe").argument("<recipeId>").requiredOption("
|
|
|
14813
15960
|
var enable = new Command("enable-capture").requiredOption("--hours <n>").requiredOption("--shop <domain-or-id>", "Shop domain or Apex shop ID").option("--url-pattern <p>", "URL pattern", collect, []).action(async function(opts) {
|
|
14814
15961
|
const hours = Number(opts.hours);
|
|
14815
15962
|
if (!Number.isFinite(hours) || hours <= 0 || hours > 48)
|
|
14816
|
-
fail(
|
|
15963
|
+
fail(
|
|
15964
|
+
this,
|
|
15965
|
+
"TRIGGERS_CONFIG_ABSENT: --hours must be greater than 0 and at most 48",
|
|
15966
|
+
EXIT_CODES.validation
|
|
15967
|
+
);
|
|
14817
15968
|
const expiresAt = new Date(Date.now() + hours * 36e5).toISOString();
|
|
14818
15969
|
const setupCapture = {
|
|
14819
15970
|
enabled: true,
|
|
14820
15971
|
expiresAt,
|
|
14821
15972
|
...opts.urlPattern.length > 0 ? { urlPatterns: opts.urlPattern } : {}
|
|
14822
15973
|
};
|
|
14823
|
-
const { data, identity } = await send(this, opts.shop, { setupCapture });
|
|
14824
|
-
const result = { verdict: "CAPTURE_ENABLED", ...
|
|
15974
|
+
const { data, identity: identity2 } = await send(this, opts.shop, { setupCapture });
|
|
15975
|
+
const result = { verdict: "CAPTURE_ENABLED", ...identity2, expiresAt, ...data };
|
|
14825
15976
|
output(
|
|
14826
15977
|
this,
|
|
14827
15978
|
result,
|
|
14828
15979
|
() => process.stdout.write(
|
|
14829
|
-
`CAPTURE_ENABLED shop_id=${
|
|
15980
|
+
`CAPTURE_ENABLED shop_id=${identity2.shop_id} domain=${identity2.domain} expires_at=${expiresAt}
|
|
14830
15981
|
`
|
|
14831
15982
|
)
|
|
14832
15983
|
);
|
|
14833
15984
|
});
|
|
14834
15985
|
var disable = new Command("disable-capture").requiredOption("--shop <domain-or-id>", "Shop domain or Apex shop ID").action(async function(opts) {
|
|
14835
|
-
const { data, identity } = await send(this, opts.shop, {
|
|
15986
|
+
const { data, identity: identity2 } = await send(this, opts.shop, {
|
|
14836
15987
|
setupCapture: { enabled: false, expiresAt: null }
|
|
14837
15988
|
});
|
|
14838
15989
|
const disabled_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
14839
|
-
const result = { verdict: "CAPTURE_DISABLED", ...
|
|
15990
|
+
const result = { verdict: "CAPTURE_DISABLED", ...identity2, disabled_at, ...data };
|
|
14840
15991
|
output(
|
|
14841
15992
|
this,
|
|
14842
15993
|
result,
|
|
14843
15994
|
() => process.stdout.write(
|
|
14844
|
-
`CAPTURE_DISABLED shop_id=${
|
|
15995
|
+
`CAPTURE_DISABLED shop_id=${identity2.shop_id} domain=${identity2.domain} disabled_at=${disabled_at}
|
|
14845
15996
|
`
|
|
14846
15997
|
)
|
|
14847
15998
|
);
|
|
@@ -15061,8 +16212,8 @@ var qa2 = new Command("qa").description("Check static link/form handover paramet
|
|
|
15061
16212
|
html = await response.text();
|
|
15062
16213
|
} catch (error) {
|
|
15063
16214
|
if (error instanceof CliError) throw error;
|
|
15064
|
-
const
|
|
15065
|
-
fail(this, `Could not fetch ${opts.from}: ${
|
|
16215
|
+
const reason2 = error instanceof Error ? error.message : String(error);
|
|
16216
|
+
fail(this, `Could not fetch ${opts.from}: ${reason2}`, EXIT_CODES.general);
|
|
15066
16217
|
}
|
|
15067
16218
|
const param = config.externalTracking.handoverQueryParam;
|
|
15068
16219
|
const baseUrl = response.url || opts.from;
|
|
@@ -15109,6 +16260,138 @@ var qa2 = new Command("qa").description("Check static link/form handover paramet
|
|
|
15109
16260
|
});
|
|
15110
16261
|
var handoverCommand = new Command("handover").description("Manage cross-domain identity handover").addCommand(get4).addCommand(set4).addCommand(off2).addCommand(qa2);
|
|
15111
16262
|
|
|
16263
|
+
// src/commands/order-journey.ts
|
|
16264
|
+
function instant(value) {
|
|
16265
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d+))?)?(Z|[+-]\d{2}:\d{2})$/.exec(
|
|
16266
|
+
value
|
|
16267
|
+
);
|
|
16268
|
+
if (!m) return null;
|
|
16269
|
+
const year = Number(m[1]), month = Number(m[2]), day = Number(m[3]);
|
|
16270
|
+
const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
|
16271
|
+
const days = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
16272
|
+
if (month < 1 || month > 12 || day < 1 || day > days[month - 1] || Number(m[4]) > 23 || Number(m[5]) > 59 || Number(m[6] ?? 0) > 59 || m[8] !== "Z" && (Number(m[8].slice(1, 3)) > 23 || Number(m[8].slice(4)) > 59))
|
|
16273
|
+
return null;
|
|
16274
|
+
const second = Date.parse(`${m[1]}-${m[2]}-${m[3]}T${m[4]}:${m[5]}:${m[6] ?? "00"}${m[8]}`);
|
|
16275
|
+
return Number.isFinite(second) ? { second, fraction: (m[7] ?? "").replace(/0+$/, "") } : null;
|
|
16276
|
+
}
|
|
16277
|
+
function compare(a, b) {
|
|
16278
|
+
if (a.second !== b.second) return a.second < b.second ? -1 : 1;
|
|
16279
|
+
const precision = Math.max(a.fraction.length, b.fraction.length);
|
|
16280
|
+
const left = a.fraction.padEnd(precision, "0"), right = b.fraction.padEnd(precision, "0");
|
|
16281
|
+
return left === right ? 0 : left < right ? -1 : 1;
|
|
16282
|
+
}
|
|
16283
|
+
var record = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
16284
|
+
var text = (v) => typeof v === "string" && v.length > 0;
|
|
16285
|
+
var reason = (v) => typeof v === "string" && (/^[a-z][a-z0-9_]{0,79}$/.test(v) || /^(publishedConfig|consentPolicy|goalMapping|sdkArtifact|sdkPolicy|identityPolicy|goalDefinition|resultWindow|inputSnapshot)_(expected_unknown|observed_unknown|mismatch|binding_mismatch)$/.test(
|
|
16286
|
+
v
|
|
16287
|
+
));
|
|
16288
|
+
var member = (v, values) => typeof v === "string" && values.includes(v);
|
|
16289
|
+
var status = (v) => member(v, ["pass", "fail", "inconclusive"]);
|
|
16290
|
+
var count = (v) => typeof v === "number" && Number.isSafeInteger(v) && v >= 0;
|
|
16291
|
+
function validReport(v) {
|
|
16292
|
+
if (!record(v) || v.schemaVersion !== 1 || v.finalStatisticalContribution !== "not_evaluated" || !record(v.evaluation) || !status(v.evaluation.status) || !member(v.evaluation.freshness, ["current", "stale", "unknown"]) || !Array.isArray(v.evaluation.stages) || !v.evaluation.stages.every(
|
|
16293
|
+
(s) => record(s) && (s.freshness === void 0 || member(s.freshness, ["current", "stale", "unknown"])) && member(s.stage, [
|
|
16294
|
+
"consent",
|
|
16295
|
+
"assignment",
|
|
16296
|
+
"event",
|
|
16297
|
+
"order_identity",
|
|
16298
|
+
"authoritative_fact",
|
|
16299
|
+
"attribution",
|
|
16300
|
+
"result"
|
|
16301
|
+
]) && status(s.status) && Array.isArray(s.reasonCodes) && s.reasonCodes.every(reason)
|
|
16302
|
+
))
|
|
16303
|
+
return false;
|
|
16304
|
+
if (v.receiptOutcomes !== void 0 && (!Array.isArray(v.receiptOutcomes) || v.receiptOutcomes.length > 6856 || !v.receiptOutcomes.every(
|
|
16305
|
+
(r) => record(r) && text(r.receiptId) && text(r.stage) && text(r.producerContract) && record(r.scope) && [r.scope.experimentId, r.scope.variationId, r.scope.goalId].every(
|
|
16306
|
+
(x) => x === null || text(x)
|
|
16307
|
+
) && member(r.freshness, ["current", "stale", "unknown"]) && Array.isArray(r.reasonCodes) && r.reasonCodes.every(reason)
|
|
16308
|
+
)))
|
|
16309
|
+
return false;
|
|
16310
|
+
const coverage = v.assignmentCoverage;
|
|
16311
|
+
if (!record(coverage) || typeof coverage.complete !== "boolean" || typeof coverage.overflowCountIsLowerBound !== "boolean" || !(coverage.overflowCount === null || count(coverage.overflowCount)))
|
|
16312
|
+
return false;
|
|
16313
|
+
return Array.isArray(v.assignments) && v.assignments.length <= 50 && v.assignments.every(
|
|
16314
|
+
(a) => record(a) && text(a.experimentId) && text(a.variationId) && (a.storedAttribution === null || typeof a.storedAttribution === "boolean") && reason(a.reasonCode) && Array.isArray(a.goals) && a.goals.length <= 50 && a.goals.every(
|
|
16315
|
+
(g) => record(g) && text(g.goalId) && member(g.status, [
|
|
16316
|
+
"eligible_for_attributed_goal_read",
|
|
16317
|
+
"excluded_from_attributed_goal_read",
|
|
16318
|
+
"unknown"
|
|
16319
|
+
]) && reason(g.reasonCode)
|
|
16320
|
+
)
|
|
16321
|
+
);
|
|
16322
|
+
}
|
|
16323
|
+
var orderJourneyCommand = new Command("order-journey").description("Inspect retained order evidence and attributed-goal eligibility (read-only)").requiredOption("--platform <platform>", "shopify, shopware, woocommerce, centra or custom").option("--order-id <id>", "Platform order ID, mutually exclusive with --order-number").option("--order-number <number>", "Order number, mutually exclusive with --order-id").requiredOption("--environment <key>", "Exact environment").requiredOption("--from <iso>", "Inclusive ISO start timestamp").requiredOption("--to <iso>", "Inclusive ISO end timestamp, at most 31 days after start").option("--include-qa", "Include QA in the canonical eligibility read").action(async function(opts) {
|
|
16324
|
+
const key = opts.orderId ?? opts.orderNumber;
|
|
16325
|
+
const from = instant(opts.from), to = instant(opts.to);
|
|
16326
|
+
if (Number(opts.orderId !== void 0) + Number(opts.orderNumber !== void 0) !== 1 || !key?.trim() || key.trim().length > 255 || /[\x00-\x1f\x7f]/.test(key) || !["shopify", "shopware", "woocommerce", "centra", "custom"].includes(opts.platform) || !/^[a-z0-9][a-z0-9_-]{0,63}$/.test(opts.environment) || !from || !to || compare(from, to) > 0 || compare(to, { ...from, second: from.second + 31 * 864e5 }) > 0 || compare(to, instant(new Date(Date.now() + 3e5).toISOString())) > 0) {
|
|
16327
|
+
fail(
|
|
16328
|
+
this,
|
|
16329
|
+
"Invalid order journey query: supply exactly one order identifier and a bounded ISO interval",
|
|
16330
|
+
EXIT_CODES.validation
|
|
16331
|
+
);
|
|
16332
|
+
}
|
|
16333
|
+
const result = await getOrderJourney(requireCredentials(), {
|
|
16334
|
+
...opts,
|
|
16335
|
+
orderId: opts.orderId?.trim(),
|
|
16336
|
+
orderNumber: opts.orderNumber?.trim()
|
|
16337
|
+
});
|
|
16338
|
+
if (!result.ok || !result.data)
|
|
16339
|
+
fail(this, result.error ?? "Order journey unavailable", classifyStatus(result.status));
|
|
16340
|
+
const report = result.data.data;
|
|
16341
|
+
if (!validReport(report)) fail(this, "Malformed order journey response", EXIT_CODES.general);
|
|
16342
|
+
output(this, { data: report }, () => {
|
|
16343
|
+
const coverage = report.assignmentCoverage;
|
|
16344
|
+
printTable(
|
|
16345
|
+
["Assignment coverage", "Overflow"],
|
|
16346
|
+
[
|
|
16347
|
+
[
|
|
16348
|
+
coverage.complete ? "complete" : "incomplete",
|
|
16349
|
+
coverage.overflowCount === null ? "unknown" : `${coverage.overflowCount} (${coverage.overflowCountIsLowerBound ? "lower bound" : "exact"})`
|
|
16350
|
+
]
|
|
16351
|
+
]
|
|
16352
|
+
);
|
|
16353
|
+
printTable(
|
|
16354
|
+
["Journey", "Freshness", "Eligibility basis", "Final contribution"],
|
|
16355
|
+
[
|
|
16356
|
+
[
|
|
16357
|
+
report.evaluation.status,
|
|
16358
|
+
report.evaluation.freshness,
|
|
16359
|
+
"attributed_goal_counts",
|
|
16360
|
+
report.finalStatisticalContribution
|
|
16361
|
+
]
|
|
16362
|
+
]
|
|
16363
|
+
);
|
|
16364
|
+
printTable(
|
|
16365
|
+
["Stage", "Status", "Freshness", "Reasons"],
|
|
16366
|
+
report.evaluation.stages.map((s) => [
|
|
16367
|
+
s.stage,
|
|
16368
|
+
s.status,
|
|
16369
|
+
s.freshness ?? "unknown",
|
|
16370
|
+
s.reasonCodes.join(", ")
|
|
16371
|
+
])
|
|
16372
|
+
);
|
|
16373
|
+
if (report.receiptOutcomes?.length)
|
|
16374
|
+
printTable(
|
|
16375
|
+
["Receipt", "Stage", "Experiment / variation / goal", "Freshness"],
|
|
16376
|
+
report.receiptOutcomes.map((r) => [
|
|
16377
|
+
r.receiptId,
|
|
16378
|
+
r.stage,
|
|
16379
|
+
[r.scope.experimentId, r.scope.variationId, r.scope.goalId].filter(Boolean).join(" / ") || "global",
|
|
16380
|
+
r.freshness
|
|
16381
|
+
])
|
|
16382
|
+
);
|
|
16383
|
+
printTable(
|
|
16384
|
+
["Experiment", "Variation", "Attribution", "Goal eligibility"],
|
|
16385
|
+
report.assignments.map((a) => [
|
|
16386
|
+
a.experimentId,
|
|
16387
|
+
a.variationId,
|
|
16388
|
+
a.storedAttribution === null ? "unknown" : a.storedAttribution ? "present" : "not observed",
|
|
16389
|
+
a.goals.length ? a.goals.map((g) => `${g.goalId}: ${g.status} (${g.reasonCode})`).join("; ") : `unknown (${a.reasonCode})`
|
|
16390
|
+
])
|
|
16391
|
+
);
|
|
16392
|
+
});
|
|
16393
|
+
});
|
|
16394
|
+
|
|
15112
16395
|
// src/lib/detect-markers.ts
|
|
15113
16396
|
var DETECTION_MARKERS = [
|
|
15114
16397
|
{
|
|
@@ -15335,11 +16618,11 @@ async function fetchPrimaryHtml(host) {
|
|
|
15335
16618
|
headers: { "user-agent": "apex-cli-doctor/1.0" },
|
|
15336
16619
|
signal: AbortSignal.timeout(PAGE_FETCH_TIMEOUT_MS)
|
|
15337
16620
|
});
|
|
15338
|
-
const
|
|
15339
|
-
if (CHALLENGE_STATUSES.includes(res.status) || /cf-chl|challenge-platform|captcha/i.test(
|
|
16621
|
+
const text2 = await res.text();
|
|
16622
|
+
if (CHALLENGE_STATUSES.includes(res.status) || /cf-chl|challenge-platform|captcha/i.test(text2))
|
|
15340
16623
|
return { protected: true };
|
|
15341
16624
|
if (!res.ok) return { error: `http ${res.status}` };
|
|
15342
|
-
return { html:
|
|
16625
|
+
return { html: text2 };
|
|
15343
16626
|
} catch (error) {
|
|
15344
16627
|
return { error: error instanceof Error ? error.message : String(error) };
|
|
15345
16628
|
}
|
|
@@ -15361,9 +16644,9 @@ function environmentByHost(envs) {
|
|
|
15361
16644
|
}
|
|
15362
16645
|
return map;
|
|
15363
16646
|
}
|
|
15364
|
-
function devPollutionCheck(eventHosts, envs) {
|
|
16647
|
+
function devPollutionCheck(eventHosts, envs, unparsedRows) {
|
|
15365
16648
|
const dev = eventHosts.filter((host) => isDevHost(host));
|
|
15366
|
-
if (!dev.length)
|
|
16649
|
+
if (!dev.length && !unparsedRows.length)
|
|
15367
16650
|
return {
|
|
15368
16651
|
checkId: "dev_pollution",
|
|
15369
16652
|
status: "PASS",
|
|
@@ -15378,6 +16661,13 @@ function devPollutionCheck(eventHosts, envs) {
|
|
|
15378
16661
|
return `${host} matches development host markers and is bound to the production environment`;
|
|
15379
16662
|
return `${host} matches development host markers, sent production-labeled traffic, and is not bound to any environment`;
|
|
15380
16663
|
});
|
|
16664
|
+
if (unparsedRows.length) {
|
|
16665
|
+
const pageviews = unparsedRows.reduce((sum, row) => sum + row.pageviews, 0);
|
|
16666
|
+
const firstPath = unparsedRows[0].path || "(empty)";
|
|
16667
|
+
evidence.push(
|
|
16668
|
+
`${pageviews} production-labeled pageview(s) carry a host the analytics read could not parse (first path: ${firstPath}); development or local-file traffic cannot be ruled out`
|
|
16669
|
+
);
|
|
16670
|
+
}
|
|
15381
16671
|
const mappable = dev.filter(
|
|
15382
16672
|
(host) => (bindings.get(host) ?? PRODUCTION_ENVIRONMENT_KEY) === PRODUCTION_ENVIRONMENT_KEY
|
|
15383
16673
|
);
|
|
@@ -15393,7 +16683,7 @@ var PURCHASE_CADENCE_WINDOW_MINUTES = PURCHASE_WINDOW_MINUTES * 4;
|
|
|
15393
16683
|
var MIN_CAPTURE_WINDOW_MINUTES = 60;
|
|
15394
16684
|
var DUPLICATE_RATE_THRESHOLD = 0.02;
|
|
15395
16685
|
var MAGNITUDE_TOLERANCE_RATE = 0.1;
|
|
15396
|
-
function
|
|
16686
|
+
function record2(value) {
|
|
15397
16687
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
15398
16688
|
}
|
|
15399
16689
|
function purchasePlane(config) {
|
|
@@ -15401,8 +16691,8 @@ function purchasePlane(config) {
|
|
|
15401
16691
|
const names = [];
|
|
15402
16692
|
const triggers = config.runtime?.dataLayerTriggers ?? config.dataLayerTriggers;
|
|
15403
16693
|
if (Array.isArray(triggers) && triggers.some((trigger) => {
|
|
15404
|
-
const row =
|
|
15405
|
-
const trackRevenue =
|
|
16694
|
+
const row = record2(trigger);
|
|
16695
|
+
const trackRevenue = record2(row?.trackRevenue);
|
|
15406
16696
|
return row?.enabled !== false && trackRevenue !== null && trackRevenue.enabled !== false;
|
|
15407
16697
|
}))
|
|
15408
16698
|
names.push("dataLayer trigger");
|
|
@@ -15444,7 +16734,7 @@ function lineItemSum(value) {
|
|
|
15444
16734
|
if (!Array.isArray(value)) return null;
|
|
15445
16735
|
let sum = 0;
|
|
15446
16736
|
for (const item of value) {
|
|
15447
|
-
const row =
|
|
16737
|
+
const row = record2(item);
|
|
15448
16738
|
const price = numeric(row?.price);
|
|
15449
16739
|
if (price === null) return null;
|
|
15450
16740
|
sum += price * (numeric(row?.quantity) ?? 1);
|
|
@@ -15838,7 +17128,10 @@ async function runDoctor(options = {}) {
|
|
|
15838
17128
|
const rows = inventory.rows;
|
|
15839
17129
|
const inventoryCaveat = inventory.truncated ? [INVENTORY_TRUNCATED_CAVEAT] : [];
|
|
15840
17130
|
const hosts = configuredHosts(domainsRes.data, envsRes.data);
|
|
15841
|
-
const
|
|
17131
|
+
const unparsedRows = rows.filter((row) => row.host.trim() === "");
|
|
17132
|
+
const eventHosts = [
|
|
17133
|
+
...new Set(rows.filter((row) => row.host.trim() !== "").map((row) => row.host.toLowerCase()))
|
|
17134
|
+
];
|
|
15842
17135
|
const checks = [];
|
|
15843
17136
|
const unbound = eventHosts.filter((host) => !hosts.has(host));
|
|
15844
17137
|
checks.push(
|
|
@@ -15859,7 +17152,9 @@ async function runDoctor(options = {}) {
|
|
|
15859
17152
|
]
|
|
15860
17153
|
}
|
|
15861
17154
|
);
|
|
15862
|
-
checks.push(
|
|
17155
|
+
checks.push(
|
|
17156
|
+
withCaveat(devPollutionCheck(eventHosts, envsRes.data, unparsedRows), inventoryCaveat)
|
|
17157
|
+
);
|
|
15863
17158
|
const primary = hostOnly(domainsRes.data.domain) ?? eventHosts[0];
|
|
15864
17159
|
const shopId = resolveShopId(options) ?? domainsRes.data.shop_id;
|
|
15865
17160
|
const workerBase = resolveWorkerBase(options);
|
|
@@ -16019,11 +17314,11 @@ function withCaveat(check, caveat) {
|
|
|
16019
17314
|
}
|
|
16020
17315
|
var DAY_MS = 864e5;
|
|
16021
17316
|
function parseDay(value) {
|
|
16022
|
-
const
|
|
16023
|
-
if (!
|
|
16024
|
-
const parsed = Date.parse(`${
|
|
17317
|
+
const text2 = value?.trim();
|
|
17318
|
+
if (!text2 || !/^\d{4}-\d{2}-\d{2}$/.test(text2)) return null;
|
|
17319
|
+
const parsed = Date.parse(`${text2}T00:00:00.000Z`);
|
|
16025
17320
|
if (Number.isNaN(parsed)) return null;
|
|
16026
|
-
return toDay(parsed) ===
|
|
17321
|
+
return toDay(parsed) === text2 ? parsed : null;
|
|
16027
17322
|
}
|
|
16028
17323
|
function toDay(value) {
|
|
16029
17324
|
return new Date(value).toISOString().slice(0, 10);
|
|
@@ -16263,7 +17558,7 @@ function urlsCommand() {
|
|
|
16263
17558
|
);
|
|
16264
17559
|
});
|
|
16265
17560
|
}
|
|
16266
|
-
var analyticsCommand = new Command("analytics").description("Read shop analytics").addCommand(analyticsSubcommand("stats", "Show dashboard analytics stats", 7)).addCommand(analyticsSubcommand("timeseries", "Show analytics timeseries", 30)).addCommand(analyticsSubcommand("traffic", "Show traffic sources", 30)).addCommand(analyticsSubcommand("pages", "Show page analytics", 30)).addCommand(analyticsSubcommand("geo", "Show geography and device analytics", 30)).addCommand(analyticsSubcommand("sessions", "Show session analytics", 30)).addCommand(analyticsSubcommand("realtime", "Show realtime analytics")).addCommand(analyticsSubcommand("entry-exit", "Show entry and exit page analytics", 30)).addCommand(consentRateCommand).addCommand(urlsCommand()).addHelpText(
|
|
17561
|
+
var analyticsCommand = new Command("analytics").description("Read shop analytics").addCommand(orderJourneyCommand).addCommand(analyticsSubcommand("stats", "Show dashboard analytics stats", 7)).addCommand(analyticsSubcommand("timeseries", "Show analytics timeseries", 30)).addCommand(analyticsSubcommand("traffic", "Show traffic sources", 30)).addCommand(analyticsSubcommand("pages", "Show page analytics", 30)).addCommand(analyticsSubcommand("geo", "Show geography and device analytics", 30)).addCommand(analyticsSubcommand("sessions", "Show session analytics", 30)).addCommand(analyticsSubcommand("realtime", "Show realtime analytics")).addCommand(analyticsSubcommand("entry-exit", "Show entry and exit page analytics", 30)).addCommand(consentRateCommand).addCommand(urlsCommand()).addHelpText(
|
|
16267
17562
|
"after",
|
|
16268
17563
|
`
|
|
16269
17564
|
Examples:
|
|
@@ -16275,7 +17570,7 @@ Examples:
|
|
|
16275
17570
|
);
|
|
16276
17571
|
|
|
16277
17572
|
// src/commands/install.ts
|
|
16278
|
-
var
|
|
17573
|
+
var import_promises4 = require("node:dns/promises");
|
|
16279
17574
|
var import_node_http2 = require("node:http");
|
|
16280
17575
|
var import_node_https = require("node:https");
|
|
16281
17576
|
var import_node_net = require("node:net");
|
|
@@ -16284,6 +17579,23 @@ var DESKTOP_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/53
|
|
|
16284
17579
|
var PRODUCTION_WORKER_BASE_URL2 = "https://events.drip-apex.com";
|
|
16285
17580
|
var UrlPolicyError = class extends Error {
|
|
16286
17581
|
};
|
|
17582
|
+
var IdentityProxyError = class extends Error {
|
|
17583
|
+
constructor(originalUrl, proxyHost) {
|
|
17584
|
+
super(
|
|
17585
|
+
`${originalUrl} redirects to ${proxyHost}, an identity-aware proxy sign-in; the install check cannot reach a storefront behind it`
|
|
17586
|
+
);
|
|
17587
|
+
this.originalUrl = originalUrl;
|
|
17588
|
+
this.proxyHost = proxyHost;
|
|
17589
|
+
}
|
|
17590
|
+
originalUrl;
|
|
17591
|
+
proxyHost;
|
|
17592
|
+
};
|
|
17593
|
+
var IDENTITY_PROXY_HOSTS = ["accounts.google.com", "login.microsoftonline.com"];
|
|
17594
|
+
var IDENTITY_PROXY_SUFFIXES = [".cloudflareaccess.com"];
|
|
17595
|
+
function identityProxyHost(host) {
|
|
17596
|
+
const normalized = normalizeHostname(host);
|
|
17597
|
+
return IDENTITY_PROXY_HOSTS.includes(normalized) || IDENTITY_PROXY_SUFFIXES.some((suffix) => normalized.endsWith(suffix));
|
|
17598
|
+
}
|
|
16287
17599
|
function normalizeHostname(host) {
|
|
16288
17600
|
return host.toLowerCase().replace(/\.$/, "");
|
|
16289
17601
|
}
|
|
@@ -16316,7 +17628,7 @@ var testResolver;
|
|
|
16316
17628
|
var testFetch;
|
|
16317
17629
|
async function resolveHost(hostname) {
|
|
16318
17630
|
const mocked = testResolver?.(hostname) ?? null;
|
|
16319
|
-
const addresses = mocked ?? await (0,
|
|
17631
|
+
const addresses = mocked ?? await (0, import_promises4.lookup)(hostname, { all: true });
|
|
16320
17632
|
return addresses.map((entry) => entry.address);
|
|
16321
17633
|
}
|
|
16322
17634
|
async function validateUrl(raw, allowedHosts) {
|
|
@@ -16427,7 +17739,7 @@ function overLimitMessage(hostname, maxBytes) {
|
|
|
16427
17739
|
return `Response from ${hostname} exceeded the ${maxBytes}-byte cap`;
|
|
16428
17740
|
}
|
|
16429
17741
|
function responseFromIncoming(res, body5, decoded) {
|
|
16430
|
-
const
|
|
17742
|
+
const status3 = res.statusCode ?? 0;
|
|
16431
17743
|
const headers = new Headers();
|
|
16432
17744
|
for (const [key, value] of Object.entries(res.headers)) {
|
|
16433
17745
|
if (Array.isArray(value)) for (const entry of value) headers.append(key, entry);
|
|
@@ -16437,9 +17749,9 @@ function responseFromIncoming(res, body5, decoded) {
|
|
|
16437
17749
|
headers.delete("content-encoding");
|
|
16438
17750
|
headers.delete("content-length");
|
|
16439
17751
|
}
|
|
16440
|
-
const nullBody =
|
|
17752
|
+
const nullBody = status3 === 101 || status3 === 204 || status3 === 205 || status3 === 304;
|
|
16441
17753
|
return new Response(nullBody ? null : body5, {
|
|
16442
|
-
status:
|
|
17754
|
+
status: status3,
|
|
16443
17755
|
statusText: res.statusMessage ?? "",
|
|
16444
17756
|
headers
|
|
16445
17757
|
});
|
|
@@ -16574,7 +17886,10 @@ async function fetchSafe(raw, allowedHosts, limits) {
|
|
|
16574
17886
|
if (![301, 302, 303, 307, 308].includes(result.response.status)) return result;
|
|
16575
17887
|
const location = result.response.headers.get("location");
|
|
16576
17888
|
if (!location) return result;
|
|
16577
|
-
|
|
17889
|
+
const next = new URL(location, target.url);
|
|
17890
|
+
if (identityProxyHost(next.hostname))
|
|
17891
|
+
throw new IdentityProxyError(raw, normalizeHostname(next.hostname));
|
|
17892
|
+
target = await validateUrl(next.toString(), allowedHosts);
|
|
16578
17893
|
}
|
|
16579
17894
|
throw new UrlPolicyError("Redirect limit exceeded (5)");
|
|
16580
17895
|
}
|
|
@@ -16584,7 +17899,7 @@ function detectChallenge(html) {
|
|
|
16584
17899
|
);
|
|
16585
17900
|
}
|
|
16586
17901
|
var LEGACY_SNIPPET_MARKER = /sdk\.drip-apex\.com|events\.drip-apex\.com|\/drip\.js\b|\bDrip\.init\s*\(|\bwindow\.drip\b/i;
|
|
16587
|
-
function pageReport(url, shopId,
|
|
17902
|
+
function pageReport(url, shopId, status3, html, workerSnippetPresent, bodyTruncated) {
|
|
16588
17903
|
const challenge = detectChallenge(html);
|
|
16589
17904
|
const shopIdMatches = html.includes(shopId);
|
|
16590
17905
|
const snippetPresent = workerSnippetPresent || LEGACY_SNIPPET_MARKER.test(html) && shopIdMatches;
|
|
@@ -16592,29 +17907,29 @@ function pageReport(url, shopId, status2, html, workerSnippetPresent, bodyTrunca
|
|
|
16592
17907
|
return {
|
|
16593
17908
|
url,
|
|
16594
17909
|
status: "WARN",
|
|
16595
|
-
httpStatus:
|
|
17910
|
+
httpStatus: status3,
|
|
16596
17911
|
snippetPresent,
|
|
16597
17912
|
shopIdMatches,
|
|
16598
17913
|
botChallenge: true,
|
|
16599
17914
|
bodyTruncated,
|
|
16600
17915
|
message: "Bot challenge detected; verify in a real browser."
|
|
16601
17916
|
};
|
|
16602
|
-
if (
|
|
17917
|
+
if (status3 < 200 || status3 >= 300)
|
|
16603
17918
|
return {
|
|
16604
17919
|
url,
|
|
16605
17920
|
status: "FAIL",
|
|
16606
|
-
httpStatus:
|
|
17921
|
+
httpStatus: status3,
|
|
16607
17922
|
snippetPresent,
|
|
16608
17923
|
shopIdMatches,
|
|
16609
17924
|
botChallenge: false,
|
|
16610
17925
|
bodyTruncated,
|
|
16611
|
-
message: `Storefront page returned HTTP ${
|
|
17926
|
+
message: `Storefront page returned HTTP ${status3}; the install cannot be verified from this response.`
|
|
16612
17927
|
};
|
|
16613
17928
|
if (!snippetPresent)
|
|
16614
17929
|
return {
|
|
16615
17930
|
url,
|
|
16616
17931
|
status: "FAIL",
|
|
16617
|
-
httpStatus:
|
|
17932
|
+
httpStatus: status3,
|
|
16618
17933
|
snippetPresent,
|
|
16619
17934
|
shopIdMatches,
|
|
16620
17935
|
botChallenge: false,
|
|
@@ -16625,7 +17940,7 @@ function pageReport(url, shopId, status2, html, workerSnippetPresent, bodyTrunca
|
|
|
16625
17940
|
return {
|
|
16626
17941
|
url,
|
|
16627
17942
|
status: "FAIL",
|
|
16628
|
-
httpStatus:
|
|
17943
|
+
httpStatus: status3,
|
|
16629
17944
|
snippetPresent,
|
|
16630
17945
|
shopIdMatches,
|
|
16631
17946
|
botChallenge: false,
|
|
@@ -16635,7 +17950,7 @@ function pageReport(url, shopId, status2, html, workerSnippetPresent, bodyTrunca
|
|
|
16635
17950
|
return {
|
|
16636
17951
|
url,
|
|
16637
17952
|
status: "PASS",
|
|
16638
|
-
httpStatus:
|
|
17953
|
+
httpStatus: status3,
|
|
16639
17954
|
snippetPresent,
|
|
16640
17955
|
shopIdMatches,
|
|
16641
17956
|
botChallenge: false,
|
|
@@ -16832,6 +18147,18 @@ var installCommand = new Command("install").description("Check Apex storefront i
|
|
|
16832
18147
|
pageReport(url, shopId, response.status, html, derivedWorkerBaseUrl !== null, truncated)
|
|
16833
18148
|
);
|
|
16834
18149
|
} catch (error) {
|
|
18150
|
+
if (error instanceof IdentityProxyError) {
|
|
18151
|
+
pages.push({
|
|
18152
|
+
url,
|
|
18153
|
+
status: "WARN",
|
|
18154
|
+
snippetPresent: false,
|
|
18155
|
+
shopIdMatches: false,
|
|
18156
|
+
botChallenge: false,
|
|
18157
|
+
blockedBy: "identity_proxy",
|
|
18158
|
+
message: `blocked_by_identity_proxy: ${error.message}. Verify from a signed-in browser.`
|
|
18159
|
+
});
|
|
18160
|
+
continue;
|
|
18161
|
+
}
|
|
16835
18162
|
if (error instanceof UrlPolicyError) fail(this, error.message, EXIT_CODES.validation);
|
|
16836
18163
|
if (error instanceof BodyDecodeError) {
|
|
16837
18164
|
pages.push({
|
|
@@ -16939,18 +18266,18 @@ var installCommand = new Command("install").description("Check Apex storefront i
|
|
|
16939
18266
|
|
|
16940
18267
|
// src/commands/flags.ts
|
|
16941
18268
|
init_source();
|
|
16942
|
-
var
|
|
16943
|
-
var
|
|
18269
|
+
var import_node_fs19 = __toESM(require("node:fs"));
|
|
18270
|
+
var import_node_path16 = __toESM(require("node:path"));
|
|
16944
18271
|
|
|
16945
18272
|
// src/edge-status.ts
|
|
16946
18273
|
function getEdgeDeliveryStatus(deliveryType, expectedWorstCaseSeconds = 360) {
|
|
16947
18274
|
return deliveryType === "api" ? { edge_delivery: "not_applicable", message: `API-delivered flag \u2014 edge delivery is not applicable. A previously cached visual payload may remain at some PoPs for up to ~${expectedWorstCaseSeconds}s; a single-vantage status cannot prove global propagation.` } : { edge_delivery: "applicable", message: null };
|
|
16948
18275
|
}
|
|
16949
18276
|
function parsePublicationMetadata(value) {
|
|
16950
|
-
const
|
|
18277
|
+
const record3 = value && typeof value === "object" ? value : {};
|
|
16951
18278
|
return {
|
|
16952
|
-
publication_revision: Number.isSafeInteger(
|
|
16953
|
-
generated_at: typeof
|
|
18279
|
+
publication_revision: Number.isSafeInteger(record3.publication_revision) && record3.publication_revision >= 0 ? record3.publication_revision : null,
|
|
18280
|
+
generated_at: typeof record3.generated_at === "string" && !Number.isNaN(Date.parse(record3.generated_at)) ? record3.generated_at : null
|
|
16954
18281
|
};
|
|
16955
18282
|
}
|
|
16956
18283
|
function compareEdgePublication(currentValue, edgeValue, expectedWorstCaseSeconds, flagUpdatedAtValue, confirmedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
@@ -16967,13 +18294,13 @@ function compareEdgePublication(currentValue, edgeValue, expectedWorstCaseSecond
|
|
|
16967
18294
|
if (edge.publication_revision < current.publication_revision) return { ...base, verdict: "propagating", relationship: "edge_behind" };
|
|
16968
18295
|
return { ...base, verdict: "live", relationship: edge.publication_revision > current.publication_revision ? "edge_ahead" : "equal" };
|
|
16969
18296
|
}
|
|
16970
|
-
function formatEdgePropagationStatus(
|
|
18297
|
+
function formatEdgePropagationStatus(status3) {
|
|
16971
18298
|
const caveat = "This checks one CDN vantage point; other PoPs may lag by up to max-age.";
|
|
16972
|
-
if (
|
|
16973
|
-
if (
|
|
16974
|
-
if (
|
|
16975
|
-
const anomaly =
|
|
16976
|
-
return `Live at edge \u2014 revision ${
|
|
18299
|
+
if (status3.verdict === "pending_publication") return `Pending publication \u2014 flag updated ${status3.flag_updated_at}, after the latest publication generated ${status3.current.generated_at ?? "never"}; the latest change is not live. After publication, allow ~${status3.expected_worst_case_seconds}s for propagation. ${caveat}`;
|
|
18300
|
+
if (status3.verdict === "unknown") return `Unknown \u2014 edge payload predates publication metadata. ${caveat}`;
|
|
18301
|
+
if (status3.verdict === "propagating") return `Propagating \u2014 edge revision ${status3.edge.publication_revision} (generated ${status3.edge.generated_at}) vs current revision ${status3.current.publication_revision} (generated ${status3.current.generated_at}); expected within ~${status3.expected_worst_case_seconds}s after generation. ${caveat}`;
|
|
18302
|
+
const anomaly = status3.relationship === "edge_ahead" ? ` (edge is ahead of current revision ${status3.current.publication_revision}; anomalous)` : "";
|
|
18303
|
+
return `Live at edge \u2014 revision ${status3.edge.publication_revision} (generated ${status3.edge.generated_at}) confirmed at edge at ${status3.confirmed_at}${anomaly}. ${caveat}`;
|
|
16977
18304
|
}
|
|
16978
18305
|
|
|
16979
18306
|
// src/commands/flags.ts
|
|
@@ -16982,9 +18309,9 @@ function body(command, value, opts) {
|
|
|
16982
18309
|
let parsed = {};
|
|
16983
18310
|
if (value) {
|
|
16984
18311
|
const source = value.startsWith("@") ? value.slice(1) : value;
|
|
16985
|
-
const
|
|
18312
|
+
const text2 = value.startsWith("@") || import_node_fs19.default.existsSync(source) ? import_node_fs19.default.readFileSync(import_node_path16.default.resolve(process.cwd(), source), "utf8") : value;
|
|
16986
18313
|
try {
|
|
16987
|
-
parsed = JSON.parse(
|
|
18314
|
+
parsed = JSON.parse(text2);
|
|
16988
18315
|
} catch (e) {
|
|
16989
18316
|
fail(
|
|
16990
18317
|
command,
|
|
@@ -17032,7 +18359,7 @@ var get5 = new Command("get").description("Get a flag").argument("<id>").action(
|
|
|
17032
18359
|
fail(this, r.error ?? "Failed to get", classifyStatus(r.status), r.errorDetails);
|
|
17033
18360
|
output(this, r.data, () => detail(r.data));
|
|
17034
18361
|
});
|
|
17035
|
-
var
|
|
18362
|
+
var status2 = new Command("status").description("Check whether a flag publication is live at the edge").argument("<id>").action(async function(id) {
|
|
17036
18363
|
const r = await getFlagStatus(requireCredentials(), id);
|
|
17037
18364
|
if (!r.ok || !r.data)
|
|
17038
18365
|
fail(this, r.error ?? "Failed to get flag status", classifyStatus(r.status), r.errorDetails);
|
|
@@ -17103,7 +18430,7 @@ function lifecycle(action74) {
|
|
|
17103
18430
|
output(this, r.data, () => detail(r.data));
|
|
17104
18431
|
});
|
|
17105
18432
|
}
|
|
17106
|
-
var flagsCommand = new Command("flags").description("Manage flags").addCommand(list6).addCommand(get5).addCommand(
|
|
18433
|
+
var flagsCommand = new Command("flags").description("Manage flags").addCommand(list6).addCommand(get5).addCommand(status2).addCommand(write("create")).addCommand(write("update")).addCommand(lifecycle("activate")).addCommand(lifecycle("pause")).addCommand(lifecycle("archive")).addHelpText(
|
|
17107
18434
|
"after",
|
|
17108
18435
|
`
|
|
17109
18436
|
Examples:
|
|
@@ -17115,8 +18442,8 @@ Examples:
|
|
|
17115
18442
|
);
|
|
17116
18443
|
|
|
17117
18444
|
// src/commands/backlog.ts
|
|
17118
|
-
var
|
|
17119
|
-
var
|
|
18445
|
+
var import_node_fs20 = __toESM(require("node:fs"));
|
|
18446
|
+
var import_node_path17 = __toESM(require("node:path"));
|
|
17120
18447
|
init_source();
|
|
17121
18448
|
var kind2 = "backlog";
|
|
17122
18449
|
var singular = "backlog item";
|
|
@@ -17124,8 +18451,8 @@ function parse(command, value) {
|
|
|
17124
18451
|
if (!value) return {};
|
|
17125
18452
|
try {
|
|
17126
18453
|
const source = value.startsWith("@") ? value.slice(1) : value;
|
|
17127
|
-
const
|
|
17128
|
-
const body5 = JSON.parse(
|
|
18454
|
+
const text2 = value.startsWith("@") || import_node_fs20.default.existsSync(source) ? import_node_fs20.default.readFileSync(import_node_path17.default.resolve(process.cwd(), source), "utf8") : value;
|
|
18455
|
+
const body5 = JSON.parse(text2);
|
|
17129
18456
|
if (!body5 || typeof body5 !== "object" || Array.isArray(body5))
|
|
17130
18457
|
throw new Error("body must be an object");
|
|
17131
18458
|
return body5;
|
|
@@ -17401,8 +18728,8 @@ var fields2 = new Command("fields").description("Manage the shop's own idea fiel
|
|
|
17401
18728
|
var backlogCommand = new Command("backlog").description("Manage backlog").addCommand(list7).addCommand(get6).addCommand(write2("create")).addCommand(write2("update")).addCommand(archive).addCommand(restore).addCommand(remove2).addCommand(fields2);
|
|
17402
18729
|
|
|
17403
18730
|
// src/commands/signals.ts
|
|
17404
|
-
var
|
|
17405
|
-
var
|
|
18731
|
+
var import_node_fs21 = __toESM(require("node:fs"));
|
|
18732
|
+
var import_node_path18 = __toESM(require("node:path"));
|
|
17406
18733
|
init_source();
|
|
17407
18734
|
var kind3 = "signals";
|
|
17408
18735
|
var singular2 = "signal";
|
|
@@ -17410,8 +18737,8 @@ function parse2(command, value) {
|
|
|
17410
18737
|
if (!value) return {};
|
|
17411
18738
|
try {
|
|
17412
18739
|
const source = value.startsWith("@") ? value.slice(1) : value;
|
|
17413
|
-
const
|
|
17414
|
-
const body5 = JSON.parse(
|
|
18740
|
+
const text2 = value.startsWith("@") || import_node_fs21.default.existsSync(source) ? import_node_fs21.default.readFileSync(import_node_path18.default.resolve(process.cwd(), source), "utf8") : value;
|
|
18741
|
+
const body5 = JSON.parse(text2);
|
|
17415
18742
|
if (!body5 || typeof body5 !== "object" || Array.isArray(body5))
|
|
17416
18743
|
throw new Error("body must be an object");
|
|
17417
18744
|
return body5;
|
|
@@ -17492,16 +18819,16 @@ var signalsCommand = new Command("signals").description("Manage signals").addCom
|
|
|
17492
18819
|
|
|
17493
18820
|
// src/commands/personalizations.ts
|
|
17494
18821
|
init_source();
|
|
17495
|
-
var
|
|
17496
|
-
var
|
|
18822
|
+
var import_node_fs22 = __toESM(require("node:fs"));
|
|
18823
|
+
var import_node_path19 = __toESM(require("node:path"));
|
|
17497
18824
|
var kind4 = "personalizations";
|
|
17498
18825
|
function body2(command, value, opts) {
|
|
17499
18826
|
let parsed = {};
|
|
17500
18827
|
if (value) {
|
|
17501
18828
|
const source = value.startsWith("@") ? value.slice(1) : value;
|
|
17502
|
-
const
|
|
18829
|
+
const text2 = value.startsWith("@") || import_node_fs22.default.existsSync(source) ? import_node_fs22.default.readFileSync(import_node_path19.default.resolve(process.cwd(), source), "utf8") : value;
|
|
17503
18830
|
try {
|
|
17504
|
-
parsed = JSON.parse(
|
|
18831
|
+
parsed = JSON.parse(text2);
|
|
17505
18832
|
} catch (e) {
|
|
17506
18833
|
fail(
|
|
17507
18834
|
command,
|
|
@@ -17581,14 +18908,14 @@ var personalizationsCommand = new Command("personalizations").description("Manag
|
|
|
17581
18908
|
|
|
17582
18909
|
// src/commands/targeting-resources.ts
|
|
17583
18910
|
init_source();
|
|
17584
|
-
var
|
|
17585
|
-
var
|
|
18911
|
+
var import_node_fs23 = __toESM(require("node:fs"));
|
|
18912
|
+
var import_node_path20 = __toESM(require("node:path"));
|
|
17586
18913
|
function body3(command, value) {
|
|
17587
18914
|
if (!value) return {};
|
|
17588
18915
|
const source = value.startsWith("@") ? value.slice(1) : value;
|
|
17589
18916
|
try {
|
|
17590
|
-
const
|
|
17591
|
-
return JSON.parse(
|
|
18917
|
+
const text2 = value.startsWith("@") || import_node_fs23.default.existsSync(source) ? import_node_fs23.default.readFileSync(import_node_path20.default.resolve(process.cwd(), source), "utf8") : value;
|
|
18918
|
+
return JSON.parse(text2);
|
|
17592
18919
|
} catch (error) {
|
|
17593
18920
|
fail(
|
|
17594
18921
|
command,
|
|
@@ -17830,8 +19157,8 @@ var targetingCommand = new Command("targeting").description("Test experiment tar
|
|
|
17830
19157
|
|
|
17831
19158
|
// src/commands/sos.ts
|
|
17832
19159
|
init_source();
|
|
17833
|
-
var
|
|
17834
|
-
var
|
|
19160
|
+
var import_node_fs24 = __toESM(require("node:fs"));
|
|
19161
|
+
var import_node_path21 = __toESM(require("node:path"));
|
|
17835
19162
|
var SOS_TYPES = /* @__PURE__ */ new Set([
|
|
17836
19163
|
"full_handover",
|
|
17837
19164
|
"design",
|
|
@@ -17844,7 +19171,7 @@ var SOS_TYPES = /* @__PURE__ */ new Set([
|
|
|
17844
19171
|
function readText(value) {
|
|
17845
19172
|
if (!value) return void 0;
|
|
17846
19173
|
if (!value.startsWith("@")) return value;
|
|
17847
|
-
return
|
|
19174
|
+
return import_node_fs24.default.readFileSync(import_node_path21.default.resolve(process.cwd(), value.slice(1)), "utf8");
|
|
17848
19175
|
}
|
|
17849
19176
|
function printTicket(ticket, reused) {
|
|
17850
19177
|
console.log();
|
|
@@ -17982,14 +19309,14 @@ var markRead = new Command("mark-read").option("--channel <key>", "Mark one chan
|
|
|
17982
19309
|
var inboxCommand = new Command("inbox").description("Read and send inbox messages").addCommand(list11).addCommand(send2).addCommand(markRead);
|
|
17983
19310
|
|
|
17984
19311
|
// src/commands/shops.ts
|
|
17985
|
-
var
|
|
17986
|
-
var
|
|
19312
|
+
var import_node_fs25 = __toESM(require("node:fs"));
|
|
19313
|
+
var import_node_path22 = __toESM(require("node:path"));
|
|
17987
19314
|
function body4(command, raw) {
|
|
17988
19315
|
if (!raw) return {};
|
|
17989
19316
|
try {
|
|
17990
19317
|
const source = raw.startsWith("@") ? raw.slice(1) : raw;
|
|
17991
|
-
const
|
|
17992
|
-
const value = JSON.parse(
|
|
19318
|
+
const text2 = raw.startsWith("@") || import_node_fs25.default.existsSync(source) ? import_node_fs25.default.readFileSync(import_node_path22.default.resolve(source), "utf8") : raw;
|
|
19319
|
+
const value = JSON.parse(text2);
|
|
17993
19320
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
17994
19321
|
throw new Error("body must be an object");
|
|
17995
19322
|
return value;
|
|
@@ -18370,7 +19697,7 @@ function parseContainer(source, id, dataLayerName = "dataLayer") {
|
|
|
18370
19697
|
vtpPaths,
|
|
18371
19698
|
ecommerceFrom: [...ecommerce][0] ?? null,
|
|
18372
19699
|
serverContainerUrl: [...serverUrls][0] ?? null,
|
|
18373
|
-
piiPaths: vtpPaths.filter((
|
|
19700
|
+
piiPaths: vtpPaths.filter((path25) => /email|phone|address|name/i.test(path25))
|
|
18374
19701
|
}
|
|
18375
19702
|
};
|
|
18376
19703
|
}
|
|
@@ -18439,11 +19766,11 @@ function isContainerField(field) {
|
|
|
18439
19766
|
const types = baseFieldTypes(field);
|
|
18440
19767
|
return types.has("obj") || types.has("arr");
|
|
18441
19768
|
}
|
|
18442
|
-
function hasSyntheticPlaceholder(
|
|
18443
|
-
return
|
|
19769
|
+
function hasSyntheticPlaceholder(path25) {
|
|
19770
|
+
return path25.split(".").map((segment) => segment.replace(/\[\]$/, "")).some((segment) => /^<[^>]+>$/.test(segment));
|
|
18444
19771
|
}
|
|
18445
|
-
function ambiguousDottedPrefix(event,
|
|
18446
|
-
const segments =
|
|
19772
|
+
function ambiguousDottedPrefix(event, path25) {
|
|
19773
|
+
const segments = path25.split(".");
|
|
18447
19774
|
for (let index = 1; index < segments.length; index += 1) {
|
|
18448
19775
|
const prefix = segments.slice(0, index).join(".");
|
|
18449
19776
|
const parent = event.fields.find((field) => field.path === prefix);
|
|
@@ -18452,42 +19779,42 @@ function ambiguousDottedPrefix(event, path24) {
|
|
|
18452
19779
|
return void 0;
|
|
18453
19780
|
}
|
|
18454
19781
|
function dimensionEligibility(event, field) {
|
|
18455
|
-
const
|
|
18456
|
-
if (hasSyntheticPlaceholder(
|
|
19782
|
+
const path25 = field.path;
|
|
19783
|
+
if (hasSyntheticPlaceholder(path25)) {
|
|
18457
19784
|
return {
|
|
18458
19785
|
eligible: false,
|
|
18459
|
-
message: `Path ${JSON.stringify(
|
|
19786
|
+
message: `Path ${JSON.stringify(path25)} is a synthetic placeholder and cannot create a dimension. Choose an observed scalar path.`
|
|
18460
19787
|
};
|
|
18461
19788
|
}
|
|
18462
19789
|
if (isContainerField(field)) {
|
|
18463
19790
|
return {
|
|
18464
19791
|
eligible: false,
|
|
18465
|
-
message: `Path ${JSON.stringify(
|
|
19792
|
+
message: `Path ${JSON.stringify(path25)} is container-typed. Choose an observed scalar path.`
|
|
18466
19793
|
};
|
|
18467
19794
|
}
|
|
18468
|
-
if (
|
|
19795
|
+
if (path25.includes("[]")) {
|
|
18469
19796
|
return {
|
|
18470
19797
|
eligible: false,
|
|
18471
|
-
message: `Path ${JSON.stringify(
|
|
19798
|
+
message: `Path ${JSON.stringify(path25)} includes an array-element segment and cannot create a reliable dimension. Choose a scalar path outside an array.`
|
|
18472
19799
|
};
|
|
18473
19800
|
}
|
|
18474
19801
|
if (!isStableField(field)) {
|
|
18475
19802
|
return {
|
|
18476
19803
|
eligible: false,
|
|
18477
|
-
message: `Path ${JSON.stringify(
|
|
19804
|
+
message: `Path ${JSON.stringify(path25)} does not have a stable observed type. Choose a path without a type collision.`
|
|
18478
19805
|
};
|
|
18479
19806
|
}
|
|
18480
19807
|
if (!event.event) {
|
|
18481
19808
|
return {
|
|
18482
19809
|
eligible: false,
|
|
18483
|
-
message: `Path ${JSON.stringify(
|
|
19810
|
+
message: `Path ${JSON.stringify(path25)} has no observed event name. Choose a path from a named event.`
|
|
18484
19811
|
};
|
|
18485
19812
|
}
|
|
18486
|
-
const ambiguousPrefix = ambiguousDottedPrefix(event,
|
|
19813
|
+
const ambiguousPrefix = ambiguousDottedPrefix(event, path25);
|
|
18487
19814
|
if (ambiguousPrefix) {
|
|
18488
19815
|
return {
|
|
18489
19816
|
eligible: false,
|
|
18490
|
-
message: `Path ${JSON.stringify(
|
|
19817
|
+
message: `Path ${JSON.stringify(path25)} could be a literal key containing dots because the discovery report has no observed container at ${JSON.stringify(ambiguousPrefix)}. Apex cannot safely create a capture for it.`
|
|
18491
19818
|
};
|
|
18492
19819
|
}
|
|
18493
19820
|
return { eligible: true };
|
|
@@ -18496,24 +19823,24 @@ function isNumericField(field) {
|
|
|
18496
19823
|
const numericTypes = /* @__PURE__ */ new Set(["num", "numstr", "numstr_comma"]);
|
|
18497
19824
|
return field.types.length > 0 && field.types.every((type) => numericTypes.has(type.split(":")[0]));
|
|
18498
19825
|
}
|
|
18499
|
-
function defaultDimensionLabel(
|
|
18500
|
-
const words =
|
|
18501
|
-
return words ? `${words[0].toUpperCase()}${words.slice(1)}` :
|
|
19826
|
+
function defaultDimensionLabel(path25) {
|
|
19827
|
+
const words = path25.split(/[._-]+/).filter(Boolean).join(" ");
|
|
19828
|
+
return words ? `${words[0].toUpperCase()}${words.slice(1)}` : path25;
|
|
18502
19829
|
}
|
|
18503
19830
|
function quoteShellArgument(value) {
|
|
18504
19831
|
return "'" + value.replaceAll("'", "'\\''") + "'";
|
|
18505
19832
|
}
|
|
18506
|
-
function createDimensionInvocation(
|
|
18507
|
-
return `apex discover --create-dimension ${quoteShellArgument(
|
|
19833
|
+
function createDimensionInvocation(path25, eventName) {
|
|
19834
|
+
return `apex discover --create-dimension ${quoteShellArgument(path25)} --event ${quoteShellArgument(eventName)}`;
|
|
18508
19835
|
}
|
|
18509
|
-
function resolveDimensionObservation(command, report,
|
|
19836
|
+
function resolveDimensionObservation(command, report, path25, eventName) {
|
|
18510
19837
|
const matches = report.data_layer.flatMap(
|
|
18511
|
-
(event) => event.fields.filter((field) => field.path ===
|
|
19838
|
+
(event) => event.fields.filter((field) => field.path === path25).map((field) => ({ event, field }))
|
|
18512
19839
|
);
|
|
18513
19840
|
if (!matches.length) {
|
|
18514
19841
|
fail(
|
|
18515
19842
|
command,
|
|
18516
|
-
`Path ${JSON.stringify(
|
|
19843
|
+
`Path ${JSON.stringify(path25)} was not found in the current discovery report. Run apex discover and copy an observed path exactly.`,
|
|
18517
19844
|
EXIT_CODES.validation
|
|
18518
19845
|
);
|
|
18519
19846
|
}
|
|
@@ -18523,7 +19850,7 @@ function resolveDimensionObservation(command, report, path24, eventName) {
|
|
|
18523
19850
|
if (!eventMatches.length) {
|
|
18524
19851
|
fail(
|
|
18525
19852
|
command,
|
|
18526
|
-
`Path ${JSON.stringify(
|
|
19853
|
+
`Path ${JSON.stringify(path25)} was not observed on event ${JSON.stringify(eventName)}. Run apex discover and copy the event name exactly.`,
|
|
18527
19854
|
EXIT_CODES.validation
|
|
18528
19855
|
);
|
|
18529
19856
|
}
|
|
@@ -18531,7 +19858,7 @@ function resolveDimensionObservation(command, report, path24, eventName) {
|
|
|
18531
19858
|
if (layers.length > 1) {
|
|
18532
19859
|
fail(
|
|
18533
19860
|
command,
|
|
18534
|
-
`Path ${JSON.stringify(
|
|
19861
|
+
`Path ${JSON.stringify(path25)} on event ${JSON.stringify(eventName)} was observed on multiple data layers: ${layers.join(", ")}. Dimensions bind to one layer; this ambiguity cannot be resolved automatically \u2014 capture the value under one layer, or define the dimension in the dashboard with the data layer name set explicitly.`,
|
|
18535
19862
|
EXIT_CODES.validation
|
|
18536
19863
|
);
|
|
18537
19864
|
}
|
|
@@ -18543,7 +19870,7 @@ function resolveDimensionObservation(command, report, path24, eventName) {
|
|
|
18543
19870
|
if (namedEvents.length > 1) {
|
|
18544
19871
|
fail(
|
|
18545
19872
|
command,
|
|
18546
|
-
`Path ${JSON.stringify(
|
|
19873
|
+
`Path ${JSON.stringify(path25)} was observed on multiple events: ${namedEvents.join(", ")}. Retry with --event <name>.`,
|
|
18547
19874
|
EXIT_CODES.validation
|
|
18548
19875
|
);
|
|
18549
19876
|
}
|
|
@@ -18552,7 +19879,7 @@ function resolveDimensionObservation(command, report, path24, eventName) {
|
|
|
18552
19879
|
if (layers.length > 1) {
|
|
18553
19880
|
fail(
|
|
18554
19881
|
command,
|
|
18555
|
-
`Path ${JSON.stringify(
|
|
19882
|
+
`Path ${JSON.stringify(path25)} on event ${JSON.stringify(namedEvents[0] ?? "")} was observed on multiple data layers: ${layers.join(", ")}. Dimensions bind to one layer; this ambiguity cannot be resolved automatically \u2014 capture the value under one layer, or define the dimension in the dashboard with the data layer name set explicitly.`,
|
|
18556
19883
|
EXIT_CODES.validation
|
|
18557
19884
|
);
|
|
18558
19885
|
}
|
|
@@ -18587,9 +19914,9 @@ function renderDiscovery(report) {
|
|
|
18587
19914
|
`
|
|
18588
19915
|
${event.event || "(unnamed event)"} [${event.dlName}] \u2014 ${event.count} observation(s)${event.matched ? " \u2014 matched" : ""}; t_ms p50=${event.t_ms_p50 ?? "n/a"}, p95=${event.t_ms_p95 ?? "n/a"}, pre-init=${event.pre_init_count}`
|
|
18589
19916
|
);
|
|
18590
|
-
for (const
|
|
19917
|
+
for (const path25 of event.paths)
|
|
18591
19918
|
lines.push(
|
|
18592
|
-
` URL ${
|
|
19919
|
+
` URL ${path25.path}: ${path25.pageviews} pageviews, ${path25.count} pushes, ${path25.pspReturn_count} PSP returns`
|
|
18593
19920
|
);
|
|
18594
19921
|
for (const field of event.fields) {
|
|
18595
19922
|
lines.push(
|
|
@@ -18767,11 +20094,11 @@ var discoverCommand = new Command("discover").description("Read back the trackin
|
|
|
18767
20094
|
);
|
|
18768
20095
|
}
|
|
18769
20096
|
if (!created.ok || !created.data) {
|
|
18770
|
-
const
|
|
20097
|
+
const reason2 = created.error ?? "The signals API rejected the request";
|
|
18771
20098
|
if (created.status === 409) {
|
|
18772
20099
|
fail(
|
|
18773
20100
|
this,
|
|
18774
|
-
`Dimension was not created: ${
|
|
20101
|
+
`Dimension was not created: ${reason2} Choose another --key or archive an existing dimension in Audience > Attributes, then retry.`,
|
|
18775
20102
|
classifyStatus(created.status),
|
|
18776
20103
|
created.errorDetails
|
|
18777
20104
|
);
|
|
@@ -18779,14 +20106,14 @@ var discoverCommand = new Command("discover").description("Read back the trackin
|
|
|
18779
20106
|
if (created.status === 400) {
|
|
18780
20107
|
fail(
|
|
18781
20108
|
this,
|
|
18782
|
-
`Dimension was not created: ${
|
|
20109
|
+
`Dimension was not created: ${reason2} Check --create-dimension, --event, --key, and --label, then retry.`,
|
|
18783
20110
|
classifyStatus(created.status),
|
|
18784
20111
|
created.errorDetails
|
|
18785
20112
|
);
|
|
18786
20113
|
}
|
|
18787
20114
|
fail(
|
|
18788
20115
|
this,
|
|
18789
|
-
`Dimension was not created: ${
|
|
20116
|
+
`Dimension was not created: ${reason2}`,
|
|
18790
20117
|
classifyStatus(created.status),
|
|
18791
20118
|
created.errorDetails
|
|
18792
20119
|
);
|
|
@@ -18802,16 +20129,16 @@ var discoverCommand = new Command("discover").description("Read back the trackin
|
|
|
18802
20129
|
});
|
|
18803
20130
|
|
|
18804
20131
|
// src/commands/skills.ts
|
|
18805
|
-
var
|
|
18806
|
-
var
|
|
20132
|
+
var import_node_fs26 = __toESM(require("node:fs"));
|
|
20133
|
+
var import_node_path23 = __toESM(require("node:path"));
|
|
18807
20134
|
function packageRoot() {
|
|
18808
20135
|
const candidates = [
|
|
18809
|
-
|
|
18810
|
-
|
|
18811
|
-
|
|
20136
|
+
import_node_path23.default.resolve(__dirname, ".."),
|
|
20137
|
+
import_node_path23.default.resolve(import_node_path23.default.dirname(process.argv[1] ?? process.cwd()), ".."),
|
|
20138
|
+
import_node_path23.default.resolve(process.cwd(), "packages/cli")
|
|
18812
20139
|
];
|
|
18813
20140
|
for (const candidate of candidates) {
|
|
18814
|
-
if (
|
|
20141
|
+
if (import_node_fs26.default.existsSync(import_node_path23.default.join(candidate, "AGENTS.md"))) return candidate;
|
|
18815
20142
|
}
|
|
18816
20143
|
throw new Error(
|
|
18817
20144
|
`Apex CLI package files not found. Looked for AGENTS.md in: ${candidates.join(", ")}`
|
|
@@ -18820,19 +20147,19 @@ function packageRoot() {
|
|
|
18820
20147
|
function skillPaths() {
|
|
18821
20148
|
const root = packageRoot();
|
|
18822
20149
|
return {
|
|
18823
|
-
agents:
|
|
18824
|
-
skill:
|
|
20150
|
+
agents: import_node_path23.default.join(root, "AGENTS.md"),
|
|
20151
|
+
skill: import_node_path23.default.join(root, "skills", "apex-cli", "SKILL.md")
|
|
18825
20152
|
};
|
|
18826
20153
|
}
|
|
18827
20154
|
function installSkill(paths, targetRoot) {
|
|
18828
|
-
const missing = [paths.skill, paths.agents].filter((filePath) => !
|
|
20155
|
+
const missing = [paths.skill, paths.agents].filter((filePath) => !import_node_fs26.default.existsSync(filePath));
|
|
18829
20156
|
if (missing.length > 0) {
|
|
18830
20157
|
throw new Error(`Apex CLI package files not found. Missing: ${missing.join(", ")}`);
|
|
18831
20158
|
}
|
|
18832
|
-
const target =
|
|
18833
|
-
|
|
18834
|
-
|
|
18835
|
-
|
|
20159
|
+
const target = import_node_path23.default.resolve(targetRoot, "apex-cli");
|
|
20160
|
+
import_node_fs26.default.mkdirSync(target, { recursive: true });
|
|
20161
|
+
import_node_fs26.default.copyFileSync(paths.skill, import_node_path23.default.join(target, "SKILL.md"));
|
|
20162
|
+
import_node_fs26.default.copyFileSync(paths.agents, import_node_path23.default.join(target, "AGENTS.md"));
|
|
18836
20163
|
return target;
|
|
18837
20164
|
}
|
|
18838
20165
|
var skillsCommand = new Command("skills").description("Print or install the Apex CLI agent skill files").option("--install", "Copy the Apex CLI skill into ./.claude/skills/apex-cli").option("--target <dir>", "Install target skills directory", ".claude/skills").option("--json", "Print JSON").action(function action67(opts) {
|
|
@@ -19262,12 +20589,12 @@ async function fetchText(url) {
|
|
|
19262
20589
|
});
|
|
19263
20590
|
return { status: res.status, text: await readResponseText(res), finalUrl: res.url };
|
|
19264
20591
|
}
|
|
19265
|
-
function abs(base,
|
|
19266
|
-
return new URL(
|
|
20592
|
+
function abs(base, path25) {
|
|
20593
|
+
return new URL(path25, base).toString();
|
|
19267
20594
|
}
|
|
19268
|
-
function looksLikeShopifyCart(
|
|
20595
|
+
function looksLikeShopifyCart(text2) {
|
|
19269
20596
|
try {
|
|
19270
|
-
const payload = JSON.parse(
|
|
20597
|
+
const payload = JSON.parse(text2);
|
|
19271
20598
|
return payload !== null && typeof payload === "object" && (typeof payload.token === "string" || payload.token === null) && Array.isArray(payload.items);
|
|
19272
20599
|
} catch {
|
|
19273
20600
|
return false;
|
|
@@ -19283,9 +20610,9 @@ async function collectSignals(url) {
|
|
|
19283
20610
|
const chunks = [`__apex_primary_status_${primary.status}__`, primary.text];
|
|
19284
20611
|
const protectedHit = primary.status === 403 || primary.status === 429 || matchDetectionMarkers(primary.text, "protected").length > 0;
|
|
19285
20612
|
if (!protectedHit) {
|
|
19286
|
-
for (const
|
|
20613
|
+
for (const path25 of ["/cart.js"]) {
|
|
19287
20614
|
try {
|
|
19288
|
-
const secondary = await fetchText(abs(primary.finalUrl || url,
|
|
20615
|
+
const secondary = await fetchText(abs(primary.finalUrl || url, path25));
|
|
19289
20616
|
if (secondary.status >= 200 && secondary.status < 300 && looksLikeShopifyCart(secondary.text)) {
|
|
19290
20617
|
chunks.push("__apex_secondary_shopify_cart_js__", secondary.text.slice(0, 2e3));
|
|
19291
20618
|
}
|
|
@@ -19580,20 +20907,20 @@ var webhooksCommand = new Command("webhooks").description("Manage shop webhooks"
|
|
|
19580
20907
|
|
|
19581
20908
|
// src/commands/simulate.ts
|
|
19582
20909
|
init_source();
|
|
19583
|
-
var
|
|
19584
|
-
var
|
|
20910
|
+
var import_node_fs27 = __toESM(require("node:fs"));
|
|
20911
|
+
var import_node_path24 = __toESM(require("node:path"));
|
|
19585
20912
|
function readJson2(file) {
|
|
19586
|
-
const
|
|
20913
|
+
const text2 = file === "-" ? import_node_fs27.default.readFileSync(0, "utf8") : import_node_fs27.default.readFileSync(import_node_path24.default.resolve(file), "utf8");
|
|
19587
20914
|
try {
|
|
19588
|
-
return JSON.parse(
|
|
20915
|
+
return JSON.parse(text2);
|
|
19589
20916
|
} catch (error) {
|
|
19590
20917
|
throw new Error(
|
|
19591
20918
|
`Invalid JSON in ${file}: ${error instanceof Error ? error.message : String(error)}`
|
|
19592
20919
|
);
|
|
19593
20920
|
}
|
|
19594
20921
|
}
|
|
19595
|
-
function plural(
|
|
19596
|
-
return `${
|
|
20922
|
+
function plural(count2, singular3) {
|
|
20923
|
+
return `${count2} ${singular3}${count2 === 1 ? "" : "s"}`;
|
|
19597
20924
|
}
|
|
19598
20925
|
function isGtagEventTuple(value) {
|
|
19599
20926
|
return Array.isArray(value) && value[0] === "event" && typeof value[1] === "string";
|
|
@@ -19705,8 +21032,8 @@ var simulateCommand = new Command("simulate").description("Simulate data-layer t
|
|
|
19705
21032
|
});
|
|
19706
21033
|
|
|
19707
21034
|
// src/commands/config-resources.ts
|
|
19708
|
-
var
|
|
19709
|
-
var
|
|
21035
|
+
var import_node_fs28 = __toESM(require("node:fs"));
|
|
21036
|
+
var import_node_path25 = __toESM(require("node:path"));
|
|
19710
21037
|
function show(resource) {
|
|
19711
21038
|
process.stdout.write(`${JSON.stringify(resource, null, 2)}
|
|
19712
21039
|
`);
|
|
@@ -19772,12 +21099,12 @@ function readFamily(family, label) {
|
|
|
19772
21099
|
}
|
|
19773
21100
|
var assetReads = readFamily("assets", "assets");
|
|
19774
21101
|
var upload = new Command("upload").description("Upload an asset").argument("<file>").action(async function(file) {
|
|
19775
|
-
const absolute =
|
|
19776
|
-
if (!
|
|
19777
|
-
const fileStat =
|
|
21102
|
+
const absolute = import_node_path25.default.resolve(process.cwd(), file);
|
|
21103
|
+
if (!import_node_fs28.default.existsSync(absolute)) fail(this, `File not found: ${file}`, 3);
|
|
21104
|
+
const fileStat = import_node_fs28.default.statSync(absolute);
|
|
19778
21105
|
if (!fileStat.isFile()) fail(this, `File not found: ${file}`, 3);
|
|
19779
21106
|
if (fileStat.size > MAX_ASSET_SIZE_BYTES) fail(this, `${file} is larger than 10 MB`, 3);
|
|
19780
|
-
const mimeType = ASSET_MIME_TYPES[
|
|
21107
|
+
const mimeType = ASSET_MIME_TYPES[import_node_path25.default.extname(absolute).toLowerCase()];
|
|
19781
21108
|
if (!mimeType)
|
|
19782
21109
|
fail(
|
|
19783
21110
|
this,
|
|
@@ -19786,8 +21113,8 @@ var upload = new Command("upload").description("Upload an asset").argument("<fil
|
|
|
19786
21113
|
);
|
|
19787
21114
|
const r = await uploadAssets(requireCredentials(), [
|
|
19788
21115
|
{
|
|
19789
|
-
name:
|
|
19790
|
-
bytes:
|
|
21116
|
+
name: import_node_path25.default.basename(absolute),
|
|
21117
|
+
bytes: import_node_fs28.default.readFileSync(absolute),
|
|
19791
21118
|
type: mimeType
|
|
19792
21119
|
}
|
|
19793
21120
|
]);
|
|
@@ -19872,6 +21199,7 @@ program2.addCommand(roadmapCommand);
|
|
|
19872
21199
|
program2.addCommand(harnessCommand);
|
|
19873
21200
|
program2.addCommand(operatorCommand);
|
|
19874
21201
|
program2.addCommand(trackingCommand);
|
|
21202
|
+
program2.addCommand(eventsCommand);
|
|
19875
21203
|
program2.addCommand(goalsCommand);
|
|
19876
21204
|
program2.addCommand(funnelsCommand);
|
|
19877
21205
|
program2.addCommand(environmentsCommand);
|
|
@@ -19913,10 +21241,18 @@ try {
|
|
|
19913
21241
|
} catch {
|
|
19914
21242
|
}
|
|
19915
21243
|
program2.hook("preAction", (_thisCommand, actionCommand) => {
|
|
19916
|
-
|
|
21244
|
+
try {
|
|
21245
|
+
configureInvocationContext();
|
|
21246
|
+
} catch (error) {
|
|
21247
|
+
beginInvocation(actionCommand);
|
|
21248
|
+
throw error;
|
|
21249
|
+
}
|
|
19917
21250
|
configureRuntime(actionCommand);
|
|
21251
|
+
beginInvocation(actionCommand, resolveContext());
|
|
19918
21252
|
});
|
|
19919
21253
|
process.on("beforeExit", () => {
|
|
19920
21254
|
flushFallbackEnvelope();
|
|
19921
21255
|
});
|
|
19922
|
-
|
|
21256
|
+
maybeCheckForUpdate().then(() => program2.parseAsync(process.argv)).then(async () => {
|
|
21257
|
+
await finishInvocation({ exitCode: process.exitCode ?? 0 });
|
|
21258
|
+
}).catch((err) => handleCliError(err, program2));
|