@drip-apex/apex-cli 0.15.3 → 0.15.4
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 +16 -10
- package/README.md +45 -23
- package/dist/cli.js +1363 -347
- package/package.json +1 -1
- package/skills/apex-cli/SKILL.md +5 -0
package/dist/cli.js
CHANGED
|
@@ -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_promises, 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_promises = __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_promises.default.access(configFilePath, import_promises.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_promises.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,7 +6823,7 @@ function detectPlatformBinary({ [platform]: platformBinary }, { wsl }) {
|
|
|
6823
6823
|
}
|
|
6824
6824
|
return detectArchBinary(platformBinary);
|
|
6825
6825
|
}
|
|
6826
|
-
var import_node_process13, import_node_buffer, import_node_path4, import_node_url, import_node_util5, import_node_child_process5,
|
|
6826
|
+
var import_node_process13, import_node_buffer, import_node_path4, import_node_url, import_node_util5, import_node_child_process5, import_promises2, 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);
|
|
@@ -6832,7 +6832,7 @@ var init_open = __esm({
|
|
|
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_promises2 = __toESM(require("node:fs/promises"), 1);
|
|
6836
6836
|
init_wsl_utils();
|
|
6837
6837
|
init_define_lazy_prop();
|
|
6838
6838
|
init_default_browser();
|
|
@@ -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_promises2.default.access(localXdgOpenPath, import_promises2.constants.X_OK);
|
|
6969
6969
|
exeLocalXdgOpen = true;
|
|
6970
6970
|
} catch {
|
|
6971
6971
|
}
|
|
@@ -7085,6 +7085,9 @@ var {
|
|
|
7085
7085
|
Help
|
|
7086
7086
|
} = import_index.default;
|
|
7087
7087
|
|
|
7088
|
+
// src/telemetry.ts
|
|
7089
|
+
var import_node_crypto2 = require("node:crypto");
|
|
7090
|
+
|
|
7088
7091
|
// ../api-client/src/client-identity.ts
|
|
7089
7092
|
var import_node_crypto = require("node:crypto");
|
|
7090
7093
|
var invocationId = (0, import_node_crypto.randomUUID)();
|
|
@@ -7137,18 +7140,25 @@ function createHttpClient(creds, options) {
|
|
|
7137
7140
|
warnedAboutVersionSkew = true;
|
|
7138
7141
|
warn3(version > 1 ? "CLI is behind the server \u2014 update.\n" : "Server API is older than this CLI supports.\n");
|
|
7139
7142
|
}
|
|
7140
|
-
async function request2(method, path26, body5, requestTimeoutMs, form = false) {
|
|
7143
|
+
async function request2(method, path26, body5, requestTimeoutMs, form = false, requestOptions) {
|
|
7141
7144
|
const started2 = performance.now();
|
|
7142
|
-
const observe = (status4, requestId2, errorCode) => {
|
|
7145
|
+
const observe = (status4, requestId2, errorCode, responseHeaders) => {
|
|
7146
|
+
const semver = (name) => {
|
|
7147
|
+
const value = responseHeaders?.get(name) ?? void 0;
|
|
7148
|
+
return value && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(value) ? value : void 0;
|
|
7149
|
+
};
|
|
7150
|
+
const serverTiming = responseHeaders?.get("server-timing");
|
|
7143
7151
|
try {
|
|
7144
|
-
options.onResponse?.({ method, path: path26, status: status4, requestId: requestId2, errorCode: typeof errorCode === "string" ? errorCode : void 0, durationMs: Math.max(0, Math.round(performance.now() - started2)) });
|
|
7152
|
+
options.onResponse?.({ method, path: path26, status: status4, requestId: requestId2, errorCode: typeof errorCode === "string" ? errorCode : void 0, durationMs: Math.max(0, Math.round(performance.now() - started2)), serverTiming: serverTiming?.slice(0, 300) || void 0, cliLatest: semver("x-apex-cli-latest"), cliMinimum: semver("x-apex-cli-minimum") });
|
|
7145
7153
|
} catch {
|
|
7146
7154
|
}
|
|
7147
7155
|
};
|
|
7148
7156
|
const fallback2 = form ? 12e4 : 3e4;
|
|
7157
|
+
const hasConfiguredTimeout = process.env.APEX_TIMEOUT_MS !== void 0;
|
|
7149
7158
|
const configured = Number(process.env.APEX_TIMEOUT_MS ?? fallback2);
|
|
7150
7159
|
const defaultMs = Number.isFinite(configured) && configured > 0 ? configured : fallback2;
|
|
7151
|
-
const
|
|
7160
|
+
const minimumMs = !hasConfiguredTimeout && requestOptions?.minTimeoutMs !== void 0 ? Math.max(defaultMs, requestOptions.minTimeoutMs) : defaultMs;
|
|
7161
|
+
const timeoutMs = requestTimeoutMs === void 0 ? minimumMs : Math.max(1, Math.min(minimumMs, requestTimeoutMs));
|
|
7152
7162
|
const headers = { ...clientIdentityHeaders(), Authorization: `Bearer ${creds.apiKey}` };
|
|
7153
7163
|
if (!form) headers["Content-Type"] = "application/json";
|
|
7154
7164
|
if (creds.credentialKind === "user" || creds.apiKey.startsWith("apx_uk_")) {
|
|
@@ -7168,6 +7178,7 @@ function createHttpClient(creds, options) {
|
|
|
7168
7178
|
} catch (error) {
|
|
7169
7179
|
const outer = error && typeof error === "object" ? error : void 0;
|
|
7170
7180
|
const nested = outer?.cause && typeof outer.cause === "object" ? outer.cause : void 0;
|
|
7181
|
+
if (options.observeNetworkErrors) observe(0);
|
|
7171
7182
|
return options.onNetworkError({
|
|
7172
7183
|
baseUrl: creds.baseUrl,
|
|
7173
7184
|
timeoutMs,
|
|
@@ -7180,11 +7191,11 @@ function createHttpClient(creds, options) {
|
|
|
7180
7191
|
inspectVersion(response.headers);
|
|
7181
7192
|
const json = await response.json().catch(() => null);
|
|
7182
7193
|
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);
|
|
7194
|
+
observe(response.status, requestId, extractErrorDetails(json)?.code, response.headers);
|
|
7184
7195
|
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
7196
|
return { ok: true, status: response.status, data: json };
|
|
7186
7197
|
}
|
|
7187
|
-
return { request: (method, path26, body5, timeoutMs) => request2(method, path26, body5, timeoutMs), requestForm: (method, path26, body5) => request2(method, path26, body5, void 0, true) };
|
|
7198
|
+
return { request: (method, path26, body5, timeoutMs, requestOptions) => request2(method, path26, body5, timeoutMs, false, requestOptions), requestForm: (method, path26, body5) => request2(method, path26, body5, void 0, true) };
|
|
7188
7199
|
}
|
|
7189
7200
|
|
|
7190
7201
|
// ../api-client/src/credentials.ts
|
|
@@ -7233,90 +7244,177 @@ var import_node_os4 = __toESM(require("node:os"));
|
|
|
7233
7244
|
|
|
7234
7245
|
// src/update.ts
|
|
7235
7246
|
var import_node_fs2 = require("node:fs");
|
|
7236
|
-
var import_promises = require("node:fs/promises");
|
|
7237
7247
|
var import_node_os2 = require("node:os");
|
|
7238
7248
|
var import_node_path2 = __toESM(require("node:path"));
|
|
7249
|
+
var UPGRADE_COMMAND = "npm install -g @drip-apex/apex-cli@latest";
|
|
7250
|
+
var NOTES_URL = "https://docs.drip-apex.com/docs/cli/commands-output";
|
|
7239
7251
|
var TTL = 24 * 60 * 60 * 1e3;
|
|
7240
7252
|
var FAILURE_TTL = 60 * 60 * 1e3;
|
|
7253
|
+
var SERVER_TTL = 7 * 24 * 60 * 60 * 1e3;
|
|
7254
|
+
var cachePath = () => import_node_path2.default.join((0, import_node_os2.homedir)(), ".drip", "update-check.json");
|
|
7255
|
+
var npmLatest;
|
|
7256
|
+
var serverLatest;
|
|
7257
|
+
var serverMinimum;
|
|
7258
|
+
var VERSION_PATTERN = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/;
|
|
7241
7259
|
function versionParts(version) {
|
|
7242
|
-
|
|
7260
|
+
const match2 = VERSION_PATTERN.exec(version);
|
|
7261
|
+
return match2 ? match2.slice(1, 4).map(Number) : void 0;
|
|
7262
|
+
}
|
|
7263
|
+
function prereleaseOf(version) {
|
|
7264
|
+
return VERSION_PATTERN.exec(version)?.[4]?.split(".") ?? [];
|
|
7265
|
+
}
|
|
7266
|
+
function comparePrerelease(left, right) {
|
|
7267
|
+
if (!left.length || !right.length) return right.length - left.length;
|
|
7268
|
+
for (let index = 0; index < Math.min(left.length, right.length); index++) {
|
|
7269
|
+
const a = left[index];
|
|
7270
|
+
const b = right[index];
|
|
7271
|
+
if (a === b) continue;
|
|
7272
|
+
const aNumeric = /^\d+$/.test(a);
|
|
7273
|
+
const bNumeric = /^\d+$/.test(b);
|
|
7274
|
+
if (aNumeric && bNumeric) return Number(a) - Number(b);
|
|
7275
|
+
if (aNumeric !== bNumeric) return aNumeric ? -1 : 1;
|
|
7276
|
+
return a < b ? -1 : 1;
|
|
7277
|
+
}
|
|
7278
|
+
return left.length - right.length;
|
|
7279
|
+
}
|
|
7280
|
+
function compareVersions(left, right) {
|
|
7281
|
+
const a = versionParts(left);
|
|
7282
|
+
const b = versionParts(right);
|
|
7283
|
+
if (!a || !b) return 0;
|
|
7284
|
+
for (let index = 0; index < 3; index++) if (a[index] !== b[index]) return a[index] - b[index];
|
|
7285
|
+
return comparePrerelease(prereleaseOf(left), prereleaseOf(right));
|
|
7286
|
+
}
|
|
7287
|
+
function readCache() {
|
|
7288
|
+
try {
|
|
7289
|
+
const value = JSON.parse((0, import_node_fs2.readFileSync)(cachePath(), "utf8"));
|
|
7290
|
+
return value && typeof value === "object" ? value : {};
|
|
7291
|
+
} catch {
|
|
7292
|
+
return {};
|
|
7293
|
+
}
|
|
7294
|
+
}
|
|
7295
|
+
function writeCache(patch, remove5 = []) {
|
|
7296
|
+
try {
|
|
7297
|
+
const value = { ...readCache(), ...patch };
|
|
7298
|
+
for (const key of remove5) delete value[key];
|
|
7299
|
+
(0, import_node_fs2.mkdirSync)(import_node_path2.default.dirname(cachePath()), { recursive: true });
|
|
7300
|
+
(0, import_node_fs2.writeFileSync)(cachePath(), JSON.stringify(value), { mode: 384 });
|
|
7301
|
+
} catch {
|
|
7302
|
+
}
|
|
7303
|
+
}
|
|
7304
|
+
function serverFresh(checkedAt) {
|
|
7305
|
+
if (typeof checkedAt !== "number" || !Number.isFinite(checkedAt)) return false;
|
|
7306
|
+
const age = Date.now() - checkedAt;
|
|
7307
|
+
return age >= 0 && age < SERVER_TTL;
|
|
7308
|
+
}
|
|
7309
|
+
function serverAges(cache) {
|
|
7310
|
+
return {
|
|
7311
|
+
latest: cache.server_latest_checked_at ?? cache.server_checked_at,
|
|
7312
|
+
minimum: cache.server_minimum_checked_at ?? cache.server_checked_at
|
|
7313
|
+
};
|
|
7314
|
+
}
|
|
7315
|
+
function loadServerCache() {
|
|
7316
|
+
const cache = readCache();
|
|
7317
|
+
const ages = serverAges(cache);
|
|
7318
|
+
if (serverFresh(ages.latest) && cache.server_latest && versionParts(cache.server_latest))
|
|
7319
|
+
serverLatest = cache.server_latest;
|
|
7320
|
+
if (serverFresh(ages.minimum) && cache.server_minimum && versionParts(cache.server_minimum))
|
|
7321
|
+
serverMinimum = cache.server_minimum;
|
|
7322
|
+
}
|
|
7323
|
+
loadServerCache();
|
|
7324
|
+
function recordServerCliPolicy(observation) {
|
|
7325
|
+
const latestSeen = Boolean(observation.cliLatest && versionParts(observation.cliLatest));
|
|
7326
|
+
const minimumSeen = Boolean(observation.cliMinimum && versionParts(observation.cliMinimum));
|
|
7327
|
+
if (!latestSeen && !minimumSeen) return;
|
|
7328
|
+
if (latestSeen && (!serverLatest || compareVersions(observation.cliLatest, serverLatest) > 0))
|
|
7329
|
+
serverLatest = observation.cliLatest;
|
|
7330
|
+
if (minimumSeen) serverMinimum = observation.cliMinimum;
|
|
7331
|
+
const now = Date.now();
|
|
7332
|
+
const ages = serverAges(readCache());
|
|
7333
|
+
const patch = {};
|
|
7334
|
+
const remove5 = ["server_checked_at"];
|
|
7335
|
+
if (latestSeen)
|
|
7336
|
+
Object.assign(patch, { server_latest: serverLatest, server_latest_checked_at: now });
|
|
7337
|
+
else if (serverLatest && serverFresh(ages.latest))
|
|
7338
|
+
Object.assign(patch, { server_latest: serverLatest, server_latest_checked_at: ages.latest });
|
|
7339
|
+
else remove5.push("server_latest", "server_latest_checked_at");
|
|
7340
|
+
if (minimumSeen)
|
|
7341
|
+
Object.assign(patch, { server_minimum: serverMinimum, server_minimum_checked_at: now });
|
|
7342
|
+
else if (serverMinimum && serverFresh(ages.minimum))
|
|
7343
|
+
Object.assign(patch, {
|
|
7344
|
+
server_minimum: serverMinimum,
|
|
7345
|
+
server_minimum_checked_at: ages.minimum
|
|
7346
|
+
});
|
|
7347
|
+
else remove5.push("server_minimum", "server_minimum_checked_at");
|
|
7348
|
+
writeCache(patch, remove5);
|
|
7349
|
+
}
|
|
7350
|
+
function clearUpdateCache() {
|
|
7351
|
+
writeCache({}, [
|
|
7352
|
+
"latest",
|
|
7353
|
+
"checked_at",
|
|
7354
|
+
"server_latest",
|
|
7355
|
+
"server_minimum",
|
|
7356
|
+
"server_latest_checked_at",
|
|
7357
|
+
"server_minimum_checked_at",
|
|
7358
|
+
"server_checked_at"
|
|
7359
|
+
]);
|
|
7360
|
+
npmLatest = serverLatest = serverMinimum = void 0;
|
|
7361
|
+
}
|
|
7362
|
+
function currentUpdateState(current, minimumOverride) {
|
|
7363
|
+
if (minimumOverride && versionParts(minimumOverride))
|
|
7364
|
+
recordServerCliPolicy({ cliMinimum: minimumOverride });
|
|
7365
|
+
const latest = [npmLatest, serverLatest, serverMinimum, current].filter((value) => Boolean(value)).reduce((highest, value) => compareVersions(value, highest) > 0 ? value : highest, current);
|
|
7366
|
+
const minimum = serverMinimum;
|
|
7367
|
+
const required = Boolean(minimum && compareVersions(current, minimum) < 0);
|
|
7368
|
+
if (!required && process.env.APEX_NO_UPDATE_CHECK === "1") return;
|
|
7369
|
+
if (!required && compareVersions(latest, current) <= 0) return;
|
|
7370
|
+
const installed = versionParts(current);
|
|
7371
|
+
const available = versionParts(latest);
|
|
7372
|
+
return {
|
|
7373
|
+
current,
|
|
7374
|
+
latest,
|
|
7375
|
+
...minimum ? { minimum } : {},
|
|
7376
|
+
required,
|
|
7377
|
+
breaking: available[0] !== installed[0] || installed[0] === 0 && available[1] !== installed[1],
|
|
7378
|
+
command: UPGRADE_COMMAND,
|
|
7379
|
+
notes_url: NOTES_URL
|
|
7380
|
+
};
|
|
7243
7381
|
}
|
|
7244
7382
|
async function checkForUpdate(current) {
|
|
7245
7383
|
if (process.env.APEX_NO_UPDATE_CHECK === "1") return;
|
|
7246
7384
|
if (process.env[["APEX", "CLI", "MOCK", "API"].join("_")] === "1" || process.env.CI) return;
|
|
7247
|
-
|
|
7248
|
-
|
|
7385
|
+
if (!versionParts(current)) return;
|
|
7386
|
+
const cache = readCache();
|
|
7387
|
+
const age = Date.now() - (cache.checked_at ?? 0);
|
|
7388
|
+
if (age >= 0 && cache.latest === null && age < FAILURE_TTL) return;
|
|
7389
|
+
if (age >= 0 && age < TTL && cache.latest && versionParts(cache.latest)) {
|
|
7390
|
+
npmLatest = cache.latest;
|
|
7391
|
+
return;
|
|
7392
|
+
}
|
|
7249
7393
|
const controller = new AbortController();
|
|
7250
7394
|
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
7395
|
try {
|
|
7302
|
-
|
|
7303
|
-
|
|
7396
|
+
const body5 = await Promise.race([
|
|
7397
|
+
fetch("https://registry.npmjs.org/@drip-apex%2Fapex-cli/latest", {
|
|
7398
|
+
headers: { Accept: "application/json" },
|
|
7399
|
+
signal: controller.signal
|
|
7400
|
+
}).then(
|
|
7401
|
+
async (response) => response.ok ? await response.json() : void 0
|
|
7402
|
+
).catch(() => void 0),
|
|
7304
7403
|
new Promise((resolve2) => {
|
|
7305
7404
|
timer = setTimeout(() => {
|
|
7306
7405
|
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
7406
|
resolve2(void 0);
|
|
7315
7407
|
}, 900);
|
|
7316
7408
|
})
|
|
7317
7409
|
]);
|
|
7410
|
+
if (!body5?.version || !versionParts(body5.version)) {
|
|
7411
|
+
writeCache({ latest: null, checked_at: Date.now() });
|
|
7412
|
+
return;
|
|
7413
|
+
}
|
|
7414
|
+
npmLatest = body5.version;
|
|
7415
|
+
writeCache({ latest: npmLatest, checked_at: Date.now() });
|
|
7318
7416
|
} catch {
|
|
7319
|
-
|
|
7417
|
+
writeCache({ latest: null, checked_at: Date.now() });
|
|
7320
7418
|
} finally {
|
|
7321
7419
|
if (timer) clearTimeout(timer);
|
|
7322
7420
|
}
|
|
@@ -7325,7 +7423,7 @@ async function checkForUpdate(current) {
|
|
|
7325
7423
|
// package.json
|
|
7326
7424
|
var package_default = {
|
|
7327
7425
|
name: "@drip-apex/apex-cli",
|
|
7328
|
-
version: "0.15.
|
|
7426
|
+
version: "0.15.4",
|
|
7329
7427
|
license: "MIT",
|
|
7330
7428
|
description: "Apex CLI for managing experiments, approved roadmap ideas, and draft workspaces.",
|
|
7331
7429
|
private: false,
|
|
@@ -7383,7 +7481,9 @@ var EXIT_CODES = {
|
|
|
7383
7481
|
auth: 2,
|
|
7384
7482
|
validation: 3,
|
|
7385
7483
|
confirmationRequired: 4,
|
|
7386
|
-
conflict: 5
|
|
7484
|
+
conflict: 5,
|
|
7485
|
+
// A check command ran and reported a definite failing verdict.
|
|
7486
|
+
checkFailed: 6
|
|
7387
7487
|
};
|
|
7388
7488
|
var MAX_RECORDED_API_FAILURES = 32;
|
|
7389
7489
|
var recentApiFailures = [];
|
|
@@ -7495,7 +7595,13 @@ function configureRuntime(command) {
|
|
|
7495
7595
|
};
|
|
7496
7596
|
}
|
|
7497
7597
|
function writeJsonDocument(envelope) {
|
|
7498
|
-
const
|
|
7598
|
+
const update6 = currentUpdateState(
|
|
7599
|
+
CLI_VERSION,
|
|
7600
|
+
envelope.ok ? void 0 : minimumFromError(envelope.error, envelope.error.reason)
|
|
7601
|
+
);
|
|
7602
|
+
const warnings = update6 ? [updateWarning(update6)] : [];
|
|
7603
|
+
writeUpdateNotice(update6);
|
|
7604
|
+
const text2 = `${JSON.stringify({ ...envelope, ...update6 ? { update: update6 } : {}, ...warnings.length ? { warnings } : {} }, null, 2)}
|
|
7499
7605
|
`;
|
|
7500
7606
|
if (wroteEnvelope) {
|
|
7501
7607
|
process.stderr.write(text2);
|
|
@@ -7600,6 +7706,7 @@ var liveSpinners = /* @__PURE__ */ new Set();
|
|
|
7600
7706
|
var exitFallback;
|
|
7601
7707
|
function drainAndExit(code) {
|
|
7602
7708
|
for (const spinner of liveSpinners) spinner.stop();
|
|
7709
|
+
writePendingUpdateNotice();
|
|
7603
7710
|
process.exitCode = code;
|
|
7604
7711
|
if (exitFallback) clearTimeout(exitFallback);
|
|
7605
7712
|
exitFallback = setTimeout(() => process.exit(code), 500);
|
|
@@ -7638,13 +7745,35 @@ async function createSpinner(message) {
|
|
|
7638
7745
|
}
|
|
7639
7746
|
return spinner.start();
|
|
7640
7747
|
}
|
|
7641
|
-
var updateNotice;
|
|
7642
7748
|
async function maybeCheckForUpdate() {
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
|
|
7749
|
+
await checkForUpdate(CLI_VERSION);
|
|
7750
|
+
}
|
|
7751
|
+
var wroteUpdateNotice = false;
|
|
7752
|
+
function updateWarning(update6) {
|
|
7753
|
+
return update6.required ? {
|
|
7754
|
+
code: "cli_upgrade_required",
|
|
7755
|
+
message: `Apex CLI ${update6.current} is below the minimum supported version ${update6.minimum}. Run: ${UPGRADE_COMMAND} (or apex upgrade), then retry.`
|
|
7756
|
+
} : {
|
|
7757
|
+
code: "cli_update_available",
|
|
7758
|
+
message: `Apex CLI ${update6.latest} is available (current ${update6.current}). Run: ${UPGRADE_COMMAND}`
|
|
7759
|
+
};
|
|
7760
|
+
}
|
|
7761
|
+
function writeUpdateNotice(update6) {
|
|
7762
|
+
if (!update6 || wroteUpdateNotice) return;
|
|
7763
|
+
wroteUpdateNotice = true;
|
|
7764
|
+
process.stderr.write(`${updateWarning(update6).message}
|
|
7646
7765
|
`);
|
|
7647
7766
|
}
|
|
7767
|
+
function writePendingUpdateNotice() {
|
|
7768
|
+
writeUpdateNotice(currentUpdateState(CLI_VERSION));
|
|
7769
|
+
}
|
|
7770
|
+
process.on?.("beforeExit", writePendingUpdateNotice);
|
|
7771
|
+
function minimumFromError(error, reason2) {
|
|
7772
|
+
if (reason2 !== "cli_upgrade_required") return;
|
|
7773
|
+
const api = error.api?.body;
|
|
7774
|
+
const value = error.details?.minimum ?? api?.error?.details?.minimum;
|
|
7775
|
+
return typeof value === "string" ? value : void 0;
|
|
7776
|
+
}
|
|
7648
7777
|
async function handleCliError(err, command) {
|
|
7649
7778
|
if (err && typeof err === "object" && "exitCode" in err && err.exitCode === 0) {
|
|
7650
7779
|
flushFallbackEnvelope();
|
|
@@ -7676,6 +7805,9 @@ async function handleCliError(err, command) {
|
|
|
7676
7805
|
process.stderr.write(`Retry: ${String(cliError.details.retry)}
|
|
7677
7806
|
`);
|
|
7678
7807
|
}
|
|
7808
|
+
writeUpdateNotice(
|
|
7809
|
+
currentUpdateState(CLI_VERSION, minimumFromError(cliError, errorReason(cliError)))
|
|
7810
|
+
);
|
|
7679
7811
|
}
|
|
7680
7812
|
await finishInvocation({ exitCode: cliError.exitCode, error: err });
|
|
7681
7813
|
drainAndExit(cliError.exitCode);
|
|
@@ -7687,9 +7819,20 @@ function errorReason(error) {
|
|
|
7687
7819
|
const body5 = error.api?.body;
|
|
7688
7820
|
const code = body5?.code ?? body5?.error?.code ?? (error.api ? details?.code : void 0);
|
|
7689
7821
|
if (typeof code === "string" && code) return code;
|
|
7690
|
-
if ([
|
|
7691
|
-
|
|
7692
|
-
|
|
7822
|
+
if ([
|
|
7823
|
+
"shop_context_required",
|
|
7824
|
+
"shop_context_mismatch",
|
|
7825
|
+
"install_check_failed",
|
|
7826
|
+
"local_file_error",
|
|
7827
|
+
"validation_failed",
|
|
7828
|
+
"member_suspended",
|
|
7829
|
+
"upgrade_failed",
|
|
7830
|
+
"conflicting_context_input",
|
|
7831
|
+
"context_not_persistable",
|
|
7832
|
+
"context_selection_required",
|
|
7833
|
+
"invalid_window",
|
|
7834
|
+
"output_exists"
|
|
7835
|
+
].includes(String(details?.code)))
|
|
7693
7836
|
return String(details?.code);
|
|
7694
7837
|
const status4 = error.api?.status;
|
|
7695
7838
|
if (status4 === 401) return "auth_required";
|
|
@@ -7705,6 +7848,14 @@ function errorReason(error) {
|
|
|
7705
7848
|
function errorNextSteps(error) {
|
|
7706
7849
|
const reason2 = errorReason(error);
|
|
7707
7850
|
const steps = [];
|
|
7851
|
+
if (reason2 === "cli_upgrade_required")
|
|
7852
|
+
steps.push(
|
|
7853
|
+
{
|
|
7854
|
+
command: UPGRADE_COMMAND,
|
|
7855
|
+
why: "This CLI version is below the server's minimum for changes; upgrade, then retry the same command."
|
|
7856
|
+
},
|
|
7857
|
+
{ command: "apex upgrade", why: "Runs the same install for you." }
|
|
7858
|
+
);
|
|
7708
7859
|
if (reason2 === "member_suspended")
|
|
7709
7860
|
return [
|
|
7710
7861
|
{
|
|
@@ -7712,6 +7863,14 @@ function errorNextSteps(error) {
|
|
|
7712
7863
|
why: "Ask an organization owner or admin to reinstate your membership."
|
|
7713
7864
|
}
|
|
7714
7865
|
];
|
|
7866
|
+
if (reason2 === "feature_disabled") {
|
|
7867
|
+
const label = typeof error.details?.label === "string" ? error.details.label : "this feature";
|
|
7868
|
+
const setting = typeof error.details?.setting === "string" ? ` (shop setting ${error.details.setting})` : "";
|
|
7869
|
+
steps.push({
|
|
7870
|
+
command: "ask your Apex contact",
|
|
7871
|
+
why: `Turn on ${label} for this shop${setting}.`
|
|
7872
|
+
});
|
|
7873
|
+
}
|
|
7715
7874
|
if (reason2 === "auth_required" || error.api?.status === 401) {
|
|
7716
7875
|
steps.push({ command: "apex login", why: "Authenticate with a valid API key." });
|
|
7717
7876
|
if (!error.api)
|
|
@@ -7751,8 +7910,10 @@ function errorNextSteps(error) {
|
|
|
7751
7910
|
const hints = error.details?.next_steps;
|
|
7752
7911
|
if (Array.isArray(hints))
|
|
7753
7912
|
for (const hint of hints) {
|
|
7754
|
-
if (hint && typeof hint.command === "string" && typeof hint.why === "string")
|
|
7755
|
-
steps.
|
|
7913
|
+
if (hint && typeof hint.command === "string" && typeof hint.why === "string") {
|
|
7914
|
+
if (!steps.some((step) => step.command === hint.command))
|
|
7915
|
+
steps.push({ command: hint.command, why: hint.why });
|
|
7916
|
+
}
|
|
7756
7917
|
}
|
|
7757
7918
|
if (reason2 === "validation_failed" && error.details?.plan)
|
|
7758
7919
|
steps.push({
|
|
@@ -7869,7 +8030,7 @@ function clearCredentials() {
|
|
|
7869
8030
|
} catch {
|
|
7870
8031
|
}
|
|
7871
8032
|
}
|
|
7872
|
-
function requireCredentials() {
|
|
8033
|
+
function requireCredentials(fallbackShopId) {
|
|
7873
8034
|
const creds = getCredentials();
|
|
7874
8035
|
if (!creds) {
|
|
7875
8036
|
throw new CliError(
|
|
@@ -7878,6 +8039,7 @@ function requireCredentials() {
|
|
|
7878
8039
|
);
|
|
7879
8040
|
}
|
|
7880
8041
|
const context = resolveContext(creds, void 0, defaultCredentialKind(creds));
|
|
8042
|
+
if (!context.shopId) context.shopId = fallbackShopId?.trim() || void 0;
|
|
7881
8043
|
if (defaultCredentialKind(creds) === "user") {
|
|
7882
8044
|
setEnvelopeContext({
|
|
7883
8045
|
credential: "user",
|
|
@@ -7917,6 +8079,53 @@ function requireCredentials() {
|
|
|
7917
8079
|
});
|
|
7918
8080
|
return { ...creds, credentialKind: "shop", resolvedContext: context };
|
|
7919
8081
|
}
|
|
8082
|
+
function resolveCheckShop(explicitShopId, fallbackShopId) {
|
|
8083
|
+
const explicit = explicitShopId?.trim() || null;
|
|
8084
|
+
const fallback2 = fallbackShopId?.trim() || null;
|
|
8085
|
+
const creds = requireCredentials(explicit ?? fallback2 ?? void 0);
|
|
8086
|
+
const selectedFlag = argvOption("shop")?.trim() || null;
|
|
8087
|
+
if (explicit && selectedFlag && explicit.toLowerCase() !== selectedFlag.toLowerCase()) {
|
|
8088
|
+
throw new CliError(
|
|
8089
|
+
`--shop-id ${explicit} does not match --shop ${selectedFlag}; pass the same shop to both or drop one.`,
|
|
8090
|
+
EXIT_CODES.validation,
|
|
8091
|
+
{
|
|
8092
|
+
code: "shop_context_mismatch",
|
|
8093
|
+
field: "shop-id",
|
|
8094
|
+
requested_shop_id: explicit,
|
|
8095
|
+
selected_shop_id: selectedFlag
|
|
8096
|
+
}
|
|
8097
|
+
);
|
|
8098
|
+
}
|
|
8099
|
+
const previous = creds.resolvedContext?.shopId?.trim() || null;
|
|
8100
|
+
const shopId = explicit ?? previous ?? fallback2;
|
|
8101
|
+
if (creds.credentialKind !== "user" || !shopId) return { creds, shopId };
|
|
8102
|
+
const resolvedContext = { ...creds.resolvedContext, shopId };
|
|
8103
|
+
setEnvelopeContext({
|
|
8104
|
+
credential: "user",
|
|
8105
|
+
org_id: resolvedContext.orgId ?? null,
|
|
8106
|
+
shop_id: shopId
|
|
8107
|
+
});
|
|
8108
|
+
return {
|
|
8109
|
+
creds: { ...creds, resolvedContext },
|
|
8110
|
+
shopId,
|
|
8111
|
+
...previous && previous.toLowerCase() !== shopId.toLowerCase() ? { overriddenShopId: previous } : {}
|
|
8112
|
+
};
|
|
8113
|
+
}
|
|
8114
|
+
function assertResponseShop(apiShopId, requestedShopId, credentialKind) {
|
|
8115
|
+
const api = apiShopId?.trim();
|
|
8116
|
+
const requested = requestedShopId?.trim();
|
|
8117
|
+
if (!requested || api && api.toLowerCase() === requested.toLowerCase()) return;
|
|
8118
|
+
throw new CliError(
|
|
8119
|
+
!api ? `The API returned no shop identity; cannot verify that it answered for the requested shop ${requested}.` : credentialKind === "shop" ? `This API key is bound to shop ${api}, not the requested shop ${requested}; use a key for that shop or drop --shop-id.` : `The API answered for shop ${api}, not the requested shop ${requested}.`,
|
|
8120
|
+
EXIT_CODES.validation,
|
|
8121
|
+
{
|
|
8122
|
+
code: "shop_context_mismatch",
|
|
8123
|
+
field: "shop-id",
|
|
8124
|
+
requested_shop_id: requested,
|
|
8125
|
+
api_shop_id: api || null
|
|
8126
|
+
}
|
|
8127
|
+
);
|
|
8128
|
+
}
|
|
7920
8129
|
function requireUserCredentials() {
|
|
7921
8130
|
const creds = getCredentials();
|
|
7922
8131
|
const environmentUserKey = creds?.credentialKind === "user" ? creds.apiKey : void 0;
|
|
@@ -7977,7 +8186,65 @@ var invocation;
|
|
|
7977
8186
|
var firstError;
|
|
7978
8187
|
var apiCalls = 0;
|
|
7979
8188
|
var finished = false;
|
|
8189
|
+
var resolvedPath = "";
|
|
8190
|
+
var helpPath;
|
|
8191
|
+
var attempted;
|
|
8192
|
+
var apiCallTimings = [];
|
|
8193
|
+
var entity;
|
|
8194
|
+
function registerTelemetryProgram(program3) {
|
|
8195
|
+
program3.hook("preAction", (_command, actionCommand) => {
|
|
8196
|
+
resolvedPath = telemetryCommandPath(actionCommand);
|
|
8197
|
+
});
|
|
8198
|
+
const registerHelp = (command) => {
|
|
8199
|
+
command.on("beforeHelp", () => {
|
|
8200
|
+
helpPath = telemetryCommandPath(command);
|
|
8201
|
+
});
|
|
8202
|
+
for (const child of command.commands) registerHelp(child);
|
|
8203
|
+
};
|
|
8204
|
+
registerHelp(program3);
|
|
8205
|
+
}
|
|
8206
|
+
function telemetryCommandPath(command) {
|
|
8207
|
+
return commandPath(command).replace(/^apex(?:\s+|$)/, "");
|
|
8208
|
+
}
|
|
8209
|
+
function recordCommanderExit(error, command) {
|
|
8210
|
+
resolvedPath = telemetryCommandPath(command);
|
|
8211
|
+
attempted = error.code === "commander.unknownCommand" && command.args[0] ? [resolvedPath, redactAttempted(command.args[0])].filter(Boolean).join(" ").slice(0, 200) : resolvedPath || void 0;
|
|
8212
|
+
}
|
|
8213
|
+
var agentHosts = /* @__PURE__ */ new Set([
|
|
8214
|
+
"claude-code",
|
|
8215
|
+
"codex",
|
|
8216
|
+
"cursor",
|
|
8217
|
+
"grok",
|
|
8218
|
+
"gemini",
|
|
8219
|
+
"copilot",
|
|
8220
|
+
"other"
|
|
8221
|
+
]);
|
|
8222
|
+
function detectAgentHost(env2, tty2) {
|
|
8223
|
+
if (env2.APEX_AGENT_HOST && agentHosts.has(env2.APEX_AGENT_HOST)) return env2.APEX_AGENT_HOST;
|
|
8224
|
+
if (env2.CLAUDECODE === "1") return "claude-code";
|
|
8225
|
+
if (env2.CODEX_THREAD_ID || env2.CODEX_SANDBOX) return "codex";
|
|
8226
|
+
if (env2.CURSOR_AGENT || env2.CURSOR_TRACE_ID) return "cursor";
|
|
8227
|
+
if (Object.keys(env2).some((name) => name.startsWith("GROK_"))) return "grok";
|
|
8228
|
+
if (env2.AI_AGENT) {
|
|
8229
|
+
const token = env2.AI_AGENT.match(/^[a-z-]+/)?.[0];
|
|
8230
|
+
return token && agentHosts.has(token) ? token : "other";
|
|
8231
|
+
}
|
|
8232
|
+
return tty2 ? "terminal" : void 0;
|
|
8233
|
+
}
|
|
8234
|
+
function detectSessionId(env2) {
|
|
8235
|
+
if (/^[A-Za-z0-9._:-]{1,128}$/.test(env2.APEX_SESSION_ID ?? "")) return env2.APEX_SESSION_ID;
|
|
8236
|
+
const hostId = env2.CLAUDE_CODE_SESSION_ID || env2.CODEX_THREAD_ID;
|
|
8237
|
+
return hostId ? `h_${(0, import_node_crypto2.createHash)("sha256").update(hostId).digest("hex").slice(0, 32)}` : void 0;
|
|
8238
|
+
}
|
|
7980
8239
|
function beginInvocation(command, context = {}) {
|
|
8240
|
+
resolvedPath = telemetryCommandPath(command);
|
|
8241
|
+
helpPath = void 0;
|
|
8242
|
+
attempted = void 0;
|
|
8243
|
+
firstError = void 0;
|
|
8244
|
+
apiCalls = 0;
|
|
8245
|
+
finished = false;
|
|
8246
|
+
apiCallTimings.length = 0;
|
|
8247
|
+
entity = void 0;
|
|
7981
8248
|
const names = /* @__PURE__ */ new Set();
|
|
7982
8249
|
for (let current = command; current; current = current.parent) {
|
|
7983
8250
|
for (const option of current.options) {
|
|
@@ -8030,7 +8297,9 @@ var staticRouteSegments = /* @__PURE__ */ new Set([
|
|
|
8030
8297
|
"duplicate",
|
|
8031
8298
|
"entry-exit",
|
|
8032
8299
|
"environments",
|
|
8300
|
+
"events",
|
|
8033
8301
|
"evidence",
|
|
8302
|
+
"export",
|
|
8034
8303
|
"exclusion-groups",
|
|
8035
8304
|
"experiments",
|
|
8036
8305
|
"external-pixels",
|
|
@@ -8124,6 +8393,24 @@ function normalizeRoute(path26) {
|
|
|
8124
8393
|
}
|
|
8125
8394
|
function recordApiResponse2(response) {
|
|
8126
8395
|
apiCalls++;
|
|
8396
|
+
if (apiCallTimings.length < 25)
|
|
8397
|
+
apiCallTimings.push({
|
|
8398
|
+
method: response.method,
|
|
8399
|
+
route: normalizeRoute(response.path),
|
|
8400
|
+
status: response.status,
|
|
8401
|
+
duration_ms: response.durationMs,
|
|
8402
|
+
...response.serverTiming ? { server_timing: response.serverTiming } : {}
|
|
8403
|
+
});
|
|
8404
|
+
if (!entity) {
|
|
8405
|
+
const match2 = response.path.match(
|
|
8406
|
+
/^\/api\/v1\/(experiments|landing-pages)\/([0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12})(?=\/|\?|$)/i
|
|
8407
|
+
);
|
|
8408
|
+
if (match2)
|
|
8409
|
+
entity = {
|
|
8410
|
+
entity_type: match2[1] === "experiments" ? "experiment" : "landing_page",
|
|
8411
|
+
entity_id: match2[2]
|
|
8412
|
+
};
|
|
8413
|
+
}
|
|
8127
8414
|
if (response.status >= 400 && !firstError)
|
|
8128
8415
|
firstError = { ...response, path: normalizeRoute(response.path) };
|
|
8129
8416
|
}
|
|
@@ -8133,20 +8420,62 @@ function classifyError(exitCode, error, status4) {
|
|
|
8133
8420
|
if (detail5.code === "commander.unknownCommand") return "unknown_command";
|
|
8134
8421
|
if (detail5.code?.startsWith("commander.")) return "validation";
|
|
8135
8422
|
if (exitCode === 4) return "confirmation_required";
|
|
8423
|
+
if (exitCode === EXIT_CODES.checkFailed) return "check_failed";
|
|
8136
8424
|
if (status4 === 401 || status4 === 403 || exitCode === 2) return "auth";
|
|
8137
|
-
if (status4 === 404) return "not_found";
|
|
8425
|
+
if (status4 === 404 || status4 === 410) return "not_found";
|
|
8138
8426
|
if (status4 === 429) return "rate_limited";
|
|
8139
8427
|
if (status4 === 409 || exitCode === 5) return "conflict";
|
|
8140
8428
|
if (status4 && status4 >= 500) return "server";
|
|
8141
8429
|
if (status4 === 400 || status4 === 422 || exitCode === 3) return "validation";
|
|
8430
|
+
if (detail5.details?.reason === "timeout" || detail5.details?.reason === "connect_timeout" || detail5.name === "TimeoutError")
|
|
8431
|
+
return "timeout";
|
|
8142
8432
|
if (Boolean(
|
|
8143
|
-
detail5.details?.reason && ["network", "dns", "
|
|
8144
|
-
|
|
8145
|
-
)
|
|
8146
|
-
) || detail5.details?.code === "network_error" || detail5.name === "TimeoutError")
|
|
8433
|
+
detail5.details?.reason && ["network", "dns", "tls", "refused", "reset", "unknown"].includes(detail5.details.reason)
|
|
8434
|
+
) || detail5.details?.code === "network_error")
|
|
8147
8435
|
return "network";
|
|
8148
8436
|
return "internal";
|
|
8149
8437
|
}
|
|
8438
|
+
function redactAttempted(token) {
|
|
8439
|
+
return /^(?:apx_|sk_|pk_|rk_|ghp_|gho_|xox|Bearer)/.test(token) || /^[A-Za-z0-9_-]{24,}$/.test(token) || /:\/\/|[/\\@=]/.test(token) || token.length > 40 ? "(redacted)" : token;
|
|
8440
|
+
}
|
|
8441
|
+
function normalizeErrorCode(value, exitCode) {
|
|
8442
|
+
const normalized = value.toLowerCase();
|
|
8443
|
+
return normalized.length <= 64 && /^[a-z0-9._-]+$/.test(normalized) ? normalized : `exit_${exitCode}`;
|
|
8444
|
+
}
|
|
8445
|
+
function invocationErrorCode(exitCode, error, commanderExit) {
|
|
8446
|
+
if (exitCode === 0) return void 0;
|
|
8447
|
+
let value = firstError?.errorCode;
|
|
8448
|
+
if (!value && firstError)
|
|
8449
|
+
value = firstError.status === 410 ? "gone" : `http_${firstError.status}`;
|
|
8450
|
+
const detail5 = error && typeof error === "object" ? error : {};
|
|
8451
|
+
if (!value && typeof detail5.details?.reason === "string" && !detail5.api) {
|
|
8452
|
+
const reason2 = detail5.details.reason.toLowerCase();
|
|
8453
|
+
value = [
|
|
8454
|
+
"network",
|
|
8455
|
+
"dns",
|
|
8456
|
+
"timeout",
|
|
8457
|
+
"connect_timeout",
|
|
8458
|
+
"tls",
|
|
8459
|
+
"refused",
|
|
8460
|
+
"reset",
|
|
8461
|
+
"unknown"
|
|
8462
|
+
].includes(reason2) ? `network_${reason2}` : reason2 || void 0;
|
|
8463
|
+
}
|
|
8464
|
+
if (!value && commanderExit && typeof detail5.code === "string")
|
|
8465
|
+
value = detail5.code.slice("commander.".length).replace(/([a-z])([A-Z])/g, "$1_$2");
|
|
8466
|
+
if (!value) {
|
|
8467
|
+
const apiCode = detail5.api?.body?.code ?? detail5.api?.body?.error?.code ?? detail5.details?.code;
|
|
8468
|
+
if (typeof apiCode === "string" && apiCode) value = apiCode;
|
|
8469
|
+
else if (detail5.api?.status === 401 || exitCode === 2) value = "auth_required";
|
|
8470
|
+
else if (detail5.api?.status === 403) value = "auth_forbidden";
|
|
8471
|
+
else if (detail5.api?.status === 404) value = "not_found";
|
|
8472
|
+
else if (detail5.api?.status === 429) value = "rate_limited";
|
|
8473
|
+
else if (detail5.api?.status === 409 || exitCode === 5) value = "conflict";
|
|
8474
|
+
else if (exitCode === 4) value = "confirmation_required";
|
|
8475
|
+
else if (exitCode === 3) value = "validation_failed";
|
|
8476
|
+
}
|
|
8477
|
+
return normalizeErrorCode(value ?? `exit_${exitCode}`, exitCode);
|
|
8478
|
+
}
|
|
8150
8479
|
async function finishInvocation({
|
|
8151
8480
|
exitCode: rawExitCode,
|
|
8152
8481
|
error
|
|
@@ -8168,10 +8497,8 @@ async function finishInvocation({
|
|
|
8168
8497
|
const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
|
|
8169
8498
|
const commanderExit = code.startsWith("commander.");
|
|
8170
8499
|
const parse3 = commanderExit && exitCode !== 0;
|
|
8171
|
-
|
|
8172
|
-
const
|
|
8173
|
-
if (attempted === "login" || attempted === "logout") return;
|
|
8174
|
-
const command = parse3 ? "(parse)" : invocation?.command ?? (commanderExit && exitCode === 0 ? attempted ?? (code === "commander.version" ? "(version)" : "(help)") : void 0);
|
|
8500
|
+
if (resolvedPath === "login" || resolvedPath === "logout") return;
|
|
8501
|
+
const command = parse3 ? "(parse)" : invocation?.command ?? (commanderExit && exitCode === 0 ? code === "commander.version" ? "(version)" : ["(help)", helpPath].filter(Boolean).join(" ") : void 0);
|
|
8175
8502
|
if (!command || command === "login" || command === "logout") return;
|
|
8176
8503
|
if (!getClientIdentity()) setClientIdentity({ client: "apex-cli", version: CLI_VERSION });
|
|
8177
8504
|
const payload = {
|
|
@@ -8185,8 +8512,12 @@ async function finishInvocation({
|
|
|
8185
8512
|
exit_code: exitCode,
|
|
8186
8513
|
error_class: classifyError(exitCode, error, firstError?.status),
|
|
8187
8514
|
api_calls: apiCalls,
|
|
8515
|
+
...apiCallTimings.length ? { api_call_timings: [...apiCallTimings] } : {},
|
|
8516
|
+
...entity,
|
|
8517
|
+
...detectAgentHost(process.env, process.stdout.isTTY === true) ? { agent_host: detectAgentHost(process.env, process.stdout.isTTY === true) } : {},
|
|
8518
|
+
...detectSessionId(process.env) ? { session_id: detectSessionId(process.env) } : {},
|
|
8188
8519
|
first_error_status: firstError?.status,
|
|
8189
|
-
first_error_code:
|
|
8520
|
+
first_error_code: invocationErrorCode(exitCode, error, commanderExit),
|
|
8190
8521
|
first_error_request_id: firstError?.requestId,
|
|
8191
8522
|
first_error_route: firstError?.path,
|
|
8192
8523
|
duration_ms: Math.max(0, Math.round(performance.now() - started))
|
|
@@ -8207,13 +8538,13 @@ init_source();
|
|
|
8207
8538
|
|
|
8208
8539
|
// src/commands/login.ts
|
|
8209
8540
|
init_source();
|
|
8210
|
-
var
|
|
8541
|
+
var import_node_crypto4 = __toESM(require("node:crypto"));
|
|
8211
8542
|
var import_node_http = __toESM(require("node:http"));
|
|
8212
8543
|
var import_node_os6 = __toESM(require("node:os"));
|
|
8213
8544
|
var import_node_readline2 = __toESM(require("node:readline"));
|
|
8214
8545
|
|
|
8215
8546
|
// src/api.ts
|
|
8216
|
-
var
|
|
8547
|
+
var import_node_crypto3 = require("node:crypto");
|
|
8217
8548
|
|
|
8218
8549
|
// ../api-client/src/landing-pages.ts
|
|
8219
8550
|
var LANDING_PAGE_MANIFEST_FILE = "apex-landing-page.json";
|
|
@@ -8378,7 +8709,11 @@ function sharedClient(creds) {
|
|
|
8378
8709
|
let client = sharedClients.get(creds);
|
|
8379
8710
|
if (!client) {
|
|
8380
8711
|
client = createHttpClient(creds, {
|
|
8381
|
-
onResponse:
|
|
8712
|
+
onResponse: (observation) => {
|
|
8713
|
+
recordApiResponse2(observation);
|
|
8714
|
+
recordServerCliPolicy?.(observation);
|
|
8715
|
+
},
|
|
8716
|
+
observeNetworkErrors: true,
|
|
8382
8717
|
transport: testTransport,
|
|
8383
8718
|
testTransportVersion: () => testTransport ? process.env[["APEX", "CLI", "MOCK", "API", "VERSION"].join("_")] : void 0,
|
|
8384
8719
|
onNetworkError: (info) => {
|
|
@@ -8428,8 +8763,14 @@ function sharedClient(creds) {
|
|
|
8428
8763
|
}
|
|
8429
8764
|
return client;
|
|
8430
8765
|
}
|
|
8431
|
-
async function request(creds, method, path26, body5, requestTimeoutMs) {
|
|
8432
|
-
const response = await sharedClient(creds).request(
|
|
8766
|
+
async function request(creds, method, path26, body5, requestTimeoutMs, options) {
|
|
8767
|
+
const response = await sharedClient(creds).request(
|
|
8768
|
+
method,
|
|
8769
|
+
path26,
|
|
8770
|
+
body5,
|
|
8771
|
+
requestTimeoutMs,
|
|
8772
|
+
options
|
|
8773
|
+
);
|
|
8433
8774
|
recordApiResponse(response);
|
|
8434
8775
|
return response;
|
|
8435
8776
|
}
|
|
@@ -8534,6 +8875,7 @@ function planExperimentFilesystem(creds, id, body5) {
|
|
|
8534
8875
|
body5
|
|
8535
8876
|
);
|
|
8536
8877
|
}
|
|
8878
|
+
var APPLY_TIMEOUT_MS = 9e4;
|
|
8537
8879
|
function applyExperimentFilesystem(creds, id, body5) {
|
|
8538
8880
|
return request(
|
|
8539
8881
|
creds,
|
|
@@ -8543,7 +8885,9 @@ function applyExperimentFilesystem(creds, id, body5) {
|
|
|
8543
8885
|
files: body5.files,
|
|
8544
8886
|
...body5.accept_qa_invalidation !== void 0 ? { accept_qa_invalidation: body5.accept_qa_invalidation } : {},
|
|
8545
8887
|
...body5.confirmTargetingReplace === true ? { confirmTargetingReplace: true } : {}
|
|
8546
|
-
}
|
|
8888
|
+
},
|
|
8889
|
+
void 0,
|
|
8890
|
+
{ minTimeoutMs: APPLY_TIMEOUT_MS }
|
|
8547
8891
|
);
|
|
8548
8892
|
}
|
|
8549
8893
|
function getExperimentQaRun(creds, id) {
|
|
@@ -8569,8 +8913,9 @@ function attachExperimentQaEvidence(creds, id, body5) {
|
|
|
8569
8913
|
function uploadExperimentQaEvidence(creds, id, form) {
|
|
8570
8914
|
return requestForm(creds, "POST", `/api/v1/experiments/${id}/qa/evidence`, form);
|
|
8571
8915
|
}
|
|
8572
|
-
function approveExperimentQa(creds, id) {
|
|
8573
|
-
return request(creds, "PATCH", `/api/v1/experiments/${id}/qa`);
|
|
8916
|
+
function approveExperimentQa(creds, id, acknowledgement) {
|
|
8917
|
+
if (!acknowledgement) return request(creds, "PATCH", `/api/v1/experiments/${id}/qa`);
|
|
8918
|
+
return request(creds, "PATCH", `/api/v1/experiments/${id}/qa`, acknowledgement);
|
|
8574
8919
|
}
|
|
8575
8920
|
function reopenExperimentQa(creds, id) {
|
|
8576
8921
|
return request(creds, "DELETE", `/api/v1/experiments/${id}/qa`);
|
|
@@ -8770,7 +9115,7 @@ function testTargeting(creds, url, opts) {
|
|
|
8770
9115
|
return request(creds, "GET", `/api/v1/targeting/test?${params.toString()}`);
|
|
8771
9116
|
}
|
|
8772
9117
|
function createSos(creds, body5, options = {}) {
|
|
8773
|
-
const idempotency_key = body5.idempotency_key ?? (0,
|
|
9118
|
+
const idempotency_key = body5.idempotency_key ?? (0, import_node_crypto3.createHash)("sha256").update(
|
|
8774
9119
|
[
|
|
8775
9120
|
body5.type,
|
|
8776
9121
|
body5.experiment_id ?? "",
|
|
@@ -9267,6 +9612,16 @@ function resolveSelection(tree, input) {
|
|
|
9267
9612
|
const shop = shopValue ? match(shopValue, org.shops, "shop") : void 0;
|
|
9268
9613
|
return { orgId: org.id, ...shop ? { shopId: shop.id } : {} };
|
|
9269
9614
|
}
|
|
9615
|
+
function resolveFlagSelection(tree, values) {
|
|
9616
|
+
if (values.org) {
|
|
9617
|
+
const org = match(values.org, tree.orgs, "organization");
|
|
9618
|
+
const shop2 = values.shop ? match(values.shop, org.shops, "shop") : void 0;
|
|
9619
|
+
return { orgId: org.id, ...shop2 ? { shopId: shop2.id } : {} };
|
|
9620
|
+
}
|
|
9621
|
+
const shops2 = tree.orgs.flatMap((org) => org.shops.map((shop2) => ({ ...shop2, orgId: org.id })));
|
|
9622
|
+
const shop = match(values.shop, shops2, "shop");
|
|
9623
|
+
return { orgId: shop.orgId, shopId: shop.id };
|
|
9624
|
+
}
|
|
9270
9625
|
function ask(question) {
|
|
9271
9626
|
const rl = import_node_readline.default.createInterface({ input: process.stdin, output: process.stderr });
|
|
9272
9627
|
return new Promise(
|
|
@@ -9279,8 +9634,23 @@ function ask(question) {
|
|
|
9279
9634
|
async function pickContext(tree) {
|
|
9280
9635
|
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
9281
9636
|
throw new CliError(
|
|
9282
|
-
"Interactive context selection requires a TTY. Pass
|
|
9283
|
-
EXIT_CODES.validation
|
|
9637
|
+
"Interactive context selection requires a TTY. Pass <org>[/<shop>] to apex use, or use --org/--shop.",
|
|
9638
|
+
EXIT_CODES.validation,
|
|
9639
|
+
{
|
|
9640
|
+
code: "context_selection_required",
|
|
9641
|
+
candidates: tree.orgs.slice(0, 20).map((org2) => ({
|
|
9642
|
+
org_id: org2.id,
|
|
9643
|
+
org_name: org2.name,
|
|
9644
|
+
shops: org2.shops.slice(0, 20).map((shop2) => ({ id: shop2.id, name: shop2.name }))
|
|
9645
|
+
})),
|
|
9646
|
+
truncated: tree.orgs.length > 20 || tree.orgs.some((org2) => org2.shops.length > 20),
|
|
9647
|
+
next_steps: [
|
|
9648
|
+
{
|
|
9649
|
+
command: "apex use <org-id>/<shop-id>",
|
|
9650
|
+
why: "Choose one of error.details.candidates."
|
|
9651
|
+
}
|
|
9652
|
+
]
|
|
9653
|
+
}
|
|
9284
9654
|
);
|
|
9285
9655
|
let org = tree.orgs.length === 1 ? tree.orgs[0] : void 0;
|
|
9286
9656
|
if (!org) {
|
|
@@ -9400,7 +9770,7 @@ function parseFormBody(body5) {
|
|
|
9400
9770
|
return new URLSearchParams(body5);
|
|
9401
9771
|
}
|
|
9402
9772
|
async function waitForBrowserLogin(baseUrl, userMode = false) {
|
|
9403
|
-
const state =
|
|
9773
|
+
const state = import_node_crypto4.default.randomBytes(24).toString("base64url");
|
|
9404
9774
|
const clientName = import_node_os6.default.hostname() || "Local machine";
|
|
9405
9775
|
return new Promise((resolve2, reject) => {
|
|
9406
9776
|
let settled = false;
|
|
@@ -9710,6 +10080,14 @@ Context: ${current.orgId ?? "none"}${current.shopId ? ` / ${current.shopId}` : "
|
|
|
9710
10080
|
|
|
9711
10081
|
// src/commands/use.ts
|
|
9712
10082
|
var useCommand = new Command("use").description("Set the current Apex organization and shop context").argument("[context]", "<org-id-or-name>[/shop-id-or-name]").action(async function(input) {
|
|
10083
|
+
const flags = this.optsWithGlobals();
|
|
10084
|
+
if (input && (flags.org || flags.shop))
|
|
10085
|
+
fail(
|
|
10086
|
+
this,
|
|
10087
|
+
"Pass the context either as <org>[/<shop>] or with --org/--shop, not both.",
|
|
10088
|
+
EXIT_CODES.validation,
|
|
10089
|
+
{ code: "conflicting_context_input" }
|
|
10090
|
+
);
|
|
9713
10091
|
const creds = requireUserCredentials();
|
|
9714
10092
|
const response = await getAccountContext(creds);
|
|
9715
10093
|
if (!response.ok || !response.data)
|
|
@@ -9719,9 +10097,32 @@ var useCommand = new Command("use").description("Set the current Apex organizati
|
|
|
9719
10097
|
classifyApiError(response.status, response.errorDetails),
|
|
9720
10098
|
response.errorDetails
|
|
9721
10099
|
);
|
|
9722
|
-
const selected2 = input ? resolveSelection(response.data.data, input) : await pickContext(response.data.data);
|
|
10100
|
+
const selected2 = input ? resolveSelection(response.data.data, input) : flags.org || flags.shop ? resolveFlagSelection(response.data.data, flags) : await pickContext(response.data.data);
|
|
9723
10101
|
const stored = getStoredCredentials();
|
|
9724
|
-
if (!stored)
|
|
10102
|
+
if (!stored) {
|
|
10103
|
+
const env2 = {
|
|
10104
|
+
DRIP_ORG: selected2.orgId,
|
|
10105
|
+
...selected2.shopId ? { DRIP_SHOP: selected2.shopId } : {}
|
|
10106
|
+
};
|
|
10107
|
+
const exportCommand = `export DRIP_ORG=${selected2.orgId}${selected2.shopId ? ` DRIP_SHOP=${selected2.shopId}` : " && unset DRIP_SHOP"}`;
|
|
10108
|
+
fail(
|
|
10109
|
+
this,
|
|
10110
|
+
`apex use cannot save a context: this run authenticates with an API key from the environment (APEX_API_KEY or DRIP_API_KEY) and no apex login credential file exists. Set DRIP_ORG=${selected2.orgId}${selected2.shopId ? ` and DRIP_SHOP=${selected2.shopId}` : " and unset DRIP_SHOP"} in the environment, or pass --org/--shop on each command.`,
|
|
10111
|
+
EXIT_CODES.validation,
|
|
10112
|
+
{
|
|
10113
|
+
code: "context_not_persistable",
|
|
10114
|
+
org_id: selected2.orgId,
|
|
10115
|
+
shop_id: selected2.shopId ?? null,
|
|
10116
|
+
env: env2,
|
|
10117
|
+
next_steps: [
|
|
10118
|
+
{
|
|
10119
|
+
command: exportCommand,
|
|
10120
|
+
why: "Selects this context for every later apex command in this environment."
|
|
10121
|
+
}
|
|
10122
|
+
]
|
|
10123
|
+
}
|
|
10124
|
+
);
|
|
10125
|
+
}
|
|
9725
10126
|
saveCredentials({ ...stored, context: selected2 });
|
|
9726
10127
|
setEnvelopeContext({
|
|
9727
10128
|
credential: "user",
|
|
@@ -9737,7 +10138,7 @@ var useCommand = new Command("use").description("Set the current Apex organizati
|
|
|
9737
10138
|
|
|
9738
10139
|
// src/commands/experiments.ts
|
|
9739
10140
|
init_source();
|
|
9740
|
-
var
|
|
10141
|
+
var import_node_crypto6 = __toESM(require("node:crypto"));
|
|
9741
10142
|
var import_node_fs8 = __toESM(require("node:fs"));
|
|
9742
10143
|
var import_node_path6 = __toESM(require("node:path"));
|
|
9743
10144
|
|
|
@@ -10052,7 +10453,7 @@ function deriveStoreUrl(patterns) {
|
|
|
10052
10453
|
}
|
|
10053
10454
|
|
|
10054
10455
|
// src/lib/asset-upload.ts
|
|
10055
|
-
var
|
|
10456
|
+
var import_node_crypto5 = __toESM(require("node:crypto"));
|
|
10056
10457
|
var import_node_fs7 = __toESM(require("node:fs"));
|
|
10057
10458
|
var import_node_path5 = __toESM(require("node:path"));
|
|
10058
10459
|
var INLINE_DATA_URI_MAX_CHARS = 4096;
|
|
@@ -10077,7 +10478,7 @@ var EXTENSION_BY_MIME = {
|
|
|
10077
10478
|
"image/avif": ".avif"
|
|
10078
10479
|
};
|
|
10079
10480
|
function contentHash(bytes) {
|
|
10080
|
-
return
|
|
10481
|
+
return import_node_crypto5.default.createHash("sha256").update(bytes).digest("hex");
|
|
10081
10482
|
}
|
|
10082
10483
|
function deliveryUrl(baseUrl, asset) {
|
|
10083
10484
|
const returnedUrl = asset.deliveryUrl ?? asset.delivery_url ?? asset.url;
|
|
@@ -11063,6 +11464,16 @@ function printExperimentResults(results2, currency) {
|
|
|
11063
11464
|
const outcomeProvenance = summary.outcome.provenance === "manager_decided" ? " (manager decided)" : "";
|
|
11064
11465
|
console.log();
|
|
11065
11466
|
console.log(source_default.bold(String(experiment2.name ?? experiment2.id ?? "Experiment results")));
|
|
11467
|
+
if (typeof results2.computed_at === "string") {
|
|
11468
|
+
const ageSeconds = Math.max(
|
|
11469
|
+
0,
|
|
11470
|
+
Math.round((Date.now() - Date.parse(results2.computed_at)) / 1e3)
|
|
11471
|
+
);
|
|
11472
|
+
const age = ageSeconds < 60 ? `${ageSeconds}s ago` : ageSeconds < 3600 ? `${Math.floor(ageSeconds / 60)}m ago` : `${Math.floor(ageSeconds / 3600)}h ago`;
|
|
11473
|
+
console.log(
|
|
11474
|
+
` Computed: ${results2.computed_at} (${age}, from ${String(results2.results_source ?? "unknown")})`
|
|
11475
|
+
);
|
|
11476
|
+
}
|
|
11066
11477
|
console.log(` Status: ${statusBadge(String(experiment2.status ?? "unknown"))}`);
|
|
11067
11478
|
const audience = audienceLine(results2.segment_applied);
|
|
11068
11479
|
if (audience) console.log(` Audience: ${audience}`);
|
|
@@ -11142,7 +11553,7 @@ var FILESYSTEM_FILE_RE = new RegExp(
|
|
|
11142
11553
|
`^(?:${pathAlternation(EXPERIMENT_FILESYSTEM_PATH_SCHEMA.topLevel)}|experiment/(?:${pathAlternation(EXPERIMENT_FILESYSTEM_PATH_SCHEMA.experiment)})|variations/[^/]+/(?:${pathAlternation(EXPERIMENT_FILESYSTEM_PATH_SCHEMA.variation)}))$`
|
|
11143
11554
|
);
|
|
11144
11555
|
function sha256Text(value) {
|
|
11145
|
-
return
|
|
11556
|
+
return import_node_crypto6.default.createHash("sha256").update(value).digest("hex");
|
|
11146
11557
|
}
|
|
11147
11558
|
function folderPath(folder) {
|
|
11148
11559
|
return import_node_path6.default.resolve(process.cwd(), folder);
|
|
@@ -11261,7 +11672,7 @@ async function rewriteExperimentFilesystemAssets(input) {
|
|
|
11261
11672
|
baseUrl: input.baseUrl,
|
|
11262
11673
|
enabled: input.enabled,
|
|
11263
11674
|
upload: async (file) => {
|
|
11264
|
-
const hash =
|
|
11675
|
+
const hash = import_node_crypto6.default.createHash("sha256").update(file.bytes).digest("hex");
|
|
11265
11676
|
const cached = input.assets[hash];
|
|
11266
11677
|
if (cached) return cached;
|
|
11267
11678
|
if (input.upload === null) return null;
|
|
@@ -11591,7 +12002,7 @@ var list = new Command("list").alias("ls").description(
|
|
|
11591
12002
|
"Filter by effective storefront URL targeting (applied server-side)"
|
|
11592
12003
|
).option("-n, --limit <n>", "Max results", "20").option("--include-archived", "Include archived experiments").option("--full", "Return full experiment details instead of the default summary").option(
|
|
11593
12004
|
"--fields <list>",
|
|
11594
|
-
"Comma-separated fields to return and table columns in order (JSON always keeps id)"
|
|
12005
|
+
"Comma-separated fields to return and table columns in order (JSON always keeps id; fields outside the summary switch to full details automatically)"
|
|
11595
12006
|
).action(async function action3(opts) {
|
|
11596
12007
|
const creds = requireCredentials();
|
|
11597
12008
|
const spinner = await createSpinner("Fetching experiments...");
|
|
@@ -11747,7 +12158,7 @@ ${JSON.stringify(data.order, null, 2)}`);
|
|
|
11747
12158
|
console.log(`
|
|
11748
12159
|
Definitions: ${data.definitions_url}`);
|
|
11749
12160
|
}
|
|
11750
|
-
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("--segment <dim:value>", "Include one audience segment").option("--exclude-segment <dim:value>", "Exclude one audience segment (include wins)").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) {
|
|
12161
|
+
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("--segment <dim:value>", "Include one audience segment").option("--exclude-segment <dim:value>", "Exclude one audience segment (include wins)").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").option("--wait <seconds>", "Wait for a cold server-side results build", "120").action(async function action7(idOrSlug, opts) {
|
|
11751
12162
|
if (opts.explain && (opts.segment !== void 0 || opts.excludeSegment !== void 0))
|
|
11752
12163
|
fail(
|
|
11753
12164
|
this,
|
|
@@ -11764,16 +12175,38 @@ var results = new Command("results").description("Read experiment results and ti
|
|
|
11764
12175
|
spinner?.fail(`Experiment "${idOrSlug}" not found`);
|
|
11765
12176
|
fail(this, `Experiment "${idOrSlug}" not found.`, EXIT_CODES.validation);
|
|
11766
12177
|
}
|
|
11767
|
-
const
|
|
12178
|
+
const waitSeconds = Number(opts.wait ?? "120");
|
|
12179
|
+
if (!Number.isFinite(waitSeconds) || waitSeconds < 0) {
|
|
12180
|
+
fail(this, "--wait must be a non-negative number of seconds", EXIT_CODES.validation);
|
|
12181
|
+
}
|
|
12182
|
+
const startedWaitingAt = Date.now();
|
|
12183
|
+
let res = opts.explain ? await getExperimentResultsExplain(creds, exp.id, opts) : await getExperimentResults(creds, exp.id, {
|
|
11768
12184
|
currency: opts.currency,
|
|
11769
12185
|
segment: opts.segment,
|
|
11770
12186
|
exclude_segment: opts.excludeSegment
|
|
11771
12187
|
});
|
|
12188
|
+
while (!opts.explain && isResultsComputing(res)) {
|
|
12189
|
+
const elapsedSeconds = (Date.now() - startedWaitingAt) / 1e3;
|
|
12190
|
+
const remainingSeconds = waitSeconds - elapsedSeconds;
|
|
12191
|
+
if (remainingSeconds <= 0) break;
|
|
12192
|
+
const requestedDelay = Number(
|
|
12193
|
+
res.retryAfter ?? res.errorDetails?.retry_after_seconds ?? 15
|
|
12194
|
+
);
|
|
12195
|
+
const delaySeconds = Math.min(30, Math.max(0, requestedDelay), remainingSeconds);
|
|
12196
|
+
if (!shouldUseJson(this))
|
|
12197
|
+
console.error(`Results are computing on the server; retrying in ${delaySeconds} s`);
|
|
12198
|
+
await new Promise((resolve2) => setTimeout(resolve2, delaySeconds * 1e3));
|
|
12199
|
+
res = await getExperimentResults(creds, exp.id, {
|
|
12200
|
+
currency: opts.currency,
|
|
12201
|
+
segment: opts.segment,
|
|
12202
|
+
exclude_segment: opts.excludeSegment
|
|
12203
|
+
});
|
|
12204
|
+
}
|
|
11772
12205
|
if (!res.ok || !res.data) {
|
|
11773
12206
|
spinner?.fail("Failed to fetch experiment results");
|
|
11774
12207
|
fail(
|
|
11775
12208
|
this,
|
|
11776
|
-
res.error ?? "Failed to fetch experiment results"
|
|
12209
|
+
`${res.error ?? "Failed to fetch experiment results"}${isResultsComputing(res) ? "; increase --wait to keep retrying" : ""}`,
|
|
11777
12210
|
classifyStatus(res.status),
|
|
11778
12211
|
res.errorDetails
|
|
11779
12212
|
);
|
|
@@ -11790,6 +12223,11 @@ var results = new Command("results").description("Read experiment results and ti
|
|
|
11790
12223
|
fail(this, error instanceof Error ? error.message : String(error));
|
|
11791
12224
|
}
|
|
11792
12225
|
});
|
|
12226
|
+
function isResultsComputing(res) {
|
|
12227
|
+
if (res.status !== 503) return false;
|
|
12228
|
+
const body5 = res.data;
|
|
12229
|
+
return body5?.error?.code === "results_computing" || res.errorDetails?.code === "results_computing";
|
|
12230
|
+
}
|
|
11793
12231
|
var DAY_NAMES = {
|
|
11794
12232
|
mon: "monday",
|
|
11795
12233
|
monday: "monday",
|
|
@@ -12864,7 +13302,45 @@ var applyFilesystem = new Command("apply").option(
|
|
|
12864
13302
|
acceptQaInvalidation = true;
|
|
12865
13303
|
}
|
|
12866
13304
|
const spinner = process.stderr.isTTY ? await createSpinner("Applying experiment filesystem...") : null;
|
|
12867
|
-
let
|
|
13305
|
+
let verifiedAfterTimeout = false;
|
|
13306
|
+
let verifiedTimeoutMs;
|
|
13307
|
+
let publicationUnconfirmed = false;
|
|
13308
|
+
const applyWithVerification = async (body5) => {
|
|
13309
|
+
try {
|
|
13310
|
+
return await applyExperimentFilesystem(creds, session.experimentId, body5);
|
|
13311
|
+
} catch (error) {
|
|
13312
|
+
if (!(error instanceof CliError) || error.details?.reason !== "timeout" && error.details?.reason !== "reset")
|
|
13313
|
+
throw error;
|
|
13314
|
+
const reason2 = error.details.reason;
|
|
13315
|
+
const timeoutMs = typeof error.details.timeoutMs === "number" ? error.details.timeoutMs : APPLY_TIMEOUT_MS;
|
|
13316
|
+
const verification = await planExperimentFilesystem(creds, session.experimentId, {
|
|
13317
|
+
files
|
|
13318
|
+
}).catch(() => null);
|
|
13319
|
+
if (verification?.ok && verification.data?.ok === true && verification.data.changes.length === 0) {
|
|
13320
|
+
verifiedAfterTimeout = true;
|
|
13321
|
+
verifiedTimeoutMs = timeoutMs;
|
|
13322
|
+
publicationUnconfirmed = verification.data.qaApproval?.status !== "draft";
|
|
13323
|
+
return {
|
|
13324
|
+
ok: true,
|
|
13325
|
+
status: 200,
|
|
13326
|
+
data: { applied: true, plan: verification.data }
|
|
13327
|
+
};
|
|
13328
|
+
}
|
|
13329
|
+
const rerun = `apex experiments apply ${shellQuote(folderArg)}`;
|
|
13330
|
+
throw new CliError(
|
|
13331
|
+
`Apply did not confirm within ${timeoutMs / 1e3}s and the server does not yet hold these changes; re-run ${rerun}`,
|
|
13332
|
+
EXIT_CODES.general,
|
|
13333
|
+
{
|
|
13334
|
+
code: "apply_unconfirmed",
|
|
13335
|
+
reason: reason2,
|
|
13336
|
+
timeoutMs,
|
|
13337
|
+
pendingChanges: verification?.ok && verification.data ? verification.data.changes.length : null,
|
|
13338
|
+
next_steps: [{ command: rerun, why: "Re-run the unconfirmed apply." }]
|
|
13339
|
+
}
|
|
13340
|
+
);
|
|
13341
|
+
}
|
|
13342
|
+
};
|
|
13343
|
+
let res = await applyWithVerification({
|
|
12868
13344
|
files,
|
|
12869
13345
|
accept_qa_invalidation: acceptQaInvalidation,
|
|
12870
13346
|
...opts.confirmTargetingReplace ? { confirmTargetingReplace: true } : {}
|
|
@@ -12885,7 +13361,7 @@ var applyFilesystem = new Command("apply").option(
|
|
|
12885
13361
|
output(this, { aborted: true }, () => console.log("Aborted."));
|
|
12886
13362
|
return;
|
|
12887
13363
|
}
|
|
12888
|
-
res = await
|
|
13364
|
+
res = await applyWithVerification({
|
|
12889
13365
|
files,
|
|
12890
13366
|
accept_qa_invalidation: acceptQaInvalidation,
|
|
12891
13367
|
confirmTargetingReplace: true
|
|
@@ -12907,7 +13383,8 @@ var applyFilesystem = new Command("apply").option(
|
|
|
12907
13383
|
plan: res.data?.plan
|
|
12908
13384
|
});
|
|
12909
13385
|
}
|
|
12910
|
-
spinner?.
|
|
13386
|
+
if (publicationUnconfirmed) spinner?.stop();
|
|
13387
|
+
else spinner?.succeed("Applied experiment filesystem");
|
|
12911
13388
|
const synced = opts.sync ? await refreshExperimentFilesystemFolderIfNeeded(creds, folder, session.experimentId) : false;
|
|
12912
13389
|
if (!synced) {
|
|
12913
13390
|
await refreshServerOwnedFilesIfNeeded(
|
|
@@ -12931,6 +13408,11 @@ var applyFilesystem = new Command("apply").option(
|
|
|
12931
13408
|
const syncMessage = synced ? "Local files were synced." : opts.sync ? "Local file sync failed; retry --sync or apex experiments pull." : "Local authored files were left unchanged; use --sync or apex experiments pull to refresh." + (res.data?.receipt?.files ? "" : " No receipt file digests; authored-file manifest entries left unchanged.");
|
|
12932
13409
|
const previewResult = await getQaPreviewUrls(creds, session.experimentId);
|
|
12933
13410
|
const { previewUrls, dashboardUrl } = previewResult;
|
|
13411
|
+
const publicationWarning = "Storefront publication is unconfirmed; visitors may still receive the previous treatment.";
|
|
13412
|
+
const publicationNextStep = {
|
|
13413
|
+
command: `apex experiments status ${shellQuote(folderArg)} --json`,
|
|
13414
|
+
why: "Check experiment status before handover; persisted files do not confirm storefront publication."
|
|
13415
|
+
};
|
|
12934
13416
|
output(
|
|
12935
13417
|
this,
|
|
12936
13418
|
{
|
|
@@ -12939,14 +13421,30 @@ var applyFilesystem = new Command("apply").option(
|
|
|
12939
13421
|
applied: true,
|
|
12940
13422
|
plan: planRes.data,
|
|
12941
13423
|
requiredAcknowledgment: requiredQaAcknowledgment(planRes.data),
|
|
12942
|
-
result: res.data,
|
|
13424
|
+
result: verifiedAfterTimeout ? null : res.data,
|
|
12943
13425
|
synced,
|
|
12944
13426
|
dashboardUrl,
|
|
12945
|
-
receipt: res.data?.receipt ?? null,
|
|
12946
|
-
previewUrls
|
|
13427
|
+
receipt: verifiedAfterTimeout ? null : res.data?.receipt ?? null,
|
|
13428
|
+
previewUrls,
|
|
13429
|
+
...verifiedAfterTimeout ? { verifiedAfterTimeout: true, timeoutMs: verifiedTimeoutMs } : {},
|
|
13430
|
+
...publicationUnconfirmed ? {
|
|
13431
|
+
persisted: true,
|
|
13432
|
+
publication: "unconfirmed",
|
|
13433
|
+
warnings: [publicationWarning],
|
|
13434
|
+
next_steps: [publicationNextStep]
|
|
13435
|
+
} : {}
|
|
12947
13436
|
},
|
|
12948
13437
|
() => {
|
|
12949
13438
|
printFilesystemPlan(planRes.data);
|
|
13439
|
+
if (verifiedAfterTimeout) {
|
|
13440
|
+
console.log(
|
|
13441
|
+
`${publicationUnconfirmed ? "Apply saved" : "Apply confirmed"}: the server already holds these changes (the response timed out after ${verifiedTimeoutMs / 1e3}s).`
|
|
13442
|
+
);
|
|
13443
|
+
}
|
|
13444
|
+
if (publicationUnconfirmed) {
|
|
13445
|
+
console.log(`Warning: ${publicationWarning}`);
|
|
13446
|
+
console.log(`Next: ${publicationNextStep.command} (${publicationNextStep.why})`);
|
|
13447
|
+
}
|
|
12950
13448
|
const receipt = res.data?.receipt;
|
|
12951
13449
|
if (receipt) {
|
|
12952
13450
|
console.log("\nReceipt");
|
|
@@ -13107,6 +13605,9 @@ var qaApprove = new Command("approve").description("Approve QA and move the expe
|
|
|
13107
13605
|
"[folder-or-id]",
|
|
13108
13606
|
"Experiment filesystem folder, ID, slug, or test key (for example BIO-085)",
|
|
13109
13607
|
"."
|
|
13608
|
+
).option(
|
|
13609
|
+
"--acknowledge-zero-traffic",
|
|
13610
|
+
"Acknowledge variations that receive no traffic for this split (Apex staff only)"
|
|
13110
13611
|
).action(async function action19(target) {
|
|
13111
13612
|
const creds = requireCredentials();
|
|
13112
13613
|
const spinner = await createSpinner("Approving QA...");
|
|
@@ -13114,7 +13615,40 @@ var qaApprove = new Command("approve").description("Approve QA and move the expe
|
|
|
13114
13615
|
const resolved = await resolveExperimentForFilesystemOrId(target);
|
|
13115
13616
|
const before = await resolveExperiment(resolved.experimentId);
|
|
13116
13617
|
if (!before) fail(this, "Experiment not found.", EXIT_CODES.validation);
|
|
13117
|
-
|
|
13618
|
+
let res = await approveExperimentQa(creds, resolved.experimentId);
|
|
13619
|
+
const details = res.errorDetails;
|
|
13620
|
+
if (!res.ok && res.status === 409 && details?.code === "degenerate_weights_ack_required") {
|
|
13621
|
+
if (!this.opts().acknowledgeZeroTraffic) {
|
|
13622
|
+
throw new CliError(
|
|
13623
|
+
`${res.error ?? "QA approval requires acknowledgement"} Rerun with --acknowledge-zero-traffic.`,
|
|
13624
|
+
classifyStatus(res.status),
|
|
13625
|
+
details
|
|
13626
|
+
);
|
|
13627
|
+
}
|
|
13628
|
+
if (details.internal_only === true) {
|
|
13629
|
+
throw new CliError(
|
|
13630
|
+
res.error ?? "Only Apex Admin or Internal users can acknowledge zero traffic.",
|
|
13631
|
+
classifyStatus(res.status),
|
|
13632
|
+
details
|
|
13633
|
+
);
|
|
13634
|
+
}
|
|
13635
|
+
if (typeof details.split_hash === "string" && details.split_hash.length > 0) {
|
|
13636
|
+
const weights = Array.isArray(details.variation_weights) ? details.variation_weights : [];
|
|
13637
|
+
for (const value of weights) {
|
|
13638
|
+
if (value && typeof value === "object") {
|
|
13639
|
+
const variation = value;
|
|
13640
|
+
process.stderr.write(
|
|
13641
|
+
`${variation.name ?? variation.id ?? "Variation"}: ${(variation.weight ?? 0) * 100}%${variation.weight === 0 ? " (no traffic)" : ""}
|
|
13642
|
+
`
|
|
13643
|
+
);
|
|
13644
|
+
}
|
|
13645
|
+
}
|
|
13646
|
+
res = await approveExperimentQa(creds, resolved.experimentId, {
|
|
13647
|
+
acknowledge_degenerate_weights: true,
|
|
13648
|
+
acknowledge_degenerate_weights_hash: details.split_hash
|
|
13649
|
+
});
|
|
13650
|
+
}
|
|
13651
|
+
}
|
|
13118
13652
|
if (!res.ok)
|
|
13119
13653
|
throw new CliError(
|
|
13120
13654
|
res.error ?? "Failed to approve QA",
|
|
@@ -13419,7 +13953,7 @@ var screenshots = new Command("screenshots").description(
|
|
|
13419
13953
|
Math.min(resumeDelayMs, Math.max(0, resumeDeadline - screenshotNow()))
|
|
13420
13954
|
)
|
|
13421
13955
|
);
|
|
13422
|
-
resumeDelayMs = Math.min(Math.round(resumeDelayMs * 1.6),
|
|
13956
|
+
resumeDelayMs = Math.min(Math.round(resumeDelayMs * 1.6), 4e3);
|
|
13423
13957
|
continue;
|
|
13424
13958
|
}
|
|
13425
13959
|
if (!existing.ok && isTransientResumeStatus(existing.status) && attempt <= 3 && screenshotNow() < resumeDeadline) {
|
|
@@ -13429,7 +13963,7 @@ var screenshots = new Command("screenshots").description(
|
|
|
13429
13963
|
Math.min(resumeDelayMs, Math.max(0, resumeDeadline - screenshotNow()))
|
|
13430
13964
|
)
|
|
13431
13965
|
);
|
|
13432
|
-
resumeDelayMs = Math.min(Math.round(resumeDelayMs * 1.6),
|
|
13966
|
+
resumeDelayMs = Math.min(Math.round(resumeDelayMs * 1.6), 4e3);
|
|
13433
13967
|
continue;
|
|
13434
13968
|
}
|
|
13435
13969
|
break;
|
|
@@ -13583,7 +14117,7 @@ var screenshots = new Command("screenshots").description(
|
|
|
13583
14117
|
);
|
|
13584
14118
|
consecutiveTransientFailures += 1;
|
|
13585
14119
|
if (consecutiveTransientFailures > 3) throw error;
|
|
13586
|
-
delayMs = Math.min(Math.round(delayMs * 1.6),
|
|
14120
|
+
delayMs = Math.min(Math.round(delayMs * 1.6), 4e3);
|
|
13587
14121
|
continue;
|
|
13588
14122
|
}
|
|
13589
14123
|
const polledToTerminalState = polled.ok && polled.data && polled.data.status !== "queued" && polled.data.status !== "processing";
|
|
@@ -13608,11 +14142,11 @@ var screenshots = new Command("screenshots").description(
|
|
|
13608
14142
|
polled.errorDetails
|
|
13609
14143
|
);
|
|
13610
14144
|
}
|
|
13611
|
-
delayMs = Math.min(Math.round(delayMs * 1.6),
|
|
14145
|
+
delayMs = Math.min(Math.round(delayMs * 1.6), 4e3);
|
|
13612
14146
|
continue;
|
|
13613
14147
|
}
|
|
13614
14148
|
consecutiveTransientFailures = 0;
|
|
13615
|
-
delayMs = Math.min(Math.round(delayMs * 1.6),
|
|
14149
|
+
delayMs = Math.min(Math.round(delayMs * 1.6), 4e3);
|
|
13616
14150
|
}
|
|
13617
14151
|
if (opts.attachQa !== false) {
|
|
13618
14152
|
const attached = await attachExperimentQaEvidence(creds, experiment2.id, {
|
|
@@ -13990,6 +14524,8 @@ init_source();
|
|
|
13990
14524
|
async function signedPreviewFromReadiness(creds, experimentId, variationId, targetUrl) {
|
|
13991
14525
|
try {
|
|
13992
14526
|
const client = createHttpClient(creds, {
|
|
14527
|
+
onResponse: recordApiResponse2,
|
|
14528
|
+
observeNetworkErrors: true,
|
|
13993
14529
|
onNetworkError: (info) => {
|
|
13994
14530
|
throw new Error(info.cause);
|
|
13995
14531
|
}
|
|
@@ -14233,8 +14769,7 @@ function loadHarnessConfig(folder) {
|
|
|
14233
14769
|
experimentName: experiment2.name ?? id,
|
|
14234
14770
|
variationSlug: slugify(variation, "variant-a"),
|
|
14235
14771
|
variationName: `Variant ${variation}`,
|
|
14236
|
-
targetUrl
|
|
14237
|
-
antiFlickerSelectors: []
|
|
14772
|
+
targetUrl
|
|
14238
14773
|
}
|
|
14239
14774
|
};
|
|
14240
14775
|
}
|
|
@@ -14318,14 +14853,15 @@ async function createSession(folder) {
|
|
|
14318
14853
|
if (!apex.targetUrl) {
|
|
14319
14854
|
throw new Error("Missing targetUrl. Run `apex harness init` and set targetUrl.");
|
|
14320
14855
|
}
|
|
14856
|
+
const resetJs = cleanupResetJs(experiment2);
|
|
14321
14857
|
const res = await createHarnessSession(creds, {
|
|
14322
14858
|
experimentSlug: apex.experimentSlug,
|
|
14323
14859
|
experimentName: apex.experimentName,
|
|
14324
14860
|
variationSlug: apex.variationSlug,
|
|
14325
14861
|
variationName: apex.variationName,
|
|
14326
14862
|
targetUrl: apex.targetUrl,
|
|
14327
|
-
resetJs:
|
|
14328
|
-
antiFlickerSelectors: apex.antiFlickerSelectors
|
|
14863
|
+
...typeof resetJs === "string" ? { resetJs } : {},
|
|
14864
|
+
...Array.isArray(apex.antiFlickerSelectors) ? { antiFlickerSelectors: apex.antiFlickerSelectors } : {}
|
|
14329
14865
|
});
|
|
14330
14866
|
if (!res.ok)
|
|
14331
14867
|
throw new CliError(
|
|
@@ -14453,7 +14989,7 @@ harnessCommand.command("publish").argument("<folder>", "AB Test Harness experime
|
|
|
14453
14989
|
|
|
14454
14990
|
// src/commands/operator.ts
|
|
14455
14991
|
init_source();
|
|
14456
|
-
var
|
|
14992
|
+
var import_node_crypto7 = __toESM(require("node:crypto"));
|
|
14457
14993
|
var import_node_fs11 = __toESM(require("node:fs"));
|
|
14458
14994
|
var import_node_path9 = __toESM(require("node:path"));
|
|
14459
14995
|
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)$/;
|
|
@@ -14498,8 +15034,13 @@ function formatWorkspaceBlockingIssues(value) {
|
|
|
14498
15034
|
return lines.join("\n");
|
|
14499
15035
|
}).join("\n");
|
|
14500
15036
|
}
|
|
15037
|
+
function formatHarnessRefusal(message, details) {
|
|
15038
|
+
const issues = formatWorkspaceBlockingIssues(details);
|
|
15039
|
+
return issues ? `${message}
|
|
15040
|
+
${issues}` : message;
|
|
15041
|
+
}
|
|
14501
15042
|
function sha256Text2(value) {
|
|
14502
|
-
return
|
|
15043
|
+
return import_node_crypto7.default.createHash("sha256").update(value).digest("hex");
|
|
14503
15044
|
}
|
|
14504
15045
|
function byteLength(value) {
|
|
14505
15046
|
return Buffer.byteLength(value, "utf8");
|
|
@@ -14692,12 +15233,11 @@ async function createExactHarnessSession(folder, session) {
|
|
|
14692
15233
|
experimentName: expRes.data.name,
|
|
14693
15234
|
variationSlug: config.variationSlug ?? variation.slug ?? slugify2(variation.name, "variation"),
|
|
14694
15235
|
variationName: variation.name,
|
|
14695
|
-
targetUrl
|
|
14696
|
-
antiFlickerSelectors: []
|
|
15236
|
+
targetUrl
|
|
14697
15237
|
});
|
|
14698
15238
|
if (!res.ok || !res.data)
|
|
14699
15239
|
throw new CliError(
|
|
14700
|
-
res.error ?? "Failed to create harness session",
|
|
15240
|
+
formatHarnessRefusal(res.error ?? "Failed to create harness session", res.errorDetails),
|
|
14701
15241
|
classifyStatus(res.status),
|
|
14702
15242
|
res.errorDetails
|
|
14703
15243
|
);
|
|
@@ -14789,7 +15329,15 @@ async function runPublish(folderArg, opts = {}) {
|
|
|
14789
15329
|
const harnessSession = await createExactHarnessSession(folder, session);
|
|
14790
15330
|
const version = await uploadCurrentArtifactVersion(folder, harnessSession.sessionId);
|
|
14791
15331
|
const published = await publishHarnessSession(creds, harnessSession.sessionId);
|
|
14792
|
-
if (!published.ok)
|
|
15332
|
+
if (!published.ok)
|
|
15333
|
+
throw new CliError(
|
|
15334
|
+
formatHarnessRefusal(
|
|
15335
|
+
published.error ?? "Failed to publish external artifacts",
|
|
15336
|
+
published.errorDetails
|
|
15337
|
+
),
|
|
15338
|
+
classifyStatus(published.status),
|
|
15339
|
+
published.errorDetails
|
|
15340
|
+
);
|
|
14793
15341
|
external = {
|
|
14794
15342
|
sessionId: harnessSession.sessionId,
|
|
14795
15343
|
version: version.version,
|
|
@@ -15436,10 +15984,81 @@ trackingCommand.command("latency").description("Measure forced-preview versus na
|
|
|
15436
15984
|
|
|
15437
15985
|
// src/commands/events.ts
|
|
15438
15986
|
var import_node_fs14 = __toESM(require("node:fs"));
|
|
15987
|
+
var maxWindowMs = 31 * 864e5;
|
|
15988
|
+
function correctedWindowStep(opts, nowMs) {
|
|
15989
|
+
const fromMs = Date.parse(opts.from);
|
|
15990
|
+
const endMs = Math.min(fromMs + maxWindowMs, nowMs);
|
|
15991
|
+
if (endMs <= fromMs) {
|
|
15992
|
+
return {
|
|
15993
|
+
command: "Choose a --from start before now.",
|
|
15994
|
+
why: "No valid export window exists from this start; --to must be after --from and at or before now."
|
|
15995
|
+
};
|
|
15996
|
+
}
|
|
15997
|
+
const quote = (value) => `'${value.replace(/'/g, "'\\''")}'`;
|
|
15998
|
+
const args = [
|
|
15999
|
+
"apex events export",
|
|
16000
|
+
`--from ${quote(opts.from)}`,
|
|
16001
|
+
`--to ${quote(new Date(endMs).toISOString())}`,
|
|
16002
|
+
`--out ${quote(opts.out ?? "events.ndjson")}`
|
|
16003
|
+
];
|
|
16004
|
+
if (opts.experiment !== void 0) args.push(`--experiment ${quote(opts.experiment)}`);
|
|
16005
|
+
if (opts.eventType !== void 0) args.push(`--event-type ${quote(opts.eventType)}`);
|
|
16006
|
+
args.push(`--qa ${quote(opts.qa)}`, `--limit ${quote(opts.limit)}`);
|
|
16007
|
+
if (opts.jsonSummary) args.push("--json-summary");
|
|
16008
|
+
return {
|
|
16009
|
+
command: args.join(" "),
|
|
16010
|
+
why: endMs === nowMs ? "Exports events through the current time with the same filters." : "Exports the first 31 days with the same filters; repeat with the next window."
|
|
16011
|
+
};
|
|
16012
|
+
}
|
|
15439
16013
|
var eventsCommand = new Command("events").description("Export raw tracking events");
|
|
15440
|
-
eventsCommand.command("export").description(
|
|
16014
|
+
eventsCommand.command("export").description(
|
|
16015
|
+
"Export every raw event in a window of at most 31 days that does not end in the future"
|
|
16016
|
+
).requiredOption(
|
|
16017
|
+
"--from <iso>",
|
|
16018
|
+
"Inclusive ISO 8601 window start; the window spans at most 31 days"
|
|
16019
|
+
).requiredOption("--to <iso>", "Exclusive ISO 8601 window end; not in the future").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) => {
|
|
15441
16020
|
if (!Number.isFinite(Date.parse(opts.from)) || !Number.isFinite(Date.parse(opts.to))) {
|
|
15442
|
-
fail(command, "--from and --to must be ISO 8601 timestamps", EXIT_CODES.validation
|
|
16021
|
+
fail(command, "--from and --to must be ISO 8601 timestamps", EXIT_CODES.validation, {
|
|
16022
|
+
code: "invalid_window",
|
|
16023
|
+
reason: "invalid_timestamp"
|
|
16024
|
+
});
|
|
16025
|
+
}
|
|
16026
|
+
const fromMs = Date.parse(opts.from);
|
|
16027
|
+
const toMs = Date.parse(opts.to);
|
|
16028
|
+
if (toMs <= fromMs)
|
|
16029
|
+
fail(command, "--to must be after --from.", EXIT_CODES.validation, {
|
|
16030
|
+
code: "invalid_window",
|
|
16031
|
+
reason: "to_not_after_from"
|
|
16032
|
+
});
|
|
16033
|
+
const now = /* @__PURE__ */ new Date();
|
|
16034
|
+
if (toMs - fromMs > maxWindowMs) {
|
|
16035
|
+
const requestedDays = ((toMs - fromMs) / 864e5).toFixed(2);
|
|
16036
|
+
fail(
|
|
16037
|
+
command,
|
|
16038
|
+
`The export window is ${requestedDays} days; the maximum is 31. Export in windows of at most 31 days.`,
|
|
16039
|
+
EXIT_CODES.validation,
|
|
16040
|
+
{
|
|
16041
|
+
code: "invalid_window",
|
|
16042
|
+
reason: "window_too_long",
|
|
16043
|
+
max_days: 31,
|
|
16044
|
+
requested_days: requestedDays,
|
|
16045
|
+
next_steps: [correctedWindowStep(opts, now.getTime())]
|
|
16046
|
+
}
|
|
16047
|
+
);
|
|
16048
|
+
}
|
|
16049
|
+
if (toMs > now.getTime()) {
|
|
16050
|
+
const nowIso = now.toISOString();
|
|
16051
|
+
fail(
|
|
16052
|
+
command,
|
|
16053
|
+
`--to is in the future; use a time at or before now (${nowIso}).`,
|
|
16054
|
+
EXIT_CODES.validation,
|
|
16055
|
+
{
|
|
16056
|
+
code: "invalid_window",
|
|
16057
|
+
reason: "to_in_future",
|
|
16058
|
+
now: nowIso,
|
|
16059
|
+
next_steps: [correctedWindowStep(opts, now.getTime())]
|
|
16060
|
+
}
|
|
16061
|
+
);
|
|
15443
16062
|
}
|
|
15444
16063
|
if (!["exclude", "include", "only"].includes(opts.qa)) {
|
|
15445
16064
|
fail(command, "--qa must be exclude, include, or only", EXIT_CODES.validation);
|
|
@@ -15479,129 +16098,150 @@ eventsCommand.command("export").description("Export every raw event in an absolu
|
|
|
15479
16098
|
output2.once("error", reject);
|
|
15480
16099
|
});
|
|
15481
16100
|
} catch (error) {
|
|
16101
|
+
const exists = error?.code === "EEXIST";
|
|
15482
16102
|
fail(
|
|
15483
16103
|
command,
|
|
15484
|
-
`Cannot write ${opts.out}: ${error instanceof Error ? error.message : String(error)}`,
|
|
15485
|
-
EXIT_CODES.general,
|
|
15486
|
-
{ code: "local_file_error" }
|
|
16104
|
+
exists ? `Cannot write ${opts.out}: the file already exists. Choose a new --out path or delete the file first.` : `Cannot write ${opts.out}: ${error instanceof Error ? error.message : String(error)}`,
|
|
16105
|
+
exists ? EXIT_CODES.validation : EXIT_CODES.general,
|
|
16106
|
+
{ code: exists ? "output_exists" : "local_file_error", path: opts.out }
|
|
15487
16107
|
);
|
|
15488
16108
|
}
|
|
15489
16109
|
}
|
|
15490
|
-
|
|
15491
|
-
const
|
|
15492
|
-
|
|
15493
|
-
|
|
15494
|
-
|
|
15495
|
-
|
|
15496
|
-
|
|
15497
|
-
|
|
15498
|
-
|
|
15499
|
-
|
|
15500
|
-
|
|
15501
|
-
|
|
15502
|
-
|
|
15503
|
-
|
|
15504
|
-
|
|
15505
|
-
|
|
15506
|
-
|
|
15507
|
-
|
|
15508
|
-
|
|
15509
|
-
|
|
15510
|
-
|
|
15511
|
-
|
|
15512
|
-
|
|
15513
|
-
|
|
15514
|
-
|
|
15515
|
-
|
|
15516
|
-
|
|
15517
|
-
|
|
15518
|
-
|
|
15519
|
-
|
|
15520
|
-
|
|
15521
|
-
|
|
15522
|
-
|
|
15523
|
-
fail(
|
|
15524
|
-
command,
|
|
15525
|
-
response.error ?? "Event export page failed",
|
|
15526
|
-
classifyStatus(response.status),
|
|
15527
|
-
response.errorDetails
|
|
15528
|
-
);
|
|
15529
|
-
}
|
|
15530
|
-
if (outputError)
|
|
15531
|
-
fail(
|
|
15532
|
-
command,
|
|
15533
|
-
`Cannot write ${opts.out}: ${outputError.message}`,
|
|
15534
|
-
EXIT_CODES.general,
|
|
15535
|
-
{
|
|
15536
|
-
code: "local_file_error"
|
|
15537
|
-
}
|
|
15538
|
-
);
|
|
15539
|
-
const page = response.data.data;
|
|
15540
|
-
totals ??= page.totals;
|
|
15541
|
-
for (const event of page.events) {
|
|
15542
|
-
try {
|
|
15543
|
-
await writeLine(`${JSON.stringify(event)}
|
|
15544
|
-
`);
|
|
15545
|
-
} catch (error) {
|
|
16110
|
+
try {
|
|
16111
|
+
const writeLine = async (line) => {
|
|
16112
|
+
const stream = output2 ?? process.stdout;
|
|
16113
|
+
const accepted = output2 ? output2.write(line) : writeRawCommandOutput(line);
|
|
16114
|
+
if (accepted) return;
|
|
16115
|
+
await new Promise((resolve2, reject) => {
|
|
16116
|
+
const onDrain = () => {
|
|
16117
|
+
stream.off("error", onError);
|
|
16118
|
+
resolve2();
|
|
16119
|
+
};
|
|
16120
|
+
const onError = (error) => {
|
|
16121
|
+
stream.off("drain", onDrain);
|
|
16122
|
+
reject(error);
|
|
16123
|
+
};
|
|
16124
|
+
stream.once("drain", onDrain);
|
|
16125
|
+
stream.once("error", onError);
|
|
16126
|
+
});
|
|
16127
|
+
};
|
|
16128
|
+
let cursor;
|
|
16129
|
+
let written = 0;
|
|
16130
|
+
let pages = 0;
|
|
16131
|
+
let totals;
|
|
16132
|
+
do {
|
|
16133
|
+
const response = await exportEventsPage(requireCredentials(), {
|
|
16134
|
+
from: opts.from,
|
|
16135
|
+
to: opts.to,
|
|
16136
|
+
experiment: opts.experiment,
|
|
16137
|
+
eventTypes: opts.eventType?.split(",").map((value) => value.trim()).filter(Boolean),
|
|
16138
|
+
qa: opts.qa,
|
|
16139
|
+
limit,
|
|
16140
|
+
cursor
|
|
16141
|
+
});
|
|
16142
|
+
if (!response.ok || !response.data) {
|
|
15546
16143
|
fail(
|
|
15547
16144
|
command,
|
|
15548
|
-
|
|
15549
|
-
|
|
15550
|
-
|
|
16145
|
+
response.error ?? "Event export page failed",
|
|
16146
|
+
classifyStatus(response.status),
|
|
16147
|
+
response.errorDetails
|
|
15551
16148
|
);
|
|
15552
16149
|
}
|
|
15553
|
-
|
|
15554
|
-
|
|
15555
|
-
|
|
15556
|
-
|
|
15557
|
-
|
|
15558
|
-
|
|
15559
|
-
|
|
15560
|
-
|
|
15561
|
-
|
|
15562
|
-
|
|
15563
|
-
|
|
15564
|
-
|
|
15565
|
-
|
|
15566
|
-
|
|
15567
|
-
|
|
15568
|
-
|
|
15569
|
-
|
|
15570
|
-
|
|
15571
|
-
|
|
16150
|
+
if (outputError)
|
|
16151
|
+
fail(
|
|
16152
|
+
command,
|
|
16153
|
+
`Cannot write ${opts.out}: ${outputError.message}`,
|
|
16154
|
+
EXIT_CODES.general,
|
|
16155
|
+
{
|
|
16156
|
+
code: "local_file_error"
|
|
16157
|
+
}
|
|
16158
|
+
);
|
|
16159
|
+
const page = response.data.data;
|
|
16160
|
+
totals ??= page.totals;
|
|
16161
|
+
for (const event of page.events) {
|
|
16162
|
+
try {
|
|
16163
|
+
await writeLine(`${JSON.stringify(event)}
|
|
16164
|
+
`);
|
|
16165
|
+
} catch (error) {
|
|
16166
|
+
fail(
|
|
16167
|
+
command,
|
|
16168
|
+
`Cannot write ${opts.out ?? "stdout"}: ${error instanceof Error ? error.message : String(error)}`,
|
|
16169
|
+
EXIT_CODES.general,
|
|
16170
|
+
{ code: "local_file_error" }
|
|
16171
|
+
);
|
|
16172
|
+
}
|
|
16173
|
+
written += 1;
|
|
16174
|
+
}
|
|
16175
|
+
pages += 1;
|
|
16176
|
+
cursor = page.next_cursor ?? void 0;
|
|
16177
|
+
} while (cursor);
|
|
16178
|
+
if (output2) {
|
|
16179
|
+
try {
|
|
16180
|
+
await new Promise((resolve2, reject) => {
|
|
16181
|
+
if (outputError) return reject(outputError);
|
|
16182
|
+
output2.once("error", reject);
|
|
16183
|
+
output2.end(resolve2);
|
|
16184
|
+
});
|
|
16185
|
+
} catch (error) {
|
|
16186
|
+
fail(
|
|
16187
|
+
command,
|
|
16188
|
+
`Cannot write ${opts.out}: ${error instanceof Error ? error.message : String(error)}`,
|
|
16189
|
+
EXIT_CODES.general,
|
|
16190
|
+
{ code: "local_file_error" }
|
|
16191
|
+
);
|
|
16192
|
+
}
|
|
16193
|
+
if (outputError)
|
|
16194
|
+
fail(
|
|
16195
|
+
command,
|
|
16196
|
+
`Cannot write ${opts.out}: ${outputError.message}`,
|
|
16197
|
+
EXIT_CODES.general,
|
|
16198
|
+
{
|
|
16199
|
+
code: "local_file_error"
|
|
16200
|
+
}
|
|
16201
|
+
);
|
|
15572
16202
|
}
|
|
15573
|
-
|
|
16203
|
+
const matches = totals ? written === totals.events : false;
|
|
16204
|
+
const summary = {
|
|
16205
|
+
events_written: written,
|
|
16206
|
+
pages,
|
|
16207
|
+
totals: totals ?? null,
|
|
16208
|
+
count_matches: matches
|
|
16209
|
+
};
|
|
16210
|
+
if (!matches)
|
|
15574
16211
|
fail(
|
|
15575
16212
|
command,
|
|
15576
|
-
|
|
16213
|
+
"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.",
|
|
15577
16214
|
EXIT_CODES.general,
|
|
15578
16215
|
{
|
|
15579
|
-
|
|
16216
|
+
reason: "export_count_mismatch",
|
|
16217
|
+
...summary
|
|
15580
16218
|
}
|
|
15581
16219
|
);
|
|
15582
|
-
|
|
15583
|
-
|
|
15584
|
-
|
|
15585
|
-
events_written: written,
|
|
15586
|
-
pages,
|
|
15587
|
-
totals: totals ?? null,
|
|
15588
|
-
count_matches: matches
|
|
15589
|
-
};
|
|
15590
|
-
if (!matches)
|
|
15591
|
-
fail(
|
|
15592
|
-
command,
|
|
15593
|
-
"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.",
|
|
15594
|
-
EXIT_CODES.general,
|
|
15595
|
-
{
|
|
15596
|
-
reason: "export_count_mismatch",
|
|
15597
|
-
...summary
|
|
15598
|
-
}
|
|
15599
|
-
);
|
|
15600
|
-
if (summaryOnStdout) writeEnvelope(summary, "apex events export");
|
|
15601
|
-
process.stderr.write(
|
|
15602
|
-
`Exported ${written} events across ${pages} page(s); total ${totals?.events ?? "unknown"}; count matches: ${matches}.
|
|
16220
|
+
if (summaryOnStdout) writeEnvelope(summary, "apex events export");
|
|
16221
|
+
process.stderr.write(
|
|
16222
|
+
`Exported ${written} events across ${pages} page(s); total ${totals?.events ?? "unknown"}; count matches: ${matches}.
|
|
15603
16223
|
`
|
|
15604
|
-
|
|
16224
|
+
);
|
|
16225
|
+
} catch (error) {
|
|
16226
|
+
if (output2 && opts.out) {
|
|
16227
|
+
output2.destroy();
|
|
16228
|
+
import_node_fs14.default.rmSync(opts.out, { force: true });
|
|
16229
|
+
const suffix = `The partial file ${opts.out} was removed.`;
|
|
16230
|
+
if (error instanceof CliError)
|
|
16231
|
+
throw new CliError(
|
|
16232
|
+
`${error.message} ${suffix}`,
|
|
16233
|
+
error.exitCode,
|
|
16234
|
+
{ ...error.details, partial_file_removed: true },
|
|
16235
|
+
error.api
|
|
16236
|
+
);
|
|
16237
|
+
throw new CliError(
|
|
16238
|
+
`${error instanceof Error ? error.message : String(error)} ${suffix}`,
|
|
16239
|
+
EXIT_CODES.general,
|
|
16240
|
+
{ code: "local_file_error", partial_file_removed: true }
|
|
16241
|
+
);
|
|
16242
|
+
}
|
|
16243
|
+
throw error;
|
|
16244
|
+
}
|
|
15605
16245
|
});
|
|
15606
16246
|
|
|
15607
16247
|
// src/commands/goals.ts
|
|
@@ -15652,6 +16292,10 @@ function sdkPageviewGoalMatch(urlPattern, href) {
|
|
|
15652
16292
|
var INVENTORY_PAGE_SIZE = 100;
|
|
15653
16293
|
var GOALS_PAGE_SIZE = 100;
|
|
15654
16294
|
var MAX_GOAL_PAGES = 100;
|
|
16295
|
+
var MAX_PAGE_LOOKUP_PAGES = 1e3;
|
|
16296
|
+
var UUID_V1_TO_V8 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
16297
|
+
var PAGE_OPTION_DESCRIPTION = "Page scope: page ID or page key (required for click goals, optional for pageview; a page key needs pages:read; a page ID does not; list pages with apex pages list)";
|
|
16298
|
+
var PAGE_NEXT_STEPS = [{ command: "apex pages list", why: "Find the page ID or key." }];
|
|
15655
16299
|
var NON_QUANTIFIABLE = /* @__PURE__ */ new Set(["(", "|", "^", "$"]);
|
|
15656
16300
|
function urlPatternDependence(pattern) {
|
|
15657
16301
|
let fragment = false;
|
|
@@ -15794,6 +16438,58 @@ function parseCapture(command, value) {
|
|
|
15794
16438
|
function cleanBody(input) {
|
|
15795
16439
|
return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== void 0));
|
|
15796
16440
|
}
|
|
16441
|
+
async function resolveGoalPageId(command, creds, value) {
|
|
16442
|
+
if (UUID_V1_TO_V8.test(value)) return value;
|
|
16443
|
+
let cursor;
|
|
16444
|
+
for (let page = 0; page < MAX_PAGE_LOOKUP_PAGES; page += 1) {
|
|
16445
|
+
const response = await requestTargetingResource(
|
|
16446
|
+
creds,
|
|
16447
|
+
"GET",
|
|
16448
|
+
"pages",
|
|
16449
|
+
void 0,
|
|
16450
|
+
void 0,
|
|
16451
|
+
100,
|
|
16452
|
+
cursor
|
|
16453
|
+
);
|
|
16454
|
+
if (!response.ok || !response.data) {
|
|
16455
|
+
if (response.status === 403) {
|
|
16456
|
+
fail(
|
|
16457
|
+
command,
|
|
16458
|
+
"Resolving a page key needs the pages:read permission. Pass the page ID instead (find it with apex pages list using a key that has pages:read, or in the dashboard).",
|
|
16459
|
+
EXIT_CODES.auth,
|
|
16460
|
+
{
|
|
16461
|
+
code: "page_lookup_forbidden",
|
|
16462
|
+
next_steps: [
|
|
16463
|
+
{
|
|
16464
|
+
command: "apex pages list",
|
|
16465
|
+
why: "Use a key with pages:read to find the page ID, or get it from the dashboard; pass --page <page ID> instead."
|
|
16466
|
+
}
|
|
16467
|
+
]
|
|
16468
|
+
}
|
|
16469
|
+
);
|
|
16470
|
+
}
|
|
16471
|
+
fail(
|
|
16472
|
+
command,
|
|
16473
|
+
response.error ?? "Failed to list pages",
|
|
16474
|
+
classifyStatus(response.status),
|
|
16475
|
+
response.errorDetails
|
|
16476
|
+
);
|
|
16477
|
+
}
|
|
16478
|
+
if (!("data" in response.data) || !Array.isArray(response.data.data)) break;
|
|
16479
|
+
const pageData = response.data;
|
|
16480
|
+
const match2 = pageData.data.find((item) => item.key === value);
|
|
16481
|
+
if (match2) return match2.id;
|
|
16482
|
+
const next = pageData.pagination?.next_cursor ?? null;
|
|
16483
|
+
if (!pageData.pagination?.has_more || !next || next === cursor) break;
|
|
16484
|
+
cursor = next;
|
|
16485
|
+
}
|
|
16486
|
+
fail(
|
|
16487
|
+
command,
|
|
16488
|
+
`No active page with key ${value} in this shop; list pages with apex pages list`,
|
|
16489
|
+
EXIT_CODES.validation,
|
|
16490
|
+
{ code: "page_not_found", next_steps: PAGE_NEXT_STEPS }
|
|
16491
|
+
);
|
|
16492
|
+
}
|
|
15797
16493
|
function printGoal(goal) {
|
|
15798
16494
|
console.log();
|
|
15799
16495
|
console.log(source_default.bold(goal.name));
|
|
@@ -15806,6 +16502,7 @@ function printGoal(goal) {
|
|
|
15806
16502
|
if (goal.text_match) console.log(` Text match: ${goal.text_match}`);
|
|
15807
16503
|
if (goal.url_pattern) console.log(` URL: ${goal.url_pattern}`);
|
|
15808
16504
|
if (goal.event_name) console.log(` Event: ${goal.event_name}`);
|
|
16505
|
+
if (goal.page_id) console.log(` Page: ${goal.page_id}`);
|
|
15809
16506
|
console.log(` Count once: ${goal.count_once ? "yes" : "no"}`);
|
|
15810
16507
|
console.log();
|
|
15811
16508
|
}
|
|
@@ -15833,8 +16530,22 @@ var list2 = new Command("list").alias("ls").description("List goals").option("-n
|
|
|
15833
16530
|
);
|
|
15834
16531
|
});
|
|
15835
16532
|
});
|
|
15836
|
-
var create2 = new Command("create").description("Create a goal").requiredOption("--name <name>", "Goal name").requiredOption("--type <type>", "click|pageview|custom|revenue").option("--measurement <measurement>", "conversion|value", "conversion").option("--value-property <property>", "Numeric event property for value goals").option("--value-unit <unit>", "currency|number|duration_seconds|percent").option("--selector <selector>", "CSS selector for click goals").option("--text-match <text>", "Text match for click goals").option("--url-pattern <pattern>", "URL pattern for pageview goals").option("--event-name <name>", "Event name for custom goals").option("--count-once", "Count only once per visitor").option("--capture <json-or-path>", "Capture JSON array or @path").action(async function action35(opts) {
|
|
16533
|
+
var create2 = new Command("create").description("Create a goal").requiredOption("--name <name>", "Goal name").requiredOption("--type <type>", "click|pageview|custom|revenue").option("--measurement <measurement>", "conversion|value", "conversion").option("--value-property <property>", "Numeric event property for value goals").option("--value-unit <unit>", "currency|number|duration_seconds|percent").option("--selector <selector>", "CSS selector for click goals").option("--text-match <text>", "Text match for click goals").option("--url-pattern <pattern>", "URL pattern for pageview goals").option("--event-name <name>", "Event name for custom goals").option("--page <id-or-key>", PAGE_OPTION_DESCRIPTION).option("--count-once", "Count only once per visitor").option("--capture <json-or-path>", "Capture JSON array or @path").action(async function action35(opts) {
|
|
15837
16534
|
const creds = requireCredentials();
|
|
16535
|
+
if (opts.type === "click" && !opts.page) {
|
|
16536
|
+
fail(
|
|
16537
|
+
this,
|
|
16538
|
+
"Click goals need a page: pass --page <page id or key> (list pages with apex pages list)",
|
|
16539
|
+
EXIT_CODES.validation,
|
|
16540
|
+
{ code: "page_required", next_steps: PAGE_NEXT_STEPS }
|
|
16541
|
+
);
|
|
16542
|
+
}
|
|
16543
|
+
if ((opts.type === "custom" || opts.type === "revenue") && opts.page) {
|
|
16544
|
+
fail(this, `--page is not supported for ${opts.type} goals`, EXIT_CODES.validation, {
|
|
16545
|
+
code: "page_not_supported"
|
|
16546
|
+
});
|
|
16547
|
+
}
|
|
16548
|
+
const pageId = typeof opts.page === "string" ? await resolveGoalPageId(this, creds, opts.page) : void 0;
|
|
15838
16549
|
const body5 = cleanBody({
|
|
15839
16550
|
name: opts.name,
|
|
15840
16551
|
type: opts.type,
|
|
@@ -15845,6 +16556,7 @@ var create2 = new Command("create").description("Create a goal").requiredOption(
|
|
|
15845
16556
|
text_match: opts.textMatch,
|
|
15846
16557
|
url_pattern: opts.urlPattern,
|
|
15847
16558
|
event_name: opts.eventName,
|
|
16559
|
+
page_id: pageId,
|
|
15848
16560
|
count_once: opts.countOnce === true,
|
|
15849
16561
|
capture: parseCapture(this, typeof opts.capture === "string" ? opts.capture : void 0)
|
|
15850
16562
|
});
|
|
@@ -15862,8 +16574,9 @@ var create2 = new Command("create").description("Create a goal").requiredOption(
|
|
|
15862
16574
|
var update2 = new Command("update").description("Update a goal").argument("<id>", "Goal ID").option("--name <name>", "Goal name").option("--type <type>", "click|pageview|custom|revenue").option("--measurement <measurement>", "conversion|value").option(
|
|
15863
16575
|
"--value-property <property>",
|
|
15864
16576
|
"Numeric event property for value goals; pass null to clear"
|
|
15865
|
-
).option("--value-unit <unit>", "currency|number|duration_seconds|percent; pass null to clear").option("--selector <selector>", "CSS selector for click goals").option("--text-match <text>", "Text match for click goals").option("--url-pattern <pattern>", "URL pattern for pageview goals").option("--event-name <name>", "Event name for custom goals").option("--count-once", "Count only once per visitor").option("--no-count-once", "Do not count only once per visitor").option("--capture <json-or-path>", "Capture JSON array or @path").action(async function action36(id, opts) {
|
|
16577
|
+
).option("--value-unit <unit>", "currency|number|duration_seconds|percent; pass null to clear").option("--selector <selector>", "CSS selector for click goals").option("--text-match <text>", "Text match for click goals").option("--url-pattern <pattern>", "URL pattern for pageview goals").option("--event-name <name>", "Event name for custom goals").option("--page <id-or-key>", `${PAGE_OPTION_DESCRIPTION}; pass null to clear`).option("--count-once", "Count only once per visitor").option("--no-count-once", "Do not count only once per visitor").option("--capture <json-or-path>", "Capture JSON array or @path").action(async function action36(id, opts) {
|
|
15866
16578
|
const creds = requireCredentials();
|
|
16579
|
+
const pageId = opts.page === "null" ? null : typeof opts.page === "string" ? await resolveGoalPageId(this, creds, opts.page) : void 0;
|
|
15867
16580
|
const body5 = cleanBody({
|
|
15868
16581
|
name: opts.name,
|
|
15869
16582
|
type: opts.type,
|
|
@@ -15874,6 +16587,7 @@ var update2 = new Command("update").description("Update a goal").argument("<id>"
|
|
|
15874
16587
|
text_match: opts.textMatch,
|
|
15875
16588
|
url_pattern: opts.urlPattern,
|
|
15876
16589
|
event_name: opts.eventName,
|
|
16590
|
+
page_id: pageId,
|
|
15877
16591
|
count_once: opts.countOnce,
|
|
15878
16592
|
capture: parseCapture(this, typeof opts.capture === "string" ? opts.capture : void 0)
|
|
15879
16593
|
});
|
|
@@ -17518,13 +18232,13 @@ var NAMED_HTML_ENTITIES = {
|
|
|
17518
18232
|
nbsp: "\xA0"
|
|
17519
18233
|
};
|
|
17520
18234
|
function decodeHtmlEntities(value) {
|
|
17521
|
-
return value.replace(/&(#[0-9]+|#[xX][0-9a-fA-F]+|[a-zA-Z]+);/g, (match2,
|
|
17522
|
-
if (
|
|
17523
|
-
const hex =
|
|
17524
|
-
const code = Number.parseInt(hex ?
|
|
18235
|
+
return value.replace(/&(#[0-9]+|#[xX][0-9a-fA-F]+|[a-zA-Z]+);/g, (match2, entity2) => {
|
|
18236
|
+
if (entity2.startsWith("#")) {
|
|
18237
|
+
const hex = entity2[1] === "x" || entity2[1] === "X";
|
|
18238
|
+
const code = Number.parseInt(hex ? entity2.slice(2) : entity2.slice(1), hex ? 16 : 10);
|
|
17525
18239
|
return Number.isInteger(code) && code >= 0 && code <= 1114111 ? String.fromCodePoint(code) : match2;
|
|
17526
18240
|
}
|
|
17527
|
-
return NAMED_HTML_ENTITIES[
|
|
18241
|
+
return NAMED_HTML_ENTITIES[entity2.toLowerCase()] ?? match2;
|
|
17528
18242
|
});
|
|
17529
18243
|
}
|
|
17530
18244
|
function printSettings(settings2) {
|
|
@@ -18294,9 +19008,6 @@ function resolveWorkerBase(options) {
|
|
|
18294
19008
|
const raw = options.worker ?? process.env.DRIP_WORKER_URL ?? PRODUCTION_WORKER_BASE_URL;
|
|
18295
19009
|
return raw.trim().replace(/\/+$/, "");
|
|
18296
19010
|
}
|
|
18297
|
-
function resolveShopId(options) {
|
|
18298
|
-
return (options.shopId ?? process.env.DRIP_SHOP_ID ?? "").trim() || null;
|
|
18299
|
-
}
|
|
18300
19011
|
function servedConfigUrl(workerBase, shopId) {
|
|
18301
19012
|
return `${workerBase}/config?shopId=${encodeURIComponent(shopId)}&env=${PRODUCTION_ENVIRONMENT_KEY}`;
|
|
18302
19013
|
}
|
|
@@ -18469,7 +19180,8 @@ async function readGoals(creds) {
|
|
|
18469
19180
|
return { goals };
|
|
18470
19181
|
}
|
|
18471
19182
|
async function runDoctor(options = {}) {
|
|
18472
|
-
const
|
|
19183
|
+
const resolved = resolveCheckShop(options.shopId, process.env.DRIP_SHOP_ID);
|
|
19184
|
+
const { creds } = resolved;
|
|
18473
19185
|
if (options.check === "purchase_capture") {
|
|
18474
19186
|
const domainsRes2 = await getInstallDomains(creds);
|
|
18475
19187
|
if (!domainsRes2.ok || !domainsRes2.data)
|
|
@@ -18479,7 +19191,9 @@ async function runDoctor(options = {}) {
|
|
|
18479
19191
|
classifyStatus(domainsRes2.status),
|
|
18480
19192
|
domainsRes2.errorDetails
|
|
18481
19193
|
);
|
|
18482
|
-
|
|
19194
|
+
if (resolved.shopId)
|
|
19195
|
+
assertResponseShop(domainsRes2.data.shop_id, resolved.shopId, creds.credentialKind);
|
|
19196
|
+
const shopId2 = resolved.shopId ?? domainsRes2.data.shop_id;
|
|
18483
19197
|
const servedResult2 = shopId2 ? await fetchServedConfig(resolveWorkerBase(options), shopId2) : {};
|
|
18484
19198
|
const readTracking = async (minutes) => {
|
|
18485
19199
|
try {
|
|
@@ -18530,6 +19244,7 @@ async function runDoctor(options = {}) {
|
|
|
18530
19244
|
apiFailureOf(res)
|
|
18531
19245
|
);
|
|
18532
19246
|
}
|
|
19247
|
+
assertResponseShop(domainsRes.data.shop_id, resolved.shopId, creds.credentialKind);
|
|
18533
19248
|
for (const [label, res] of [
|
|
18534
19249
|
["url inventory", inventory],
|
|
18535
19250
|
["goals", goals]
|
|
@@ -18573,7 +19288,7 @@ async function runDoctor(options = {}) {
|
|
|
18573
19288
|
withCaveat(devPollutionCheck(eventHosts, envsRes.data, unparsedRows), inventoryCaveat)
|
|
18574
19289
|
);
|
|
18575
19290
|
const primary = hostOnly(domainsRes.data.domain) ?? eventHosts[0];
|
|
18576
|
-
const shopId =
|
|
19291
|
+
const shopId = resolved.shopId ?? domainsRes.data.shop_id;
|
|
18577
19292
|
const workerBase = resolveWorkerBase(options);
|
|
18578
19293
|
const [page, servedResult] = await Promise.all([
|
|
18579
19294
|
primary ? fetchPrimaryHtml(primary) : Promise.resolve({}),
|
|
@@ -18794,7 +19509,7 @@ function consentRateCheck(items) {
|
|
|
18794
19509
|
}
|
|
18795
19510
|
var doctorCommand = new Command("doctor").description("Check shop configuration against observed analytics").option(
|
|
18796
19511
|
"--shop-id <uuid>",
|
|
18797
|
-
"
|
|
19512
|
+
"Apex shop ID to check; API requests use it too (DRIP_SHOP_ID is the fallback when no shop is selected)"
|
|
18798
19513
|
).option("--check <id>", "Run one check (for example purchase_capture)").option(
|
|
18799
19514
|
"--worker <url>",
|
|
18800
19515
|
`Apex Worker base URL; defaults to DRIP_WORKER_URL or ${PRODUCTION_WORKER_BASE_URL}`
|
|
@@ -18803,7 +19518,7 @@ var doctorCommand = new Command("doctor").description("Check shop configuration
|
|
|
18803
19518
|
if (options.check && data.checks.length === 0)
|
|
18804
19519
|
fail(this, `Unknown doctor check: ${options.check}`, EXIT_CODES.validation);
|
|
18805
19520
|
output(this, data, () => printDoctorReport(data.checks));
|
|
18806
|
-
if (data.checks.some((c) => c.status === "FAIL")) process.exitCode = EXIT_CODES.
|
|
19521
|
+
if (data.checks.some((c) => c.status === "FAIL")) process.exitCode = EXIT_CODES.checkFailed;
|
|
18807
19522
|
});
|
|
18808
19523
|
function printDoctorReport(checks) {
|
|
18809
19524
|
printTable(
|
|
@@ -18987,7 +19702,7 @@ Examples:
|
|
|
18987
19702
|
);
|
|
18988
19703
|
|
|
18989
19704
|
// src/commands/install.ts
|
|
18990
|
-
var
|
|
19705
|
+
var import_promises3 = require("node:dns/promises");
|
|
18991
19706
|
var import_node_http2 = require("node:http");
|
|
18992
19707
|
var import_node_https = require("node:https");
|
|
18993
19708
|
var import_node_net = require("node:net");
|
|
@@ -19045,7 +19760,7 @@ var testResolver;
|
|
|
19045
19760
|
var testFetch;
|
|
19046
19761
|
async function resolveHost(hostname) {
|
|
19047
19762
|
const mocked = testResolver?.(hostname) ?? null;
|
|
19048
|
-
const addresses = mocked ?? await (0,
|
|
19763
|
+
const addresses = mocked ?? await (0, import_promises3.lookup)(hostname, { all: true });
|
|
19049
19764
|
return addresses.map((entry) => entry.address);
|
|
19050
19765
|
}
|
|
19051
19766
|
async function validateUrl(raw, allowedHosts) {
|
|
@@ -19525,16 +20240,16 @@ async function checkServedConfig(configUrl, workerBaseUrl, workerBaseSource) {
|
|
|
19525
20240
|
};
|
|
19526
20241
|
}
|
|
19527
20242
|
var installCommand = new Command("install").description("Check Apex storefront installation").command("check").description(
|
|
19528
|
-
"Verify snippet, served config, and recent tracking
|
|
20243
|
+
"Verify snippet, served config, and recent tracking for --shop-id (which also selects the shop for API requests), the selected shop, or DRIP_SHOP_ID"
|
|
19529
20244
|
).option("--url <url>", "Check one explicit bound storefront URL").option(
|
|
19530
20245
|
"--shop-id <id>",
|
|
19531
|
-
"Apex shop ID to verify (falls back to --shop/apex use, then DRIP_SHOP_ID)"
|
|
20246
|
+
"Apex shop ID to verify; API requests use it too (falls back to --shop/apex use, then DRIP_SHOP_ID)"
|
|
19532
20247
|
).option(
|
|
19533
20248
|
"--worker-url <url>",
|
|
19534
20249
|
"Apex Worker base URL that serves /config (defaults to the URL in the served snippet, then DRIP_WORKER_URL, then the production worker)"
|
|
19535
20250
|
).option("--environment <key>", "Environment key to request from the worker", "production").action(async function action65(opts) {
|
|
19536
|
-
const
|
|
19537
|
-
const
|
|
20251
|
+
const resolved = resolveCheckShop(opts.shopId, process.env.DRIP_SHOP_ID);
|
|
20252
|
+
const { creds, shopId, overriddenShopId } = resolved;
|
|
19538
20253
|
if (!shopId)
|
|
19539
20254
|
fail(
|
|
19540
20255
|
this,
|
|
@@ -19542,6 +20257,10 @@ var installCommand = new Command("install").description("Check Apex storefront i
|
|
|
19542
20257
|
EXIT_CODES.validation,
|
|
19543
20258
|
{ field: "shop-id" }
|
|
19544
20259
|
);
|
|
20260
|
+
if (overriddenShopId)
|
|
20261
|
+
console.error(
|
|
20262
|
+
`Checking shop ${shopId} from --shop-id instead of the selected shop ${overriddenShopId}.`
|
|
20263
|
+
);
|
|
19545
20264
|
const domains = await getInstallDomains(creds);
|
|
19546
20265
|
if (!domains.ok || !domains.data)
|
|
19547
20266
|
fail(
|
|
@@ -19550,6 +20269,7 @@ var installCommand = new Command("install").description("Check Apex storefront i
|
|
|
19550
20269
|
classifyStatus(domains.status),
|
|
19551
20270
|
domains.errorDetails
|
|
19552
20271
|
);
|
|
20272
|
+
assertResponseShop(domains.data.shop_id, shopId, creds.credentialKind);
|
|
19553
20273
|
const mockStoreOrigins = cliTestSetting("STORE_ORIGINS");
|
|
19554
20274
|
const storeOrigins = cliTestSetting("API") === "1" && mockStoreOrigins ? mockStoreOrigins.split(",") : domains.data.storeOrigins;
|
|
19555
20275
|
const origins = opts.url ? [opts.url] : storeOrigins;
|
|
@@ -19581,7 +20301,16 @@ var installCommand = new Command("install").description("Check Apex storefront i
|
|
|
19581
20301
|
});
|
|
19582
20302
|
continue;
|
|
19583
20303
|
}
|
|
19584
|
-
if (error instanceof UrlPolicyError)
|
|
20304
|
+
if (error instanceof UrlPolicyError) {
|
|
20305
|
+
const boundHosts = [...allowedHosts].sort();
|
|
20306
|
+
const unbound = error.message.includes("host is not bound to this shop");
|
|
20307
|
+
fail(
|
|
20308
|
+
this,
|
|
20309
|
+
unbound ? `${error.message} (shop ${shopId} is bound to: ${boundHosts.join(", ")})` : error.message,
|
|
20310
|
+
EXIT_CODES.validation,
|
|
20311
|
+
unbound ? { code: "url_not_bound", field: "url", shop_id: shopId, bound_hosts: boundHosts } : { field: "url" }
|
|
20312
|
+
);
|
|
20313
|
+
}
|
|
19585
20314
|
if (error instanceof BodyDecodeError) {
|
|
19586
20315
|
pages.push({
|
|
19587
20316
|
url,
|
|
@@ -19618,10 +20347,12 @@ var installCommand = new Command("install").description("Check Apex storefront i
|
|
|
19618
20347
|
);
|
|
19619
20348
|
let tracking;
|
|
19620
20349
|
let trackingExitCode = null;
|
|
20350
|
+
let trackingApiFailure;
|
|
19621
20351
|
try {
|
|
19622
20352
|
const result = await verifyTracking(creds);
|
|
19623
20353
|
if (!result.ok || !result.data) {
|
|
19624
20354
|
trackingExitCode = classifyStatus(result.status);
|
|
20355
|
+
trackingApiFailure = apiFailureOf(result);
|
|
19625
20356
|
tracking = {
|
|
19626
20357
|
status: "FAIL",
|
|
19627
20358
|
message: result.error ?? "Failed to verify tracking",
|
|
@@ -19645,6 +20376,11 @@ var installCommand = new Command("install").description("Check Apex storefront i
|
|
|
19645
20376
|
if (servedConfig.status !== "PASS") warnings.push(servedConfig.message);
|
|
19646
20377
|
if (tracking.status === "WARN") warnings.push(tracking.message);
|
|
19647
20378
|
const failed = pages.some((page) => page.status === "FAIL") || servedConfig.status === "FAIL" || tracking.status === "FAIL";
|
|
20379
|
+
const failedChecks = [
|
|
20380
|
+
...pages.filter((page) => page.status === "FAIL").map((page) => ({ check: "page", url: page.url, message: page.message })),
|
|
20381
|
+
...servedConfig.status === "FAIL" ? [{ check: "served_config", url: servedConfig.url, message: servedConfig.message }] : [],
|
|
20382
|
+
...tracking.status === "FAIL" ? [{ check: "tracking", message: tracking.message }] : []
|
|
20383
|
+
];
|
|
19648
20384
|
const data = { ok: !failed, pages, warnings, servedConfig, tracking };
|
|
19649
20385
|
const renderReport = () => {
|
|
19650
20386
|
printTable(
|
|
@@ -19676,12 +20412,24 @@ var installCommand = new Command("install").description("Check Apex storefront i
|
|
|
19676
20412
|
};
|
|
19677
20413
|
if (failed) {
|
|
19678
20414
|
if (!shouldUseJson(this)) renderReport();
|
|
19679
|
-
|
|
19680
|
-
|
|
19681
|
-
|
|
19682
|
-
|
|
19683
|
-
|
|
19684
|
-
|
|
20415
|
+
if (trackingExitCode !== null)
|
|
20416
|
+
fail(
|
|
20417
|
+
this,
|
|
20418
|
+
`Apex install check could not verify tracking: ${tracking.message}`,
|
|
20419
|
+
trackingExitCode,
|
|
20420
|
+
{ ...data, shop_id: shopId, failed_checks: failedChecks },
|
|
20421
|
+
trackingApiFailure
|
|
20422
|
+
);
|
|
20423
|
+
const summaries = failedChecks.map((entry) => {
|
|
20424
|
+
const label = entry.check === "page" && "url" in entry ? `page ${entry.url}` : entry.check === "served_config" ? "served config" : "tracking";
|
|
20425
|
+
return `${label}: ${entry.message.replace(/\.$/, "")}`;
|
|
20426
|
+
});
|
|
20427
|
+
fail(this, `Apex install check failed: ${summaries.join("; ")}`, EXIT_CODES.checkFailed, {
|
|
20428
|
+
...data,
|
|
20429
|
+
code: "install_check_failed",
|
|
20430
|
+
shop_id: shopId,
|
|
20431
|
+
failed_checks: failedChecks
|
|
20432
|
+
});
|
|
19685
20433
|
}
|
|
19686
20434
|
output(this, data, renderReport);
|
|
19687
20435
|
}).parent;
|
|
@@ -20332,6 +21080,258 @@ var personalizationsCommand = new Command("personalizations").description("Manag
|
|
|
20332
21080
|
init_source();
|
|
20333
21081
|
var import_node_fs23 = __toESM(require("node:fs"));
|
|
20334
21082
|
var import_node_path20 = __toESM(require("node:path"));
|
|
21083
|
+
|
|
21084
|
+
// src/generated/exclusion-group-body.schema.json
|
|
21085
|
+
var exclusion_group_body_schema_default = {
|
|
21086
|
+
create: {
|
|
21087
|
+
type: "object",
|
|
21088
|
+
additionalProperties: false,
|
|
21089
|
+
properties: {
|
|
21090
|
+
name: {
|
|
21091
|
+
type: "string",
|
|
21092
|
+
minLength: 1,
|
|
21093
|
+
maxLength: 120,
|
|
21094
|
+
description: "Group name, 1 to 120 characters."
|
|
21095
|
+
},
|
|
21096
|
+
description: {
|
|
21097
|
+
type: ["string", "null"]
|
|
21098
|
+
},
|
|
21099
|
+
members: {
|
|
21100
|
+
type: "array",
|
|
21101
|
+
minItems: 2,
|
|
21102
|
+
description: "At least 2 distinct experiments. Use one shape per member: experiment_id with traffic_share as a fraction (0 to 1, shares total 1), or the dashboard shape experimentId with share as a percentage (0 to 100, shares total 100). Shares use 0.1 percent steps.",
|
|
21103
|
+
items: {
|
|
21104
|
+
oneOf: [
|
|
21105
|
+
{
|
|
21106
|
+
type: "object",
|
|
21107
|
+
additionalProperties: false,
|
|
21108
|
+
properties: {
|
|
21109
|
+
experiment_id: {
|
|
21110
|
+
type: "string",
|
|
21111
|
+
format: "uuid",
|
|
21112
|
+
description: "Internal experiment UUID. Public ids and test keys are not resolved here."
|
|
21113
|
+
},
|
|
21114
|
+
traffic_share: {
|
|
21115
|
+
type: "number",
|
|
21116
|
+
minimum: 0,
|
|
21117
|
+
maximum: 1
|
|
21118
|
+
}
|
|
21119
|
+
},
|
|
21120
|
+
required: ["experiment_id", "traffic_share"]
|
|
21121
|
+
},
|
|
21122
|
+
{
|
|
21123
|
+
type: "object",
|
|
21124
|
+
additionalProperties: false,
|
|
21125
|
+
properties: {
|
|
21126
|
+
experimentId: {
|
|
21127
|
+
type: "string",
|
|
21128
|
+
format: "uuid",
|
|
21129
|
+
description: "Internal experiment UUID. Public ids and test keys are not resolved here."
|
|
21130
|
+
},
|
|
21131
|
+
share: {
|
|
21132
|
+
type: "number",
|
|
21133
|
+
minimum: 0,
|
|
21134
|
+
maximum: 100
|
|
21135
|
+
}
|
|
21136
|
+
},
|
|
21137
|
+
required: ["experimentId", "share"]
|
|
21138
|
+
}
|
|
21139
|
+
]
|
|
21140
|
+
}
|
|
21141
|
+
}
|
|
21142
|
+
},
|
|
21143
|
+
required: ["name", "members"]
|
|
21144
|
+
},
|
|
21145
|
+
update: {
|
|
21146
|
+
type: "object",
|
|
21147
|
+
additionalProperties: false,
|
|
21148
|
+
properties: {
|
|
21149
|
+
name: {
|
|
21150
|
+
type: "string",
|
|
21151
|
+
minLength: 1,
|
|
21152
|
+
maxLength: 120,
|
|
21153
|
+
description: "Group name, 1 to 120 characters."
|
|
21154
|
+
},
|
|
21155
|
+
description: {
|
|
21156
|
+
type: ["string", "null"]
|
|
21157
|
+
},
|
|
21158
|
+
members: {
|
|
21159
|
+
type: "array",
|
|
21160
|
+
minItems: 2,
|
|
21161
|
+
description: "At least 2 distinct experiments. Use one shape per member: experiment_id with traffic_share as a fraction (0 to 1, shares total 1), or the dashboard shape experimentId with share as a percentage (0 to 100, shares total 100). Shares use 0.1 percent steps.",
|
|
21162
|
+
items: {
|
|
21163
|
+
oneOf: [
|
|
21164
|
+
{
|
|
21165
|
+
type: "object",
|
|
21166
|
+
additionalProperties: false,
|
|
21167
|
+
properties: {
|
|
21168
|
+
experiment_id: {
|
|
21169
|
+
type: "string",
|
|
21170
|
+
format: "uuid",
|
|
21171
|
+
description: "Internal experiment UUID. Public ids and test keys are not resolved here."
|
|
21172
|
+
},
|
|
21173
|
+
traffic_share: {
|
|
21174
|
+
type: "number",
|
|
21175
|
+
minimum: 0,
|
|
21176
|
+
maximum: 1
|
|
21177
|
+
}
|
|
21178
|
+
},
|
|
21179
|
+
required: ["experiment_id", "traffic_share"]
|
|
21180
|
+
},
|
|
21181
|
+
{
|
|
21182
|
+
type: "object",
|
|
21183
|
+
additionalProperties: false,
|
|
21184
|
+
properties: {
|
|
21185
|
+
experimentId: {
|
|
21186
|
+
type: "string",
|
|
21187
|
+
format: "uuid",
|
|
21188
|
+
description: "Internal experiment UUID. Public ids and test keys are not resolved here."
|
|
21189
|
+
},
|
|
21190
|
+
share: {
|
|
21191
|
+
type: "number",
|
|
21192
|
+
minimum: 0,
|
|
21193
|
+
maximum: 100
|
|
21194
|
+
}
|
|
21195
|
+
},
|
|
21196
|
+
required: ["experimentId", "share"]
|
|
21197
|
+
}
|
|
21198
|
+
]
|
|
21199
|
+
}
|
|
21200
|
+
}
|
|
21201
|
+
}
|
|
21202
|
+
},
|
|
21203
|
+
"replace-members": {
|
|
21204
|
+
type: "object",
|
|
21205
|
+
additionalProperties: false,
|
|
21206
|
+
properties: {
|
|
21207
|
+
members: {
|
|
21208
|
+
type: "array",
|
|
21209
|
+
minItems: 2,
|
|
21210
|
+
description: "At least 2 distinct experiments. Use one shape per member: experiment_id with traffic_share as a fraction (0 to 1, shares total 1), or the dashboard shape experimentId with share as a percentage (0 to 100, shares total 100). Shares use 0.1 percent steps.",
|
|
21211
|
+
items: {
|
|
21212
|
+
oneOf: [
|
|
21213
|
+
{
|
|
21214
|
+
type: "object",
|
|
21215
|
+
additionalProperties: false,
|
|
21216
|
+
properties: {
|
|
21217
|
+
experiment_id: {
|
|
21218
|
+
type: "string",
|
|
21219
|
+
format: "uuid",
|
|
21220
|
+
description: "Internal experiment UUID. Public ids and test keys are not resolved here."
|
|
21221
|
+
},
|
|
21222
|
+
traffic_share: {
|
|
21223
|
+
type: "number",
|
|
21224
|
+
minimum: 0,
|
|
21225
|
+
maximum: 1
|
|
21226
|
+
}
|
|
21227
|
+
},
|
|
21228
|
+
required: ["experiment_id", "traffic_share"]
|
|
21229
|
+
},
|
|
21230
|
+
{
|
|
21231
|
+
type: "object",
|
|
21232
|
+
additionalProperties: false,
|
|
21233
|
+
properties: {
|
|
21234
|
+
experimentId: {
|
|
21235
|
+
type: "string",
|
|
21236
|
+
format: "uuid",
|
|
21237
|
+
description: "Internal experiment UUID. Public ids and test keys are not resolved here."
|
|
21238
|
+
},
|
|
21239
|
+
share: {
|
|
21240
|
+
type: "number",
|
|
21241
|
+
minimum: 0,
|
|
21242
|
+
maximum: 100
|
|
21243
|
+
}
|
|
21244
|
+
},
|
|
21245
|
+
required: ["experimentId", "share"]
|
|
21246
|
+
}
|
|
21247
|
+
]
|
|
21248
|
+
}
|
|
21249
|
+
}
|
|
21250
|
+
},
|
|
21251
|
+
required: ["members"]
|
|
21252
|
+
}
|
|
21253
|
+
};
|
|
21254
|
+
|
|
21255
|
+
// src/commands/schema.ts
|
|
21256
|
+
var commandBodySchemas = /* @__PURE__ */ new WeakMap();
|
|
21257
|
+
function setCommandBodySchema(command, schema) {
|
|
21258
|
+
commandBodySchemas.set(command, schema);
|
|
21259
|
+
return command;
|
|
21260
|
+
}
|
|
21261
|
+
function optionSchema(option) {
|
|
21262
|
+
return {
|
|
21263
|
+
flags: option.flags,
|
|
21264
|
+
...option.argChoices ? { choices: option.argChoices } : {},
|
|
21265
|
+
description: option.description,
|
|
21266
|
+
required: option.required,
|
|
21267
|
+
optional: option.optional,
|
|
21268
|
+
mandatory: Boolean(option.mandatory),
|
|
21269
|
+
variadic: option.variadic,
|
|
21270
|
+
...option.defaultValue === void 0 ? {} : { defaultValue: option.defaultValue }
|
|
21271
|
+
};
|
|
21272
|
+
}
|
|
21273
|
+
function commandSchema(command) {
|
|
21274
|
+
return {
|
|
21275
|
+
name: command.name(),
|
|
21276
|
+
aliases: command.aliases(),
|
|
21277
|
+
description: command.description(),
|
|
21278
|
+
usage: command.usage(),
|
|
21279
|
+
arguments: command.registeredArguments.map((arg) => ({
|
|
21280
|
+
name: arg.name(),
|
|
21281
|
+
required: arg.required,
|
|
21282
|
+
variadic: arg.variadic,
|
|
21283
|
+
description: arg.description,
|
|
21284
|
+
...arg.defaultValue === void 0 ? {} : { defaultValue: arg.defaultValue }
|
|
21285
|
+
})),
|
|
21286
|
+
options: command.options.map(optionSchema),
|
|
21287
|
+
...commandBodySchemas.has(command) ? { body: commandBodySchemas.get(command) } : {},
|
|
21288
|
+
commands: command.commands.map(commandSchema)
|
|
21289
|
+
};
|
|
21290
|
+
}
|
|
21291
|
+
function createSchemaCommand(program3) {
|
|
21292
|
+
return new Command("schema").description("Print the current Apex CLI command, flag, and mandatory-option schema").option("--json", "Print JSON schema").action(function action78() {
|
|
21293
|
+
const schema = {
|
|
21294
|
+
schemaVersion: 2,
|
|
21295
|
+
contract: {
|
|
21296
|
+
optionValueArity: "Options' required and optional fields describe whether the option's value is required or optional when the flag is given.",
|
|
21297
|
+
mandatoryOptions: "An option's mandatory field is true only when the flag itself must be passed.",
|
|
21298
|
+
requiredArguments: "An argument's required field means the positional is mandatory.",
|
|
21299
|
+
body: "A command's body field is the JSON Schema of the JSON document it sends."
|
|
21300
|
+
},
|
|
21301
|
+
...commandSchema(program3),
|
|
21302
|
+
files: experiment_filesystem_schema_default
|
|
21303
|
+
};
|
|
21304
|
+
output(this, schema, () => {
|
|
21305
|
+
process.stdout.write(`${JSON.stringify(schema, null, 2)}
|
|
21306
|
+
`);
|
|
21307
|
+
});
|
|
21308
|
+
});
|
|
21309
|
+
}
|
|
21310
|
+
|
|
21311
|
+
// src/commands/targeting-resources.ts
|
|
21312
|
+
var exclusionHelp = `Body shape: {"name":"...","members":[...]} (update fields are optional)
|
|
21313
|
+
Members: at least 2 distinct experiments; use experiment_id + traffic_share (0 to 1, total 1) or experimentId + share (0 to 100, total 100). Shares use 0.1 percent steps.
|
|
21314
|
+
Find experiment ids with: apex experiments list --fields id,name
|
|
21315
|
+
Example: apex exclusion-groups create '{"name":"PDP tests","members":[{"experiment_id":"<id-1>","traffic_share":0.5},{"experiment_id":"<id-2>","traffic_share":0.5}]}'
|
|
21316
|
+
Full JSON Schema: apex schema --json (this command's body key)`;
|
|
21317
|
+
function withBodyHelp(command, action78) {
|
|
21318
|
+
setCommandBodySchema(command, exclusion_group_body_schema_default[action78]);
|
|
21319
|
+
command.addHelpText("after", `
|
|
21320
|
+
${exclusionHelp}
|
|
21321
|
+
`);
|
|
21322
|
+
}
|
|
21323
|
+
function exclusionFailureDetails(kind5, action78, status4, details) {
|
|
21324
|
+
if (kind5 !== "exclusion-groups" || status4 !== 400 && status4 !== 422) return details;
|
|
21325
|
+
return {
|
|
21326
|
+
...details,
|
|
21327
|
+
next_steps: [
|
|
21328
|
+
{
|
|
21329
|
+
command: `apex exclusion-groups ${action78} --help`,
|
|
21330
|
+
why: "Shows the body shape, member rules and an example; error.details.issues names each failing field."
|
|
21331
|
+
}
|
|
21332
|
+
]
|
|
21333
|
+
};
|
|
21334
|
+
}
|
|
20335
21335
|
function body3(command, value) {
|
|
20336
21336
|
if (!value) return {};
|
|
20337
21337
|
const source = value.startsWith("@") ? value.slice(1) : value;
|
|
@@ -20409,7 +21409,10 @@ function targetingResourceCommand(kind5) {
|
|
|
20409
21409
|
`${action78 === "create" ? "Create" : "Update"} a ${singular3}`
|
|
20410
21410
|
);
|
|
20411
21411
|
if (action78 === "update") command.argument("<id>");
|
|
20412
|
-
command.argument(
|
|
21412
|
+
command.argument(
|
|
21413
|
+
"[json]",
|
|
21414
|
+
kind5 === "exclusion-groups" ? "Request body as inline JSON, @file, or a file path (shape below)" : "Inline JSON, @file, or file path"
|
|
21415
|
+
).option("--file <path>", "JSON body file").action(async function(...args) {
|
|
20413
21416
|
const opts = this.opts();
|
|
20414
21417
|
const id = action78 === "update" ? String(args[0]) : void 0;
|
|
20415
21418
|
const value = opts.file ? `@${opts.file}` : String(args[action78 === "update" ? 1 : 0] ?? "") || void 0;
|
|
@@ -20428,10 +21431,11 @@ function targetingResourceCommand(kind5) {
|
|
|
20428
21431
|
this,
|
|
20429
21432
|
response.error ?? `Failed to ${action78}`,
|
|
20430
21433
|
classifyStatus(response.status),
|
|
20431
|
-
response.errorDetails
|
|
21434
|
+
exclusionFailureDetails(kind5, action78, response.status, response.errorDetails)
|
|
20432
21435
|
);
|
|
20433
21436
|
output(this, response.data, () => console.log(source_default.green(`${action78}d ${singular3}`)));
|
|
20434
21437
|
});
|
|
21438
|
+
if (kind5 === "exclusion-groups") withBodyHelp(command, action78);
|
|
20435
21439
|
root.addCommand(command);
|
|
20436
21440
|
}
|
|
20437
21441
|
const deleteCommand = new Command("delete").description(`Delete a ${singular3}`).argument("<id>");
|
|
@@ -20470,9 +21474,9 @@ function targetingResourceCommand(kind5) {
|
|
|
20470
21474
|
output(this, response.data ?? null, () => console.log(source_default.green(`Deleted ${singular3}`)));
|
|
20471
21475
|
})
|
|
20472
21476
|
);
|
|
20473
|
-
if (kind5 === "exclusion-groups")
|
|
21477
|
+
if (kind5 === "exclusion-groups") {
|
|
20474
21478
|
root.addCommand(
|
|
20475
|
-
new Command("replace-members").description("Replace exclusion group members").argument("<id>").argument("<json>", "
|
|
21479
|
+
new Command("replace-members").description("Replace exclusion group members").argument("<id>").argument("<json>", "Request body as inline JSON, @file, or a file path (shape below)").action(async function(id, value) {
|
|
20476
21480
|
const response = await requestTargetingResource(
|
|
20477
21481
|
requireCredentials(),
|
|
20478
21482
|
"PUT",
|
|
@@ -20485,11 +21489,18 @@ function targetingResourceCommand(kind5) {
|
|
|
20485
21489
|
this,
|
|
20486
21490
|
response.error ?? "Failed to replace members",
|
|
20487
21491
|
classifyStatus(response.status),
|
|
20488
|
-
|
|
21492
|
+
exclusionFailureDetails(
|
|
21493
|
+
kind5,
|
|
21494
|
+
"replace-members",
|
|
21495
|
+
response.status,
|
|
21496
|
+
response.errorDetails
|
|
21497
|
+
)
|
|
20489
21498
|
);
|
|
20490
21499
|
output(this, response.data, () => console.log(source_default.green("Replaced members")));
|
|
20491
21500
|
})
|
|
20492
21501
|
);
|
|
21502
|
+
withBodyHelp(root.commands.at(-1), "replace-members");
|
|
21503
|
+
}
|
|
20493
21504
|
return root;
|
|
20494
21505
|
}
|
|
20495
21506
|
var pagesCommand = targetingResourceCommand("pages");
|
|
@@ -20578,7 +21589,7 @@ var test2 = new Command("test").description("Evaluate experiment URL targeting f
|
|
|
20578
21589
|
var targetingCommand = new Command("targeting").description("Test experiment targeting").addCommand(test2);
|
|
20579
21590
|
|
|
20580
21591
|
// src/commands/sos.ts
|
|
20581
|
-
var
|
|
21592
|
+
var import_node_crypto8 = require("node:crypto");
|
|
20582
21593
|
init_source();
|
|
20583
21594
|
var import_node_fs24 = __toESM(require("node:fs"));
|
|
20584
21595
|
var import_node_path21 = __toESM(require("node:path"));
|
|
@@ -20689,7 +21700,7 @@ var create4 = new Command("create").description("Create an Apex SOS ticket").opt
|
|
|
20689
21700
|
const bytes = file.bytes;
|
|
20690
21701
|
await uploadSosAttachment(grant.signed_url, file.content_type, bytes);
|
|
20691
21702
|
attachmentPaths.push(grant.path);
|
|
20692
|
-
attachmentDigests.push((0,
|
|
21703
|
+
attachmentDigests.push((0, import_node_crypto8.createHash)("sha256").update(bytes).digest("hex"));
|
|
20693
21704
|
}
|
|
20694
21705
|
}
|
|
20695
21706
|
const description = readText(opts.description) ?? descriptionArg ?? `Apex CLI ${opts.type} SOS request${opts.loom ? ` with Loom evidence: ${opts.loom}` : "."}`;
|
|
@@ -21002,55 +22013,6 @@ var shopsCommand = new Command("shops").description("Manage organization shops")
|
|
|
21002
22013
|
settings("auto-stop", "Read or update auto-stop defaults", { on: "enable", off: "disable" })
|
|
21003
22014
|
).addCommand(settings("testing-defaults", "Read or update testing defaults")).addCommand(settings("guardian", "Read or update guardian defaults", { on: "on", off: "off" })).addCommand(profile).addCommand(shopware);
|
|
21004
22015
|
|
|
21005
|
-
// src/commands/schema.ts
|
|
21006
|
-
function optionSchema(option) {
|
|
21007
|
-
return {
|
|
21008
|
-
flags: option.flags,
|
|
21009
|
-
...option.argChoices ? { choices: option.argChoices } : {},
|
|
21010
|
-
description: option.description,
|
|
21011
|
-
required: option.required,
|
|
21012
|
-
optional: option.optional,
|
|
21013
|
-
mandatory: Boolean(option.mandatory),
|
|
21014
|
-
variadic: option.variadic,
|
|
21015
|
-
...option.defaultValue === void 0 ? {} : { defaultValue: option.defaultValue }
|
|
21016
|
-
};
|
|
21017
|
-
}
|
|
21018
|
-
function commandSchema(command) {
|
|
21019
|
-
return {
|
|
21020
|
-
name: command.name(),
|
|
21021
|
-
aliases: command.aliases(),
|
|
21022
|
-
description: command.description(),
|
|
21023
|
-
usage: command.usage(),
|
|
21024
|
-
arguments: command.registeredArguments.map((arg) => ({
|
|
21025
|
-
name: arg.name(),
|
|
21026
|
-
required: arg.required,
|
|
21027
|
-
variadic: arg.variadic,
|
|
21028
|
-
description: arg.description,
|
|
21029
|
-
...arg.defaultValue === void 0 ? {} : { defaultValue: arg.defaultValue }
|
|
21030
|
-
})),
|
|
21031
|
-
options: command.options.map(optionSchema),
|
|
21032
|
-
commands: command.commands.map(commandSchema)
|
|
21033
|
-
};
|
|
21034
|
-
}
|
|
21035
|
-
function createSchemaCommand(program3) {
|
|
21036
|
-
return new Command("schema").description("Print the current Apex CLI command, flag, and mandatory-option schema").option("--json", "Print JSON schema").action(function action78() {
|
|
21037
|
-
const schema = {
|
|
21038
|
-
schemaVersion: 2,
|
|
21039
|
-
contract: {
|
|
21040
|
-
optionValueArity: "Options' required and optional fields describe whether the option's value is required or optional when the flag is given.",
|
|
21041
|
-
mandatoryOptions: "An option's mandatory field is true only when the flag itself must be passed.",
|
|
21042
|
-
requiredArguments: "An argument's required field means the positional is mandatory."
|
|
21043
|
-
},
|
|
21044
|
-
...commandSchema(program3),
|
|
21045
|
-
files: experiment_filesystem_schema_default
|
|
21046
|
-
};
|
|
21047
|
-
output(this, schema, () => {
|
|
21048
|
-
process.stdout.write(`${JSON.stringify(schema, null, 2)}
|
|
21049
|
-
`);
|
|
21050
|
-
});
|
|
21051
|
-
});
|
|
21052
|
-
}
|
|
21053
|
-
|
|
21054
22016
|
// src/lib/gtm-miner.ts
|
|
21055
22017
|
function extractContainerReferences(html) {
|
|
21056
22018
|
const results2 = /* @__PURE__ */ new Map();
|
|
@@ -22085,7 +23047,7 @@ async function confirm(c, force, label, retry) {
|
|
|
22085
23047
|
}
|
|
22086
23048
|
return true;
|
|
22087
23049
|
}
|
|
22088
|
-
var orgCommand = new Command("org").description("Manage the current organization");
|
|
23050
|
+
var orgCommand = new Command("org").alias("orgs").description("Manage the current organization");
|
|
22089
23051
|
orgCommand.command("get").action(async function() {
|
|
22090
23052
|
await run2(this, getOrg(requireUserCredentials()), "get organization");
|
|
22091
23053
|
});
|
|
@@ -22188,23 +23150,38 @@ orgCommand.command("remove-member").argument("<memberId>").option("-f, --force")
|
|
|
22188
23150
|
return;
|
|
22189
23151
|
await run2(this, removeOrgMember(requireUserCredentials(), id), "remove member");
|
|
22190
23152
|
});
|
|
22191
|
-
var teamCommand = new Command("team").description("Manage a shop team");
|
|
22192
|
-
|
|
23153
|
+
var teamCommand = new Command("team").alias("teams").description("Manage a shop team");
|
|
23154
|
+
var teamShopDescription = "Shop ID; defaults to the selected shop (--shop before the subcommand, DRIP_SHOP, or apex use)";
|
|
23155
|
+
function teamShop(command, explicit) {
|
|
23156
|
+
if (explicit) return explicit;
|
|
23157
|
+
const selected2 = requireUserCredentials().resolvedContext?.shopId;
|
|
23158
|
+
if (selected2) return selected2;
|
|
23159
|
+
fail(
|
|
23160
|
+
command,
|
|
23161
|
+
"apex team needs a shop. Pass --shop <id>, or select one with apex use <org>/<shop>.",
|
|
23162
|
+
3,
|
|
23163
|
+
{ code: "shop_context_required", retry: "apex use <org>/<shop>", flag: "--shop" }
|
|
23164
|
+
);
|
|
23165
|
+
}
|
|
23166
|
+
teamCommand.command("members").option("--shop <id>", teamShopDescription).action(async function(o) {
|
|
23167
|
+
o.shop = teamShop(this, o.shop);
|
|
22193
23168
|
await run2(this, listTeamMembers(requireUserCredentials(), o.shop), "list team");
|
|
22194
23169
|
});
|
|
22195
|
-
teamCommand.command("invite").argument("<email>").
|
|
23170
|
+
teamCommand.command("invite").argument("<email>").option("--shop <id>", teamShopDescription).addOption(
|
|
22196
23171
|
new Option(
|
|
22197
23172
|
"--role <role>",
|
|
22198
23173
|
"Role: editor (create and run tests, manage settings, invite people) or client_viewer (view tests, results and pipeline)"
|
|
22199
23174
|
).choices(["editor", "client_viewer"]).makeOptionMandatory()
|
|
22200
23175
|
).action(async function(email, o) {
|
|
23176
|
+
o.shop = teamShop(this, o.shop);
|
|
22201
23177
|
await run2(
|
|
22202
23178
|
this,
|
|
22203
23179
|
inviteTeam(requireUserCredentials(), email, o.shop, o.role),
|
|
22204
23180
|
"invite team member"
|
|
22205
23181
|
);
|
|
22206
23182
|
});
|
|
22207
|
-
teamCommand.command("revoke-invite").argument("<id>").
|
|
23183
|
+
teamCommand.command("revoke-invite").argument("<id>").option("--shop <id>", teamShopDescription).action(async function(id, o) {
|
|
23184
|
+
o.shop = teamShop(this, o.shop);
|
|
22208
23185
|
await run2(
|
|
22209
23186
|
this,
|
|
22210
23187
|
revokeTeamInvite(requireUserCredentials(), id, o.shop),
|
|
@@ -22216,10 +23193,12 @@ teamCommand.command("set-role").argument("<memberId>").addArgument(
|
|
|
22216
23193
|
"<role>",
|
|
22217
23194
|
"Role: editor (create and run tests, manage settings, invite people) or client_viewer (view tests, results and pipeline)"
|
|
22218
23195
|
).choices(["editor", "client_viewer"])
|
|
22219
|
-
).
|
|
23196
|
+
).option("--shop <id>", teamShopDescription).action(async function(id, role, o) {
|
|
23197
|
+
o.shop = teamShop(this, o.shop);
|
|
22220
23198
|
await run2(this, setTeamMember(requireUserCredentials(), id, o.shop, role), "set role");
|
|
22221
23199
|
});
|
|
22222
|
-
teamCommand.command("remove").argument("<memberId>").
|
|
23200
|
+
teamCommand.command("remove").argument("<memberId>").option("--shop <id>", teamShopDescription).option("-f, --force").action(async function(id, o) {
|
|
23201
|
+
o.shop = teamShop(this, o.shop);
|
|
22223
23202
|
if (!await confirm(
|
|
22224
23203
|
this,
|
|
22225
23204
|
o.force,
|
|
@@ -22230,6 +23209,38 @@ teamCommand.command("remove").argument("<memberId>").requiredOption("--shop <id>
|
|
|
22230
23209
|
await run2(this, removeTeamMember(requireUserCredentials(), id, o.shop), "remove member");
|
|
22231
23210
|
});
|
|
22232
23211
|
|
|
23212
|
+
// src/commands/upgrade.ts
|
|
23213
|
+
var import_node_child_process7 = require("node:child_process");
|
|
23214
|
+
var upgradeCommand = new Command("upgrade").description("Install the latest Apex CLI version").option("--dry-run", "Print the install command without running it").action(function(options) {
|
|
23215
|
+
if (options.dryRun) {
|
|
23216
|
+
output(
|
|
23217
|
+
this,
|
|
23218
|
+
{ command: UPGRADE_COMMAND, ran: false, exit_code: null, previous_version: CLI_VERSION },
|
|
23219
|
+
() => console.log(UPGRADE_COMMAND)
|
|
23220
|
+
);
|
|
23221
|
+
return;
|
|
23222
|
+
}
|
|
23223
|
+
const npmBinary = process.env.APEX_NPM_BIN || (process.platform === "win32" ? "npm.cmd" : "npm");
|
|
23224
|
+
const json = shouldUseJson(this);
|
|
23225
|
+
const windows = process.platform === "win32";
|
|
23226
|
+
const command = windows ? `"${npmBinary}"` : npmBinary;
|
|
23227
|
+
const result = (0, import_node_child_process7.spawnSync)(command, ["install", "-g", "@drip-apex/apex-cli@latest"], {
|
|
23228
|
+
stdio: json ? ["inherit", 2, 2] : "inherit",
|
|
23229
|
+
shell: windows
|
|
23230
|
+
});
|
|
23231
|
+
if (result.error || result.status !== 0)
|
|
23232
|
+
fail(this, result.error?.message || "Apex CLI upgrade failed", EXIT_CODES.general, {
|
|
23233
|
+
code: "upgrade_failed",
|
|
23234
|
+
next_steps: [{ command: UPGRADE_COMMAND, why: "Run the install command manually." }]
|
|
23235
|
+
});
|
|
23236
|
+
clearUpdateCache();
|
|
23237
|
+
output(
|
|
23238
|
+
this,
|
|
23239
|
+
{ command: UPGRADE_COMMAND, ran: true, exit_code: 0, previous_version: CLI_VERSION },
|
|
23240
|
+
() => console.log("Apex CLI upgraded.")
|
|
23241
|
+
);
|
|
23242
|
+
});
|
|
23243
|
+
|
|
22233
23244
|
// src/commands/account.ts
|
|
22234
23245
|
function userCredentials2() {
|
|
22235
23246
|
return requireUserCredentials();
|
|
@@ -22835,7 +23846,7 @@ var import_node_fs29 = __toESM(require("node:fs"));
|
|
|
22835
23846
|
var import_node_path26 = __toESM(require("node:path"));
|
|
22836
23847
|
|
|
22837
23848
|
// src/commands/landing-pages.ts
|
|
22838
|
-
var
|
|
23849
|
+
var import_node_crypto9 = __toESM(require("node:crypto"));
|
|
22839
23850
|
var import_node_fs28 = __toESM(require("node:fs"));
|
|
22840
23851
|
var import_node_path25 = __toESM(require("node:path"));
|
|
22841
23852
|
function apiFailure(command, response, fallback2) {
|
|
@@ -22849,7 +23860,7 @@ function apiFailure(command, response, fallback2) {
|
|
|
22849
23860
|
}
|
|
22850
23861
|
}
|
|
22851
23862
|
function sha256(body5) {
|
|
22852
|
-
return
|
|
23863
|
+
return import_node_crypto9.default.createHash("sha256").update(body5).digest("hex");
|
|
22853
23864
|
}
|
|
22854
23865
|
function markerPath(folder) {
|
|
22855
23866
|
return import_node_path25.default.join(folder, LANDING_PAGE_CHECKOUT_MARKER);
|
|
@@ -23598,7 +24609,10 @@ function addJsonOption(command) {
|
|
|
23598
24609
|
if (!command.options.some((option) => option.long === "--shop"))
|
|
23599
24610
|
command.option("--shop <id>", "Use a shop for this invocation");
|
|
23600
24611
|
}
|
|
23601
|
-
command.exitOverride((err) =>
|
|
24612
|
+
command.exitOverride((err) => {
|
|
24613
|
+
recordCommanderExit(err, command);
|
|
24614
|
+
interceptCliError(err, command);
|
|
24615
|
+
});
|
|
23602
24616
|
for (const child of command.commands) addJsonOption(child);
|
|
23603
24617
|
}
|
|
23604
24618
|
var program2 = new Command().name("apex").description(source_default.bold("Apex CLI") + " \u2014 A/B testing experiments from the command line").version(CLI_VERSION).showSuggestionAfterError(true).showHelpAfterError(true);
|
|
@@ -23654,7 +24668,9 @@ program2.addCommand(skillsCommand);
|
|
|
23654
24668
|
program2.addCommand(adminCommand, { hidden: true });
|
|
23655
24669
|
program2.addCommand(orgCommand);
|
|
23656
24670
|
program2.addCommand(teamCommand);
|
|
24671
|
+
program2.addCommand(upgradeCommand);
|
|
23657
24672
|
addJsonOption(program2);
|
|
24673
|
+
registerTelemetryProgram(program2);
|
|
23658
24674
|
var optionTerminatorIndex = process.argv.indexOf("--");
|
|
23659
24675
|
var optionArgv = optionTerminatorIndex === -1 ? process.argv : process.argv.slice(0, optionTerminatorIndex);
|
|
23660
24676
|
if (optionArgv.includes("--json")) program2.setOptionValue("json", true);
|