@drip-apex/apex-cli 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +45 -1
- package/README.md +71 -0
- package/dist/cli.js +1958 -651
- 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.0",
|
|
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
|
}
|
|
@@ -7911,6 +8379,16 @@ function getExperimentResults(creds, id, opts) {
|
|
|
7911
8379
|
`/api/v1/experiments/${id}/results${qs ? `?${qs}` : ""}`
|
|
7912
8380
|
);
|
|
7913
8381
|
}
|
|
8382
|
+
function getExperimentResultsExplain(creds, id, opts) {
|
|
8383
|
+
const params = new URLSearchParams();
|
|
8384
|
+
for (const key of ["currency", "goal", "order"])
|
|
8385
|
+
if (opts[key]) params.set(key, opts[key]);
|
|
8386
|
+
return request(
|
|
8387
|
+
creds,
|
|
8388
|
+
"GET",
|
|
8389
|
+
`/api/v1/experiments/${id}/results/explain?${params}`
|
|
8390
|
+
);
|
|
8391
|
+
}
|
|
7914
8392
|
function createExperimentScreenshots(creds, id, body5) {
|
|
7915
8393
|
return request(creds, "POST", `/api/v1/experiments/${id}/screenshots`, body5);
|
|
7916
8394
|
}
|
|
@@ -7937,7 +8415,10 @@ function applyExperimentFilesystem(creds, id, body5) {
|
|
|
7937
8415
|
creds,
|
|
7938
8416
|
"POST",
|
|
7939
8417
|
`/api/v1/experiments/${id}/filesystem/apply`,
|
|
7940
|
-
|
|
8418
|
+
{
|
|
8419
|
+
files: body5.files,
|
|
8420
|
+
...body5.confirmTargetingReplace === true ? { confirmTargetingReplace: true } : {}
|
|
8421
|
+
}
|
|
7941
8422
|
);
|
|
7942
8423
|
}
|
|
7943
8424
|
function getExperimentQaRun(creds, id) {
|
|
@@ -8104,6 +8585,15 @@ function verifyTracking(creds, opts) {
|
|
|
8104
8585
|
`/api/v1/tracking/verify${qs ? `?${qs}` : ""}`
|
|
8105
8586
|
);
|
|
8106
8587
|
}
|
|
8588
|
+
function exportEventsPage(creds, opts) {
|
|
8589
|
+
const params = new URLSearchParams({ from: opts.from, to: opts.to });
|
|
8590
|
+
if (opts.experiment) params.set("experiment", opts.experiment);
|
|
8591
|
+
if (opts.eventTypes?.length) params.set("event_type", opts.eventTypes.join(","));
|
|
8592
|
+
if (opts.qa) params.set("qa", opts.qa);
|
|
8593
|
+
if (opts.limit) params.set("limit", String(opts.limit));
|
|
8594
|
+
if (opts.cursor) params.set("cursor", opts.cursor);
|
|
8595
|
+
return request(creds, "GET", `/api/v1/events/export?${params}`);
|
|
8596
|
+
}
|
|
8107
8597
|
function discoverTrackingSchema(creds, minutes) {
|
|
8108
8598
|
const query = minutes ? `?minutes=${encodeURIComponent(String(minutes))}` : "";
|
|
8109
8599
|
return request(creds, "GET", `/api/v1/tracking/discovery${query}`);
|
|
@@ -8114,7 +8604,7 @@ function testTargeting(creds, url, opts) {
|
|
|
8114
8604
|
return request(creds, "GET", `/api/v1/targeting/test?${params.toString()}`);
|
|
8115
8605
|
}
|
|
8116
8606
|
function createSos(creds, body5) {
|
|
8117
|
-
const idempotency_key = body5.idempotency_key ?? (0,
|
|
8607
|
+
const idempotency_key = body5.idempotency_key ?? (0, import_node_crypto2.createHash)("sha256").update(
|
|
8118
8608
|
[
|
|
8119
8609
|
body5.type,
|
|
8120
8610
|
body5.experiment_id ?? "",
|
|
@@ -8265,8 +8755,8 @@ function requestTargetingResource(creds, method, kind5, id, body5, limit, cursor
|
|
|
8265
8755
|
if (limit) params.set("limit", String(limit));
|
|
8266
8756
|
if (cursor) params.set("cursor", cursor);
|
|
8267
8757
|
const query = params.toString();
|
|
8268
|
-
const
|
|
8269
|
-
return request(creds, method,
|
|
8758
|
+
const path25 = `/api/v1/${kind5}${id ? `/${id.split("/").map(encodeURIComponent).join("/")}` : ""}${query ? `?${query}` : ""}`;
|
|
8759
|
+
return request(creds, method, path25, body5);
|
|
8270
8760
|
}
|
|
8271
8761
|
function managedPath(kind5, id, action74) {
|
|
8272
8762
|
return `/api/v1/${kind5}${id ? `/${encodeURIComponent(id)}` : ""}${action74 ? `/${action74}` : ""}`;
|
|
@@ -8502,6 +8992,24 @@ function getWebhookDeliveries(creds, id, limit) {
|
|
|
8502
8992
|
function testWebhook(creds, id) {
|
|
8503
8993
|
return request(creds, "POST", `/api/v1/webhooks/${encodeURIComponent(id)}/test`);
|
|
8504
8994
|
}
|
|
8995
|
+
function getOrderJourney(creds, opts) {
|
|
8996
|
+
const params = new URLSearchParams({
|
|
8997
|
+
platform: opts.platform,
|
|
8998
|
+
environment: opts.environment,
|
|
8999
|
+
from: opts.from,
|
|
9000
|
+
to: opts.to
|
|
9001
|
+
});
|
|
9002
|
+
if (opts.orderId !== void 0) params.set("platform_order_id", opts.orderId);
|
|
9003
|
+
if (opts.orderNumber !== void 0) params.set("order_number", opts.orderNumber);
|
|
9004
|
+
if (opts.includeQa) params.set("include_qa", "true");
|
|
9005
|
+
return request(
|
|
9006
|
+
creds,
|
|
9007
|
+
"GET",
|
|
9008
|
+
`/api/v1/analytics/order-journey?${params}`,
|
|
9009
|
+
void 0,
|
|
9010
|
+
45e3
|
|
9011
|
+
);
|
|
9012
|
+
}
|
|
8505
9013
|
|
|
8506
9014
|
// src/context.ts
|
|
8507
9015
|
var import_node_readline = __toESM(require("node:readline"));
|
|
@@ -8587,9 +9095,9 @@ var STATUS_COLORS = {
|
|
|
8587
9095
|
paused: source_default.yellow,
|
|
8588
9096
|
completed: source_default.blue
|
|
8589
9097
|
};
|
|
8590
|
-
function statusBadge(
|
|
8591
|
-
const colorize = STATUS_COLORS[
|
|
8592
|
-
return colorize(
|
|
9098
|
+
function statusBadge(status3) {
|
|
9099
|
+
const colorize = STATUS_COLORS[status3] ?? source_default.white;
|
|
9100
|
+
return colorize(status3.toUpperCase());
|
|
8593
9101
|
}
|
|
8594
9102
|
function printTable(headers, rows, widths) {
|
|
8595
9103
|
const colWidths = widths ?? headers.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length)));
|
|
@@ -8667,8 +9175,8 @@ function parseFormBody(body5) {
|
|
|
8667
9175
|
return new URLSearchParams(body5);
|
|
8668
9176
|
}
|
|
8669
9177
|
async function waitForBrowserLogin(baseUrl, userMode = false) {
|
|
8670
|
-
const state =
|
|
8671
|
-
const clientName =
|
|
9178
|
+
const state = import_node_crypto3.default.randomBytes(24).toString("base64url");
|
|
9179
|
+
const clientName = import_node_os6.default.hostname() || "Local machine";
|
|
8672
9180
|
return new Promise((resolve2, reject) => {
|
|
8673
9181
|
let settled = false;
|
|
8674
9182
|
let timeout = null;
|
|
@@ -8999,9 +9507,9 @@ var useCommand = new Command("use").description("Set the current Apex organizati
|
|
|
8999
9507
|
|
|
9000
9508
|
// src/commands/experiments.ts
|
|
9001
9509
|
init_source();
|
|
9002
|
-
var
|
|
9003
|
-
var
|
|
9004
|
-
var
|
|
9510
|
+
var import_node_crypto5 = __toESM(require("node:crypto"));
|
|
9511
|
+
var import_node_fs8 = __toESM(require("node:fs"));
|
|
9512
|
+
var import_node_path6 = __toESM(require("node:path"));
|
|
9005
9513
|
|
|
9006
9514
|
// src/lib/qa-vocabulary.ts
|
|
9007
9515
|
var checklistStatuses = [
|
|
@@ -9031,6 +9539,30 @@ var evidenceKinds = [
|
|
|
9031
9539
|
"readiness"
|
|
9032
9540
|
];
|
|
9033
9541
|
|
|
9542
|
+
// src/lib/experiment-handover.ts
|
|
9543
|
+
function summarizeHandoverQa(value) {
|
|
9544
|
+
const record3 = (value2) => value2 !== null && typeof value2 === "object" && !Array.isArray(value2) ? value2 : {};
|
|
9545
|
+
const qa3 = record3(value);
|
|
9546
|
+
const run3 = record3(qa3.latestRun);
|
|
9547
|
+
const summary = record3(run3.summary);
|
|
9548
|
+
const count2 = (value2) => typeof value2 === "number" && Number.isInteger(value2) && value2 >= 0 ? value2 : null;
|
|
9549
|
+
let requiredDone = count2(summary.completedRequired);
|
|
9550
|
+
let requiredTotal = count2(summary.required);
|
|
9551
|
+
if (requiredDone !== null && requiredTotal !== null && requiredDone > requiredTotal) {
|
|
9552
|
+
requiredDone = null;
|
|
9553
|
+
requiredTotal = null;
|
|
9554
|
+
}
|
|
9555
|
+
const blockers = Array.isArray(summary.blockers) && summary.blockers.every((item) => typeof item === "string") ? summary.blockers : null;
|
|
9556
|
+
const approvedAt = typeof qa3.qaApprovedAt === "string" ? Date.parse(qa3.qaApprovedAt) : Number.NaN;
|
|
9557
|
+
return {
|
|
9558
|
+
run_status: typeof run3.status === "string" ? run3.status : null,
|
|
9559
|
+
required_done: requiredDone,
|
|
9560
|
+
required_total: requiredTotal,
|
|
9561
|
+
blockers,
|
|
9562
|
+
approval_present: Number.isFinite(approvedAt)
|
|
9563
|
+
};
|
|
9564
|
+
}
|
|
9565
|
+
|
|
9034
9566
|
// src/lib/dashboard-url.ts
|
|
9035
9567
|
function buildDashboardUrl(baseUrl, shopId, experimentId) {
|
|
9036
9568
|
if (!shopId) return null;
|
|
@@ -9148,8 +9680,8 @@ function pageEntries(raw) {
|
|
|
9148
9680
|
}
|
|
9149
9681
|
return [...byId.values()];
|
|
9150
9682
|
}
|
|
9151
|
-
function urlRuleClause(
|
|
9152
|
-
return
|
|
9683
|
+
function urlRuleClause(count2) {
|
|
9684
|
+
return count2 === 1 ? "1 URL rule" : `${count2} URL rules`;
|
|
9153
9685
|
}
|
|
9154
9686
|
function effectivePageTargetingLabel(targeting) {
|
|
9155
9687
|
const raw = isRecord(targeting) ? targeting : {};
|
|
@@ -9194,9 +9726,9 @@ function deriveStoreUrl(patterns) {
|
|
|
9194
9726
|
}
|
|
9195
9727
|
|
|
9196
9728
|
// src/lib/asset-upload.ts
|
|
9197
|
-
var
|
|
9198
|
-
var
|
|
9199
|
-
var
|
|
9729
|
+
var import_node_crypto4 = __toESM(require("node:crypto"));
|
|
9730
|
+
var import_node_fs7 = __toESM(require("node:fs"));
|
|
9731
|
+
var import_node_path5 = __toESM(require("node:path"));
|
|
9200
9732
|
var INLINE_DATA_URI_MAX_CHARS = 4096;
|
|
9201
9733
|
var DATA_IMAGE_URI_PATTERN = /data:image\/[a-z0-9.+-]+(?:(?:;[^;,\s"'`]*)*;base64,[A-Za-z0-9+/=\s]+|(?:;[^,\s"'`]*)?,[^\s"'`]+)/gi;
|
|
9202
9734
|
var QUOTED_LOCAL_IMAGE_PATTERN = /(["'])([^"'\n]+?\.(?:png|jpe?g|gif|webp|svg|avif))\1/gi;
|
|
@@ -9219,7 +9751,7 @@ var EXTENSION_BY_MIME = {
|
|
|
9219
9751
|
"image/avif": ".avif"
|
|
9220
9752
|
};
|
|
9221
9753
|
function contentHash(bytes) {
|
|
9222
|
-
return
|
|
9754
|
+
return import_node_crypto4.default.createHash("sha256").update(bytes).digest("hex");
|
|
9223
9755
|
}
|
|
9224
9756
|
function deliveryUrl(baseUrl, asset) {
|
|
9225
9757
|
const returnedUrl = asset.deliveryUrl ?? asset.delivery_url ?? asset.url;
|
|
@@ -9307,23 +9839,23 @@ function createAssetReferenceRewriter(options) {
|
|
|
9307
9839
|
if (reference.startsWith("/") || reference.startsWith("data:") || reference.includes("://")) {
|
|
9308
9840
|
return null;
|
|
9309
9841
|
}
|
|
9310
|
-
const absolutePath =
|
|
9842
|
+
const absolutePath = import_node_path5.default.resolve(baseDir, reference);
|
|
9311
9843
|
let realBaseDir;
|
|
9312
9844
|
let realPath;
|
|
9313
9845
|
try {
|
|
9314
|
-
realBaseDir =
|
|
9315
|
-
realPath =
|
|
9846
|
+
realBaseDir = import_node_fs7.default.realpathSync(baseDir);
|
|
9847
|
+
realPath = import_node_fs7.default.realpathSync(absolutePath);
|
|
9316
9848
|
} catch {
|
|
9317
9849
|
return null;
|
|
9318
9850
|
}
|
|
9319
|
-
const relativeRealPath =
|
|
9320
|
-
if (relativeRealPath.startsWith("..") ||
|
|
9321
|
-
if (!
|
|
9322
|
-
const bytes =
|
|
9323
|
-
const extension =
|
|
9851
|
+
const relativeRealPath = import_node_path5.default.relative(realBaseDir, realPath);
|
|
9852
|
+
if (relativeRealPath.startsWith("..") || import_node_path5.default.isAbsolute(relativeRealPath)) return null;
|
|
9853
|
+
if (!import_node_fs7.default.statSync(realPath).isFile()) return null;
|
|
9854
|
+
const bytes = import_node_fs7.default.readFileSync(realPath);
|
|
9855
|
+
const extension = import_node_path5.default.extname(realPath).toLowerCase();
|
|
9324
9856
|
return uploadOnce(
|
|
9325
9857
|
{
|
|
9326
|
-
name:
|
|
9858
|
+
name: import_node_path5.default.basename(absolutePath),
|
|
9327
9859
|
bytes,
|
|
9328
9860
|
type: MIME_BY_EXTENSION[extension]
|
|
9329
9861
|
},
|
|
@@ -9500,6 +10032,16 @@ function printExperimentResults(results2, currency) {
|
|
|
9500
10032
|
[32, 8, 6, 8, 10]
|
|
9501
10033
|
);
|
|
9502
10034
|
}
|
|
10035
|
+
const metrics = Array.isArray(results2.metrics) ? results2.metrics : [];
|
|
10036
|
+
if (metrics.length > 0) {
|
|
10037
|
+
console.log();
|
|
10038
|
+
console.log(source_default.bold("Goal counting"));
|
|
10039
|
+
metrics.forEach((metric) => {
|
|
10040
|
+
const basis = metric.counting === "once" ? "unique visitors" : metric.counting === "every" ? "every event" : "unknown basis";
|
|
10041
|
+
console.log(` - ${String(metric.name ?? metric.goalId ?? "Goal")}: ${basis}`);
|
|
10042
|
+
});
|
|
10043
|
+
}
|
|
10044
|
+
console.log(" Definitions: https://docs.drip-apex.com/docs/experiments/metric-definitions");
|
|
9503
10045
|
const warnings = Array.isArray(results2.warnings) ? results2.warnings : [];
|
|
9504
10046
|
if (warnings.length > 0) {
|
|
9505
10047
|
console.log();
|
|
@@ -9528,39 +10070,59 @@ var FILESYSTEM_FILE_RE = new RegExp(
|
|
|
9528
10070
|
`^(?:${pathAlternation(EXPERIMENT_FILESYSTEM_PATH_SCHEMA.topLevel)}|experiment/(?:${pathAlternation(EXPERIMENT_FILESYSTEM_PATH_SCHEMA.experiment)})|variations/[^/]+/(?:${pathAlternation(EXPERIMENT_FILESYSTEM_PATH_SCHEMA.variation)}))$`
|
|
9529
10071
|
);
|
|
9530
10072
|
function sha256Text(value) {
|
|
9531
|
-
return
|
|
10073
|
+
return import_node_crypto5.default.createHash("sha256").update(value).digest("hex");
|
|
9532
10074
|
}
|
|
9533
10075
|
function folderPath(folder) {
|
|
9534
|
-
return
|
|
10076
|
+
return import_node_path6.default.resolve(process.cwd(), folder);
|
|
9535
10077
|
}
|
|
9536
10078
|
function filesystemApexDir(folder) {
|
|
9537
|
-
return
|
|
10079
|
+
return import_node_path6.default.join(folder, ".apex");
|
|
9538
10080
|
}
|
|
9539
10081
|
function filesystemSessionPath(folder) {
|
|
9540
|
-
return
|
|
10082
|
+
return import_node_path6.default.join(filesystemApexDir(folder), "experiment-filesystem.json");
|
|
9541
10083
|
}
|
|
9542
10084
|
function writeFilesystemSession(folder, session) {
|
|
9543
|
-
|
|
9544
|
-
|
|
9545
|
-
|
|
10085
|
+
import_node_fs8.default.mkdirSync(filesystemApexDir(folder), { recursive: true });
|
|
10086
|
+
import_node_fs8.default.writeFileSync(
|
|
10087
|
+
import_node_path6.default.join(filesystemApexDir(folder), ".gitignore"),
|
|
9546
10088
|
"experiment-filesystem.json\n",
|
|
9547
10089
|
"utf8"
|
|
9548
10090
|
);
|
|
9549
|
-
|
|
10091
|
+
import_node_fs8.default.writeFileSync(filesystemSessionPath(folder), `${JSON.stringify(session, null, 2)}
|
|
9550
10092
|
`, "utf8");
|
|
9551
10093
|
}
|
|
9552
10094
|
function readFilesystemSession(folder) {
|
|
9553
10095
|
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."
|
|
10096
|
+
if (!import_node_fs8.default.existsSync(filePath)) {
|
|
10097
|
+
const workspaceMarker = import_node_path6.default.join(filesystemApexDir(folder), "session.json");
|
|
10098
|
+
if (import_node_fs8.default.existsSync(workspaceMarker)) {
|
|
10099
|
+
throw new CliError(
|
|
10100
|
+
"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.",
|
|
10101
|
+
EXIT_CODES.general,
|
|
10102
|
+
{
|
|
10103
|
+
code: "validation_failed",
|
|
10104
|
+
next_steps: [
|
|
10105
|
+
{ command: "apex workspace status", why: "Use the command for this folder format." },
|
|
10106
|
+
{
|
|
10107
|
+
command: "apex experiments pull",
|
|
10108
|
+
why: "Create the required folder in a different directory."
|
|
10109
|
+
}
|
|
10110
|
+
]
|
|
10111
|
+
}
|
|
9559
10112
|
);
|
|
9560
10113
|
}
|
|
9561
|
-
throw new
|
|
10114
|
+
throw new CliError(
|
|
10115
|
+
`Missing ${filePath}. Run apex experiments pull first.`,
|
|
10116
|
+
EXIT_CODES.general,
|
|
10117
|
+
{
|
|
10118
|
+
code: "local_file_error",
|
|
10119
|
+
next_steps: [
|
|
10120
|
+
{ command: "apex experiments pull", why: "Create the missing local folder metadata." }
|
|
10121
|
+
]
|
|
10122
|
+
}
|
|
10123
|
+
);
|
|
9562
10124
|
}
|
|
9563
|
-
return JSON.parse(
|
|
10125
|
+
return JSON.parse(import_node_fs8.default.readFileSync(filePath, "utf8"));
|
|
9564
10126
|
}
|
|
9565
10127
|
function validateFilesystemPath(relativePath) {
|
|
9566
10128
|
const normalized = relativePath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
@@ -9576,17 +10138,17 @@ function writeExperimentFilesystemFiles(folder, files) {
|
|
|
9576
10138
|
}));
|
|
9577
10139
|
for (const file of validatedFiles) {
|
|
9578
10140
|
const relativePath = file.relativePath;
|
|
9579
|
-
const absPath =
|
|
9580
|
-
|
|
9581
|
-
|
|
10141
|
+
const absPath = import_node_path6.default.join(folder, relativePath);
|
|
10142
|
+
import_node_fs8.default.mkdirSync(import_node_path6.default.dirname(absPath), { recursive: true });
|
|
10143
|
+
import_node_fs8.default.writeFileSync(absPath, file.content, "utf8");
|
|
9582
10144
|
}
|
|
9583
10145
|
}
|
|
9584
10146
|
function listExperimentFilesystemFiles(folder, session) {
|
|
9585
10147
|
const files = [];
|
|
9586
10148
|
for (const fileName of EXPERIMENT_FILESYSTEM_PATH_SCHEMA.topLevel) {
|
|
9587
|
-
const absPath =
|
|
9588
|
-
if (!
|
|
9589
|
-
const content =
|
|
10149
|
+
const absPath = import_node_path6.default.join(folder, fileName);
|
|
10150
|
+
if (!import_node_fs8.default.existsSync(absPath)) continue;
|
|
10151
|
+
const content = import_node_fs8.default.readFileSync(absPath, "utf8");
|
|
9590
10152
|
const sha256 = sha256Text(content);
|
|
9591
10153
|
if (SERVER_OWNED_EXPERIMENT_FILES.has(fileName)) {
|
|
9592
10154
|
if (session.files[fileName]?.sha256 !== sha256) {
|
|
@@ -9600,21 +10162,21 @@ function listExperimentFilesystemFiles(folder, session) {
|
|
|
9600
10162
|
}
|
|
9601
10163
|
for (const fileName of EXPERIMENT_FILESYSTEM_PATH_SCHEMA.experiment) {
|
|
9602
10164
|
const relativePath = `experiment/${fileName}`;
|
|
9603
|
-
const absPath =
|
|
9604
|
-
if (!
|
|
9605
|
-
const content =
|
|
10165
|
+
const absPath = import_node_path6.default.join(folder, relativePath);
|
|
10166
|
+
if (!import_node_fs8.default.existsSync(absPath)) continue;
|
|
10167
|
+
const content = import_node_fs8.default.readFileSync(absPath, "utf8");
|
|
9606
10168
|
files.push({ path: relativePath, content, sha256: sha256Text(content) });
|
|
9607
10169
|
}
|
|
9608
|
-
const variationsRoot =
|
|
9609
|
-
if (
|
|
9610
|
-
for (const variationDir of
|
|
10170
|
+
const variationsRoot = import_node_path6.default.join(folder, "variations");
|
|
10171
|
+
if (import_node_fs8.default.existsSync(variationsRoot)) {
|
|
10172
|
+
for (const variationDir of import_node_fs8.default.readdirSync(variationsRoot, { withFileTypes: true })) {
|
|
9611
10173
|
if (!variationDir.isDirectory()) continue;
|
|
9612
|
-
const base =
|
|
10174
|
+
const base = import_node_path6.default.join(variationsRoot, variationDir.name);
|
|
9613
10175
|
for (const fileName of EXPERIMENT_FILESYSTEM_PATH_SCHEMA.variation) {
|
|
9614
|
-
const absPath =
|
|
9615
|
-
if (!
|
|
9616
|
-
const relativePath = validateFilesystemPath(
|
|
9617
|
-
const content =
|
|
10176
|
+
const absPath = import_node_path6.default.join(base, fileName);
|
|
10177
|
+
if (!import_node_fs8.default.existsSync(absPath)) continue;
|
|
10178
|
+
const relativePath = validateFilesystemPath(import_node_path6.default.relative(folder, absPath));
|
|
10179
|
+
const content = import_node_fs8.default.readFileSync(absPath, "utf8");
|
|
9618
10180
|
files.push({ path: relativePath, content, sha256: sha256Text(content) });
|
|
9619
10181
|
}
|
|
9620
10182
|
}
|
|
@@ -9627,7 +10189,7 @@ async function rewriteExperimentFilesystemAssets(input) {
|
|
|
9627
10189
|
baseUrl: input.baseUrl,
|
|
9628
10190
|
enabled: input.enabled,
|
|
9629
10191
|
upload: async (file) => {
|
|
9630
|
-
const hash =
|
|
10192
|
+
const hash = import_node_crypto5.default.createHash("sha256").update(file.bytes).digest("hex");
|
|
9631
10193
|
const cached = input.assets[hash];
|
|
9632
10194
|
if (cached) return cached;
|
|
9633
10195
|
if (input.upload === null) return null;
|
|
@@ -9656,7 +10218,7 @@ async function rewriteExperimentFilesystemAssets(input) {
|
|
|
9656
10218
|
}
|
|
9657
10219
|
const content = await rewriter.rewrite(
|
|
9658
10220
|
file.content,
|
|
9659
|
-
|
|
10221
|
+
import_node_path6.default.dirname(import_node_path6.default.join(input.folder, file.path))
|
|
9660
10222
|
);
|
|
9661
10223
|
return { ...file, content, sha256: sha256Text(content) };
|
|
9662
10224
|
})
|
|
@@ -9667,9 +10229,17 @@ function printFilesystemPlan(plan) {
|
|
|
9667
10229
|
console.log(source_default.red("\nErrors"));
|
|
9668
10230
|
plan.errors.forEach((error) => console.log(` - ${error}`));
|
|
9669
10231
|
}
|
|
9670
|
-
|
|
10232
|
+
const compatibilityWarnings = (plan.previewCompatibility ?? []).map(
|
|
10233
|
+
(finding) => `${finding.path}:${finding.line}:${finding.column} ${finding.rule}: ${finding.message}. Try: ${finding.alternative}`
|
|
10234
|
+
);
|
|
10235
|
+
if (compatibilityWarnings.length > 0) {
|
|
10236
|
+
console.log(source_default.yellow("\nPreview compatibility"));
|
|
10237
|
+
compatibilityWarnings.forEach((warning) => console.log(` - ${warning}`));
|
|
10238
|
+
}
|
|
10239
|
+
const otherWarnings = plan.warnings.filter((warning) => !compatibilityWarnings.includes(warning));
|
|
10240
|
+
if (otherWarnings.length > 0) {
|
|
9671
10241
|
console.log(source_default.yellow("\nWarnings"));
|
|
9672
|
-
|
|
10242
|
+
otherWarnings.forEach((warning) => console.log(` - ${warning}`));
|
|
9673
10243
|
}
|
|
9674
10244
|
if ((plan.blockingIssues?.length ?? 0) > 0) {
|
|
9675
10245
|
console.log(source_default.red("\nBlocking issues"));
|
|
@@ -9744,6 +10314,10 @@ function printQaPreviewUrls(experimentId, result) {
|
|
|
9744
10314
|
);
|
|
9745
10315
|
}
|
|
9746
10316
|
}
|
|
10317
|
+
function qaVersionMarker(value) {
|
|
10318
|
+
const version = value;
|
|
10319
|
+
return version.stale ? " [stale: checked against an earlier version]" : version.unversioned ? " [unversioned]" : "";
|
|
10320
|
+
}
|
|
9747
10321
|
function printQaRun(run3) {
|
|
9748
10322
|
console.log();
|
|
9749
10323
|
console.log(source_default.bold(`QA run ${run3.id}`));
|
|
@@ -9760,7 +10334,7 @@ function printQaRun(run3) {
|
|
|
9760
10334
|
for (const item of run3.checklist) {
|
|
9761
10335
|
const required = item.required ? source_default.yellow(" required") : "";
|
|
9762
10336
|
const notes = item.notes ? source_default.dim(` \u2014 ${item.notes}`) : "";
|
|
9763
|
-
console.log(` ${statusBadge(item.status)} ${item.id}${required}`);
|
|
10337
|
+
console.log(` ${statusBadge(item.status)} ${item.id}${required}${qaVersionMarker(item)}`);
|
|
9764
10338
|
console.log(` ${item.title}${notes}`);
|
|
9765
10339
|
}
|
|
9766
10340
|
if (run3.urls.length > 0) {
|
|
@@ -9771,10 +10345,10 @@ function printQaRun(run3) {
|
|
|
9771
10345
|
}
|
|
9772
10346
|
if ((run3.evidenceRecords ?? []).length > 0) {
|
|
9773
10347
|
console.log(source_default.bold("\nEvidence records"));
|
|
9774
|
-
for (const
|
|
9775
|
-
const device =
|
|
10348
|
+
for (const record3 of run3.evidenceRecords ?? []) {
|
|
10349
|
+
const device = record3.device ? ` \xB7 ${record3.device}` : "";
|
|
9776
10350
|
console.log(
|
|
9777
|
-
` - ${
|
|
10351
|
+
` - ${record3.kind} \xB7 ${record3.label} \xB7 ${record3.status}${device} \xB7 ${record3.capturedAt}${qaVersionMarker(record3)}`
|
|
9778
10352
|
);
|
|
9779
10353
|
}
|
|
9780
10354
|
}
|
|
@@ -9795,8 +10369,8 @@ async function confirmApply(message) {
|
|
|
9795
10369
|
function parseEvidenceInput(value) {
|
|
9796
10370
|
if (!value) return void 0;
|
|
9797
10371
|
const source = value.startsWith("@") ? value.slice(1) : value;
|
|
9798
|
-
const
|
|
9799
|
-
const parsed = JSON.parse(
|
|
10372
|
+
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;
|
|
10373
|
+
const parsed = JSON.parse(text2);
|
|
9800
10374
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
9801
10375
|
throw new Error("QA evidence must be a JSON object");
|
|
9802
10376
|
}
|
|
@@ -9840,7 +10414,7 @@ async function resolveExperimentForFilesystemOrId(value) {
|
|
|
9840
10414
|
return {
|
|
9841
10415
|
experimentId: session.experimentId,
|
|
9842
10416
|
folder: candidate,
|
|
9843
|
-
label:
|
|
10417
|
+
label: import_node_path6.default.basename(candidate)
|
|
9844
10418
|
};
|
|
9845
10419
|
}
|
|
9846
10420
|
const exp = await resolveExperiment(value);
|
|
@@ -10026,7 +10600,53 @@ var detachIdea = new Command("detach-idea").description("Detach an experiment fr
|
|
|
10026
10600
|
);
|
|
10027
10601
|
output(this, res.data, () => printSuccess("Idea detached."));
|
|
10028
10602
|
});
|
|
10029
|
-
|
|
10603
|
+
function printResultsExplain(body5, samples) {
|
|
10604
|
+
const data = body5.data;
|
|
10605
|
+
console.log("Diagnostic results explanation");
|
|
10606
|
+
for (const arm of data.arms) {
|
|
10607
|
+
console.log(`
|
|
10608
|
+
${arm.name} (${arm.variation_id})`);
|
|
10609
|
+
for (const name of ["visitors", "conversions", "orders", "revenue"]) {
|
|
10610
|
+
const metric = arm[name];
|
|
10611
|
+
if (!metric) continue;
|
|
10612
|
+
console.log(` ${name}`);
|
|
10613
|
+
for (const [label, count2] of Object.entries(metric.counts ?? {}))
|
|
10614
|
+
console.log(` ${label.padEnd(28)} ${count2}`);
|
|
10615
|
+
console.log(
|
|
10616
|
+
` Counted: ${metric.classified_counted ?? "unavailable"} Reported: ${metric.reported ?? "unavailable"} Residual: ${metric.residual ?? "unavailable"}`
|
|
10617
|
+
);
|
|
10618
|
+
if (metric.explanation) console.log(` ${metric.explanation}`);
|
|
10619
|
+
if (samples && metric.samples) {
|
|
10620
|
+
for (const [label, entries] of Object.entries(
|
|
10621
|
+
metric.samples
|
|
10622
|
+
)) {
|
|
10623
|
+
for (const entry of entries)
|
|
10624
|
+
console.log(
|
|
10625
|
+
` ${label}: ${entry.order_key || entry.event_id} ${entry.amount ?? ""} ${entry.currency ?? ""} ${entry.timestamp ?? ""} arm_only=${entry.arm_only ?? false}`
|
|
10626
|
+
);
|
|
10627
|
+
}
|
|
10628
|
+
}
|
|
10629
|
+
}
|
|
10630
|
+
}
|
|
10631
|
+
if (data.order) console.log(`
|
|
10632
|
+
Order trail
|
|
10633
|
+
${JSON.stringify(data.order, null, 2)}`);
|
|
10634
|
+
const others = data.other_variations ?? [];
|
|
10635
|
+
if (others.length > 0) {
|
|
10636
|
+
console.log("\nOther variations (not part of this test)");
|
|
10637
|
+
for (const other of others) console.log(` ${other.variation_id}: ${other.row_count} rows`);
|
|
10638
|
+
}
|
|
10639
|
+
const limitations = data.limitations ?? [];
|
|
10640
|
+
if (limitations.length > 0) {
|
|
10641
|
+
console.log("\nLimitations");
|
|
10642
|
+
for (const limitation of limitations) console.log(` - ${limitation}`);
|
|
10643
|
+
}
|
|
10644
|
+
console.log(`
|
|
10645
|
+
Definitions: ${data.definitions_url}`);
|
|
10646
|
+
}
|
|
10647
|
+
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) {
|
|
10648
|
+
if (!opts.explain && (opts.goal || opts.order || opts.samples))
|
|
10649
|
+
fail(this, "--goal, --order and --samples require --explain", EXIT_CODES.validation);
|
|
10030
10650
|
const creds = requireCredentials();
|
|
10031
10651
|
const spinner = await createSpinner("Fetching experiment results...");
|
|
10032
10652
|
try {
|
|
@@ -10035,7 +10655,7 @@ var results = new Command("results").description("Read experiment results and ti
|
|
|
10035
10655
|
spinner?.fail(`Experiment "${idOrSlug}" not found`);
|
|
10036
10656
|
fail(this, `Experiment "${idOrSlug}" not found.`, EXIT_CODES.validation);
|
|
10037
10657
|
}
|
|
10038
|
-
const res = await getExperimentResults(creds, exp.id, { currency: opts.currency });
|
|
10658
|
+
const res = opts.explain ? await getExperimentResultsExplain(creds, exp.id, opts) : await getExperimentResults(creds, exp.id, { currency: opts.currency });
|
|
10039
10659
|
if (!res.ok || !res.data) {
|
|
10040
10660
|
spinner?.fail("Failed to fetch experiment results");
|
|
10041
10661
|
fail(
|
|
@@ -10049,7 +10669,7 @@ var results = new Command("results").description("Read experiment results and ti
|
|
|
10049
10669
|
output(
|
|
10050
10670
|
this,
|
|
10051
10671
|
res.data,
|
|
10052
|
-
() => printExperimentResults(res.data, opts.currency)
|
|
10672
|
+
() => opts.explain ? printResultsExplain(res.data, Boolean(opts.samples)) : printExperimentResults(res.data, opts.currency)
|
|
10053
10673
|
);
|
|
10054
10674
|
} catch (error) {
|
|
10055
10675
|
spinner?.fail("Failed to fetch experiment results");
|
|
@@ -10132,7 +10752,7 @@ function parseHttpDestination(value) {
|
|
|
10132
10752
|
function readCreateJson(command, reference) {
|
|
10133
10753
|
const file = reference.startsWith("@") ? reference.slice(1) : reference;
|
|
10134
10754
|
try {
|
|
10135
|
-
const parsed = JSON.parse(
|
|
10755
|
+
const parsed = JSON.parse(import_node_fs8.default.readFileSync(import_node_path6.default.resolve(process.cwd(), file), "utf8"));
|
|
10136
10756
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
10137
10757
|
throw new Error("expected an object");
|
|
10138
10758
|
return parsed;
|
|
@@ -10168,8 +10788,8 @@ function normalizeNewGoalKey(key) {
|
|
|
10168
10788
|
return normalized === "page" ? "page_id" : normalized;
|
|
10169
10789
|
}
|
|
10170
10790
|
function validateNewGoal(command, value, body5) {
|
|
10171
|
-
const invalid = (
|
|
10172
|
-
const
|
|
10791
|
+
const invalid = (reason2) => fail(command, `Invalid --goal "new:${value}"; ${reason2}`, EXIT_CODES.validation);
|
|
10792
|
+
const text2 = (field) => {
|
|
10173
10793
|
const raw = body5[field];
|
|
10174
10794
|
return typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
10175
10795
|
};
|
|
@@ -10179,12 +10799,12 @@ function validateNewGoal(command, value, body5) {
|
|
|
10179
10799
|
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
10800
|
);
|
|
10181
10801
|
}
|
|
10182
|
-
const type =
|
|
10183
|
-
const name =
|
|
10802
|
+
const type = text2("type");
|
|
10803
|
+
const name = text2("name");
|
|
10184
10804
|
if (!type || !name) invalid("type and name are required");
|
|
10185
10805
|
if (!GOAL_TYPES.includes(type)) invalid(`type must be one of ${GOAL_TYPES.join(", ")}`);
|
|
10186
10806
|
if (name.length > 255) invalid("name must be 255 characters or fewer");
|
|
10187
|
-
const measurement =
|
|
10807
|
+
const measurement = text2("measurement") ?? "conversion";
|
|
10188
10808
|
if (!GOAL_MEASUREMENTS.includes(measurement))
|
|
10189
10809
|
invalid(`measurement must be one of ${GOAL_MEASUREMENTS.join(", ")}`);
|
|
10190
10810
|
if (body5.count_once !== void 0) {
|
|
@@ -10192,21 +10812,21 @@ function validateNewGoal(command, value, body5) {
|
|
|
10192
10812
|
if (flag !== "true" && flag !== "false") invalid("count-once must be true or false");
|
|
10193
10813
|
body5.count_once = flag === "true";
|
|
10194
10814
|
}
|
|
10195
|
-
if (type === "click" && !
|
|
10815
|
+
if (type === "click" && !text2("selector") && !text2("text_match"))
|
|
10196
10816
|
invalid("click goals need selector=<css> or text-match=<text>");
|
|
10197
|
-
if (type === "pageview" && !
|
|
10817
|
+
if (type === "pageview" && !text2("page_id") && !text2("url_pattern"))
|
|
10198
10818
|
invalid("pageview goals need page=<page-id> or url-pattern=<pattern>");
|
|
10199
|
-
if (type === "custom" && !
|
|
10200
|
-
const eventName =
|
|
10819
|
+
if (type === "custom" && !text2("event_name")) invalid("custom goals need event-name=<name>");
|
|
10820
|
+
const eventName = text2("event_name");
|
|
10201
10821
|
if (eventName && !GOAL_EVENT_NAME.test(eventName))
|
|
10202
10822
|
invalid("event-name must be 1-120 letters, numbers, dots, colons, underscores, or hyphens");
|
|
10203
|
-
if ((type === "custom" || type === "revenue") &&
|
|
10823
|
+
if ((type === "custom" || type === "revenue") && text2("page_id"))
|
|
10204
10824
|
invalid(`page is not supported for ${type} goals`);
|
|
10205
|
-
const pageId =
|
|
10825
|
+
const pageId = text2("page_id");
|
|
10206
10826
|
if (pageId && !GOAL_UUID.test(pageId))
|
|
10207
10827
|
invalid("page must be a page UUID (list them with `apex pages list`)");
|
|
10208
|
-
const valueProperty =
|
|
10209
|
-
const valueUnit =
|
|
10828
|
+
const valueProperty = text2("value_property");
|
|
10829
|
+
const valueUnit = text2("value_unit");
|
|
10210
10830
|
if (measurement !== "value" && (valueProperty || valueUnit))
|
|
10211
10831
|
invalid("value-property and value-unit need measurement=value");
|
|
10212
10832
|
if (type === "revenue" && valueProperty)
|
|
@@ -10730,7 +11350,7 @@ var pullFilesystem = new Command("pull").description("Pull an experiment as an e
|
|
|
10730
11350
|
{
|
|
10731
11351
|
folder,
|
|
10732
11352
|
experimentId: exp.id,
|
|
10733
|
-
files: res.data.files.map((file) =>
|
|
11353
|
+
files: res.data.files.map((file) => import_node_path6.default.join(folder, file.path))
|
|
10734
11354
|
},
|
|
10735
11355
|
() => {
|
|
10736
11356
|
spinner.succeed(`Pulled ${exp.name}`);
|
|
@@ -10742,6 +11362,252 @@ var pullFilesystem = new Command("pull").description("Pull an experiment as an e
|
|
|
10742
11362
|
throw error;
|
|
10743
11363
|
}
|
|
10744
11364
|
});
|
|
11365
|
+
var experimentStatus = new Command("status").description(
|
|
11366
|
+
"Read experiment context, local changes, QA, and handover next steps without writing"
|
|
11367
|
+
).argument("[folder-or-id]", "Pulled folder, experiment ID, slug, or test key", ".").action(async function(target) {
|
|
11368
|
+
const creds = requireCredentials();
|
|
11369
|
+
const candidate = folderPath(target);
|
|
11370
|
+
const isFolder = import_node_fs8.default.existsSync(candidate) && import_node_fs8.default.statSync(candidate).isDirectory();
|
|
11371
|
+
const hasApexDir = isFolder && import_node_fs8.default.existsSync(import_node_path6.default.join(candidate, ".apex"));
|
|
11372
|
+
if (isFolder && !hasApexDir && target === ".") {
|
|
11373
|
+
fail(this, "This folder is not a pulled experiment.", EXIT_CODES.validation, {
|
|
11374
|
+
code: "validation_failed",
|
|
11375
|
+
next_steps: [
|
|
11376
|
+
{
|
|
11377
|
+
command: "apex experiments status <experiment-id-or-test-key>",
|
|
11378
|
+
why: "Read the saved experiment without a local folder."
|
|
11379
|
+
},
|
|
11380
|
+
{
|
|
11381
|
+
command: "apex experiments pull <experiment-id-or-test-key> --out <folder>",
|
|
11382
|
+
why: "Pull the experiment to compare local files before handover."
|
|
11383
|
+
}
|
|
11384
|
+
]
|
|
11385
|
+
});
|
|
11386
|
+
}
|
|
11387
|
+
const session = hasApexDir ? readFilesystemSession(candidate) : null;
|
|
11388
|
+
if (session && new URL(session.baseUrl).origin !== new URL(creds.baseUrl).origin) {
|
|
11389
|
+
fail(
|
|
11390
|
+
this,
|
|
11391
|
+
"This folder was pulled from a different base URL. Select that base URL before inspecting it.",
|
|
11392
|
+
EXIT_CODES.validation
|
|
11393
|
+
);
|
|
11394
|
+
}
|
|
11395
|
+
const response = session ? await getExperiment(creds, session.experimentId) : null;
|
|
11396
|
+
if (response && (!response.ok || !response.data)) {
|
|
11397
|
+
throw new CliError(
|
|
11398
|
+
response.error ?? "Failed to read experiment",
|
|
11399
|
+
classifyStatus(response.status),
|
|
11400
|
+
response.errorDetails,
|
|
11401
|
+
apiFailureOf(response)
|
|
11402
|
+
);
|
|
11403
|
+
}
|
|
11404
|
+
const experiment = response?.data ?? await resolveExperiment(target);
|
|
11405
|
+
if (!experiment) fail(this, "Experiment not found.");
|
|
11406
|
+
const snapshot = await getExperimentFilesystem(creds, experiment.id);
|
|
11407
|
+
if (!snapshot.ok || !snapshot.data) {
|
|
11408
|
+
throw new CliError(
|
|
11409
|
+
snapshot.error ?? "Failed to read experiment filesystem",
|
|
11410
|
+
classifyStatus(snapshot.status),
|
|
11411
|
+
snapshot.errorDetails,
|
|
11412
|
+
apiFailureOf(snapshot)
|
|
11413
|
+
);
|
|
11414
|
+
}
|
|
11415
|
+
let versions = null;
|
|
11416
|
+
if (session) {
|
|
11417
|
+
const localFiles = listExperimentFilesystemFiles(candidate, session);
|
|
11418
|
+
const localHashes = new Map(localFiles.map((file) => [file.path, file.sha256]));
|
|
11419
|
+
for (const name of SERVER_OWNED_EXPERIMENT_FILES) {
|
|
11420
|
+
const file = import_node_path6.default.join(candidate, name);
|
|
11421
|
+
if (import_node_fs8.default.existsSync(file)) localHashes.set(name, sha256Text(import_node_fs8.default.readFileSync(file, "utf8")));
|
|
11422
|
+
}
|
|
11423
|
+
const paths = [.../* @__PURE__ */ new Set([...Object.keys(session.files), ...localHashes.keys()])].sort();
|
|
11424
|
+
const files = paths.map((file) => {
|
|
11425
|
+
const local = localHashes.get(file) ?? null;
|
|
11426
|
+
const pulled = session.files[file]?.sha256 ?? null;
|
|
11427
|
+
return {
|
|
11428
|
+
path: file,
|
|
11429
|
+
state: !local ? "deleted" : !pulled ? "added" : local === pulled ? "unchanged" : "modified",
|
|
11430
|
+
local_sha256: local,
|
|
11431
|
+
pulled_sha256: pulled
|
|
11432
|
+
};
|
|
11433
|
+
});
|
|
11434
|
+
const plannedFiles = await rewriteExperimentFilesystemAssets({
|
|
11435
|
+
files: localFiles,
|
|
11436
|
+
folder: candidate,
|
|
11437
|
+
baseUrl: creds.baseUrl,
|
|
11438
|
+
enabled: true,
|
|
11439
|
+
upload: null,
|
|
11440
|
+
assets: session.assets ?? {},
|
|
11441
|
+
credentials: creds
|
|
11442
|
+
});
|
|
11443
|
+
const plan = await planExperimentFilesystem(creds, experiment.id, { files: plannedFiles });
|
|
11444
|
+
if (!plan.ok || !plan.data) {
|
|
11445
|
+
throw new CliError(
|
|
11446
|
+
plan.error ?? "Failed to read saved state",
|
|
11447
|
+
classifyStatus(plan.status),
|
|
11448
|
+
plan.errorDetails,
|
|
11449
|
+
apiFailureOf(plan)
|
|
11450
|
+
);
|
|
11451
|
+
}
|
|
11452
|
+
versions = {
|
|
11453
|
+
files,
|
|
11454
|
+
saved: {
|
|
11455
|
+
would_change: plan.data.changes.length > 0,
|
|
11456
|
+
invalidates_qa: plan.data.invalidatesQa,
|
|
11457
|
+
blocking_issue_count: plan.data.blockingIssues?.filter((issue) => issue.severity === "blocking").length ?? 0,
|
|
11458
|
+
valid: plan.data.ok,
|
|
11459
|
+
errors: plan.data.errors,
|
|
11460
|
+
warnings: plan.data.warnings
|
|
11461
|
+
}
|
|
11462
|
+
};
|
|
11463
|
+
}
|
|
11464
|
+
const qaFile = snapshot.data.files.find((file) => file.path === "qa.json");
|
|
11465
|
+
let qaSnapshot = null;
|
|
11466
|
+
let qaUnreadable = false;
|
|
11467
|
+
if (qaFile) {
|
|
11468
|
+
try {
|
|
11469
|
+
qaSnapshot = JSON.parse(qaFile.content);
|
|
11470
|
+
} catch {
|
|
11471
|
+
qaUnreadable = true;
|
|
11472
|
+
}
|
|
11473
|
+
}
|
|
11474
|
+
const qa3 = {
|
|
11475
|
+
...summarizeHandoverQa(qaSnapshot),
|
|
11476
|
+
ready_version_id: experiment.ready_version_id ?? null
|
|
11477
|
+
};
|
|
11478
|
+
const shopId = experiment.shop_id ?? creds.resolvedContext?.shopId ?? null;
|
|
11479
|
+
let org = {
|
|
11480
|
+
id: creds.resolvedContext?.orgId ?? null,
|
|
11481
|
+
name: null
|
|
11482
|
+
};
|
|
11483
|
+
let shop = { id: shopId, name: null };
|
|
11484
|
+
const notes = [];
|
|
11485
|
+
if (qaUnreadable) notes.push("The saved qa.json could not be parsed; QA counts are unknown.");
|
|
11486
|
+
if (!session)
|
|
11487
|
+
notes.push(
|
|
11488
|
+
"Versions are unavailable without a pulled folder. Pull the experiment to compare local files."
|
|
11489
|
+
);
|
|
11490
|
+
if (creds.credentialKind === "user") {
|
|
11491
|
+
const context = await getAccountContext(creds);
|
|
11492
|
+
const owner = context.data?.data?.orgs?.find(
|
|
11493
|
+
(item) => item.shops?.some((item2) => item2.id === shopId)
|
|
11494
|
+
);
|
|
11495
|
+
if (owner) {
|
|
11496
|
+
org = { id: owner.id, name: owner.name };
|
|
11497
|
+
shop = { id: shopId, name: owner.shops.find((item) => item.id === shopId)?.name ?? null };
|
|
11498
|
+
} else notes.push("Organization and shop names are unavailable from account context.");
|
|
11499
|
+
} else
|
|
11500
|
+
notes.push(
|
|
11501
|
+
"Shop credentials do not expose account context; organization and shop names are unknown."
|
|
11502
|
+
);
|
|
11503
|
+
const nextSteps = [];
|
|
11504
|
+
const ref = shellQuote(session ? target : experiment.id);
|
|
11505
|
+
if (!session)
|
|
11506
|
+
nextSteps.push({
|
|
11507
|
+
command: `apex experiments pull ${ref} --out <folder>`,
|
|
11508
|
+
why: "Pull files to verify the local version."
|
|
11509
|
+
});
|
|
11510
|
+
if (versions && (versions.files.some((file) => file.state !== "unchanged") || versions.saved.would_change || !versions.saved.valid || versions.saved.blocking_issue_count > 0)) {
|
|
11511
|
+
nextSteps.push(
|
|
11512
|
+
{
|
|
11513
|
+
command: `apex experiments plan ${ref} --json`,
|
|
11514
|
+
why: "Review local changes, saved differences, and blocking issues."
|
|
11515
|
+
},
|
|
11516
|
+
{
|
|
11517
|
+
command: `apex experiments apply ${ref} --json`,
|
|
11518
|
+
why: "Save the reviewed changes after resolving blockers and any QA invalidation acknowledgment."
|
|
11519
|
+
}
|
|
11520
|
+
);
|
|
11521
|
+
}
|
|
11522
|
+
if (qa3.required_done === null || qa3.required_total === null || qa3.required_done < qa3.required_total || qa3.blockers === null || qa3.blockers.length > 0) {
|
|
11523
|
+
nextSteps.push(
|
|
11524
|
+
{
|
|
11525
|
+
command: `apex experiments qa status ${ref} --json`,
|
|
11526
|
+
why: "Inspect missing required checks and evidence before recording QA results. This separate command can create a QA run."
|
|
11527
|
+
},
|
|
11528
|
+
{
|
|
11529
|
+
command: `apex experiments qa check ${ref} --item <item-id> --status pass`,
|
|
11530
|
+
why: "Record each missing required check; attach supporting evidence with apex experiments qa evidence."
|
|
11531
|
+
}
|
|
11532
|
+
);
|
|
11533
|
+
} else if (!qa3.approval_present) {
|
|
11534
|
+
nextSteps.push({
|
|
11535
|
+
command: `apex experiments qa approve ${ref}`,
|
|
11536
|
+
why: "Required QA checks are complete; approve after verifying the saved version."
|
|
11537
|
+
});
|
|
11538
|
+
}
|
|
11539
|
+
const locallyChanged = versions !== null && (versions.files.some((file) => file.state !== "unchanged") || versions.saved.would_change || !versions.saved.valid || versions.saved.blocking_issue_count > 0);
|
|
11540
|
+
const qaComplete = qa3.required_done !== null && qa3.required_total !== null && qa3.required_done === qa3.required_total && qa3.blockers !== null && qa3.blockers.length === 0;
|
|
11541
|
+
const readyVersionKnown = typeof qa3.ready_version_id === "string" && qa3.ready_version_id.length > 0;
|
|
11542
|
+
const approvalCurrent = qa3.approval_present && readyVersionKnown && !locallyChanged;
|
|
11543
|
+
const lifecycleReady = experiment.status === "ready_to_launch";
|
|
11544
|
+
const readyForHandover = lifecycleReady && approvalCurrent && qaComplete;
|
|
11545
|
+
if (approvalCurrent && qaComplete && !lifecycleReady)
|
|
11546
|
+
notes.push(`The experiment status is ${experiment.status}; handover needs ready_to_launch.`);
|
|
11547
|
+
if (qa3.approval_present && !readyVersionKnown && qaComplete && !locallyChanged)
|
|
11548
|
+
nextSteps.push({
|
|
11549
|
+
command: `apex experiments qa approve ${ref}`,
|
|
11550
|
+
why: "The approval is not bound to a saved version; approve again to bind it before handover."
|
|
11551
|
+
});
|
|
11552
|
+
if (readyForHandover)
|
|
11553
|
+
nextSteps.push({
|
|
11554
|
+
command: "launch in the Apex dashboard",
|
|
11555
|
+
why: "QA is approved for the saved version. Starting a test is a human decision."
|
|
11556
|
+
});
|
|
11557
|
+
if (!versions)
|
|
11558
|
+
notes.push(
|
|
11559
|
+
"Without a pulled folder, readiness reflects the saved version only; local edits elsewhere are not visible."
|
|
11560
|
+
);
|
|
11561
|
+
const data = {
|
|
11562
|
+
context: {
|
|
11563
|
+
org,
|
|
11564
|
+
shop,
|
|
11565
|
+
credential_kind: creds.credentialKind ?? "unknown",
|
|
11566
|
+
base_url: creds.baseUrl
|
|
11567
|
+
},
|
|
11568
|
+
experiment: {
|
|
11569
|
+
id: experiment.id,
|
|
11570
|
+
test_key: experiment.test_key ?? null,
|
|
11571
|
+
name: experiment.name,
|
|
11572
|
+
status: experiment.status,
|
|
11573
|
+
dashboard_url: buildDashboardUrl(creds.baseUrl, shopId, experiment.id)
|
|
11574
|
+
},
|
|
11575
|
+
versions,
|
|
11576
|
+
qa: { ...qa3, approval_current: approvalCurrent },
|
|
11577
|
+
next_steps: nextSteps,
|
|
11578
|
+
ready_for_handover: readyForHandover,
|
|
11579
|
+
notes
|
|
11580
|
+
};
|
|
11581
|
+
output(this, data, () => {
|
|
11582
|
+
console.log(
|
|
11583
|
+
`Context: ${org.name ?? org.id ?? "unknown organization"} / ${shop.name ?? shop.id ?? "unknown shop"}
|
|
11584
|
+
Credential: ${data.context.credential_kind}
|
|
11585
|
+
Base URL: ${creds.baseUrl}`
|
|
11586
|
+
);
|
|
11587
|
+
console.log(
|
|
11588
|
+
`Experiment: ${experiment.test_key ?? experiment.id} / ${experiment.name} (${experiment.status})
|
|
11589
|
+
Dashboard: ${data.experiment.dashboard_url ?? "unknown"}`
|
|
11590
|
+
);
|
|
11591
|
+
console.log("Versions:");
|
|
11592
|
+
if (versions) {
|
|
11593
|
+
for (const file of versions.files) console.log(` ${file.state}: ${file.path}`);
|
|
11594
|
+
console.log(
|
|
11595
|
+
` Saved: would change ${versions.saved.would_change}; invalidates QA ${versions.saved.invalidates_qa}; blocking issues ${versions.saved.blocking_issue_count}`
|
|
11596
|
+
);
|
|
11597
|
+
for (const message of [...versions.saved.errors, ...versions.saved.warnings])
|
|
11598
|
+
console.log(` ${message}`);
|
|
11599
|
+
} else console.log(" unknown (no pulled folder)");
|
|
11600
|
+
console.log(
|
|
11601
|
+
`QA: ${qa3.run_status ?? "unknown"}; required ${qa3.required_done ?? "unknown"}/${qa3.required_total ?? "unknown"}; approval ${qa3.approval_present}; current for this version: ${approvalCurrent}`
|
|
11602
|
+
);
|
|
11603
|
+
for (const blocker of qa3.blockers ?? []) console.log(` Blocker: ${blocker}`);
|
|
11604
|
+
console.log(`Ready for handover: ${readyForHandover ? "yes" : "no"}
|
|
11605
|
+
Next steps:`);
|
|
11606
|
+
for (const step of nextSteps) console.log(` ${step.command}
|
|
11607
|
+
${step.why}`);
|
|
11608
|
+
for (const note of notes) console.log(` ${note}`);
|
|
11609
|
+
});
|
|
11610
|
+
});
|
|
10745
11611
|
var planFilesystem = new Command("plan").description("Plan experiment filesystem changes").argument("[folder]", "Experiment filesystem folder", ".").action(async function action14(folderArg) {
|
|
10746
11612
|
const creds = requireCredentials();
|
|
10747
11613
|
try {
|
|
@@ -10788,7 +11654,10 @@ var planFilesystem = new Command("plan").description("Plan experiment filesystem
|
|
|
10788
11654
|
fail(this, error instanceof Error ? error.message : String(error));
|
|
10789
11655
|
}
|
|
10790
11656
|
});
|
|
10791
|
-
var applyFilesystem = new Command("apply").option(
|
|
11657
|
+
var applyFilesystem = new Command("apply").option(
|
|
11658
|
+
"--confirm-targeting-replace",
|
|
11659
|
+
"Confirm that attaching a Page may replace URL targeting authored through the API"
|
|
11660
|
+
).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
11661
|
const creds = requireCredentials();
|
|
10793
11662
|
try {
|
|
10794
11663
|
const folder = folderPath(folderArg);
|
|
@@ -10822,13 +11691,19 @@ var applyFilesystem = new Command("apply").option("--sync", "Refresh local autho
|
|
|
10822
11691
|
if (!shouldUseJson(this)) printFilesystemPlan(planRes.data);
|
|
10823
11692
|
if (opts.assetUpload === false && planRes.data.blockingIssues?.some((issue) => /inline|hosted asset/i.test(issue.message))) {
|
|
10824
11693
|
process.stderr.write(
|
|
10825
|
-
`Retry with hosted asset upload: apex experiments apply ${folderArg}
|
|
11694
|
+
`Retry with hosted asset upload: apex experiments apply ${shellQuote(folderArg)}
|
|
10826
11695
|
`
|
|
10827
11696
|
);
|
|
10828
11697
|
}
|
|
10829
11698
|
fail(this, blockingIssueSummary(planRes.data.blockingIssues), EXIT_CODES.validation, {
|
|
10830
11699
|
errors: planRes.data.errors,
|
|
10831
11700
|
plan: planRes.data,
|
|
11701
|
+
next_steps: opts.assetUpload === false && planRes.data.blockingIssues?.some((issue) => /inline|hosted asset/i.test(issue.message)) ? [
|
|
11702
|
+
{
|
|
11703
|
+
command: `apex experiments apply ${shellQuote(folderArg)}`,
|
|
11704
|
+
why: "Retry with hosted asset upload."
|
|
11705
|
+
}
|
|
11706
|
+
] : [],
|
|
10832
11707
|
requiredAcknowledgment: planRes.data.invalidatesQa ? "accept-qa-invalidation" : null
|
|
10833
11708
|
});
|
|
10834
11709
|
}
|
|
@@ -10873,7 +11748,31 @@ var applyFilesystem = new Command("apply").option("--sync", "Refresh local autho
|
|
|
10873
11748
|
}
|
|
10874
11749
|
}
|
|
10875
11750
|
const spinner = process.stderr.isTTY ? await createSpinner("Applying experiment filesystem...") : null;
|
|
10876
|
-
|
|
11751
|
+
let res = await applyExperimentFilesystem(creds, session.experimentId, {
|
|
11752
|
+
files,
|
|
11753
|
+
...opts.confirmTargetingReplace ? { confirmTargetingReplace: true } : {}
|
|
11754
|
+
});
|
|
11755
|
+
if (!res.ok && res.status === 409 && res.errorDetails?.code === "confirm_targeting_replace_required" && !opts.confirmTargetingReplace) {
|
|
11756
|
+
spinner?.stop();
|
|
11757
|
+
const ok = await confirmApply(
|
|
11758
|
+
"Attaching this Page replaces URL targeting authored through the API. Continue?"
|
|
11759
|
+
);
|
|
11760
|
+
if (ok === null) {
|
|
11761
|
+
confirmationRequired(
|
|
11762
|
+
this,
|
|
11763
|
+
"Attaching this Page replaces URL targeting authored through the API and requires confirmation.",
|
|
11764
|
+
`apex experiments apply ${folderArg} --confirm-targeting-replace`
|
|
11765
|
+
);
|
|
11766
|
+
}
|
|
11767
|
+
if (!ok) {
|
|
11768
|
+
output(this, { aborted: true }, () => console.log("Aborted."));
|
|
11769
|
+
return;
|
|
11770
|
+
}
|
|
11771
|
+
res = await applyExperimentFilesystem(creds, session.experimentId, {
|
|
11772
|
+
files,
|
|
11773
|
+
confirmTargetingReplace: true
|
|
11774
|
+
});
|
|
11775
|
+
}
|
|
10877
11776
|
if (!res.ok || !res.data?.applied) {
|
|
10878
11777
|
spinner?.fail("Failed to apply experiment filesystem");
|
|
10879
11778
|
fail(this, res.error ?? "Apply failed", classifyStatus(res.status), {
|
|
@@ -11154,8 +12053,8 @@ var qaReopen = new Command("reopen").description("Reopen QA and clear scheduled
|
|
|
11154
12053
|
});
|
|
11155
12054
|
function parseJsonValue(value) {
|
|
11156
12055
|
const source = value.startsWith("@") ? value.slice(1) : value;
|
|
11157
|
-
const
|
|
11158
|
-
return JSON.parse(
|
|
12056
|
+
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;
|
|
12057
|
+
return JSON.parse(text2);
|
|
11159
12058
|
}
|
|
11160
12059
|
function collectFile(value, previous = []) {
|
|
11161
12060
|
return [...previous, value];
|
|
@@ -11207,7 +12106,7 @@ var qaEvidence = new Command("evidence").description("Attach screenshot jobs or
|
|
|
11207
12106
|
classifyStatus(current.status),
|
|
11208
12107
|
current.errorDetails
|
|
11209
12108
|
);
|
|
11210
|
-
for (const
|
|
12109
|
+
for (const record3 of current.data.run.evidenceRecords) previousIds.add(record3.id);
|
|
11211
12110
|
}
|
|
11212
12111
|
let response;
|
|
11213
12112
|
if (opts.job) {
|
|
@@ -11226,12 +12125,12 @@ var qaEvidence = new Command("evidence").description("Attach screenshot jobs or
|
|
|
11226
12125
|
const variationId = resolveEvidenceVariation(experiment, opts.variation);
|
|
11227
12126
|
const form = new FormData();
|
|
11228
12127
|
for (const filePath of opts.file) {
|
|
11229
|
-
const absolutePath =
|
|
11230
|
-
if (!
|
|
11231
|
-
const bytes =
|
|
11232
|
-
const extension =
|
|
12128
|
+
const absolutePath = import_node_path6.default.resolve(process.cwd(), filePath);
|
|
12129
|
+
if (!import_node_fs8.default.existsSync(absolutePath)) throw new Error(`Evidence file not found: ${filePath}`);
|
|
12130
|
+
const bytes = import_node_fs8.default.readFileSync(absolutePath);
|
|
12131
|
+
const extension = import_node_path6.default.extname(absolutePath).toLowerCase();
|
|
11233
12132
|
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],
|
|
12133
|
+
form.append("files", new File([bytes], import_node_path6.default.basename(absolutePath), { type: mimeType }));
|
|
11235
12134
|
}
|
|
11236
12135
|
if (opts.label) form.append("label", opts.label);
|
|
11237
12136
|
const device = normalizeEvidenceDevice(opts.device);
|
|
@@ -11252,7 +12151,7 @@ var qaEvidence = new Command("evidence").description("Attach screenshot jobs or
|
|
|
11252
12151
|
const run3 = response.data.run;
|
|
11253
12152
|
const data = opts.summary ? qaSummary(
|
|
11254
12153
|
run3,
|
|
11255
|
-
run3.evidenceRecords.filter((
|
|
12154
|
+
run3.evidenceRecords.filter((record3) => !previousIds.has(record3.id))
|
|
11256
12155
|
) : response.data;
|
|
11257
12156
|
output(this, data, () => {
|
|
11258
12157
|
if (opts.summary) process.stdout.write(`${JSON.stringify(data, null, 2)}
|
|
@@ -11278,8 +12177,8 @@ function shellQuote(value) {
|
|
|
11278
12177
|
if (value.length > 0 && /^[A-Za-z0-9_@%+=:,./-]+$/.test(value)) return value;
|
|
11279
12178
|
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
11280
12179
|
}
|
|
11281
|
-
function isTransientResumeStatus(
|
|
11282
|
-
return
|
|
12180
|
+
function isTransientResumeStatus(status3) {
|
|
12181
|
+
return status3 === 429 || status3 !== void 0 && status3 >= 500;
|
|
11283
12182
|
}
|
|
11284
12183
|
function screenshotTimeoutMessage(job, experimentRef, out, seconds, flags = {}) {
|
|
11285
12184
|
const progress = job.progress ? ` Last progress: ${job.progress.captured}/${job.progress.total} captured.` : "";
|
|
@@ -11563,8 +12462,8 @@ var screenshots = new Command("screenshots").description(
|
|
|
11563
12462
|
}
|
|
11564
12463
|
if (job.status === "failed")
|
|
11565
12464
|
fail(this, `Screenshot job failed: ${job.error ?? "unknown error"}`);
|
|
11566
|
-
const outputDir =
|
|
11567
|
-
|
|
12465
|
+
const outputDir = import_node_path6.default.resolve(process.cwd(), opts.out);
|
|
12466
|
+
import_node_fs8.default.mkdirSync(outputDir, { recursive: true });
|
|
11568
12467
|
const rows = [];
|
|
11569
12468
|
const errors = [];
|
|
11570
12469
|
const usedFilenames = /* @__PURE__ */ new Set();
|
|
@@ -11622,8 +12521,8 @@ var screenshots = new Command("screenshots").description(
|
|
|
11622
12521
|
try {
|
|
11623
12522
|
const response = await fetch(url);
|
|
11624
12523
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
11625
|
-
await
|
|
11626
|
-
|
|
12524
|
+
await import_node_fs8.default.promises.writeFile(
|
|
12525
|
+
import_node_path6.default.join(outputDir, filename),
|
|
11627
12526
|
Buffer.from(await response.arrayBuffer())
|
|
11628
12527
|
);
|
|
11629
12528
|
workerResults.push({ index, row: [variation.name, device, filename] });
|
|
@@ -11650,7 +12549,7 @@ var screenshots = new Command("screenshots").description(
|
|
|
11650
12549
|
printTable(["VARIATION", "DEVICE", "FILE"], rows, [24, 20, 48]);
|
|
11651
12550
|
});
|
|
11652
12551
|
});
|
|
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(
|
|
12552
|
+
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
12553
|
"after",
|
|
11655
12554
|
`
|
|
11656
12555
|
Examples:
|
|
@@ -11663,9 +12562,9 @@ Examples:
|
|
|
11663
12562
|
|
|
11664
12563
|
// src/commands/push.ts
|
|
11665
12564
|
init_source();
|
|
11666
|
-
var
|
|
11667
|
-
var
|
|
11668
|
-
var
|
|
12565
|
+
var import_node_fs9 = __toESM(require("node:fs"));
|
|
12566
|
+
var import_node_os7 = __toESM(require("node:os"));
|
|
12567
|
+
var import_node_path7 = __toESM(require("node:path"));
|
|
11669
12568
|
var import_node_url2 = require("node:url");
|
|
11670
12569
|
function toApiBody(exp) {
|
|
11671
12570
|
return {
|
|
@@ -11706,7 +12605,7 @@ async function rewriteExperimentAssets(exp, rewrite, baseDir) {
|
|
|
11706
12605
|
};
|
|
11707
12606
|
}
|
|
11708
12607
|
async function importConfigModule(absPath) {
|
|
11709
|
-
const ext =
|
|
12608
|
+
const ext = import_node_path7.default.extname(absPath).toLowerCase();
|
|
11710
12609
|
if (ext === ".js" || ext === ".mjs" || ext === ".cjs") {
|
|
11711
12610
|
return await import((0, import_node_url2.pathToFileURL)(absPath).href);
|
|
11712
12611
|
}
|
|
@@ -11720,8 +12619,8 @@ async function importConfigModule(absPath) {
|
|
|
11720
12619
|
` + (err instanceof Error ? err.message : String(err))
|
|
11721
12620
|
);
|
|
11722
12621
|
}
|
|
11723
|
-
const tmpDir =
|
|
11724
|
-
const outFile =
|
|
12622
|
+
const tmpDir = import_node_fs9.default.mkdtempSync(import_node_path7.default.join(import_node_os7.default.tmpdir(), "drip-config-"));
|
|
12623
|
+
const outFile = import_node_path7.default.join(tmpDir, "config.mjs");
|
|
11725
12624
|
try {
|
|
11726
12625
|
await esbuild.build({
|
|
11727
12626
|
entryPoints: [absPath],
|
|
@@ -11737,7 +12636,7 @@ async function importConfigModule(absPath) {
|
|
|
11737
12636
|
return await import(href);
|
|
11738
12637
|
} finally {
|
|
11739
12638
|
try {
|
|
11740
|
-
|
|
12639
|
+
import_node_fs9.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
11741
12640
|
} catch {
|
|
11742
12641
|
}
|
|
11743
12642
|
}
|
|
@@ -11758,7 +12657,7 @@ var pushCommand = new Command("push").description("Push experiment definitions f
|
|
|
11758
12657
|
);
|
|
11759
12658
|
}
|
|
11760
12659
|
const spinner = process.stderr.isTTY ? await createSpinner(`Loading config from ${source_default.cyan(configPath)}...`) : null;
|
|
11761
|
-
const absoluteConfigPath =
|
|
12660
|
+
const absoluteConfigPath = import_node_path7.default.resolve(configPath);
|
|
11762
12661
|
let experiments;
|
|
11763
12662
|
try {
|
|
11764
12663
|
const mod = await importConfigModule(absoluteConfigPath);
|
|
@@ -11825,7 +12724,7 @@ var pushCommand = new Command("push").description("Push experiment definitions f
|
|
|
11825
12724
|
return;
|
|
11826
12725
|
}
|
|
11827
12726
|
const assetRewriter = createAssetReferenceRewriter({
|
|
11828
|
-
baseDir:
|
|
12727
|
+
baseDir: import_node_path7.default.dirname(absoluteConfigPath),
|
|
11829
12728
|
baseUrl: creds.baseUrl,
|
|
11830
12729
|
enabled: opts.assetUpload !== false,
|
|
11831
12730
|
upload: async (file) => {
|
|
@@ -11855,7 +12754,7 @@ var pushCommand = new Command("push").description("Push experiment definitions f
|
|
|
11855
12754
|
let body5;
|
|
11856
12755
|
try {
|
|
11857
12756
|
body5 = toApiBody(
|
|
11858
|
-
await rewriteExperimentAssets(exp, assetRewriter,
|
|
12757
|
+
await rewriteExperimentAssets(exp, assetRewriter, import_node_path7.default.dirname(absoluteConfigPath))
|
|
11859
12758
|
);
|
|
11860
12759
|
} catch (error) {
|
|
11861
12760
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -12058,25 +12957,25 @@ var previewCommand = new Command("preview").description(
|
|
|
12058
12957
|
|
|
12059
12958
|
// src/commands/harness.ts
|
|
12060
12959
|
init_source();
|
|
12061
|
-
var
|
|
12062
|
-
var
|
|
12960
|
+
var import_node_fs10 = __toESM(require("node:fs"));
|
|
12961
|
+
var import_node_path8 = __toESM(require("node:path"));
|
|
12063
12962
|
var import_node_child_process6 = require("node:child_process");
|
|
12064
12963
|
function slugify(value, fallback2 = "variation") {
|
|
12065
12964
|
const slug = value.normalize("NFKD").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
12066
12965
|
return slug || fallback2;
|
|
12067
12966
|
}
|
|
12068
12967
|
function readJson(filePath) {
|
|
12069
|
-
if (!
|
|
12070
|
-
return JSON.parse(
|
|
12968
|
+
if (!import_node_fs10.default.existsSync(filePath)) return null;
|
|
12969
|
+
return JSON.parse(import_node_fs10.default.readFileSync(filePath, "utf8"));
|
|
12071
12970
|
}
|
|
12072
12971
|
function resolveFolder(folder) {
|
|
12073
|
-
return
|
|
12972
|
+
return import_node_path8.default.resolve(process.cwd(), folder);
|
|
12074
12973
|
}
|
|
12075
12974
|
function experimentConfigPath(folder) {
|
|
12076
|
-
return
|
|
12975
|
+
return import_node_path8.default.join(folder, "experiment.config.json");
|
|
12077
12976
|
}
|
|
12078
12977
|
function apexConfigPath(folder) {
|
|
12079
|
-
return
|
|
12978
|
+
return import_node_path8.default.join(folder, "apex.harness.json");
|
|
12080
12979
|
}
|
|
12081
12980
|
function loadHarnessConfig(folder) {
|
|
12082
12981
|
const experiment = readJson(experimentConfigPath(folder)) ?? {};
|
|
@@ -12085,8 +12984,8 @@ function loadHarnessConfig(folder) {
|
|
|
12085
12984
|
const targetOrigin = experiment.targetOrigin?.replace(/\/+$/, "") ?? "";
|
|
12086
12985
|
const defaultPath = experiment.defaultPath ?? "/";
|
|
12087
12986
|
const targetUrl = targetOrigin ? new URL(defaultPath, targetOrigin).toString() : "";
|
|
12088
|
-
const id = experiment.id ??
|
|
12089
|
-
const variation =
|
|
12987
|
+
const id = experiment.id ?? import_node_path8.default.basename(import_node_path8.default.dirname(folder));
|
|
12988
|
+
const variation = import_node_path8.default.basename(folder);
|
|
12090
12989
|
return {
|
|
12091
12990
|
experiment,
|
|
12092
12991
|
apex: {
|
|
@@ -12121,37 +13020,37 @@ function runAbHarness(args, cwd) {
|
|
|
12121
13020
|
function artifactPaths(folder, experiment) {
|
|
12122
13021
|
const variant = experiment.variant;
|
|
12123
13022
|
if (variant?.endsWith(".bundle.js")) {
|
|
12124
|
-
const jsPath =
|
|
12125
|
-
const cssPath =
|
|
12126
|
-
return { jsPath, cssPath:
|
|
13023
|
+
const jsPath = import_node_path8.default.join(folder, variant.replace(/\.bundle\.js$/, ".js"));
|
|
13024
|
+
const cssPath = import_node_path8.default.join(folder, variant.replace(/\.bundle\.js$/, ".css"));
|
|
13025
|
+
return { jsPath, cssPath: import_node_fs10.default.existsSync(cssPath) ? cssPath : null };
|
|
12127
13026
|
}
|
|
12128
|
-
const buildDir =
|
|
12129
|
-
const files =
|
|
13027
|
+
const buildDir = import_node_path8.default.join(folder, "build");
|
|
13028
|
+
const files = import_node_fs10.default.existsSync(buildDir) ? import_node_fs10.default.readdirSync(buildDir) : [];
|
|
12130
13029
|
const js = files.find(
|
|
12131
13030
|
(file) => file.endsWith(".js") && !file.endsWith(".bundle.js") && !file.endsWith(".min.js")
|
|
12132
13031
|
);
|
|
12133
13032
|
const css = files.find((file) => file.endsWith(".css") && !file.endsWith(".min.css"));
|
|
12134
13033
|
if (!js) throw new Error("Could not find harness JS artifact in build/");
|
|
12135
13034
|
return {
|
|
12136
|
-
jsPath:
|
|
12137
|
-
cssPath: css ?
|
|
13035
|
+
jsPath: import_node_path8.default.join(buildDir, js),
|
|
13036
|
+
cssPath: css ? import_node_path8.default.join(buildDir, css) : null
|
|
12138
13037
|
};
|
|
12139
13038
|
}
|
|
12140
13039
|
async function uploadVersion(params) {
|
|
12141
13040
|
const creds = requireCredentials();
|
|
12142
13041
|
const { jsPath, cssPath } = artifactPaths(params.folder, params.experiment);
|
|
12143
|
-
if (!
|
|
13042
|
+
if (!import_node_fs10.default.existsSync(jsPath)) throw new Error(`Missing JS artifact: ${jsPath}`);
|
|
12144
13043
|
const form = new FormData();
|
|
12145
13044
|
form.append(
|
|
12146
13045
|
"js",
|
|
12147
|
-
new Blob([
|
|
12148
|
-
|
|
13046
|
+
new Blob([import_node_fs10.default.readFileSync(jsPath)], { type: "application/javascript" }),
|
|
13047
|
+
import_node_path8.default.basename(jsPath)
|
|
12149
13048
|
);
|
|
12150
|
-
if (cssPath &&
|
|
13049
|
+
if (cssPath && import_node_fs10.default.existsSync(cssPath) && import_node_fs10.default.statSync(cssPath).size > 0) {
|
|
12151
13050
|
form.append(
|
|
12152
13051
|
"css",
|
|
12153
|
-
new Blob([
|
|
12154
|
-
|
|
13052
|
+
new Blob([import_node_fs10.default.readFileSync(cssPath)], { type: "text/css" }),
|
|
13053
|
+
import_node_path8.default.basename(cssPath)
|
|
12155
13054
|
);
|
|
12156
13055
|
}
|
|
12157
13056
|
form.append(
|
|
@@ -12160,8 +13059,8 @@ async function uploadVersion(params) {
|
|
|
12160
13059
|
...params.apex,
|
|
12161
13060
|
resetJs: cleanupResetJs(params.experiment),
|
|
12162
13061
|
builtAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12163
|
-
jsPath:
|
|
12164
|
-
cssPath: cssPath ?
|
|
13062
|
+
jsPath: import_node_path8.default.relative(params.folder, jsPath),
|
|
13063
|
+
cssPath: cssPath ? import_node_path8.default.relative(params.folder, cssPath) : null
|
|
12165
13064
|
})
|
|
12166
13065
|
);
|
|
12167
13066
|
const res = await uploadHarnessVersion(creds, params.session.sessionId, form);
|
|
@@ -12202,11 +13101,11 @@ var harnessCommand = new Command("harness").description(
|
|
|
12202
13101
|
harnessCommand.command("init").argument("<folder>", "AB Test Harness experiment folder").option("--force", "Overwrite existing apex.harness.json").action(function action24(folderArg, opts) {
|
|
12203
13102
|
const folder = resolveFolder(folderArg);
|
|
12204
13103
|
const targetPath = apexConfigPath(folder);
|
|
12205
|
-
if (
|
|
13104
|
+
if (import_node_fs10.default.existsSync(targetPath) && !opts.force) {
|
|
12206
13105
|
fail(this, `${targetPath} already exists. Use --force to overwrite.`, EXIT_CODES.conflict);
|
|
12207
13106
|
}
|
|
12208
13107
|
const { apex } = loadHarnessConfig(folder);
|
|
12209
|
-
|
|
13108
|
+
import_node_fs10.default.writeFileSync(targetPath, `${JSON.stringify(apex, null, 2)}
|
|
12210
13109
|
`, "utf8");
|
|
12211
13110
|
output(
|
|
12212
13111
|
this,
|
|
@@ -12272,7 +13171,7 @@ harnessCommand.command("dev").argument("<folder>", "AB Test Harness experiment f
|
|
|
12272
13171
|
}
|
|
12273
13172
|
};
|
|
12274
13173
|
let debounce = null;
|
|
12275
|
-
const watcher =
|
|
13174
|
+
const watcher = import_node_fs10.default.watch(folder, { recursive: true }, (_event, filename) => {
|
|
12276
13175
|
const name = String(filename ?? "");
|
|
12277
13176
|
if (!name || name.startsWith("build/") || name.includes("/build/")) return;
|
|
12278
13177
|
if (debounce) clearTimeout(debounce);
|
|
@@ -12314,9 +13213,9 @@ harnessCommand.command("publish").argument("<folder>", "AB Test Harness experime
|
|
|
12314
13213
|
|
|
12315
13214
|
// src/commands/operator.ts
|
|
12316
13215
|
init_source();
|
|
12317
|
-
var
|
|
12318
|
-
var
|
|
12319
|
-
var
|
|
13216
|
+
var import_node_crypto6 = __toESM(require("node:crypto"));
|
|
13217
|
+
var import_node_fs11 = __toESM(require("node:fs"));
|
|
13218
|
+
var import_node_path9 = __toESM(require("node:path"));
|
|
12320
13219
|
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
13220
|
function workspaceIssueFile(field, evidence) {
|
|
12322
13221
|
if (field === "custom_js" || field === "reset_js") return "src/index.js";
|
|
@@ -12360,7 +13259,7 @@ function formatWorkspaceBlockingIssues(value) {
|
|
|
12360
13259
|
}).join("\n");
|
|
12361
13260
|
}
|
|
12362
13261
|
function sha256Text2(value) {
|
|
12363
|
-
return
|
|
13262
|
+
return import_node_crypto6.default.createHash("sha256").update(value).digest("hex");
|
|
12364
13263
|
}
|
|
12365
13264
|
function byteLength(value) {
|
|
12366
13265
|
return Buffer.byteLength(value, "utf8");
|
|
@@ -12370,40 +13269,60 @@ function slugify2(value, fallback2 = "operator") {
|
|
|
12370
13269
|
return slug || fallback2;
|
|
12371
13270
|
}
|
|
12372
13271
|
function folderPath2(folder) {
|
|
12373
|
-
return
|
|
13272
|
+
return import_node_path9.default.resolve(process.cwd(), folder);
|
|
12374
13273
|
}
|
|
12375
13274
|
function apexDir(folder) {
|
|
12376
|
-
return
|
|
13275
|
+
return import_node_path9.default.join(folder, ".apex");
|
|
12377
13276
|
}
|
|
12378
13277
|
function sessionPath(folder) {
|
|
12379
|
-
return
|
|
13278
|
+
return import_node_path9.default.join(apexDir(folder), "session.json");
|
|
12380
13279
|
}
|
|
12381
13280
|
function readJsonFile(filePath) {
|
|
12382
|
-
if (!
|
|
12383
|
-
return JSON.parse(
|
|
13281
|
+
if (!import_node_fs11.default.existsSync(filePath)) return null;
|
|
13282
|
+
return JSON.parse(import_node_fs11.default.readFileSync(filePath, "utf8"));
|
|
12384
13283
|
}
|
|
12385
13284
|
function writeJsonFile(filePath, value) {
|
|
12386
|
-
|
|
12387
|
-
|
|
13285
|
+
import_node_fs11.default.mkdirSync(import_node_path9.default.dirname(filePath), { recursive: true });
|
|
13286
|
+
import_node_fs11.default.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}
|
|
12388
13287
|
`, "utf8");
|
|
12389
13288
|
}
|
|
12390
13289
|
function readSession(folder) {
|
|
12391
13290
|
const session = readJsonFile(sessionPath(folder));
|
|
12392
13291
|
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."
|
|
13292
|
+
const filesystemMarker = import_node_path9.default.join(apexDir(folder), "experiment-filesystem.json");
|
|
13293
|
+
if (import_node_fs11.default.existsSync(filesystemMarker)) {
|
|
13294
|
+
throw new CliError(
|
|
13295
|
+
"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.",
|
|
13296
|
+
EXIT_CODES.general,
|
|
13297
|
+
{
|
|
13298
|
+
code: "validation_failed",
|
|
13299
|
+
next_steps: [
|
|
13300
|
+
{ command: "apex experiments plan", why: "Use the command for this folder format." },
|
|
13301
|
+
{
|
|
13302
|
+
command: "apex workspace checkout",
|
|
13303
|
+
why: "Create the required folder in a different directory."
|
|
13304
|
+
}
|
|
13305
|
+
]
|
|
13306
|
+
}
|
|
12397
13307
|
);
|
|
12398
13308
|
}
|
|
12399
|
-
throw new
|
|
13309
|
+
throw new CliError(
|
|
13310
|
+
`Missing ${sessionPath(folder)}. Run apex workspace checkout first.`,
|
|
13311
|
+
EXIT_CODES.general,
|
|
13312
|
+
{
|
|
13313
|
+
code: "local_file_error",
|
|
13314
|
+
next_steps: [
|
|
13315
|
+
{ command: "apex workspace checkout", why: "Create the missing local folder metadata." }
|
|
13316
|
+
]
|
|
13317
|
+
}
|
|
13318
|
+
);
|
|
12400
13319
|
}
|
|
12401
13320
|
return session;
|
|
12402
13321
|
}
|
|
12403
13322
|
function writeSession(folder, session) {
|
|
12404
|
-
|
|
12405
|
-
|
|
12406
|
-
|
|
13323
|
+
import_node_fs11.default.mkdirSync(apexDir(folder), { recursive: true });
|
|
13324
|
+
import_node_fs11.default.writeFileSync(
|
|
13325
|
+
import_node_path9.default.join(apexDir(folder), ".gitignore"),
|
|
12407
13326
|
"session.json\nharness-session.json\n",
|
|
12408
13327
|
"utf8"
|
|
12409
13328
|
);
|
|
@@ -12427,15 +13346,15 @@ function listLocalWorkspaceFiles(folder) {
|
|
|
12427
13346
|
"brand.md"
|
|
12428
13347
|
];
|
|
12429
13348
|
function visit2(dir) {
|
|
12430
|
-
if (!
|
|
12431
|
-
for (const entry of
|
|
12432
|
-
const absPath =
|
|
13349
|
+
if (!import_node_fs11.default.existsSync(dir)) return;
|
|
13350
|
+
for (const entry of import_node_fs11.default.readdirSync(dir, { withFileTypes: true })) {
|
|
13351
|
+
const absPath = import_node_path9.default.join(dir, entry.name);
|
|
12433
13352
|
if (entry.isDirectory()) {
|
|
12434
13353
|
visit2(absPath);
|
|
12435
13354
|
continue;
|
|
12436
13355
|
}
|
|
12437
|
-
const relativePath = validateCanonicalPath(
|
|
12438
|
-
const content =
|
|
13356
|
+
const relativePath = validateCanonicalPath(import_node_path9.default.relative(folder, absPath));
|
|
13357
|
+
const content = import_node_fs11.default.readFileSync(absPath, "utf8");
|
|
12439
13358
|
files.push({
|
|
12440
13359
|
path: relativePath,
|
|
12441
13360
|
absPath,
|
|
@@ -12445,11 +13364,11 @@ function listLocalWorkspaceFiles(folder) {
|
|
|
12445
13364
|
});
|
|
12446
13365
|
}
|
|
12447
13366
|
}
|
|
12448
|
-
for (const root of roots) visit2(
|
|
13367
|
+
for (const root of roots) visit2(import_node_path9.default.join(folder, root));
|
|
12449
13368
|
for (const file of topLevel) {
|
|
12450
|
-
const absPath =
|
|
12451
|
-
if (!
|
|
12452
|
-
const content =
|
|
13369
|
+
const absPath = import_node_path9.default.join(folder, file);
|
|
13370
|
+
if (!import_node_fs11.default.existsSync(absPath)) continue;
|
|
13371
|
+
const content = import_node_fs11.default.readFileSync(absPath, "utf8");
|
|
12453
13372
|
files.push({
|
|
12454
13373
|
path: file,
|
|
12455
13374
|
absPath,
|
|
@@ -12473,7 +13392,7 @@ function validateJsonFiles(files) {
|
|
|
12473
13392
|
}
|
|
12474
13393
|
}
|
|
12475
13394
|
function readExperimentConfig(folder) {
|
|
12476
|
-
return readJsonFile(
|
|
13395
|
+
return readJsonFile(import_node_path9.default.join(folder, "experiment.config.json")) ?? {};
|
|
12477
13396
|
}
|
|
12478
13397
|
function resolveVariation(exp, variationArg) {
|
|
12479
13398
|
const variations = exp.variations ?? [];
|
|
@@ -12490,9 +13409,9 @@ function resolveVariation(exp, variationArg) {
|
|
|
12490
13409
|
function writeWorkspaceFiles(folder, workspace) {
|
|
12491
13410
|
for (const file of workspace.files) {
|
|
12492
13411
|
const relativePath = validateCanonicalPath(file.path);
|
|
12493
|
-
const absPath =
|
|
12494
|
-
|
|
12495
|
-
|
|
13412
|
+
const absPath = import_node_path9.default.join(folder, relativePath);
|
|
13413
|
+
import_node_fs11.default.mkdirSync(import_node_path9.default.dirname(absPath), { recursive: true });
|
|
13414
|
+
import_node_fs11.default.writeFileSync(absPath, file.content, "utf8");
|
|
12496
13415
|
}
|
|
12497
13416
|
}
|
|
12498
13417
|
function sessionFromWorkspace(input) {
|
|
@@ -12561,10 +13480,10 @@ async function uploadCurrentArtifactVersion(folder, sessionId) {
|
|
|
12561
13480
|
form.append(
|
|
12562
13481
|
"js",
|
|
12563
13482
|
new Blob([js.content], { type: "application/javascript" }),
|
|
12564
|
-
|
|
13483
|
+
import_node_path9.default.basename(js.path)
|
|
12565
13484
|
);
|
|
12566
13485
|
if (css && css.content.trim()) {
|
|
12567
|
-
form.append("css", new Blob([css.content], { type: "text/css" }),
|
|
13486
|
+
form.append("css", new Blob([css.content], { type: "text/css" }), import_node_path9.default.basename(css.path));
|
|
12568
13487
|
}
|
|
12569
13488
|
form.append("manifest", JSON.stringify(manifest));
|
|
12570
13489
|
const res = await uploadHarnessVersion(creds, sessionId, form);
|
|
@@ -12683,7 +13602,7 @@ operatorCommand.command("checkout").argument("<experiment-id>", "Apex experiment
|
|
|
12683
13602
|
classifyStatus(workspace.status),
|
|
12684
13603
|
workspace.errorDetails
|
|
12685
13604
|
);
|
|
12686
|
-
|
|
13605
|
+
import_node_fs11.default.mkdirSync(folder, { recursive: true });
|
|
12687
13606
|
writeWorkspaceFiles(folder, workspace.data);
|
|
12688
13607
|
writeSession(
|
|
12689
13608
|
folder,
|
|
@@ -12740,6 +13659,7 @@ operatorCommand.command("status").argument("[folder]", "Operator workspace folde
|
|
|
12740
13659
|
}
|
|
12741
13660
|
);
|
|
12742
13661
|
} catch (error) {
|
|
13662
|
+
if (error instanceof CliError) throw error;
|
|
12743
13663
|
fail(this, error instanceof Error ? error.message : String(error));
|
|
12744
13664
|
}
|
|
12745
13665
|
});
|
|
@@ -12749,8 +13669,8 @@ operatorCommand.command("dev").argument("[folder]", "Operator workspace folder",
|
|
|
12749
13669
|
const session = readSession(folder);
|
|
12750
13670
|
const harnessSession = await createExactHarnessSession(folder, session);
|
|
12751
13671
|
const version = await uploadCurrentArtifactVersion(folder, harnessSession.sessionId);
|
|
12752
|
-
|
|
12753
|
-
writeJsonFile(
|
|
13672
|
+
import_node_fs11.default.mkdirSync(apexDir(folder), { recursive: true });
|
|
13673
|
+
writeJsonFile(import_node_path9.default.join(apexDir(folder), "harness-session.json"), {
|
|
12754
13674
|
sessionId: harnessSession.sessionId,
|
|
12755
13675
|
experimentId: harnessSession.experimentId,
|
|
12756
13676
|
variationId: harnessSession.variationId,
|
|
@@ -12805,7 +13725,7 @@ operatorCommand.command("dev").argument("[folder]", "Operator workspace folder",
|
|
|
12805
13725
|
}
|
|
12806
13726
|
};
|
|
12807
13727
|
let timer = null;
|
|
12808
|
-
const watcher =
|
|
13728
|
+
const watcher = import_node_fs11.default.watch(folder, { recursive: true }, (_event, filename) => {
|
|
12809
13729
|
const name = String(filename ?? "");
|
|
12810
13730
|
if (!name || name.startsWith(".apex/") || name.includes("/.apex/")) return;
|
|
12811
13731
|
if (timer) clearTimeout(timer);
|
|
@@ -12840,13 +13760,13 @@ operatorCommand.command("publish").argument("[folder]", "Operator workspace fold
|
|
|
12840
13760
|
|
|
12841
13761
|
// src/commands/roadmap.ts
|
|
12842
13762
|
init_source();
|
|
12843
|
-
var
|
|
12844
|
-
var
|
|
13763
|
+
var import_node_fs12 = __toESM(require("node:fs"));
|
|
13764
|
+
var import_node_path10 = __toESM(require("node:path"));
|
|
12845
13765
|
var LEGACY_OUTPUT_REMOVAL_VERSION = "0.7.0";
|
|
12846
13766
|
function truncate(value, max) {
|
|
12847
|
-
const
|
|
12848
|
-
if (
|
|
12849
|
-
return `${
|
|
13767
|
+
const text2 = typeof value === "string" ? value : value == null ? "" : String(value);
|
|
13768
|
+
if (text2.length <= max) return text2;
|
|
13769
|
+
return `${text2.slice(0, Math.max(0, max - 3))}...`;
|
|
12850
13770
|
}
|
|
12851
13771
|
function score(value) {
|
|
12852
13772
|
if (typeof value !== "number" || !Number.isFinite(value)) return "-";
|
|
@@ -12951,9 +13871,9 @@ var handoff = new Command("handoff").description(
|
|
|
12951
13871
|
`;
|
|
12952
13872
|
const outputData = opts.instructions ? { roadmapItemId: id, instructions: String(payload.instructions ?? "") } : payload;
|
|
12953
13873
|
if (opts.out || opts.output) {
|
|
12954
|
-
const target =
|
|
12955
|
-
|
|
12956
|
-
|
|
13874
|
+
const target = import_node_path10.default.resolve(process.cwd(), opts.out ?? opts.output);
|
|
13875
|
+
import_node_fs12.default.mkdirSync(import_node_path10.default.dirname(target), { recursive: true });
|
|
13876
|
+
import_node_fs12.default.writeFileSync(target, rendered, "utf8");
|
|
12957
13877
|
output(this, { path: target, roadmapItemId: id }, () => printSuccess(`Wrote ${target}`));
|
|
12958
13878
|
return;
|
|
12959
13879
|
}
|
|
@@ -12967,8 +13887,8 @@ roadmapCommand.addCommand(handoff);
|
|
|
12967
13887
|
|
|
12968
13888
|
// src/commands/tracking.ts
|
|
12969
13889
|
init_source();
|
|
12970
|
-
var
|
|
12971
|
-
var
|
|
13890
|
+
var import_node_fs13 = __toESM(require("node:fs"));
|
|
13891
|
+
var import_node_path11 = __toESM(require("node:path"));
|
|
12972
13892
|
function normalizeUrl(input) {
|
|
12973
13893
|
const value = input.trim();
|
|
12974
13894
|
if (!value) throw new Error("URL cannot be empty");
|
|
@@ -13014,17 +13934,17 @@ function commonCookieDomain(hostnames) {
|
|
|
13014
13934
|
return suffix && suffixes.every((candidate) => candidate === suffix) ? `.${suffix}` : null;
|
|
13015
13935
|
}
|
|
13016
13936
|
function defaultManifestPath() {
|
|
13017
|
-
return
|
|
13937
|
+
return import_node_path11.default.resolve(process.cwd(), ".apex", "tracking-manifest.json");
|
|
13018
13938
|
}
|
|
13019
13939
|
function readManifest(filePath) {
|
|
13020
|
-
if (!
|
|
13021
|
-
const parsed = JSON.parse(
|
|
13940
|
+
if (!import_node_fs13.default.existsSync(filePath)) throw new Error(`Manifest not found: ${filePath}`);
|
|
13941
|
+
const parsed = JSON.parse(import_node_fs13.default.readFileSync(filePath, "utf8"));
|
|
13022
13942
|
if (parsed.schemaVersion !== 1) throw new Error("Unsupported tracking manifest schema");
|
|
13023
13943
|
return parsed;
|
|
13024
13944
|
}
|
|
13025
13945
|
function writeManifest(filePath, manifest) {
|
|
13026
|
-
|
|
13027
|
-
|
|
13946
|
+
import_node_fs13.default.mkdirSync(import_node_path11.default.dirname(filePath), { recursive: true });
|
|
13947
|
+
import_node_fs13.default.writeFileSync(filePath, `${JSON.stringify(manifest, null, 2)}
|
|
13028
13948
|
`, "utf8");
|
|
13029
13949
|
}
|
|
13030
13950
|
function buildSnippet(shopId, endpoint) {
|
|
@@ -13091,7 +14011,7 @@ function buildManifest(urlArgs, opts) {
|
|
|
13091
14011
|
};
|
|
13092
14012
|
}
|
|
13093
14013
|
function manifestPath(input) {
|
|
13094
|
-
return
|
|
14014
|
+
return import_node_path11.default.resolve(process.cwd(), input ?? defaultManifestPath());
|
|
13095
14015
|
}
|
|
13096
14016
|
function printManifestPlan(manifest) {
|
|
13097
14017
|
console.log(source_default.bold("Apex tracking ingestion plan"));
|
|
@@ -13181,12 +14101,15 @@ trackingCommand.command("verify").description("Verify live tracking events for t
|
|
|
13181
14101
|
["Last event", totals.last_event_at ?? "-"]
|
|
13182
14102
|
]
|
|
13183
14103
|
);
|
|
14104
|
+
if (res.data.truncated) {
|
|
14105
|
+
console.log(`Showing the newest ${totals.events} events; totals cover this sample only`);
|
|
14106
|
+
}
|
|
13184
14107
|
const eventTypes = Object.entries(totals.event_types);
|
|
13185
14108
|
if (eventTypes.length > 0) {
|
|
13186
14109
|
console.log();
|
|
13187
14110
|
printTable(
|
|
13188
14111
|
["EVENT TYPE", "COUNT"],
|
|
13189
|
-
eventTypes.map(([eventType,
|
|
14112
|
+
eventTypes.map(([eventType, count2]) => [eventType, String(count2)])
|
|
13190
14113
|
);
|
|
13191
14114
|
}
|
|
13192
14115
|
if (res.data.recent_events.length > 0) {
|
|
@@ -13261,10 +14184,180 @@ trackingCommand.command("latency").description("Measure forced-preview versus na
|
|
|
13261
14184
|
});
|
|
13262
14185
|
});
|
|
13263
14186
|
|
|
14187
|
+
// src/commands/events.ts
|
|
14188
|
+
var import_node_fs14 = __toESM(require("node:fs"));
|
|
14189
|
+
var eventsCommand = new Command("events").description("Export raw tracking events");
|
|
14190
|
+
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) => {
|
|
14191
|
+
if (!Number.isFinite(Date.parse(opts.from)) || !Number.isFinite(Date.parse(opts.to))) {
|
|
14192
|
+
fail(command, "--from and --to must be ISO 8601 timestamps", EXIT_CODES.validation);
|
|
14193
|
+
}
|
|
14194
|
+
if (!["exclude", "include", "only"].includes(opts.qa)) {
|
|
14195
|
+
fail(command, "--qa must be exclude, include, or only", EXIT_CODES.validation);
|
|
14196
|
+
}
|
|
14197
|
+
const limit = Number(opts.limit);
|
|
14198
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 1e3) {
|
|
14199
|
+
fail(command, "--limit must be an integer from 1 to 1000", EXIT_CODES.validation);
|
|
14200
|
+
}
|
|
14201
|
+
const summaryOnStdout = Boolean(opts.jsonSummary) || isJsonMode();
|
|
14202
|
+
if (isJsonMode() && !opts.out) {
|
|
14203
|
+
fail(
|
|
14204
|
+
command,
|
|
14205
|
+
"In JSON mode (--json or piped stdout) pass --out <file> for the NDJSON rows; stdout carries one summary document.",
|
|
14206
|
+
EXIT_CODES.validation,
|
|
14207
|
+
{
|
|
14208
|
+
next_steps: [
|
|
14209
|
+
{
|
|
14210
|
+
command: "apex events export --from <iso> --to <iso> --out events.ndjson",
|
|
14211
|
+
why: "Write rows to a file."
|
|
14212
|
+
}
|
|
14213
|
+
]
|
|
14214
|
+
}
|
|
14215
|
+
);
|
|
14216
|
+
}
|
|
14217
|
+
if (opts.jsonSummary && !opts.out) {
|
|
14218
|
+
fail(command, "--json-summary requires --out for the event rows", EXIT_CODES.validation);
|
|
14219
|
+
}
|
|
14220
|
+
const output2 = opts.out ? import_node_fs14.default.createWriteStream(opts.out, { flags: "wx" }) : null;
|
|
14221
|
+
let outputError = null;
|
|
14222
|
+
output2?.on("error", (error) => {
|
|
14223
|
+
outputError = error;
|
|
14224
|
+
});
|
|
14225
|
+
if (output2) {
|
|
14226
|
+
try {
|
|
14227
|
+
await new Promise((resolve2, reject) => {
|
|
14228
|
+
output2.once("open", () => resolve2());
|
|
14229
|
+
output2.once("error", reject);
|
|
14230
|
+
});
|
|
14231
|
+
} catch (error) {
|
|
14232
|
+
fail(
|
|
14233
|
+
command,
|
|
14234
|
+
`Cannot write ${opts.out}: ${error instanceof Error ? error.message : String(error)}`,
|
|
14235
|
+
EXIT_CODES.general,
|
|
14236
|
+
{ code: "local_file_error" }
|
|
14237
|
+
);
|
|
14238
|
+
}
|
|
14239
|
+
}
|
|
14240
|
+
const writeLine = async (line) => {
|
|
14241
|
+
const stream = output2 ?? process.stdout;
|
|
14242
|
+
const accepted = output2 ? output2.write(line) : writeRawCommandOutput(line);
|
|
14243
|
+
if (accepted) return;
|
|
14244
|
+
await new Promise((resolve2, reject) => {
|
|
14245
|
+
const onDrain = () => {
|
|
14246
|
+
stream.off("error", onError);
|
|
14247
|
+
resolve2();
|
|
14248
|
+
};
|
|
14249
|
+
const onError = (error) => {
|
|
14250
|
+
stream.off("drain", onDrain);
|
|
14251
|
+
reject(error);
|
|
14252
|
+
};
|
|
14253
|
+
stream.once("drain", onDrain);
|
|
14254
|
+
stream.once("error", onError);
|
|
14255
|
+
});
|
|
14256
|
+
};
|
|
14257
|
+
let cursor;
|
|
14258
|
+
let written = 0;
|
|
14259
|
+
let pages = 0;
|
|
14260
|
+
let totals;
|
|
14261
|
+
do {
|
|
14262
|
+
const response = await exportEventsPage(requireCredentials(), {
|
|
14263
|
+
from: opts.from,
|
|
14264
|
+
to: opts.to,
|
|
14265
|
+
experiment: opts.experiment,
|
|
14266
|
+
eventTypes: opts.eventType?.split(",").map((value) => value.trim()).filter(Boolean),
|
|
14267
|
+
qa: opts.qa,
|
|
14268
|
+
limit,
|
|
14269
|
+
cursor
|
|
14270
|
+
});
|
|
14271
|
+
if (!response.ok || !response.data) {
|
|
14272
|
+
output2?.destroy();
|
|
14273
|
+
fail(
|
|
14274
|
+
command,
|
|
14275
|
+
response.error ?? "Event export page failed",
|
|
14276
|
+
classifyStatus(response.status),
|
|
14277
|
+
response.errorDetails
|
|
14278
|
+
);
|
|
14279
|
+
}
|
|
14280
|
+
if (outputError)
|
|
14281
|
+
fail(
|
|
14282
|
+
command,
|
|
14283
|
+
`Cannot write ${opts.out}: ${outputError.message}`,
|
|
14284
|
+
EXIT_CODES.general,
|
|
14285
|
+
{
|
|
14286
|
+
code: "local_file_error"
|
|
14287
|
+
}
|
|
14288
|
+
);
|
|
14289
|
+
const page = response.data.data;
|
|
14290
|
+
totals ??= page.totals;
|
|
14291
|
+
for (const event of page.events) {
|
|
14292
|
+
try {
|
|
14293
|
+
await writeLine(`${JSON.stringify(event)}
|
|
14294
|
+
`);
|
|
14295
|
+
} catch (error) {
|
|
14296
|
+
fail(
|
|
14297
|
+
command,
|
|
14298
|
+
`Cannot write ${opts.out ?? "stdout"}: ${error instanceof Error ? error.message : String(error)}`,
|
|
14299
|
+
EXIT_CODES.general,
|
|
14300
|
+
{ code: "local_file_error" }
|
|
14301
|
+
);
|
|
14302
|
+
}
|
|
14303
|
+
written += 1;
|
|
14304
|
+
}
|
|
14305
|
+
pages += 1;
|
|
14306
|
+
cursor = page.next_cursor ?? void 0;
|
|
14307
|
+
} while (cursor);
|
|
14308
|
+
if (output2) {
|
|
14309
|
+
try {
|
|
14310
|
+
await new Promise((resolve2, reject) => {
|
|
14311
|
+
if (outputError) return reject(outputError);
|
|
14312
|
+
output2.once("error", reject);
|
|
14313
|
+
output2.end(resolve2);
|
|
14314
|
+
});
|
|
14315
|
+
} catch (error) {
|
|
14316
|
+
fail(
|
|
14317
|
+
command,
|
|
14318
|
+
`Cannot write ${opts.out}: ${error instanceof Error ? error.message : String(error)}`,
|
|
14319
|
+
EXIT_CODES.general,
|
|
14320
|
+
{ code: "local_file_error" }
|
|
14321
|
+
);
|
|
14322
|
+
}
|
|
14323
|
+
if (outputError)
|
|
14324
|
+
fail(
|
|
14325
|
+
command,
|
|
14326
|
+
`Cannot write ${opts.out}: ${outputError.message}`,
|
|
14327
|
+
EXIT_CODES.general,
|
|
14328
|
+
{
|
|
14329
|
+
code: "local_file_error"
|
|
14330
|
+
}
|
|
14331
|
+
);
|
|
14332
|
+
}
|
|
14333
|
+
const matches = totals ? written === totals.events : false;
|
|
14334
|
+
const summary = {
|
|
14335
|
+
events_written: written,
|
|
14336
|
+
pages,
|
|
14337
|
+
totals: totals ?? null,
|
|
14338
|
+
count_matches: matches
|
|
14339
|
+
};
|
|
14340
|
+
if (!matches)
|
|
14341
|
+
fail(
|
|
14342
|
+
command,
|
|
14343
|
+
"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.",
|
|
14344
|
+
EXIT_CODES.general,
|
|
14345
|
+
{
|
|
14346
|
+
reason: "export_count_mismatch",
|
|
14347
|
+
...summary
|
|
14348
|
+
}
|
|
14349
|
+
);
|
|
14350
|
+
if (summaryOnStdout) writeEnvelope(summary, "apex events export");
|
|
14351
|
+
process.stderr.write(
|
|
14352
|
+
`Exported ${written} events across ${pages} page(s); total ${totals?.events ?? "unknown"}; count matches: ${matches}.
|
|
14353
|
+
`
|
|
14354
|
+
);
|
|
14355
|
+
});
|
|
14356
|
+
|
|
13264
14357
|
// src/commands/goals.ts
|
|
13265
14358
|
init_source();
|
|
13266
|
-
var
|
|
13267
|
-
var
|
|
14359
|
+
var import_node_fs15 = __toESM(require("node:fs"));
|
|
14360
|
+
var import_node_path12 = __toESM(require("node:path"));
|
|
13268
14361
|
|
|
13269
14362
|
// ../sdk/src/safe-regex.ts
|
|
13270
14363
|
var MAX_PATTERN_LENGTH = 1024;
|
|
@@ -13440,9 +14533,9 @@ function printSuggestions(data) {
|
|
|
13440
14533
|
function parseCapture(command, value) {
|
|
13441
14534
|
if (!value) return void 0;
|
|
13442
14535
|
const source = value.startsWith("@") ? value.slice(1) : value;
|
|
13443
|
-
const
|
|
14536
|
+
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
14537
|
try {
|
|
13445
|
-
return JSON.parse(
|
|
14538
|
+
return JSON.parse(text2);
|
|
13446
14539
|
} catch (error) {
|
|
13447
14540
|
const detail5 = error instanceof Error ? error.message : String(error);
|
|
13448
14541
|
fail(command, `Invalid --capture JSON: ${detail5}`, EXIT_CODES.validation);
|
|
@@ -13586,10 +14679,10 @@ var test = new Command("test").description("Dry run a goal URL pattern against t
|
|
|
13586
14679
|
const days = Math.max(1, Number.parseInt(opts.days, 10) || 14);
|
|
13587
14680
|
const dependence = urlPatternDependence(pattern);
|
|
13588
14681
|
if (dependence !== "none") {
|
|
13589
|
-
const
|
|
13590
|
-
output(this, { verdict: "indeterminate", pattern, dependence, reason }, () => {
|
|
14682
|
+
const reason2 = DEPENDENCE_REASON[dependence];
|
|
14683
|
+
output(this, { verdict: "indeterminate", pattern, dependence, reason: reason2 }, () => {
|
|
13591
14684
|
console.log(source_default.yellow("INDETERMINATE"));
|
|
13592
|
-
console.log(
|
|
14685
|
+
console.log(reason2);
|
|
13593
14686
|
});
|
|
13594
14687
|
return;
|
|
13595
14688
|
}
|
|
@@ -13693,12 +14786,12 @@ Examples:
|
|
|
13693
14786
|
|
|
13694
14787
|
// src/commands/funnels.ts
|
|
13695
14788
|
init_source();
|
|
13696
|
-
var
|
|
13697
|
-
var
|
|
14789
|
+
var import_node_fs16 = __toESM(require("node:fs"));
|
|
14790
|
+
var import_node_path13 = __toESM(require("node:path"));
|
|
13698
14791
|
function readSteps(command, file) {
|
|
13699
14792
|
let parsed;
|
|
13700
14793
|
try {
|
|
13701
|
-
parsed = JSON.parse(
|
|
14794
|
+
parsed = JSON.parse(import_node_fs16.default.readFileSync(import_node_path13.default.resolve(process.cwd(), file), "utf8"));
|
|
13702
14795
|
} catch (error) {
|
|
13703
14796
|
fail(
|
|
13704
14797
|
command,
|
|
@@ -14012,8 +15105,8 @@ var off = new Command("off").description("Disable consent gating").action(async
|
|
|
14012
15105
|
var consentCommand = new Command("consent").description("Manage consent gating").addCommand(get2).addCommand(set).addCommand(off);
|
|
14013
15106
|
|
|
14014
15107
|
// src/commands/project-code.ts
|
|
14015
|
-
var
|
|
14016
|
-
var
|
|
15108
|
+
var import_node_fs17 = require("node:fs");
|
|
15109
|
+
var import_node_path14 = require("node:path");
|
|
14017
15110
|
|
|
14018
15111
|
// src/commands/runtime-settings-shop.ts
|
|
14019
15112
|
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 +15151,7 @@ var fields = ["enabled", "css", "helperJs", "js"];
|
|
|
14058
15151
|
function readPatch(command, file) {
|
|
14059
15152
|
let input;
|
|
14060
15153
|
try {
|
|
14061
|
-
input = JSON.parse((0,
|
|
15154
|
+
input = JSON.parse((0, import_node_fs17.readFileSync)(file, "utf8"));
|
|
14062
15155
|
} catch (error) {
|
|
14063
15156
|
fail(command, `Cannot read project code JSON: ${String(error)}`, EXIT_CODES.validation);
|
|
14064
15157
|
}
|
|
@@ -14118,15 +15211,15 @@ var projectCodeCommand = new Command("project-code").description(
|
|
|
14118
15211
|
);
|
|
14119
15212
|
projectCodeCommand.addCommand(
|
|
14120
15213
|
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, ...
|
|
15214
|
+
const { credentials: _credentials, ...identity2 } = await identifyRuntimeSettingsShop(
|
|
14122
15215
|
command,
|
|
14123
15216
|
options.shop
|
|
14124
15217
|
);
|
|
14125
|
-
const current = await readCurrent(command, { ...
|
|
15218
|
+
const current = await readCurrent(command, { ...identity2, credentials: _credentials });
|
|
14126
15219
|
output(
|
|
14127
15220
|
command,
|
|
14128
|
-
{ ...
|
|
14129
|
-
() => process.stdout.write(`${JSON.stringify({ ...
|
|
15221
|
+
{ ...identity2, ...current },
|
|
15222
|
+
() => process.stdout.write(`${JSON.stringify({ ...identity2, ...current }, null, 2)}
|
|
14130
15223
|
`)
|
|
14131
15224
|
);
|
|
14132
15225
|
})
|
|
@@ -14144,11 +15237,11 @@ for (const mode of ["diff", "set"]) {
|
|
|
14144
15237
|
const patch = readPatch(actionCommand, options.file);
|
|
14145
15238
|
const shop = await identifyRuntimeSettingsShop(actionCommand, options.shop);
|
|
14146
15239
|
const current = await readCurrent(actionCommand, shop);
|
|
14147
|
-
const { credentials: _credentials, ...
|
|
15240
|
+
const { credentials: _credentials, ...identity2 } = shop;
|
|
14148
15241
|
const diff = effectiveDiff(current.projectCode, patch);
|
|
14149
15242
|
if (mode === "diff" || diff.changedFields.length === 0) {
|
|
14150
15243
|
const result2 = {
|
|
14151
|
-
...
|
|
15244
|
+
...identity2,
|
|
14152
15245
|
status: diff.changedFields.length ? "changed" : "unchanged",
|
|
14153
15246
|
version: current.version,
|
|
14154
15247
|
diff
|
|
@@ -14161,11 +15254,11 @@ for (const mode of ["diff", "set"]) {
|
|
|
14161
15254
|
);
|
|
14162
15255
|
return;
|
|
14163
15256
|
}
|
|
14164
|
-
const backupPath = (0,
|
|
15257
|
+
const backupPath = (0, import_node_path14.resolve)(options.backup);
|
|
14165
15258
|
try {
|
|
14166
|
-
(0,
|
|
15259
|
+
(0, import_node_fs17.writeFileSync)(
|
|
14167
15260
|
backupPath,
|
|
14168
|
-
`${JSON.stringify({ ...
|
|
15261
|
+
`${JSON.stringify({ ...identity2, ...current, capturedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)}
|
|
14169
15262
|
`,
|
|
14170
15263
|
{ flag: "wx", mode: 384 }
|
|
14171
15264
|
);
|
|
@@ -14195,14 +15288,14 @@ for (const mode of ["diff", "set"]) {
|
|
|
14195
15288
|
`Project code saved; publication ${data.precompile.status}: ${data.precompile.summary}`
|
|
14196
15289
|
);
|
|
14197
15290
|
}
|
|
14198
|
-
if (data.shop_id !==
|
|
15291
|
+
if (data.shop_id !== identity2.shop_id)
|
|
14199
15292
|
warnings.push(
|
|
14200
15293
|
"The write response has a different shop identity; inspect the saved state before another write."
|
|
14201
15294
|
);
|
|
14202
15295
|
for (const warning of warnings) process.stderr.write(`WARNING: ${warning}
|
|
14203
15296
|
`);
|
|
14204
15297
|
const result = {
|
|
14205
|
-
...
|
|
15298
|
+
...identity2,
|
|
14206
15299
|
status: "saved",
|
|
14207
15300
|
diff,
|
|
14208
15301
|
backupPath,
|
|
@@ -14222,12 +15315,12 @@ for (const mode of ["diff", "set"]) {
|
|
|
14222
15315
|
}
|
|
14223
15316
|
|
|
14224
15317
|
// src/commands/triggers.ts
|
|
14225
|
-
var
|
|
14226
|
-
var
|
|
15318
|
+
var import_node_fs18 = __toESM(require("node:fs"));
|
|
15319
|
+
var import_node_path15 = __toESM(require("node:path"));
|
|
14227
15320
|
|
|
14228
15321
|
// src/trigger-proposal.ts
|
|
14229
|
-
function runtimePath(
|
|
14230
|
-
return
|
|
15322
|
+
function runtimePath(path25) {
|
|
15323
|
+
return path25.replaceAll("[]", ".0");
|
|
14231
15324
|
}
|
|
14232
15325
|
function fieldsFrom(value, prefix = "", descriptorList = false) {
|
|
14233
15326
|
if (!value || typeof value !== "object") return [];
|
|
@@ -14240,18 +15333,18 @@ function fieldsFrom(value, prefix = "", descriptorList = false) {
|
|
|
14240
15333
|
)
|
|
14241
15334
|
);
|
|
14242
15335
|
}
|
|
14243
|
-
const
|
|
14244
|
-
const explicitPath = typeof
|
|
15336
|
+
const record3 = value;
|
|
15337
|
+
const explicitPath = typeof record3.path === "string" ? record3.path : void 0;
|
|
14245
15338
|
if (descriptorList && explicitPath) {
|
|
14246
|
-
const rawValues =
|
|
15339
|
+
const rawValues = record3.values ?? record3.samples ?? record3.examples ?? record3.example;
|
|
14247
15340
|
const values = Array.isArray(rawValues) ? rawValues : rawValues === void 0 ? [] : [rawValues];
|
|
14248
|
-
const magnitude = [
|
|
15341
|
+
const magnitude = [record3.mean, record3.average, record3.max, record3.magnitude].find(
|
|
14249
15342
|
(candidate) => typeof candidate === "number"
|
|
14250
15343
|
);
|
|
14251
|
-
const numericType = Array.isArray(
|
|
15344
|
+
const numericType = Array.isArray(record3.types) && record3.types.length > 0 && record3.types.every((type) => type === "num" || type === "numstr" || type === "numstr_comma");
|
|
14252
15345
|
return [{ path: runtimePath(explicitPath), values, magnitude, numericType }];
|
|
14253
15346
|
}
|
|
14254
|
-
return Object.entries(
|
|
15347
|
+
return Object.entries(record3).flatMap(([key, child]) => {
|
|
14255
15348
|
if (["eventName", "event_name", "name", "count", "frequency", "evidence"].includes(key))
|
|
14256
15349
|
return [];
|
|
14257
15350
|
const next = prefix ? `${prefix}.${key}` : key;
|
|
@@ -14284,8 +15377,8 @@ function isDescriptorArray(value) {
|
|
|
14284
15377
|
]);
|
|
14285
15378
|
return Array.isArray(value) && value.length > 0 && value.every((item) => {
|
|
14286
15379
|
if (!item || typeof item !== "object" || Array.isArray(item)) return false;
|
|
14287
|
-
const
|
|
14288
|
-
return typeof
|
|
15380
|
+
const record3 = item;
|
|
15381
|
+
return typeof record3.path === "string" && record3.path.length > 0 && Object.keys(record3).every((key) => descriptorKeys.has(key));
|
|
14289
15382
|
});
|
|
14290
15383
|
}
|
|
14291
15384
|
function candidatesFrom(report) {
|
|
@@ -14305,9 +15398,9 @@ function candidatesFrom(report) {
|
|
|
14305
15398
|
}
|
|
14306
15399
|
return [root];
|
|
14307
15400
|
}
|
|
14308
|
-
function score2(
|
|
15401
|
+
function score2(path25, patterns) {
|
|
14309
15402
|
return patterns.reduce(
|
|
14310
|
-
(total, pattern, index) => total + (pattern.test(
|
|
15403
|
+
(total, pattern, index) => total + (pattern.test(path25) ? patterns.length - index : 0),
|
|
14311
15404
|
0
|
|
14312
15405
|
);
|
|
14313
15406
|
}
|
|
@@ -14441,10 +15534,10 @@ var whenOps = new Set(runtime_settings_trigger_constants_default.whenOps);
|
|
|
14441
15534
|
function strictObject(value, location, keys) {
|
|
14442
15535
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
14443
15536
|
throw new Error(`${location} must be an object`);
|
|
14444
|
-
const
|
|
14445
|
-
for (const key of Object.keys(
|
|
15537
|
+
const record3 = value;
|
|
15538
|
+
for (const key of Object.keys(record3))
|
|
14446
15539
|
if (!keys.has(key)) throw new Error(`${location}.${key} is not allowed`);
|
|
14447
|
-
return
|
|
15540
|
+
return record3;
|
|
14448
15541
|
}
|
|
14449
15542
|
function optionalType(value, type, location) {
|
|
14450
15543
|
if (value !== void 0 && typeof value !== type)
|
|
@@ -14471,8 +15564,8 @@ function strictSource(value, location, allowSignal, required = false) {
|
|
|
14471
15564
|
function defaultMatchesValueType(value, valueType) {
|
|
14472
15565
|
if (valueType === "number") {
|
|
14473
15566
|
if (typeof value !== "string") return Number.isFinite(Number(value));
|
|
14474
|
-
const
|
|
14475
|
-
const normalized = /^-?\d{1,3}(\.\d{3})+,\d+$/.test(
|
|
15567
|
+
const text2 = String(value).trim();
|
|
15568
|
+
const normalized = /^-?\d{1,3}(\.\d{3})+,\d+$/.test(text2) ? text2.replace(/\./g, "").replace(",", ".") : /^-?\d+,\d+$/.test(text2) ? text2.replace(",", ".") : text2;
|
|
14476
15569
|
return Number.isFinite(Number(normalized));
|
|
14477
15570
|
}
|
|
14478
15571
|
if (valueType === "boolean") {
|
|
@@ -14580,19 +15673,19 @@ function strictTriggers(value) {
|
|
|
14580
15673
|
return value.map((item, index) => {
|
|
14581
15674
|
if (!item || typeof item !== "object" || Array.isArray(item))
|
|
14582
15675
|
throw new Error(`trigger[${index}] must be an object`);
|
|
14583
|
-
const
|
|
14584
|
-
for (const key of Object.keys(
|
|
15676
|
+
const record3 = item;
|
|
15677
|
+
for (const key of Object.keys(record3))
|
|
14585
15678
|
if (!allowed.has(key)) throw new Error(`trigger[${index}].${key} is not allowed`);
|
|
14586
|
-
if (typeof
|
|
15679
|
+
if (typeof record3.eventName !== "string" || !record3.eventName.trim())
|
|
14587
15680
|
throw new Error(`trigger[${index}].eventName must be a non-empty string`);
|
|
14588
15681
|
for (const key of ["enabled", "reEvaluate", "trackPageview"])
|
|
14589
|
-
optionalType(
|
|
15682
|
+
optionalType(record3[key], "boolean", `trigger[${index}].${key}`);
|
|
14590
15683
|
for (const key of ["dataLayerName", "trackEventName"])
|
|
14591
|
-
optionalType(
|
|
14592
|
-
strictWhen(
|
|
14593
|
-
if (
|
|
15684
|
+
optionalType(record3[key], "string", `trigger[${index}].${key}`);
|
|
15685
|
+
strictWhen(record3.when, `trigger[${index}].when`);
|
|
15686
|
+
if (record3.trackRevenue !== void 0) {
|
|
14594
15687
|
const revenue = strictObject(
|
|
14595
|
-
|
|
15688
|
+
record3.trackRevenue,
|
|
14596
15689
|
`trigger[${index}].trackRevenue`,
|
|
14597
15690
|
/* @__PURE__ */ new Set([
|
|
14598
15691
|
"enabled",
|
|
@@ -14615,21 +15708,21 @@ function strictTriggers(value) {
|
|
|
14615
15708
|
);
|
|
14616
15709
|
}
|
|
14617
15710
|
}
|
|
14618
|
-
if (
|
|
14619
|
-
if (!Array.isArray(
|
|
15711
|
+
if (record3.mappings !== void 0) {
|
|
15712
|
+
if (!Array.isArray(record3.mappings))
|
|
14620
15713
|
throw new Error(`trigger[${index}].mappings must be an array`);
|
|
14621
|
-
|
|
15714
|
+
record3.mappings.forEach(
|
|
14622
15715
|
(mapping, mappingIndex) => strictMapping(mapping, `trigger[${index}].mappings[${mappingIndex}]`, false)
|
|
14623
15716
|
);
|
|
14624
15717
|
}
|
|
14625
|
-
if (
|
|
14626
|
-
if (!Array.isArray(
|
|
15718
|
+
if (record3.properties !== void 0) {
|
|
15719
|
+
if (!Array.isArray(record3.properties))
|
|
14627
15720
|
throw new Error(`trigger[${index}].properties must be an array`);
|
|
14628
|
-
|
|
15721
|
+
record3.properties.forEach(
|
|
14629
15722
|
(mapping, mappingIndex) => strictMapping(mapping, `trigger[${index}].properties[${mappingIndex}]`, true)
|
|
14630
15723
|
);
|
|
14631
15724
|
}
|
|
14632
|
-
return
|
|
15725
|
+
return record3;
|
|
14633
15726
|
});
|
|
14634
15727
|
}
|
|
14635
15728
|
function writeWarnings(result) {
|
|
@@ -14649,9 +15742,22 @@ async function identify(command, requestedShop) {
|
|
|
14649
15742
|
resolvedContext: { ...credentials.resolvedContext, shopId: void 0 }
|
|
14650
15743
|
};
|
|
14651
15744
|
const context = await getAccountContext(discoveryCredentials);
|
|
14652
|
-
|
|
14653
|
-
|
|
14654
|
-
|
|
15745
|
+
if (!context.ok) {
|
|
15746
|
+
fail(
|
|
15747
|
+
command,
|
|
15748
|
+
`${context.status === 401 || context.status === 403 ? "TRIGGERS_AUTH" : "TRIGGERS_CONFIG_ABSENT"}: ${context.error ?? "account context unavailable"}`,
|
|
15749
|
+
classifyStatus(context.status),
|
|
15750
|
+
context.errorDetails,
|
|
15751
|
+
apiFailureOf(context)
|
|
15752
|
+
);
|
|
15753
|
+
}
|
|
15754
|
+
const match2 = context.data?.data?.orgs?.flatMap((org) => org.shops).find((shop) => shop.domain.toLowerCase() === requestedShop.toLowerCase());
|
|
15755
|
+
if (!match2) {
|
|
15756
|
+
fail(
|
|
15757
|
+
command,
|
|
15758
|
+
"TRIGGERS_CONFIG_ABSENT: --shop domain is not available to this user",
|
|
15759
|
+
EXIT_CODES.validation
|
|
15760
|
+
);
|
|
14655
15761
|
}
|
|
14656
15762
|
credentials = {
|
|
14657
15763
|
...credentials,
|
|
@@ -14660,46 +15766,49 @@ async function identify(command, requestedShop) {
|
|
|
14660
15766
|
}
|
|
14661
15767
|
const res = await getInstallDomains(credentials);
|
|
14662
15768
|
if (!res.ok || !res.data) {
|
|
14663
|
-
const exitCode = res.status === 401 || res.status === 403 ? 4 : 2;
|
|
14664
15769
|
fail(
|
|
14665
15770
|
command,
|
|
14666
|
-
`${
|
|
14667
|
-
|
|
15771
|
+
`${res.status === 401 || res.status === 403 ? "TRIGGERS_AUTH" : "TRIGGERS_CONFIG_ABSENT"}: ${res.error ?? "shop identity unavailable"}`,
|
|
15772
|
+
classifyStatus(res.status)
|
|
14668
15773
|
);
|
|
14669
15774
|
}
|
|
14670
|
-
const
|
|
14671
|
-
if (requestedShop !==
|
|
14672
|
-
fail(
|
|
14673
|
-
|
|
15775
|
+
const identity2 = { shop_id: res.data.shop_id, domain: res.data.domain };
|
|
15776
|
+
if (requestedShop !== identity2.shop_id && requestedShop.toLowerCase() !== identity2.domain.toLowerCase())
|
|
15777
|
+
fail(
|
|
15778
|
+
command,
|
|
15779
|
+
"TRIGGERS_CONFIG_ABSENT: --shop does not match the authenticated shop",
|
|
15780
|
+
EXIT_CODES.validation
|
|
15781
|
+
);
|
|
15782
|
+
return { ...identity2, credentials };
|
|
14674
15783
|
}
|
|
14675
15784
|
async function send(command, requestedShop, body5) {
|
|
14676
|
-
const
|
|
14677
|
-
const res = await updateRuntimeSettings(
|
|
15785
|
+
const identity2 = await identify(command, requestedShop);
|
|
15786
|
+
const res = await updateRuntimeSettings(identity2.credentials, body5);
|
|
14678
15787
|
if (!res.ok || !res.data)
|
|
14679
15788
|
fail(
|
|
14680
15789
|
command,
|
|
14681
15790
|
`${res.status === 401 || res.status === 403 ? "TRIGGERS_AUTH" : "TRIGGERS_CONFIG_ABSENT"}: ${res.error ?? "Runtime settings update failed"}`,
|
|
14682
|
-
res.status
|
|
15791
|
+
classifyStatus(res.status),
|
|
14683
15792
|
res.errorDetails
|
|
14684
15793
|
);
|
|
14685
|
-
const { credentials: _credentials, ...publicIdentity } =
|
|
15794
|
+
const { credentials: _credentials, ...publicIdentity } = identity2;
|
|
14686
15795
|
return { data: res.data, identity: publicIdentity };
|
|
14687
15796
|
}
|
|
14688
15797
|
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(
|
|
15798
|
+
const identity2 = await identify(this, opts.shop);
|
|
15799
|
+
const res = await getRuntimeSettings(identity2.credentials);
|
|
14691
15800
|
if (!res.ok || !res.data)
|
|
14692
15801
|
fail(
|
|
14693
15802
|
this,
|
|
14694
15803
|
`${res.status === 401 || res.status === 403 ? "TRIGGERS_AUTH" : "TRIGGERS_CONFIG_ABSENT"}: ${res.error ?? "Runtime settings read failed"}`,
|
|
14695
|
-
|
|
15804
|
+
classifyStatus(res.status),
|
|
14696
15805
|
res.errorDetails
|
|
14697
15806
|
);
|
|
14698
15807
|
const triggers = res.data.runtime_settings.dataLayerTriggers ?? [];
|
|
14699
|
-
const { credentials: _credentials, ...publicIdentity } =
|
|
15808
|
+
const { credentials: _credentials, ...publicIdentity } = identity2;
|
|
14700
15809
|
output(this, { verdict: "TRIGGERS_GET", ...publicIdentity, triggers }, () => {
|
|
14701
15810
|
process.stdout.write(
|
|
14702
|
-
`TRIGGERS_GET shop_id=${
|
|
15811
|
+
`TRIGGERS_GET shop_id=${identity2.shop_id} domain=${identity2.domain} count=${triggers.length}
|
|
14703
15812
|
`
|
|
14704
15813
|
);
|
|
14705
15814
|
printTable(
|
|
@@ -14716,24 +15825,24 @@ var get3 = new Command("get").description("Get data layer triggers").requiredOpt
|
|
|
14716
15825
|
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
15826
|
let triggers;
|
|
14718
15827
|
try {
|
|
14719
|
-
triggers = strictTriggers(JSON.parse(
|
|
15828
|
+
triggers = strictTriggers(JSON.parse(import_node_fs18.default.readFileSync(import_node_path15.default.resolve(opts.file), "utf8")));
|
|
14720
15829
|
} catch (error) {
|
|
14721
15830
|
fail(
|
|
14722
15831
|
this,
|
|
14723
15832
|
`TRIGGERS_CONFIG_ABSENT: ${error instanceof Error ? error.message : String(error)}`,
|
|
14724
|
-
|
|
15833
|
+
EXIT_CODES.validation
|
|
14725
15834
|
);
|
|
14726
15835
|
}
|
|
14727
|
-
const { data, identity } = await send(this, opts.shop, { dataLayerTriggers: triggers });
|
|
15836
|
+
const { data, identity: identity2 } = await send(this, opts.shop, { dataLayerTriggers: triggers });
|
|
14728
15837
|
const result = {
|
|
14729
15838
|
verdict: "TRIGGERS_SET",
|
|
14730
|
-
...
|
|
15839
|
+
...identity2,
|
|
14731
15840
|
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14732
15841
|
...data
|
|
14733
15842
|
};
|
|
14734
15843
|
output(this, result, () => {
|
|
14735
15844
|
process.stdout.write(
|
|
14736
|
-
`TRIGGERS_SET shop_id=${
|
|
15845
|
+
`TRIGGERS_SET shop_id=${identity2.shop_id} domain=${identity2.domain} updated_at=${result.updated_at} count=${triggers.length}
|
|
14737
15846
|
`
|
|
14738
15847
|
);
|
|
14739
15848
|
writeWarnings(result);
|
|
@@ -14743,17 +15852,21 @@ var propose = new Command("propose").requiredOption("--from-file <u2-report.json
|
|
|
14743
15852
|
let proposals;
|
|
14744
15853
|
let report;
|
|
14745
15854
|
try {
|
|
14746
|
-
report = JSON.parse(
|
|
15855
|
+
report = JSON.parse(import_node_fs18.default.readFileSync(import_node_path15.default.resolve(opts.fromFile), "utf8"));
|
|
14747
15856
|
proposals = proposeTriggers(report);
|
|
14748
15857
|
} catch (error) {
|
|
14749
|
-
fail(this, error instanceof Error ? error.message : String(error),
|
|
15858
|
+
fail(this, error instanceof Error ? error.message : String(error), EXIT_CODES.validation);
|
|
14750
15859
|
}
|
|
14751
15860
|
const reportBody = report.data && typeof report.data === "object" && !Array.isArray(report.data) ? report.data : report;
|
|
14752
15861
|
const reportShop = reportBody.shop ?? reportBody.shop_context ?? {};
|
|
14753
15862
|
const shop_id = String(reportShop.shop_id ?? reportShop.id ?? reportBody.shop_id ?? "");
|
|
14754
15863
|
const domain = String(reportShop.domain ?? reportBody.domain ?? "");
|
|
14755
15864
|
if (!shop_id || !domain || opts.shop !== shop_id && opts.shop.toLowerCase() !== domain.toLowerCase()) {
|
|
14756
|
-
fail(
|
|
15865
|
+
fail(
|
|
15866
|
+
this,
|
|
15867
|
+
"TRIGGERS_CONFIG_ABSENT: --shop must match the report shop_id or domain",
|
|
15868
|
+
EXIT_CODES.validation
|
|
15869
|
+
);
|
|
14757
15870
|
}
|
|
14758
15871
|
if (!proposals.length)
|
|
14759
15872
|
fail(this, "TRIGGERS_NO_DATA: report has no usable purchase candidates", 3);
|
|
@@ -14782,29 +15895,34 @@ var recipe = new Command("apply-recipe").argument("<recipeId>").requiredOption("
|
|
|
14782
15895
|
const params = Object.fromEntries(
|
|
14783
15896
|
opts.param.map((pair) => {
|
|
14784
15897
|
const i = pair.indexOf("=");
|
|
14785
|
-
if (i < 1)
|
|
15898
|
+
if (i < 1)
|
|
15899
|
+
fail(
|
|
15900
|
+
this,
|
|
15901
|
+
`TRIGGERS_CONFIG_ABSENT: invalid --param ${pair}; expected k=v`,
|
|
15902
|
+
EXIT_CODES.validation
|
|
15903
|
+
);
|
|
14786
15904
|
return [pair.slice(0, i), parseParamValue(pair.slice(i + 1))];
|
|
14787
15905
|
})
|
|
14788
15906
|
);
|
|
14789
|
-
const
|
|
14790
|
-
const res = await applyRuntimeSettingsRecipe(
|
|
15907
|
+
const identity2 = await identify(this, opts.shop);
|
|
15908
|
+
const res = await applyRuntimeSettingsRecipe(identity2.credentials, { recipeId, params });
|
|
14791
15909
|
if (!res.ok || !res.data)
|
|
14792
15910
|
fail(
|
|
14793
15911
|
this,
|
|
14794
15912
|
`${res.status === 401 || res.status === 403 ? "TRIGGERS_AUTH" : "TRIGGERS_CONFIG_ABSENT"}: ${res.error ?? "Recipe failed"}`,
|
|
14795
|
-
res.status
|
|
15913
|
+
classifyStatus(res.status),
|
|
14796
15914
|
res.errorDetails
|
|
14797
15915
|
);
|
|
14798
15916
|
const result = {
|
|
14799
15917
|
verdict: "TRIGGERS_APPLIED",
|
|
14800
15918
|
...res.data,
|
|
14801
|
-
shop_id:
|
|
14802
|
-
domain:
|
|
15919
|
+
shop_id: identity2.shop_id,
|
|
15920
|
+
domain: identity2.domain,
|
|
14803
15921
|
applied_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
14804
15922
|
};
|
|
14805
15923
|
output(this, result, () => {
|
|
14806
15924
|
process.stdout.write(
|
|
14807
|
-
`TRIGGERS_APPLIED shop_id=${
|
|
15925
|
+
`TRIGGERS_APPLIED shop_id=${identity2.shop_id} domain=${identity2.domain} applied_at=${result.applied_at} recipe=${recipeId}
|
|
14808
15926
|
`
|
|
14809
15927
|
);
|
|
14810
15928
|
writeWarnings(result);
|
|
@@ -14813,35 +15931,39 @@ var recipe = new Command("apply-recipe").argument("<recipeId>").requiredOption("
|
|
|
14813
15931
|
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
15932
|
const hours = Number(opts.hours);
|
|
14815
15933
|
if (!Number.isFinite(hours) || hours <= 0 || hours > 48)
|
|
14816
|
-
fail(
|
|
15934
|
+
fail(
|
|
15935
|
+
this,
|
|
15936
|
+
"TRIGGERS_CONFIG_ABSENT: --hours must be greater than 0 and at most 48",
|
|
15937
|
+
EXIT_CODES.validation
|
|
15938
|
+
);
|
|
14817
15939
|
const expiresAt = new Date(Date.now() + hours * 36e5).toISOString();
|
|
14818
15940
|
const setupCapture = {
|
|
14819
15941
|
enabled: true,
|
|
14820
15942
|
expiresAt,
|
|
14821
15943
|
...opts.urlPattern.length > 0 ? { urlPatterns: opts.urlPattern } : {}
|
|
14822
15944
|
};
|
|
14823
|
-
const { data, identity } = await send(this, opts.shop, { setupCapture });
|
|
14824
|
-
const result = { verdict: "CAPTURE_ENABLED", ...
|
|
15945
|
+
const { data, identity: identity2 } = await send(this, opts.shop, { setupCapture });
|
|
15946
|
+
const result = { verdict: "CAPTURE_ENABLED", ...identity2, expiresAt, ...data };
|
|
14825
15947
|
output(
|
|
14826
15948
|
this,
|
|
14827
15949
|
result,
|
|
14828
15950
|
() => process.stdout.write(
|
|
14829
|
-
`CAPTURE_ENABLED shop_id=${
|
|
15951
|
+
`CAPTURE_ENABLED shop_id=${identity2.shop_id} domain=${identity2.domain} expires_at=${expiresAt}
|
|
14830
15952
|
`
|
|
14831
15953
|
)
|
|
14832
15954
|
);
|
|
14833
15955
|
});
|
|
14834
15956
|
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, {
|
|
15957
|
+
const { data, identity: identity2 } = await send(this, opts.shop, {
|
|
14836
15958
|
setupCapture: { enabled: false, expiresAt: null }
|
|
14837
15959
|
});
|
|
14838
15960
|
const disabled_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
14839
|
-
const result = { verdict: "CAPTURE_DISABLED", ...
|
|
15961
|
+
const result = { verdict: "CAPTURE_DISABLED", ...identity2, disabled_at, ...data };
|
|
14840
15962
|
output(
|
|
14841
15963
|
this,
|
|
14842
15964
|
result,
|
|
14843
15965
|
() => process.stdout.write(
|
|
14844
|
-
`CAPTURE_DISABLED shop_id=${
|
|
15966
|
+
`CAPTURE_DISABLED shop_id=${identity2.shop_id} domain=${identity2.domain} disabled_at=${disabled_at}
|
|
14845
15967
|
`
|
|
14846
15968
|
)
|
|
14847
15969
|
);
|
|
@@ -15061,8 +16183,8 @@ var qa2 = new Command("qa").description("Check static link/form handover paramet
|
|
|
15061
16183
|
html = await response.text();
|
|
15062
16184
|
} catch (error) {
|
|
15063
16185
|
if (error instanceof CliError) throw error;
|
|
15064
|
-
const
|
|
15065
|
-
fail(this, `Could not fetch ${opts.from}: ${
|
|
16186
|
+
const reason2 = error instanceof Error ? error.message : String(error);
|
|
16187
|
+
fail(this, `Could not fetch ${opts.from}: ${reason2}`, EXIT_CODES.general);
|
|
15066
16188
|
}
|
|
15067
16189
|
const param = config.externalTracking.handoverQueryParam;
|
|
15068
16190
|
const baseUrl = response.url || opts.from;
|
|
@@ -15109,6 +16231,138 @@ var qa2 = new Command("qa").description("Check static link/form handover paramet
|
|
|
15109
16231
|
});
|
|
15110
16232
|
var handoverCommand = new Command("handover").description("Manage cross-domain identity handover").addCommand(get4).addCommand(set4).addCommand(off2).addCommand(qa2);
|
|
15111
16233
|
|
|
16234
|
+
// src/commands/order-journey.ts
|
|
16235
|
+
function instant(value) {
|
|
16236
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d+))?)?(Z|[+-]\d{2}:\d{2})$/.exec(
|
|
16237
|
+
value
|
|
16238
|
+
);
|
|
16239
|
+
if (!m) return null;
|
|
16240
|
+
const year = Number(m[1]), month = Number(m[2]), day = Number(m[3]);
|
|
16241
|
+
const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
|
16242
|
+
const days = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
16243
|
+
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))
|
|
16244
|
+
return null;
|
|
16245
|
+
const second = Date.parse(`${m[1]}-${m[2]}-${m[3]}T${m[4]}:${m[5]}:${m[6] ?? "00"}${m[8]}`);
|
|
16246
|
+
return Number.isFinite(second) ? { second, fraction: (m[7] ?? "").replace(/0+$/, "") } : null;
|
|
16247
|
+
}
|
|
16248
|
+
function compare(a, b) {
|
|
16249
|
+
if (a.second !== b.second) return a.second < b.second ? -1 : 1;
|
|
16250
|
+
const precision = Math.max(a.fraction.length, b.fraction.length);
|
|
16251
|
+
const left = a.fraction.padEnd(precision, "0"), right = b.fraction.padEnd(precision, "0");
|
|
16252
|
+
return left === right ? 0 : left < right ? -1 : 1;
|
|
16253
|
+
}
|
|
16254
|
+
var record = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
16255
|
+
var text = (v) => typeof v === "string" && v.length > 0;
|
|
16256
|
+
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(
|
|
16257
|
+
v
|
|
16258
|
+
));
|
|
16259
|
+
var member = (v, values) => typeof v === "string" && values.includes(v);
|
|
16260
|
+
var status = (v) => member(v, ["pass", "fail", "inconclusive"]);
|
|
16261
|
+
var count = (v) => typeof v === "number" && Number.isSafeInteger(v) && v >= 0;
|
|
16262
|
+
function validReport(v) {
|
|
16263
|
+
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(
|
|
16264
|
+
(s) => record(s) && (s.freshness === void 0 || member(s.freshness, ["current", "stale", "unknown"])) && member(s.stage, [
|
|
16265
|
+
"consent",
|
|
16266
|
+
"assignment",
|
|
16267
|
+
"event",
|
|
16268
|
+
"order_identity",
|
|
16269
|
+
"authoritative_fact",
|
|
16270
|
+
"attribution",
|
|
16271
|
+
"result"
|
|
16272
|
+
]) && status(s.status) && Array.isArray(s.reasonCodes) && s.reasonCodes.every(reason)
|
|
16273
|
+
))
|
|
16274
|
+
return false;
|
|
16275
|
+
if (v.receiptOutcomes !== void 0 && (!Array.isArray(v.receiptOutcomes) || v.receiptOutcomes.length > 6856 || !v.receiptOutcomes.every(
|
|
16276
|
+
(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(
|
|
16277
|
+
(x) => x === null || text(x)
|
|
16278
|
+
) && member(r.freshness, ["current", "stale", "unknown"]) && Array.isArray(r.reasonCodes) && r.reasonCodes.every(reason)
|
|
16279
|
+
)))
|
|
16280
|
+
return false;
|
|
16281
|
+
const coverage = v.assignmentCoverage;
|
|
16282
|
+
if (!record(coverage) || typeof coverage.complete !== "boolean" || typeof coverage.overflowCountIsLowerBound !== "boolean" || !(coverage.overflowCount === null || count(coverage.overflowCount)))
|
|
16283
|
+
return false;
|
|
16284
|
+
return Array.isArray(v.assignments) && v.assignments.length <= 50 && v.assignments.every(
|
|
16285
|
+
(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(
|
|
16286
|
+
(g) => record(g) && text(g.goalId) && member(g.status, [
|
|
16287
|
+
"eligible_for_attributed_goal_read",
|
|
16288
|
+
"excluded_from_attributed_goal_read",
|
|
16289
|
+
"unknown"
|
|
16290
|
+
]) && reason(g.reasonCode)
|
|
16291
|
+
)
|
|
16292
|
+
);
|
|
16293
|
+
}
|
|
16294
|
+
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) {
|
|
16295
|
+
const key = opts.orderId ?? opts.orderNumber;
|
|
16296
|
+
const from = instant(opts.from), to = instant(opts.to);
|
|
16297
|
+
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) {
|
|
16298
|
+
fail(
|
|
16299
|
+
this,
|
|
16300
|
+
"Invalid order journey query: supply exactly one order identifier and a bounded ISO interval",
|
|
16301
|
+
EXIT_CODES.validation
|
|
16302
|
+
);
|
|
16303
|
+
}
|
|
16304
|
+
const result = await getOrderJourney(requireCredentials(), {
|
|
16305
|
+
...opts,
|
|
16306
|
+
orderId: opts.orderId?.trim(),
|
|
16307
|
+
orderNumber: opts.orderNumber?.trim()
|
|
16308
|
+
});
|
|
16309
|
+
if (!result.ok || !result.data)
|
|
16310
|
+
fail(this, result.error ?? "Order journey unavailable", classifyStatus(result.status));
|
|
16311
|
+
const report = result.data.data;
|
|
16312
|
+
if (!validReport(report)) fail(this, "Malformed order journey response", EXIT_CODES.general);
|
|
16313
|
+
output(this, { data: report }, () => {
|
|
16314
|
+
const coverage = report.assignmentCoverage;
|
|
16315
|
+
printTable(
|
|
16316
|
+
["Assignment coverage", "Overflow"],
|
|
16317
|
+
[
|
|
16318
|
+
[
|
|
16319
|
+
coverage.complete ? "complete" : "incomplete",
|
|
16320
|
+
coverage.overflowCount === null ? "unknown" : `${coverage.overflowCount} (${coverage.overflowCountIsLowerBound ? "lower bound" : "exact"})`
|
|
16321
|
+
]
|
|
16322
|
+
]
|
|
16323
|
+
);
|
|
16324
|
+
printTable(
|
|
16325
|
+
["Journey", "Freshness", "Eligibility basis", "Final contribution"],
|
|
16326
|
+
[
|
|
16327
|
+
[
|
|
16328
|
+
report.evaluation.status,
|
|
16329
|
+
report.evaluation.freshness,
|
|
16330
|
+
"attributed_goal_counts",
|
|
16331
|
+
report.finalStatisticalContribution
|
|
16332
|
+
]
|
|
16333
|
+
]
|
|
16334
|
+
);
|
|
16335
|
+
printTable(
|
|
16336
|
+
["Stage", "Status", "Freshness", "Reasons"],
|
|
16337
|
+
report.evaluation.stages.map((s) => [
|
|
16338
|
+
s.stage,
|
|
16339
|
+
s.status,
|
|
16340
|
+
s.freshness ?? "unknown",
|
|
16341
|
+
s.reasonCodes.join(", ")
|
|
16342
|
+
])
|
|
16343
|
+
);
|
|
16344
|
+
if (report.receiptOutcomes?.length)
|
|
16345
|
+
printTable(
|
|
16346
|
+
["Receipt", "Stage", "Experiment / variation / goal", "Freshness"],
|
|
16347
|
+
report.receiptOutcomes.map((r) => [
|
|
16348
|
+
r.receiptId,
|
|
16349
|
+
r.stage,
|
|
16350
|
+
[r.scope.experimentId, r.scope.variationId, r.scope.goalId].filter(Boolean).join(" / ") || "global",
|
|
16351
|
+
r.freshness
|
|
16352
|
+
])
|
|
16353
|
+
);
|
|
16354
|
+
printTable(
|
|
16355
|
+
["Experiment", "Variation", "Attribution", "Goal eligibility"],
|
|
16356
|
+
report.assignments.map((a) => [
|
|
16357
|
+
a.experimentId,
|
|
16358
|
+
a.variationId,
|
|
16359
|
+
a.storedAttribution === null ? "unknown" : a.storedAttribution ? "present" : "not observed",
|
|
16360
|
+
a.goals.length ? a.goals.map((g) => `${g.goalId}: ${g.status} (${g.reasonCode})`).join("; ") : `unknown (${a.reasonCode})`
|
|
16361
|
+
])
|
|
16362
|
+
);
|
|
16363
|
+
});
|
|
16364
|
+
});
|
|
16365
|
+
|
|
15112
16366
|
// src/lib/detect-markers.ts
|
|
15113
16367
|
var DETECTION_MARKERS = [
|
|
15114
16368
|
{
|
|
@@ -15335,11 +16589,11 @@ async function fetchPrimaryHtml(host) {
|
|
|
15335
16589
|
headers: { "user-agent": "apex-cli-doctor/1.0" },
|
|
15336
16590
|
signal: AbortSignal.timeout(PAGE_FETCH_TIMEOUT_MS)
|
|
15337
16591
|
});
|
|
15338
|
-
const
|
|
15339
|
-
if (CHALLENGE_STATUSES.includes(res.status) || /cf-chl|challenge-platform|captcha/i.test(
|
|
16592
|
+
const text2 = await res.text();
|
|
16593
|
+
if (CHALLENGE_STATUSES.includes(res.status) || /cf-chl|challenge-platform|captcha/i.test(text2))
|
|
15340
16594
|
return { protected: true };
|
|
15341
16595
|
if (!res.ok) return { error: `http ${res.status}` };
|
|
15342
|
-
return { html:
|
|
16596
|
+
return { html: text2 };
|
|
15343
16597
|
} catch (error) {
|
|
15344
16598
|
return { error: error instanceof Error ? error.message : String(error) };
|
|
15345
16599
|
}
|
|
@@ -15361,9 +16615,9 @@ function environmentByHost(envs) {
|
|
|
15361
16615
|
}
|
|
15362
16616
|
return map;
|
|
15363
16617
|
}
|
|
15364
|
-
function devPollutionCheck(eventHosts, envs) {
|
|
16618
|
+
function devPollutionCheck(eventHosts, envs, unparsedRows) {
|
|
15365
16619
|
const dev = eventHosts.filter((host) => isDevHost(host));
|
|
15366
|
-
if (!dev.length)
|
|
16620
|
+
if (!dev.length && !unparsedRows.length)
|
|
15367
16621
|
return {
|
|
15368
16622
|
checkId: "dev_pollution",
|
|
15369
16623
|
status: "PASS",
|
|
@@ -15378,6 +16632,13 @@ function devPollutionCheck(eventHosts, envs) {
|
|
|
15378
16632
|
return `${host} matches development host markers and is bound to the production environment`;
|
|
15379
16633
|
return `${host} matches development host markers, sent production-labeled traffic, and is not bound to any environment`;
|
|
15380
16634
|
});
|
|
16635
|
+
if (unparsedRows.length) {
|
|
16636
|
+
const pageviews = unparsedRows.reduce((sum, row) => sum + row.pageviews, 0);
|
|
16637
|
+
const firstPath = unparsedRows[0].path || "(empty)";
|
|
16638
|
+
evidence.push(
|
|
16639
|
+
`${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`
|
|
16640
|
+
);
|
|
16641
|
+
}
|
|
15381
16642
|
const mappable = dev.filter(
|
|
15382
16643
|
(host) => (bindings.get(host) ?? PRODUCTION_ENVIRONMENT_KEY) === PRODUCTION_ENVIRONMENT_KEY
|
|
15383
16644
|
);
|
|
@@ -15393,7 +16654,7 @@ var PURCHASE_CADENCE_WINDOW_MINUTES = PURCHASE_WINDOW_MINUTES * 4;
|
|
|
15393
16654
|
var MIN_CAPTURE_WINDOW_MINUTES = 60;
|
|
15394
16655
|
var DUPLICATE_RATE_THRESHOLD = 0.02;
|
|
15395
16656
|
var MAGNITUDE_TOLERANCE_RATE = 0.1;
|
|
15396
|
-
function
|
|
16657
|
+
function record2(value) {
|
|
15397
16658
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
15398
16659
|
}
|
|
15399
16660
|
function purchasePlane(config) {
|
|
@@ -15401,8 +16662,8 @@ function purchasePlane(config) {
|
|
|
15401
16662
|
const names = [];
|
|
15402
16663
|
const triggers = config.runtime?.dataLayerTriggers ?? config.dataLayerTriggers;
|
|
15403
16664
|
if (Array.isArray(triggers) && triggers.some((trigger) => {
|
|
15404
|
-
const row =
|
|
15405
|
-
const trackRevenue =
|
|
16665
|
+
const row = record2(trigger);
|
|
16666
|
+
const trackRevenue = record2(row?.trackRevenue);
|
|
15406
16667
|
return row?.enabled !== false && trackRevenue !== null && trackRevenue.enabled !== false;
|
|
15407
16668
|
}))
|
|
15408
16669
|
names.push("dataLayer trigger");
|
|
@@ -15444,7 +16705,7 @@ function lineItemSum(value) {
|
|
|
15444
16705
|
if (!Array.isArray(value)) return null;
|
|
15445
16706
|
let sum = 0;
|
|
15446
16707
|
for (const item of value) {
|
|
15447
|
-
const row =
|
|
16708
|
+
const row = record2(item);
|
|
15448
16709
|
const price = numeric(row?.price);
|
|
15449
16710
|
if (price === null) return null;
|
|
15450
16711
|
sum += price * (numeric(row?.quantity) ?? 1);
|
|
@@ -15838,7 +17099,10 @@ async function runDoctor(options = {}) {
|
|
|
15838
17099
|
const rows = inventory.rows;
|
|
15839
17100
|
const inventoryCaveat = inventory.truncated ? [INVENTORY_TRUNCATED_CAVEAT] : [];
|
|
15840
17101
|
const hosts = configuredHosts(domainsRes.data, envsRes.data);
|
|
15841
|
-
const
|
|
17102
|
+
const unparsedRows = rows.filter((row) => row.host.trim() === "");
|
|
17103
|
+
const eventHosts = [
|
|
17104
|
+
...new Set(rows.filter((row) => row.host.trim() !== "").map((row) => row.host.toLowerCase()))
|
|
17105
|
+
];
|
|
15842
17106
|
const checks = [];
|
|
15843
17107
|
const unbound = eventHosts.filter((host) => !hosts.has(host));
|
|
15844
17108
|
checks.push(
|
|
@@ -15859,7 +17123,9 @@ async function runDoctor(options = {}) {
|
|
|
15859
17123
|
]
|
|
15860
17124
|
}
|
|
15861
17125
|
);
|
|
15862
|
-
checks.push(
|
|
17126
|
+
checks.push(
|
|
17127
|
+
withCaveat(devPollutionCheck(eventHosts, envsRes.data, unparsedRows), inventoryCaveat)
|
|
17128
|
+
);
|
|
15863
17129
|
const primary = hostOnly(domainsRes.data.domain) ?? eventHosts[0];
|
|
15864
17130
|
const shopId = resolveShopId(options) ?? domainsRes.data.shop_id;
|
|
15865
17131
|
const workerBase = resolveWorkerBase(options);
|
|
@@ -16019,11 +17285,11 @@ function withCaveat(check, caveat) {
|
|
|
16019
17285
|
}
|
|
16020
17286
|
var DAY_MS = 864e5;
|
|
16021
17287
|
function parseDay(value) {
|
|
16022
|
-
const
|
|
16023
|
-
if (!
|
|
16024
|
-
const parsed = Date.parse(`${
|
|
17288
|
+
const text2 = value?.trim();
|
|
17289
|
+
if (!text2 || !/^\d{4}-\d{2}-\d{2}$/.test(text2)) return null;
|
|
17290
|
+
const parsed = Date.parse(`${text2}T00:00:00.000Z`);
|
|
16025
17291
|
if (Number.isNaN(parsed)) return null;
|
|
16026
|
-
return toDay(parsed) ===
|
|
17292
|
+
return toDay(parsed) === text2 ? parsed : null;
|
|
16027
17293
|
}
|
|
16028
17294
|
function toDay(value) {
|
|
16029
17295
|
return new Date(value).toISOString().slice(0, 10);
|
|
@@ -16263,7 +17529,7 @@ function urlsCommand() {
|
|
|
16263
17529
|
);
|
|
16264
17530
|
});
|
|
16265
17531
|
}
|
|
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(
|
|
17532
|
+
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
17533
|
"after",
|
|
16268
17534
|
`
|
|
16269
17535
|
Examples:
|
|
@@ -16275,7 +17541,7 @@ Examples:
|
|
|
16275
17541
|
);
|
|
16276
17542
|
|
|
16277
17543
|
// src/commands/install.ts
|
|
16278
|
-
var
|
|
17544
|
+
var import_promises4 = require("node:dns/promises");
|
|
16279
17545
|
var import_node_http2 = require("node:http");
|
|
16280
17546
|
var import_node_https = require("node:https");
|
|
16281
17547
|
var import_node_net = require("node:net");
|
|
@@ -16284,6 +17550,23 @@ var DESKTOP_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/53
|
|
|
16284
17550
|
var PRODUCTION_WORKER_BASE_URL2 = "https://events.drip-apex.com";
|
|
16285
17551
|
var UrlPolicyError = class extends Error {
|
|
16286
17552
|
};
|
|
17553
|
+
var IdentityProxyError = class extends Error {
|
|
17554
|
+
constructor(originalUrl, proxyHost) {
|
|
17555
|
+
super(
|
|
17556
|
+
`${originalUrl} redirects to ${proxyHost}, an identity-aware proxy sign-in; the install check cannot reach a storefront behind it`
|
|
17557
|
+
);
|
|
17558
|
+
this.originalUrl = originalUrl;
|
|
17559
|
+
this.proxyHost = proxyHost;
|
|
17560
|
+
}
|
|
17561
|
+
originalUrl;
|
|
17562
|
+
proxyHost;
|
|
17563
|
+
};
|
|
17564
|
+
var IDENTITY_PROXY_HOSTS = ["accounts.google.com", "login.microsoftonline.com"];
|
|
17565
|
+
var IDENTITY_PROXY_SUFFIXES = [".cloudflareaccess.com"];
|
|
17566
|
+
function identityProxyHost(host) {
|
|
17567
|
+
const normalized = normalizeHostname(host);
|
|
17568
|
+
return IDENTITY_PROXY_HOSTS.includes(normalized) || IDENTITY_PROXY_SUFFIXES.some((suffix) => normalized.endsWith(suffix));
|
|
17569
|
+
}
|
|
16287
17570
|
function normalizeHostname(host) {
|
|
16288
17571
|
return host.toLowerCase().replace(/\.$/, "");
|
|
16289
17572
|
}
|
|
@@ -16316,7 +17599,7 @@ var testResolver;
|
|
|
16316
17599
|
var testFetch;
|
|
16317
17600
|
async function resolveHost(hostname) {
|
|
16318
17601
|
const mocked = testResolver?.(hostname) ?? null;
|
|
16319
|
-
const addresses = mocked ?? await (0,
|
|
17602
|
+
const addresses = mocked ?? await (0, import_promises4.lookup)(hostname, { all: true });
|
|
16320
17603
|
return addresses.map((entry) => entry.address);
|
|
16321
17604
|
}
|
|
16322
17605
|
async function validateUrl(raw, allowedHosts) {
|
|
@@ -16427,7 +17710,7 @@ function overLimitMessage(hostname, maxBytes) {
|
|
|
16427
17710
|
return `Response from ${hostname} exceeded the ${maxBytes}-byte cap`;
|
|
16428
17711
|
}
|
|
16429
17712
|
function responseFromIncoming(res, body5, decoded) {
|
|
16430
|
-
const
|
|
17713
|
+
const status3 = res.statusCode ?? 0;
|
|
16431
17714
|
const headers = new Headers();
|
|
16432
17715
|
for (const [key, value] of Object.entries(res.headers)) {
|
|
16433
17716
|
if (Array.isArray(value)) for (const entry of value) headers.append(key, entry);
|
|
@@ -16437,9 +17720,9 @@ function responseFromIncoming(res, body5, decoded) {
|
|
|
16437
17720
|
headers.delete("content-encoding");
|
|
16438
17721
|
headers.delete("content-length");
|
|
16439
17722
|
}
|
|
16440
|
-
const nullBody =
|
|
17723
|
+
const nullBody = status3 === 101 || status3 === 204 || status3 === 205 || status3 === 304;
|
|
16441
17724
|
return new Response(nullBody ? null : body5, {
|
|
16442
|
-
status:
|
|
17725
|
+
status: status3,
|
|
16443
17726
|
statusText: res.statusMessage ?? "",
|
|
16444
17727
|
headers
|
|
16445
17728
|
});
|
|
@@ -16574,7 +17857,10 @@ async function fetchSafe(raw, allowedHosts, limits) {
|
|
|
16574
17857
|
if (![301, 302, 303, 307, 308].includes(result.response.status)) return result;
|
|
16575
17858
|
const location = result.response.headers.get("location");
|
|
16576
17859
|
if (!location) return result;
|
|
16577
|
-
|
|
17860
|
+
const next = new URL(location, target.url);
|
|
17861
|
+
if (identityProxyHost(next.hostname))
|
|
17862
|
+
throw new IdentityProxyError(raw, normalizeHostname(next.hostname));
|
|
17863
|
+
target = await validateUrl(next.toString(), allowedHosts);
|
|
16578
17864
|
}
|
|
16579
17865
|
throw new UrlPolicyError("Redirect limit exceeded (5)");
|
|
16580
17866
|
}
|
|
@@ -16584,7 +17870,7 @@ function detectChallenge(html) {
|
|
|
16584
17870
|
);
|
|
16585
17871
|
}
|
|
16586
17872
|
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,
|
|
17873
|
+
function pageReport(url, shopId, status3, html, workerSnippetPresent, bodyTruncated) {
|
|
16588
17874
|
const challenge = detectChallenge(html);
|
|
16589
17875
|
const shopIdMatches = html.includes(shopId);
|
|
16590
17876
|
const snippetPresent = workerSnippetPresent || LEGACY_SNIPPET_MARKER.test(html) && shopIdMatches;
|
|
@@ -16592,29 +17878,29 @@ function pageReport(url, shopId, status2, html, workerSnippetPresent, bodyTrunca
|
|
|
16592
17878
|
return {
|
|
16593
17879
|
url,
|
|
16594
17880
|
status: "WARN",
|
|
16595
|
-
httpStatus:
|
|
17881
|
+
httpStatus: status3,
|
|
16596
17882
|
snippetPresent,
|
|
16597
17883
|
shopIdMatches,
|
|
16598
17884
|
botChallenge: true,
|
|
16599
17885
|
bodyTruncated,
|
|
16600
17886
|
message: "Bot challenge detected; verify in a real browser."
|
|
16601
17887
|
};
|
|
16602
|
-
if (
|
|
17888
|
+
if (status3 < 200 || status3 >= 300)
|
|
16603
17889
|
return {
|
|
16604
17890
|
url,
|
|
16605
17891
|
status: "FAIL",
|
|
16606
|
-
httpStatus:
|
|
17892
|
+
httpStatus: status3,
|
|
16607
17893
|
snippetPresent,
|
|
16608
17894
|
shopIdMatches,
|
|
16609
17895
|
botChallenge: false,
|
|
16610
17896
|
bodyTruncated,
|
|
16611
|
-
message: `Storefront page returned HTTP ${
|
|
17897
|
+
message: `Storefront page returned HTTP ${status3}; the install cannot be verified from this response.`
|
|
16612
17898
|
};
|
|
16613
17899
|
if (!snippetPresent)
|
|
16614
17900
|
return {
|
|
16615
17901
|
url,
|
|
16616
17902
|
status: "FAIL",
|
|
16617
|
-
httpStatus:
|
|
17903
|
+
httpStatus: status3,
|
|
16618
17904
|
snippetPresent,
|
|
16619
17905
|
shopIdMatches,
|
|
16620
17906
|
botChallenge: false,
|
|
@@ -16625,7 +17911,7 @@ function pageReport(url, shopId, status2, html, workerSnippetPresent, bodyTrunca
|
|
|
16625
17911
|
return {
|
|
16626
17912
|
url,
|
|
16627
17913
|
status: "FAIL",
|
|
16628
|
-
httpStatus:
|
|
17914
|
+
httpStatus: status3,
|
|
16629
17915
|
snippetPresent,
|
|
16630
17916
|
shopIdMatches,
|
|
16631
17917
|
botChallenge: false,
|
|
@@ -16635,7 +17921,7 @@ function pageReport(url, shopId, status2, html, workerSnippetPresent, bodyTrunca
|
|
|
16635
17921
|
return {
|
|
16636
17922
|
url,
|
|
16637
17923
|
status: "PASS",
|
|
16638
|
-
httpStatus:
|
|
17924
|
+
httpStatus: status3,
|
|
16639
17925
|
snippetPresent,
|
|
16640
17926
|
shopIdMatches,
|
|
16641
17927
|
botChallenge: false,
|
|
@@ -16832,6 +18118,18 @@ var installCommand = new Command("install").description("Check Apex storefront i
|
|
|
16832
18118
|
pageReport(url, shopId, response.status, html, derivedWorkerBaseUrl !== null, truncated)
|
|
16833
18119
|
);
|
|
16834
18120
|
} catch (error) {
|
|
18121
|
+
if (error instanceof IdentityProxyError) {
|
|
18122
|
+
pages.push({
|
|
18123
|
+
url,
|
|
18124
|
+
status: "WARN",
|
|
18125
|
+
snippetPresent: false,
|
|
18126
|
+
shopIdMatches: false,
|
|
18127
|
+
botChallenge: false,
|
|
18128
|
+
blockedBy: "identity_proxy",
|
|
18129
|
+
message: `blocked_by_identity_proxy: ${error.message}. Verify from a signed-in browser.`
|
|
18130
|
+
});
|
|
18131
|
+
continue;
|
|
18132
|
+
}
|
|
16835
18133
|
if (error instanceof UrlPolicyError) fail(this, error.message, EXIT_CODES.validation);
|
|
16836
18134
|
if (error instanceof BodyDecodeError) {
|
|
16837
18135
|
pages.push({
|
|
@@ -16939,18 +18237,18 @@ var installCommand = new Command("install").description("Check Apex storefront i
|
|
|
16939
18237
|
|
|
16940
18238
|
// src/commands/flags.ts
|
|
16941
18239
|
init_source();
|
|
16942
|
-
var
|
|
16943
|
-
var
|
|
18240
|
+
var import_node_fs19 = __toESM(require("node:fs"));
|
|
18241
|
+
var import_node_path16 = __toESM(require("node:path"));
|
|
16944
18242
|
|
|
16945
18243
|
// src/edge-status.ts
|
|
16946
18244
|
function getEdgeDeliveryStatus(deliveryType, expectedWorstCaseSeconds = 360) {
|
|
16947
18245
|
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
18246
|
}
|
|
16949
18247
|
function parsePublicationMetadata(value) {
|
|
16950
|
-
const
|
|
18248
|
+
const record3 = value && typeof value === "object" ? value : {};
|
|
16951
18249
|
return {
|
|
16952
|
-
publication_revision: Number.isSafeInteger(
|
|
16953
|
-
generated_at: typeof
|
|
18250
|
+
publication_revision: Number.isSafeInteger(record3.publication_revision) && record3.publication_revision >= 0 ? record3.publication_revision : null,
|
|
18251
|
+
generated_at: typeof record3.generated_at === "string" && !Number.isNaN(Date.parse(record3.generated_at)) ? record3.generated_at : null
|
|
16954
18252
|
};
|
|
16955
18253
|
}
|
|
16956
18254
|
function compareEdgePublication(currentValue, edgeValue, expectedWorstCaseSeconds, flagUpdatedAtValue, confirmedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
@@ -16967,13 +18265,13 @@ function compareEdgePublication(currentValue, edgeValue, expectedWorstCaseSecond
|
|
|
16967
18265
|
if (edge.publication_revision < current.publication_revision) return { ...base, verdict: "propagating", relationship: "edge_behind" };
|
|
16968
18266
|
return { ...base, verdict: "live", relationship: edge.publication_revision > current.publication_revision ? "edge_ahead" : "equal" };
|
|
16969
18267
|
}
|
|
16970
|
-
function formatEdgePropagationStatus(
|
|
18268
|
+
function formatEdgePropagationStatus(status3) {
|
|
16971
18269
|
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 ${
|
|
18270
|
+
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}`;
|
|
18271
|
+
if (status3.verdict === "unknown") return `Unknown \u2014 edge payload predates publication metadata. ${caveat}`;
|
|
18272
|
+
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}`;
|
|
18273
|
+
const anomaly = status3.relationship === "edge_ahead" ? ` (edge is ahead of current revision ${status3.current.publication_revision}; anomalous)` : "";
|
|
18274
|
+
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
18275
|
}
|
|
16978
18276
|
|
|
16979
18277
|
// src/commands/flags.ts
|
|
@@ -16982,9 +18280,9 @@ function body(command, value, opts) {
|
|
|
16982
18280
|
let parsed = {};
|
|
16983
18281
|
if (value) {
|
|
16984
18282
|
const source = value.startsWith("@") ? value.slice(1) : value;
|
|
16985
|
-
const
|
|
18283
|
+
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
18284
|
try {
|
|
16987
|
-
parsed = JSON.parse(
|
|
18285
|
+
parsed = JSON.parse(text2);
|
|
16988
18286
|
} catch (e) {
|
|
16989
18287
|
fail(
|
|
16990
18288
|
command,
|
|
@@ -17032,7 +18330,7 @@ var get5 = new Command("get").description("Get a flag").argument("<id>").action(
|
|
|
17032
18330
|
fail(this, r.error ?? "Failed to get", classifyStatus(r.status), r.errorDetails);
|
|
17033
18331
|
output(this, r.data, () => detail(r.data));
|
|
17034
18332
|
});
|
|
17035
|
-
var
|
|
18333
|
+
var status2 = new Command("status").description("Check whether a flag publication is live at the edge").argument("<id>").action(async function(id) {
|
|
17036
18334
|
const r = await getFlagStatus(requireCredentials(), id);
|
|
17037
18335
|
if (!r.ok || !r.data)
|
|
17038
18336
|
fail(this, r.error ?? "Failed to get flag status", classifyStatus(r.status), r.errorDetails);
|
|
@@ -17103,7 +18401,7 @@ function lifecycle(action74) {
|
|
|
17103
18401
|
output(this, r.data, () => detail(r.data));
|
|
17104
18402
|
});
|
|
17105
18403
|
}
|
|
17106
|
-
var flagsCommand = new Command("flags").description("Manage flags").addCommand(list6).addCommand(get5).addCommand(
|
|
18404
|
+
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
18405
|
"after",
|
|
17108
18406
|
`
|
|
17109
18407
|
Examples:
|
|
@@ -17115,8 +18413,8 @@ Examples:
|
|
|
17115
18413
|
);
|
|
17116
18414
|
|
|
17117
18415
|
// src/commands/backlog.ts
|
|
17118
|
-
var
|
|
17119
|
-
var
|
|
18416
|
+
var import_node_fs20 = __toESM(require("node:fs"));
|
|
18417
|
+
var import_node_path17 = __toESM(require("node:path"));
|
|
17120
18418
|
init_source();
|
|
17121
18419
|
var kind2 = "backlog";
|
|
17122
18420
|
var singular = "backlog item";
|
|
@@ -17124,8 +18422,8 @@ function parse(command, value) {
|
|
|
17124
18422
|
if (!value) return {};
|
|
17125
18423
|
try {
|
|
17126
18424
|
const source = value.startsWith("@") ? value.slice(1) : value;
|
|
17127
|
-
const
|
|
17128
|
-
const body5 = JSON.parse(
|
|
18425
|
+
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;
|
|
18426
|
+
const body5 = JSON.parse(text2);
|
|
17129
18427
|
if (!body5 || typeof body5 !== "object" || Array.isArray(body5))
|
|
17130
18428
|
throw new Error("body must be an object");
|
|
17131
18429
|
return body5;
|
|
@@ -17401,8 +18699,8 @@ var fields2 = new Command("fields").description("Manage the shop's own idea fiel
|
|
|
17401
18699
|
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
18700
|
|
|
17403
18701
|
// src/commands/signals.ts
|
|
17404
|
-
var
|
|
17405
|
-
var
|
|
18702
|
+
var import_node_fs21 = __toESM(require("node:fs"));
|
|
18703
|
+
var import_node_path18 = __toESM(require("node:path"));
|
|
17406
18704
|
init_source();
|
|
17407
18705
|
var kind3 = "signals";
|
|
17408
18706
|
var singular2 = "signal";
|
|
@@ -17410,8 +18708,8 @@ function parse2(command, value) {
|
|
|
17410
18708
|
if (!value) return {};
|
|
17411
18709
|
try {
|
|
17412
18710
|
const source = value.startsWith("@") ? value.slice(1) : value;
|
|
17413
|
-
const
|
|
17414
|
-
const body5 = JSON.parse(
|
|
18711
|
+
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;
|
|
18712
|
+
const body5 = JSON.parse(text2);
|
|
17415
18713
|
if (!body5 || typeof body5 !== "object" || Array.isArray(body5))
|
|
17416
18714
|
throw new Error("body must be an object");
|
|
17417
18715
|
return body5;
|
|
@@ -17492,16 +18790,16 @@ var signalsCommand = new Command("signals").description("Manage signals").addCom
|
|
|
17492
18790
|
|
|
17493
18791
|
// src/commands/personalizations.ts
|
|
17494
18792
|
init_source();
|
|
17495
|
-
var
|
|
17496
|
-
var
|
|
18793
|
+
var import_node_fs22 = __toESM(require("node:fs"));
|
|
18794
|
+
var import_node_path19 = __toESM(require("node:path"));
|
|
17497
18795
|
var kind4 = "personalizations";
|
|
17498
18796
|
function body2(command, value, opts) {
|
|
17499
18797
|
let parsed = {};
|
|
17500
18798
|
if (value) {
|
|
17501
18799
|
const source = value.startsWith("@") ? value.slice(1) : value;
|
|
17502
|
-
const
|
|
18800
|
+
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
18801
|
try {
|
|
17504
|
-
parsed = JSON.parse(
|
|
18802
|
+
parsed = JSON.parse(text2);
|
|
17505
18803
|
} catch (e) {
|
|
17506
18804
|
fail(
|
|
17507
18805
|
command,
|
|
@@ -17581,14 +18879,14 @@ var personalizationsCommand = new Command("personalizations").description("Manag
|
|
|
17581
18879
|
|
|
17582
18880
|
// src/commands/targeting-resources.ts
|
|
17583
18881
|
init_source();
|
|
17584
|
-
var
|
|
17585
|
-
var
|
|
18882
|
+
var import_node_fs23 = __toESM(require("node:fs"));
|
|
18883
|
+
var import_node_path20 = __toESM(require("node:path"));
|
|
17586
18884
|
function body3(command, value) {
|
|
17587
18885
|
if (!value) return {};
|
|
17588
18886
|
const source = value.startsWith("@") ? value.slice(1) : value;
|
|
17589
18887
|
try {
|
|
17590
|
-
const
|
|
17591
|
-
return JSON.parse(
|
|
18888
|
+
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;
|
|
18889
|
+
return JSON.parse(text2);
|
|
17592
18890
|
} catch (error) {
|
|
17593
18891
|
fail(
|
|
17594
18892
|
command,
|
|
@@ -17830,8 +19128,8 @@ var targetingCommand = new Command("targeting").description("Test experiment tar
|
|
|
17830
19128
|
|
|
17831
19129
|
// src/commands/sos.ts
|
|
17832
19130
|
init_source();
|
|
17833
|
-
var
|
|
17834
|
-
var
|
|
19131
|
+
var import_node_fs24 = __toESM(require("node:fs"));
|
|
19132
|
+
var import_node_path21 = __toESM(require("node:path"));
|
|
17835
19133
|
var SOS_TYPES = /* @__PURE__ */ new Set([
|
|
17836
19134
|
"full_handover",
|
|
17837
19135
|
"design",
|
|
@@ -17844,7 +19142,7 @@ var SOS_TYPES = /* @__PURE__ */ new Set([
|
|
|
17844
19142
|
function readText(value) {
|
|
17845
19143
|
if (!value) return void 0;
|
|
17846
19144
|
if (!value.startsWith("@")) return value;
|
|
17847
|
-
return
|
|
19145
|
+
return import_node_fs24.default.readFileSync(import_node_path21.default.resolve(process.cwd(), value.slice(1)), "utf8");
|
|
17848
19146
|
}
|
|
17849
19147
|
function printTicket(ticket, reused) {
|
|
17850
19148
|
console.log();
|
|
@@ -17982,14 +19280,14 @@ var markRead = new Command("mark-read").option("--channel <key>", "Mark one chan
|
|
|
17982
19280
|
var inboxCommand = new Command("inbox").description("Read and send inbox messages").addCommand(list11).addCommand(send2).addCommand(markRead);
|
|
17983
19281
|
|
|
17984
19282
|
// src/commands/shops.ts
|
|
17985
|
-
var
|
|
17986
|
-
var
|
|
19283
|
+
var import_node_fs25 = __toESM(require("node:fs"));
|
|
19284
|
+
var import_node_path22 = __toESM(require("node:path"));
|
|
17987
19285
|
function body4(command, raw) {
|
|
17988
19286
|
if (!raw) return {};
|
|
17989
19287
|
try {
|
|
17990
19288
|
const source = raw.startsWith("@") ? raw.slice(1) : raw;
|
|
17991
|
-
const
|
|
17992
|
-
const value = JSON.parse(
|
|
19289
|
+
const text2 = raw.startsWith("@") || import_node_fs25.default.existsSync(source) ? import_node_fs25.default.readFileSync(import_node_path22.default.resolve(source), "utf8") : raw;
|
|
19290
|
+
const value = JSON.parse(text2);
|
|
17993
19291
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
17994
19292
|
throw new Error("body must be an object");
|
|
17995
19293
|
return value;
|
|
@@ -18370,7 +19668,7 @@ function parseContainer(source, id, dataLayerName = "dataLayer") {
|
|
|
18370
19668
|
vtpPaths,
|
|
18371
19669
|
ecommerceFrom: [...ecommerce][0] ?? null,
|
|
18372
19670
|
serverContainerUrl: [...serverUrls][0] ?? null,
|
|
18373
|
-
piiPaths: vtpPaths.filter((
|
|
19671
|
+
piiPaths: vtpPaths.filter((path25) => /email|phone|address|name/i.test(path25))
|
|
18374
19672
|
}
|
|
18375
19673
|
};
|
|
18376
19674
|
}
|
|
@@ -18439,11 +19737,11 @@ function isContainerField(field) {
|
|
|
18439
19737
|
const types = baseFieldTypes(field);
|
|
18440
19738
|
return types.has("obj") || types.has("arr");
|
|
18441
19739
|
}
|
|
18442
|
-
function hasSyntheticPlaceholder(
|
|
18443
|
-
return
|
|
19740
|
+
function hasSyntheticPlaceholder(path25) {
|
|
19741
|
+
return path25.split(".").map((segment) => segment.replace(/\[\]$/, "")).some((segment) => /^<[^>]+>$/.test(segment));
|
|
18444
19742
|
}
|
|
18445
|
-
function ambiguousDottedPrefix(event,
|
|
18446
|
-
const segments =
|
|
19743
|
+
function ambiguousDottedPrefix(event, path25) {
|
|
19744
|
+
const segments = path25.split(".");
|
|
18447
19745
|
for (let index = 1; index < segments.length; index += 1) {
|
|
18448
19746
|
const prefix = segments.slice(0, index).join(".");
|
|
18449
19747
|
const parent = event.fields.find((field) => field.path === prefix);
|
|
@@ -18452,42 +19750,42 @@ function ambiguousDottedPrefix(event, path24) {
|
|
|
18452
19750
|
return void 0;
|
|
18453
19751
|
}
|
|
18454
19752
|
function dimensionEligibility(event, field) {
|
|
18455
|
-
const
|
|
18456
|
-
if (hasSyntheticPlaceholder(
|
|
19753
|
+
const path25 = field.path;
|
|
19754
|
+
if (hasSyntheticPlaceholder(path25)) {
|
|
18457
19755
|
return {
|
|
18458
19756
|
eligible: false,
|
|
18459
|
-
message: `Path ${JSON.stringify(
|
|
19757
|
+
message: `Path ${JSON.stringify(path25)} is a synthetic placeholder and cannot create a dimension. Choose an observed scalar path.`
|
|
18460
19758
|
};
|
|
18461
19759
|
}
|
|
18462
19760
|
if (isContainerField(field)) {
|
|
18463
19761
|
return {
|
|
18464
19762
|
eligible: false,
|
|
18465
|
-
message: `Path ${JSON.stringify(
|
|
19763
|
+
message: `Path ${JSON.stringify(path25)} is container-typed. Choose an observed scalar path.`
|
|
18466
19764
|
};
|
|
18467
19765
|
}
|
|
18468
|
-
if (
|
|
19766
|
+
if (path25.includes("[]")) {
|
|
18469
19767
|
return {
|
|
18470
19768
|
eligible: false,
|
|
18471
|
-
message: `Path ${JSON.stringify(
|
|
19769
|
+
message: `Path ${JSON.stringify(path25)} includes an array-element segment and cannot create a reliable dimension. Choose a scalar path outside an array.`
|
|
18472
19770
|
};
|
|
18473
19771
|
}
|
|
18474
19772
|
if (!isStableField(field)) {
|
|
18475
19773
|
return {
|
|
18476
19774
|
eligible: false,
|
|
18477
|
-
message: `Path ${JSON.stringify(
|
|
19775
|
+
message: `Path ${JSON.stringify(path25)} does not have a stable observed type. Choose a path without a type collision.`
|
|
18478
19776
|
};
|
|
18479
19777
|
}
|
|
18480
19778
|
if (!event.event) {
|
|
18481
19779
|
return {
|
|
18482
19780
|
eligible: false,
|
|
18483
|
-
message: `Path ${JSON.stringify(
|
|
19781
|
+
message: `Path ${JSON.stringify(path25)} has no observed event name. Choose a path from a named event.`
|
|
18484
19782
|
};
|
|
18485
19783
|
}
|
|
18486
|
-
const ambiguousPrefix = ambiguousDottedPrefix(event,
|
|
19784
|
+
const ambiguousPrefix = ambiguousDottedPrefix(event, path25);
|
|
18487
19785
|
if (ambiguousPrefix) {
|
|
18488
19786
|
return {
|
|
18489
19787
|
eligible: false,
|
|
18490
|
-
message: `Path ${JSON.stringify(
|
|
19788
|
+
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
19789
|
};
|
|
18492
19790
|
}
|
|
18493
19791
|
return { eligible: true };
|
|
@@ -18496,24 +19794,24 @@ function isNumericField(field) {
|
|
|
18496
19794
|
const numericTypes = /* @__PURE__ */ new Set(["num", "numstr", "numstr_comma"]);
|
|
18497
19795
|
return field.types.length > 0 && field.types.every((type) => numericTypes.has(type.split(":")[0]));
|
|
18498
19796
|
}
|
|
18499
|
-
function defaultDimensionLabel(
|
|
18500
|
-
const words =
|
|
18501
|
-
return words ? `${words[0].toUpperCase()}${words.slice(1)}` :
|
|
19797
|
+
function defaultDimensionLabel(path25) {
|
|
19798
|
+
const words = path25.split(/[._-]+/).filter(Boolean).join(" ");
|
|
19799
|
+
return words ? `${words[0].toUpperCase()}${words.slice(1)}` : path25;
|
|
18502
19800
|
}
|
|
18503
19801
|
function quoteShellArgument(value) {
|
|
18504
19802
|
return "'" + value.replaceAll("'", "'\\''") + "'";
|
|
18505
19803
|
}
|
|
18506
|
-
function createDimensionInvocation(
|
|
18507
|
-
return `apex discover --create-dimension ${quoteShellArgument(
|
|
19804
|
+
function createDimensionInvocation(path25, eventName) {
|
|
19805
|
+
return `apex discover --create-dimension ${quoteShellArgument(path25)} --event ${quoteShellArgument(eventName)}`;
|
|
18508
19806
|
}
|
|
18509
|
-
function resolveDimensionObservation(command, report,
|
|
19807
|
+
function resolveDimensionObservation(command, report, path25, eventName) {
|
|
18510
19808
|
const matches = report.data_layer.flatMap(
|
|
18511
|
-
(event) => event.fields.filter((field) => field.path ===
|
|
19809
|
+
(event) => event.fields.filter((field) => field.path === path25).map((field) => ({ event, field }))
|
|
18512
19810
|
);
|
|
18513
19811
|
if (!matches.length) {
|
|
18514
19812
|
fail(
|
|
18515
19813
|
command,
|
|
18516
|
-
`Path ${JSON.stringify(
|
|
19814
|
+
`Path ${JSON.stringify(path25)} was not found in the current discovery report. Run apex discover and copy an observed path exactly.`,
|
|
18517
19815
|
EXIT_CODES.validation
|
|
18518
19816
|
);
|
|
18519
19817
|
}
|
|
@@ -18523,7 +19821,7 @@ function resolveDimensionObservation(command, report, path24, eventName) {
|
|
|
18523
19821
|
if (!eventMatches.length) {
|
|
18524
19822
|
fail(
|
|
18525
19823
|
command,
|
|
18526
|
-
`Path ${JSON.stringify(
|
|
19824
|
+
`Path ${JSON.stringify(path25)} was not observed on event ${JSON.stringify(eventName)}. Run apex discover and copy the event name exactly.`,
|
|
18527
19825
|
EXIT_CODES.validation
|
|
18528
19826
|
);
|
|
18529
19827
|
}
|
|
@@ -18531,7 +19829,7 @@ function resolveDimensionObservation(command, report, path24, eventName) {
|
|
|
18531
19829
|
if (layers.length > 1) {
|
|
18532
19830
|
fail(
|
|
18533
19831
|
command,
|
|
18534
|
-
`Path ${JSON.stringify(
|
|
19832
|
+
`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
19833
|
EXIT_CODES.validation
|
|
18536
19834
|
);
|
|
18537
19835
|
}
|
|
@@ -18543,7 +19841,7 @@ function resolveDimensionObservation(command, report, path24, eventName) {
|
|
|
18543
19841
|
if (namedEvents.length > 1) {
|
|
18544
19842
|
fail(
|
|
18545
19843
|
command,
|
|
18546
|
-
`Path ${JSON.stringify(
|
|
19844
|
+
`Path ${JSON.stringify(path25)} was observed on multiple events: ${namedEvents.join(", ")}. Retry with --event <name>.`,
|
|
18547
19845
|
EXIT_CODES.validation
|
|
18548
19846
|
);
|
|
18549
19847
|
}
|
|
@@ -18552,7 +19850,7 @@ function resolveDimensionObservation(command, report, path24, eventName) {
|
|
|
18552
19850
|
if (layers.length > 1) {
|
|
18553
19851
|
fail(
|
|
18554
19852
|
command,
|
|
18555
|
-
`Path ${JSON.stringify(
|
|
19853
|
+
`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
19854
|
EXIT_CODES.validation
|
|
18557
19855
|
);
|
|
18558
19856
|
}
|
|
@@ -18587,9 +19885,9 @@ function renderDiscovery(report) {
|
|
|
18587
19885
|
`
|
|
18588
19886
|
${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
19887
|
);
|
|
18590
|
-
for (const
|
|
19888
|
+
for (const path25 of event.paths)
|
|
18591
19889
|
lines.push(
|
|
18592
|
-
` URL ${
|
|
19890
|
+
` URL ${path25.path}: ${path25.pageviews} pageviews, ${path25.count} pushes, ${path25.pspReturn_count} PSP returns`
|
|
18593
19891
|
);
|
|
18594
19892
|
for (const field of event.fields) {
|
|
18595
19893
|
lines.push(
|
|
@@ -18767,11 +20065,11 @@ var discoverCommand = new Command("discover").description("Read back the trackin
|
|
|
18767
20065
|
);
|
|
18768
20066
|
}
|
|
18769
20067
|
if (!created.ok || !created.data) {
|
|
18770
|
-
const
|
|
20068
|
+
const reason2 = created.error ?? "The signals API rejected the request";
|
|
18771
20069
|
if (created.status === 409) {
|
|
18772
20070
|
fail(
|
|
18773
20071
|
this,
|
|
18774
|
-
`Dimension was not created: ${
|
|
20072
|
+
`Dimension was not created: ${reason2} Choose another --key or archive an existing dimension in Audience > Attributes, then retry.`,
|
|
18775
20073
|
classifyStatus(created.status),
|
|
18776
20074
|
created.errorDetails
|
|
18777
20075
|
);
|
|
@@ -18779,14 +20077,14 @@ var discoverCommand = new Command("discover").description("Read back the trackin
|
|
|
18779
20077
|
if (created.status === 400) {
|
|
18780
20078
|
fail(
|
|
18781
20079
|
this,
|
|
18782
|
-
`Dimension was not created: ${
|
|
20080
|
+
`Dimension was not created: ${reason2} Check --create-dimension, --event, --key, and --label, then retry.`,
|
|
18783
20081
|
classifyStatus(created.status),
|
|
18784
20082
|
created.errorDetails
|
|
18785
20083
|
);
|
|
18786
20084
|
}
|
|
18787
20085
|
fail(
|
|
18788
20086
|
this,
|
|
18789
|
-
`Dimension was not created: ${
|
|
20087
|
+
`Dimension was not created: ${reason2}`,
|
|
18790
20088
|
classifyStatus(created.status),
|
|
18791
20089
|
created.errorDetails
|
|
18792
20090
|
);
|
|
@@ -18802,16 +20100,16 @@ var discoverCommand = new Command("discover").description("Read back the trackin
|
|
|
18802
20100
|
});
|
|
18803
20101
|
|
|
18804
20102
|
// src/commands/skills.ts
|
|
18805
|
-
var
|
|
18806
|
-
var
|
|
20103
|
+
var import_node_fs26 = __toESM(require("node:fs"));
|
|
20104
|
+
var import_node_path23 = __toESM(require("node:path"));
|
|
18807
20105
|
function packageRoot() {
|
|
18808
20106
|
const candidates = [
|
|
18809
|
-
|
|
18810
|
-
|
|
18811
|
-
|
|
20107
|
+
import_node_path23.default.resolve(__dirname, ".."),
|
|
20108
|
+
import_node_path23.default.resolve(import_node_path23.default.dirname(process.argv[1] ?? process.cwd()), ".."),
|
|
20109
|
+
import_node_path23.default.resolve(process.cwd(), "packages/cli")
|
|
18812
20110
|
];
|
|
18813
20111
|
for (const candidate of candidates) {
|
|
18814
|
-
if (
|
|
20112
|
+
if (import_node_fs26.default.existsSync(import_node_path23.default.join(candidate, "AGENTS.md"))) return candidate;
|
|
18815
20113
|
}
|
|
18816
20114
|
throw new Error(
|
|
18817
20115
|
`Apex CLI package files not found. Looked for AGENTS.md in: ${candidates.join(", ")}`
|
|
@@ -18820,19 +20118,19 @@ function packageRoot() {
|
|
|
18820
20118
|
function skillPaths() {
|
|
18821
20119
|
const root = packageRoot();
|
|
18822
20120
|
return {
|
|
18823
|
-
agents:
|
|
18824
|
-
skill:
|
|
20121
|
+
agents: import_node_path23.default.join(root, "AGENTS.md"),
|
|
20122
|
+
skill: import_node_path23.default.join(root, "skills", "apex-cli", "SKILL.md")
|
|
18825
20123
|
};
|
|
18826
20124
|
}
|
|
18827
20125
|
function installSkill(paths, targetRoot) {
|
|
18828
|
-
const missing = [paths.skill, paths.agents].filter((filePath) => !
|
|
20126
|
+
const missing = [paths.skill, paths.agents].filter((filePath) => !import_node_fs26.default.existsSync(filePath));
|
|
18829
20127
|
if (missing.length > 0) {
|
|
18830
20128
|
throw new Error(`Apex CLI package files not found. Missing: ${missing.join(", ")}`);
|
|
18831
20129
|
}
|
|
18832
|
-
const target =
|
|
18833
|
-
|
|
18834
|
-
|
|
18835
|
-
|
|
20130
|
+
const target = import_node_path23.default.resolve(targetRoot, "apex-cli");
|
|
20131
|
+
import_node_fs26.default.mkdirSync(target, { recursive: true });
|
|
20132
|
+
import_node_fs26.default.copyFileSync(paths.skill, import_node_path23.default.join(target, "SKILL.md"));
|
|
20133
|
+
import_node_fs26.default.copyFileSync(paths.agents, import_node_path23.default.join(target, "AGENTS.md"));
|
|
18836
20134
|
return target;
|
|
18837
20135
|
}
|
|
18838
20136
|
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 +20560,12 @@ async function fetchText(url) {
|
|
|
19262
20560
|
});
|
|
19263
20561
|
return { status: res.status, text: await readResponseText(res), finalUrl: res.url };
|
|
19264
20562
|
}
|
|
19265
|
-
function abs(base,
|
|
19266
|
-
return new URL(
|
|
20563
|
+
function abs(base, path25) {
|
|
20564
|
+
return new URL(path25, base).toString();
|
|
19267
20565
|
}
|
|
19268
|
-
function looksLikeShopifyCart(
|
|
20566
|
+
function looksLikeShopifyCart(text2) {
|
|
19269
20567
|
try {
|
|
19270
|
-
const payload = JSON.parse(
|
|
20568
|
+
const payload = JSON.parse(text2);
|
|
19271
20569
|
return payload !== null && typeof payload === "object" && (typeof payload.token === "string" || payload.token === null) && Array.isArray(payload.items);
|
|
19272
20570
|
} catch {
|
|
19273
20571
|
return false;
|
|
@@ -19283,9 +20581,9 @@ async function collectSignals(url) {
|
|
|
19283
20581
|
const chunks = [`__apex_primary_status_${primary.status}__`, primary.text];
|
|
19284
20582
|
const protectedHit = primary.status === 403 || primary.status === 429 || matchDetectionMarkers(primary.text, "protected").length > 0;
|
|
19285
20583
|
if (!protectedHit) {
|
|
19286
|
-
for (const
|
|
20584
|
+
for (const path25 of ["/cart.js"]) {
|
|
19287
20585
|
try {
|
|
19288
|
-
const secondary = await fetchText(abs(primary.finalUrl || url,
|
|
20586
|
+
const secondary = await fetchText(abs(primary.finalUrl || url, path25));
|
|
19289
20587
|
if (secondary.status >= 200 && secondary.status < 300 && looksLikeShopifyCart(secondary.text)) {
|
|
19290
20588
|
chunks.push("__apex_secondary_shopify_cart_js__", secondary.text.slice(0, 2e3));
|
|
19291
20589
|
}
|
|
@@ -19580,20 +20878,20 @@ var webhooksCommand = new Command("webhooks").description("Manage shop webhooks"
|
|
|
19580
20878
|
|
|
19581
20879
|
// src/commands/simulate.ts
|
|
19582
20880
|
init_source();
|
|
19583
|
-
var
|
|
19584
|
-
var
|
|
20881
|
+
var import_node_fs27 = __toESM(require("node:fs"));
|
|
20882
|
+
var import_node_path24 = __toESM(require("node:path"));
|
|
19585
20883
|
function readJson2(file) {
|
|
19586
|
-
const
|
|
20884
|
+
const text2 = file === "-" ? import_node_fs27.default.readFileSync(0, "utf8") : import_node_fs27.default.readFileSync(import_node_path24.default.resolve(file), "utf8");
|
|
19587
20885
|
try {
|
|
19588
|
-
return JSON.parse(
|
|
20886
|
+
return JSON.parse(text2);
|
|
19589
20887
|
} catch (error) {
|
|
19590
20888
|
throw new Error(
|
|
19591
20889
|
`Invalid JSON in ${file}: ${error instanceof Error ? error.message : String(error)}`
|
|
19592
20890
|
);
|
|
19593
20891
|
}
|
|
19594
20892
|
}
|
|
19595
|
-
function plural(
|
|
19596
|
-
return `${
|
|
20893
|
+
function plural(count2, singular3) {
|
|
20894
|
+
return `${count2} ${singular3}${count2 === 1 ? "" : "s"}`;
|
|
19597
20895
|
}
|
|
19598
20896
|
function isGtagEventTuple(value) {
|
|
19599
20897
|
return Array.isArray(value) && value[0] === "event" && typeof value[1] === "string";
|
|
@@ -19705,8 +21003,8 @@ var simulateCommand = new Command("simulate").description("Simulate data-layer t
|
|
|
19705
21003
|
});
|
|
19706
21004
|
|
|
19707
21005
|
// src/commands/config-resources.ts
|
|
19708
|
-
var
|
|
19709
|
-
var
|
|
21006
|
+
var import_node_fs28 = __toESM(require("node:fs"));
|
|
21007
|
+
var import_node_path25 = __toESM(require("node:path"));
|
|
19710
21008
|
function show(resource) {
|
|
19711
21009
|
process.stdout.write(`${JSON.stringify(resource, null, 2)}
|
|
19712
21010
|
`);
|
|
@@ -19772,12 +21070,12 @@ function readFamily(family, label) {
|
|
|
19772
21070
|
}
|
|
19773
21071
|
var assetReads = readFamily("assets", "assets");
|
|
19774
21072
|
var upload = new Command("upload").description("Upload an asset").argument("<file>").action(async function(file) {
|
|
19775
|
-
const absolute =
|
|
19776
|
-
if (!
|
|
19777
|
-
const fileStat =
|
|
21073
|
+
const absolute = import_node_path25.default.resolve(process.cwd(), file);
|
|
21074
|
+
if (!import_node_fs28.default.existsSync(absolute)) fail(this, `File not found: ${file}`, 3);
|
|
21075
|
+
const fileStat = import_node_fs28.default.statSync(absolute);
|
|
19778
21076
|
if (!fileStat.isFile()) fail(this, `File not found: ${file}`, 3);
|
|
19779
21077
|
if (fileStat.size > MAX_ASSET_SIZE_BYTES) fail(this, `${file} is larger than 10 MB`, 3);
|
|
19780
|
-
const mimeType = ASSET_MIME_TYPES[
|
|
21078
|
+
const mimeType = ASSET_MIME_TYPES[import_node_path25.default.extname(absolute).toLowerCase()];
|
|
19781
21079
|
if (!mimeType)
|
|
19782
21080
|
fail(
|
|
19783
21081
|
this,
|
|
@@ -19786,8 +21084,8 @@ var upload = new Command("upload").description("Upload an asset").argument("<fil
|
|
|
19786
21084
|
);
|
|
19787
21085
|
const r = await uploadAssets(requireCredentials(), [
|
|
19788
21086
|
{
|
|
19789
|
-
name:
|
|
19790
|
-
bytes:
|
|
21087
|
+
name: import_node_path25.default.basename(absolute),
|
|
21088
|
+
bytes: import_node_fs28.default.readFileSync(absolute),
|
|
19791
21089
|
type: mimeType
|
|
19792
21090
|
}
|
|
19793
21091
|
]);
|
|
@@ -19872,6 +21170,7 @@ program2.addCommand(roadmapCommand);
|
|
|
19872
21170
|
program2.addCommand(harnessCommand);
|
|
19873
21171
|
program2.addCommand(operatorCommand);
|
|
19874
21172
|
program2.addCommand(trackingCommand);
|
|
21173
|
+
program2.addCommand(eventsCommand);
|
|
19875
21174
|
program2.addCommand(goalsCommand);
|
|
19876
21175
|
program2.addCommand(funnelsCommand);
|
|
19877
21176
|
program2.addCommand(environmentsCommand);
|
|
@@ -19913,10 +21212,18 @@ try {
|
|
|
19913
21212
|
} catch {
|
|
19914
21213
|
}
|
|
19915
21214
|
program2.hook("preAction", (_thisCommand, actionCommand) => {
|
|
19916
|
-
|
|
21215
|
+
try {
|
|
21216
|
+
configureInvocationContext();
|
|
21217
|
+
} catch (error) {
|
|
21218
|
+
beginInvocation(actionCommand);
|
|
21219
|
+
throw error;
|
|
21220
|
+
}
|
|
19917
21221
|
configureRuntime(actionCommand);
|
|
21222
|
+
beginInvocation(actionCommand, resolveContext());
|
|
19918
21223
|
});
|
|
19919
21224
|
process.on("beforeExit", () => {
|
|
19920
21225
|
flushFallbackEnvelope();
|
|
19921
21226
|
});
|
|
19922
|
-
|
|
21227
|
+
maybeCheckForUpdate().then(() => program2.parseAsync(process.argv)).then(async () => {
|
|
21228
|
+
await finishInvocation({ exitCode: process.exitCode ?? 0 });
|
|
21229
|
+
}).catch((err) => handleCliError(err, program2));
|