@drip-apex/apex-cli 0.15.2 → 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 +46 -23
- package/dist/cli.js +2600 -518
- 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);
|
|
@@ -7589,7 +7695,8 @@ function classifyApiError(status4, details) {
|
|
|
7589
7695
|
const code = details?.code;
|
|
7590
7696
|
if (code === "shop_context_required" || code === "org_context_required")
|
|
7591
7697
|
return EXIT_CODES.validation;
|
|
7592
|
-
if (code === "shop_access_denied" || code === "org_access_denied"
|
|
7698
|
+
if (code === "shop_access_denied" || code === "org_access_denied" || code === "member_suspended")
|
|
7699
|
+
return EXIT_CODES.auth;
|
|
7593
7700
|
return classifyStatus(status4);
|
|
7594
7701
|
}
|
|
7595
7702
|
function confirmationRequired(command, message, retry) {
|
|
@@ -7599,6 +7706,7 @@ var liveSpinners = /* @__PURE__ */ new Set();
|
|
|
7599
7706
|
var exitFallback;
|
|
7600
7707
|
function drainAndExit(code) {
|
|
7601
7708
|
for (const spinner of liveSpinners) spinner.stop();
|
|
7709
|
+
writePendingUpdateNotice();
|
|
7602
7710
|
process.exitCode = code;
|
|
7603
7711
|
if (exitFallback) clearTimeout(exitFallback);
|
|
7604
7712
|
exitFallback = setTimeout(() => process.exit(code), 500);
|
|
@@ -7637,13 +7745,35 @@ async function createSpinner(message) {
|
|
|
7637
7745
|
}
|
|
7638
7746
|
return spinner.start();
|
|
7639
7747
|
}
|
|
7640
|
-
var updateNotice;
|
|
7641
7748
|
async function maybeCheckForUpdate() {
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
|
|
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}
|
|
7645
7765
|
`);
|
|
7646
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
|
+
}
|
|
7647
7777
|
async function handleCliError(err, command) {
|
|
7648
7778
|
if (err && typeof err === "object" && "exitCode" in err && err.exitCode === 0) {
|
|
7649
7779
|
flushFallbackEnvelope();
|
|
@@ -7675,6 +7805,9 @@ async function handleCliError(err, command) {
|
|
|
7675
7805
|
process.stderr.write(`Retry: ${String(cliError.details.retry)}
|
|
7676
7806
|
`);
|
|
7677
7807
|
}
|
|
7808
|
+
writeUpdateNotice(
|
|
7809
|
+
currentUpdateState(CLI_VERSION, minimumFromError(cliError, errorReason(cliError)))
|
|
7810
|
+
);
|
|
7678
7811
|
}
|
|
7679
7812
|
await finishInvocation({ exitCode: cliError.exitCode, error: err });
|
|
7680
7813
|
drainAndExit(cliError.exitCode);
|
|
@@ -7686,9 +7819,20 @@ function errorReason(error) {
|
|
|
7686
7819
|
const body5 = error.api?.body;
|
|
7687
7820
|
const code = body5?.code ?? body5?.error?.code ?? (error.api ? details?.code : void 0);
|
|
7688
7821
|
if (typeof code === "string" && code) return code;
|
|
7689
|
-
if ([
|
|
7690
|
-
|
|
7691
|
-
|
|
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)))
|
|
7692
7836
|
return String(details?.code);
|
|
7693
7837
|
const status4 = error.api?.status;
|
|
7694
7838
|
if (status4 === 401) return "auth_required";
|
|
@@ -7704,6 +7848,29 @@ function errorReason(error) {
|
|
|
7704
7848
|
function errorNextSteps(error) {
|
|
7705
7849
|
const reason2 = errorReason(error);
|
|
7706
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
|
+
);
|
|
7859
|
+
if (reason2 === "member_suspended")
|
|
7860
|
+
return [
|
|
7861
|
+
{
|
|
7862
|
+
command: "contact organization admin",
|
|
7863
|
+
why: "Ask an organization owner or admin to reinstate your membership."
|
|
7864
|
+
}
|
|
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
|
+
}
|
|
7707
7874
|
if (reason2 === "auth_required" || error.api?.status === 401) {
|
|
7708
7875
|
steps.push({ command: "apex login", why: "Authenticate with a valid API key." });
|
|
7709
7876
|
if (!error.api)
|
|
@@ -7743,8 +7910,10 @@ function errorNextSteps(error) {
|
|
|
7743
7910
|
const hints = error.details?.next_steps;
|
|
7744
7911
|
if (Array.isArray(hints))
|
|
7745
7912
|
for (const hint of hints) {
|
|
7746
|
-
if (hint && typeof hint.command === "string" && typeof hint.why === "string")
|
|
7747
|
-
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
|
+
}
|
|
7748
7917
|
}
|
|
7749
7918
|
if (reason2 === "validation_failed" && error.details?.plan)
|
|
7750
7919
|
steps.push({
|
|
@@ -7861,7 +8030,7 @@ function clearCredentials() {
|
|
|
7861
8030
|
} catch {
|
|
7862
8031
|
}
|
|
7863
8032
|
}
|
|
7864
|
-
function requireCredentials() {
|
|
8033
|
+
function requireCredentials(fallbackShopId) {
|
|
7865
8034
|
const creds = getCredentials();
|
|
7866
8035
|
if (!creds) {
|
|
7867
8036
|
throw new CliError(
|
|
@@ -7870,6 +8039,7 @@ function requireCredentials() {
|
|
|
7870
8039
|
);
|
|
7871
8040
|
}
|
|
7872
8041
|
const context = resolveContext(creds, void 0, defaultCredentialKind(creds));
|
|
8042
|
+
if (!context.shopId) context.shopId = fallbackShopId?.trim() || void 0;
|
|
7873
8043
|
if (defaultCredentialKind(creds) === "user") {
|
|
7874
8044
|
setEnvelopeContext({
|
|
7875
8045
|
credential: "user",
|
|
@@ -7909,6 +8079,53 @@ function requireCredentials() {
|
|
|
7909
8079
|
});
|
|
7910
8080
|
return { ...creds, credentialKind: "shop", resolvedContext: context };
|
|
7911
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
|
+
}
|
|
7912
8129
|
function requireUserCredentials() {
|
|
7913
8130
|
const creds = getCredentials();
|
|
7914
8131
|
const environmentUserKey = creds?.credentialKind === "user" ? creds.apiKey : void 0;
|
|
@@ -7969,7 +8186,65 @@ var invocation;
|
|
|
7969
8186
|
var firstError;
|
|
7970
8187
|
var apiCalls = 0;
|
|
7971
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
|
+
}
|
|
7972
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;
|
|
7973
8248
|
const names = /* @__PURE__ */ new Set();
|
|
7974
8249
|
for (let current = command; current; current = current.parent) {
|
|
7975
8250
|
for (const option of current.options) {
|
|
@@ -8022,7 +8297,9 @@ var staticRouteSegments = /* @__PURE__ */ new Set([
|
|
|
8022
8297
|
"duplicate",
|
|
8023
8298
|
"entry-exit",
|
|
8024
8299
|
"environments",
|
|
8300
|
+
"events",
|
|
8025
8301
|
"evidence",
|
|
8302
|
+
"export",
|
|
8026
8303
|
"exclusion-groups",
|
|
8027
8304
|
"experiments",
|
|
8028
8305
|
"external-pixels",
|
|
@@ -8116,6 +8393,24 @@ function normalizeRoute(path26) {
|
|
|
8116
8393
|
}
|
|
8117
8394
|
function recordApiResponse2(response) {
|
|
8118
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
|
+
}
|
|
8119
8414
|
if (response.status >= 400 && !firstError)
|
|
8120
8415
|
firstError = { ...response, path: normalizeRoute(response.path) };
|
|
8121
8416
|
}
|
|
@@ -8125,20 +8420,62 @@ function classifyError(exitCode, error, status4) {
|
|
|
8125
8420
|
if (detail5.code === "commander.unknownCommand") return "unknown_command";
|
|
8126
8421
|
if (detail5.code?.startsWith("commander.")) return "validation";
|
|
8127
8422
|
if (exitCode === 4) return "confirmation_required";
|
|
8423
|
+
if (exitCode === EXIT_CODES.checkFailed) return "check_failed";
|
|
8128
8424
|
if (status4 === 401 || status4 === 403 || exitCode === 2) return "auth";
|
|
8129
|
-
if (status4 === 404) return "not_found";
|
|
8425
|
+
if (status4 === 404 || status4 === 410) return "not_found";
|
|
8130
8426
|
if (status4 === 429) return "rate_limited";
|
|
8131
8427
|
if (status4 === 409 || exitCode === 5) return "conflict";
|
|
8132
8428
|
if (status4 && status4 >= 500) return "server";
|
|
8133
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";
|
|
8134
8432
|
if (Boolean(
|
|
8135
|
-
detail5.details?.reason && ["network", "dns", "
|
|
8136
|
-
|
|
8137
|
-
)
|
|
8138
|
-
) || 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")
|
|
8139
8435
|
return "network";
|
|
8140
8436
|
return "internal";
|
|
8141
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
|
+
}
|
|
8142
8479
|
async function finishInvocation({
|
|
8143
8480
|
exitCode: rawExitCode,
|
|
8144
8481
|
error
|
|
@@ -8160,10 +8497,8 @@ async function finishInvocation({
|
|
|
8160
8497
|
const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
|
|
8161
8498
|
const commanderExit = code.startsWith("commander.");
|
|
8162
8499
|
const parse3 = commanderExit && exitCode !== 0;
|
|
8163
|
-
|
|
8164
|
-
const
|
|
8165
|
-
if (attempted === "login" || attempted === "logout") return;
|
|
8166
|
-
const command = parse3 ? "(parse)" : invocation?.command ?? (commanderExit && exitCode === 0 ? attempted ?? (code === "commander.version" ? "(version)" : "(help)") : void 0);
|
|
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);
|
|
8167
8502
|
if (!command || command === "login" || command === "logout") return;
|
|
8168
8503
|
if (!getClientIdentity()) setClientIdentity({ client: "apex-cli", version: CLI_VERSION });
|
|
8169
8504
|
const payload = {
|
|
@@ -8177,8 +8512,12 @@ async function finishInvocation({
|
|
|
8177
8512
|
exit_code: exitCode,
|
|
8178
8513
|
error_class: classifyError(exitCode, error, firstError?.status),
|
|
8179
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) } : {},
|
|
8180
8519
|
first_error_status: firstError?.status,
|
|
8181
|
-
first_error_code:
|
|
8520
|
+
first_error_code: invocationErrorCode(exitCode, error, commanderExit),
|
|
8182
8521
|
first_error_request_id: firstError?.requestId,
|
|
8183
8522
|
first_error_route: firstError?.path,
|
|
8184
8523
|
duration_ms: Math.max(0, Math.round(performance.now() - started))
|
|
@@ -8199,13 +8538,13 @@ init_source();
|
|
|
8199
8538
|
|
|
8200
8539
|
// src/commands/login.ts
|
|
8201
8540
|
init_source();
|
|
8202
|
-
var
|
|
8541
|
+
var import_node_crypto4 = __toESM(require("node:crypto"));
|
|
8203
8542
|
var import_node_http = __toESM(require("node:http"));
|
|
8204
8543
|
var import_node_os6 = __toESM(require("node:os"));
|
|
8205
8544
|
var import_node_readline2 = __toESM(require("node:readline"));
|
|
8206
8545
|
|
|
8207
8546
|
// src/api.ts
|
|
8208
|
-
var
|
|
8547
|
+
var import_node_crypto3 = require("node:crypto");
|
|
8209
8548
|
|
|
8210
8549
|
// ../api-client/src/landing-pages.ts
|
|
8211
8550
|
var LANDING_PAGE_MANIFEST_FILE = "apex-landing-page.json";
|
|
@@ -8370,7 +8709,11 @@ function sharedClient(creds) {
|
|
|
8370
8709
|
let client = sharedClients.get(creds);
|
|
8371
8710
|
if (!client) {
|
|
8372
8711
|
client = createHttpClient(creds, {
|
|
8373
|
-
onResponse:
|
|
8712
|
+
onResponse: (observation) => {
|
|
8713
|
+
recordApiResponse2(observation);
|
|
8714
|
+
recordServerCliPolicy?.(observation);
|
|
8715
|
+
},
|
|
8716
|
+
observeNetworkErrors: true,
|
|
8374
8717
|
transport: testTransport,
|
|
8375
8718
|
testTransportVersion: () => testTransport ? process.env[["APEX", "CLI", "MOCK", "API", "VERSION"].join("_")] : void 0,
|
|
8376
8719
|
onNetworkError: (info) => {
|
|
@@ -8420,8 +8763,14 @@ function sharedClient(creds) {
|
|
|
8420
8763
|
}
|
|
8421
8764
|
return client;
|
|
8422
8765
|
}
|
|
8423
|
-
async function request(creds, method, path26, body5, requestTimeoutMs) {
|
|
8424
|
-
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
|
+
);
|
|
8425
8774
|
recordApiResponse(response);
|
|
8426
8775
|
return response;
|
|
8427
8776
|
}
|
|
@@ -8438,6 +8787,7 @@ function listExperiments(creds, opts) {
|
|
|
8438
8787
|
if (opts?.includeArchived) params.set("archived", "1");
|
|
8439
8788
|
if (opts?.view) params.set("view", opts.view);
|
|
8440
8789
|
if (opts?.fields !== void 0) params.set("fields", opts.fields);
|
|
8790
|
+
if (opts?.url !== void 0) params.set("url", opts.url);
|
|
8441
8791
|
const qs = params.toString();
|
|
8442
8792
|
return request(creds, "GET", `/api/v1/experiments${qs ? `?${qs}` : ""}`);
|
|
8443
8793
|
}
|
|
@@ -8485,6 +8835,8 @@ function getExperimentFilesystem(creds, id) {
|
|
|
8485
8835
|
function getExperimentResults(creds, id, opts) {
|
|
8486
8836
|
const params = new URLSearchParams();
|
|
8487
8837
|
if (opts?.currency) params.set("currency", opts.currency);
|
|
8838
|
+
for (const key of ["segment", "exclude_segment"])
|
|
8839
|
+
if (opts?.[key] !== void 0) params.set(key, opts[key]);
|
|
8488
8840
|
const qs = params.toString();
|
|
8489
8841
|
return request(
|
|
8490
8842
|
creds,
|
|
@@ -8523,6 +8875,7 @@ function planExperimentFilesystem(creds, id, body5) {
|
|
|
8523
8875
|
body5
|
|
8524
8876
|
);
|
|
8525
8877
|
}
|
|
8878
|
+
var APPLY_TIMEOUT_MS = 9e4;
|
|
8526
8879
|
function applyExperimentFilesystem(creds, id, body5) {
|
|
8527
8880
|
return request(
|
|
8528
8881
|
creds,
|
|
@@ -8532,12 +8885,25 @@ function applyExperimentFilesystem(creds, id, body5) {
|
|
|
8532
8885
|
files: body5.files,
|
|
8533
8886
|
...body5.accept_qa_invalidation !== void 0 ? { accept_qa_invalidation: body5.accept_qa_invalidation } : {},
|
|
8534
8887
|
...body5.confirmTargetingReplace === true ? { confirmTargetingReplace: true } : {}
|
|
8535
|
-
}
|
|
8888
|
+
},
|
|
8889
|
+
void 0,
|
|
8890
|
+
{ minTimeoutMs: APPLY_TIMEOUT_MS }
|
|
8536
8891
|
);
|
|
8537
8892
|
}
|
|
8538
8893
|
function getExperimentQaRun(creds, id) {
|
|
8539
8894
|
return request(creds, "GET", `/api/v1/experiments/${id}/qa/run`);
|
|
8540
8895
|
}
|
|
8896
|
+
function getExperimentQaEvents(creds, id, options = {}) {
|
|
8897
|
+
const params = new URLSearchParams();
|
|
8898
|
+
if (options.session) params.set("qa_session", options.session);
|
|
8899
|
+
if (options.minutes !== void 0) params.set("minutes", String(options.minutes));
|
|
8900
|
+
if (options.type) params.set("type", options.type);
|
|
8901
|
+
return request(
|
|
8902
|
+
creds,
|
|
8903
|
+
"GET",
|
|
8904
|
+
`/api/v1/experiments/${id}/qa/events${params.size ? `?${params}` : ""}`
|
|
8905
|
+
);
|
|
8906
|
+
}
|
|
8541
8907
|
function updateExperimentQaRun(creds, id, body5) {
|
|
8542
8908
|
return request(creds, "PATCH", `/api/v1/experiments/${id}/qa/run`, body5);
|
|
8543
8909
|
}
|
|
@@ -8547,8 +8913,9 @@ function attachExperimentQaEvidence(creds, id, body5) {
|
|
|
8547
8913
|
function uploadExperimentQaEvidence(creds, id, form) {
|
|
8548
8914
|
return requestForm(creds, "POST", `/api/v1/experiments/${id}/qa/evidence`, form);
|
|
8549
8915
|
}
|
|
8550
|
-
function approveExperimentQa(creds, id) {
|
|
8551
|
-
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);
|
|
8552
8919
|
}
|
|
8553
8920
|
function reopenExperimentQa(creds, id) {
|
|
8554
8921
|
return request(creds, "DELETE", `/api/v1/experiments/${id}/qa`);
|
|
@@ -8748,7 +9115,7 @@ function testTargeting(creds, url, opts) {
|
|
|
8748
9115
|
return request(creds, "GET", `/api/v1/targeting/test?${params.toString()}`);
|
|
8749
9116
|
}
|
|
8750
9117
|
function createSos(creds, body5, options = {}) {
|
|
8751
|
-
const idempotency_key = body5.idempotency_key ?? (0,
|
|
9118
|
+
const idempotency_key = body5.idempotency_key ?? (0, import_node_crypto3.createHash)("sha256").update(
|
|
8752
9119
|
[
|
|
8753
9120
|
body5.type,
|
|
8754
9121
|
body5.experiment_id ?? "",
|
|
@@ -8891,6 +9258,12 @@ function createAdminCustomer(creds, body5) {
|
|
|
8891
9258
|
function setAdminCustomerCategory(creds, orgId, body5) {
|
|
8892
9259
|
return request(userCredentials(creds), "PATCH", `/api/v1/admin/customers/${orgId}`, body5);
|
|
8893
9260
|
}
|
|
9261
|
+
function setAdminCustomerManagedBy(creds, orgId, body5) {
|
|
9262
|
+
return request(userCredentials(creds), "PATCH", `/api/v1/admin/customers/${orgId}`, body5);
|
|
9263
|
+
}
|
|
9264
|
+
function setAdminCustomerWorkspaceType(creds, orgId, body5) {
|
|
9265
|
+
return request(userCredentials(creds), "PATCH", `/api/v1/admin/customers/${orgId}`, body5);
|
|
9266
|
+
}
|
|
8894
9267
|
function getAdminSettings(creds) {
|
|
8895
9268
|
return request(userCredentials(creds), "GET", "/api/v1/admin/settings");
|
|
8896
9269
|
}
|
|
@@ -9045,6 +9418,17 @@ function revokeOrgInvite(creds, id) {
|
|
|
9045
9418
|
function setOrgMember(creds, id, body5) {
|
|
9046
9419
|
return request(userCreds(creds), "PATCH", `/api/v1/org/members/${encodeURIComponent(id)}`, body5);
|
|
9047
9420
|
}
|
|
9421
|
+
function suspendOrgMember(creds, id, reason2) {
|
|
9422
|
+
return request(userCreds(creds), "PATCH", `/api/v1/org/members/${encodeURIComponent(id)}`, {
|
|
9423
|
+
suspended: true,
|
|
9424
|
+
...reason2 ? { reason: reason2 } : {}
|
|
9425
|
+
});
|
|
9426
|
+
}
|
|
9427
|
+
function reinstateOrgMember(creds, id) {
|
|
9428
|
+
return request(userCreds(creds), "PATCH", `/api/v1/org/members/${encodeURIComponent(id)}`, {
|
|
9429
|
+
suspended: false
|
|
9430
|
+
});
|
|
9431
|
+
}
|
|
9048
9432
|
function removeOrgMember(creds, id) {
|
|
9049
9433
|
return request(userCreds(creds), "DELETE", `/api/v1/org/members/${encodeURIComponent(id)}`);
|
|
9050
9434
|
}
|
|
@@ -9188,6 +9572,14 @@ function getOrderJourney(creds, opts) {
|
|
|
9188
9572
|
45e3
|
|
9189
9573
|
);
|
|
9190
9574
|
}
|
|
9575
|
+
function moveAdminCustomerStores(creds, orgId, body5) {
|
|
9576
|
+
return request(
|
|
9577
|
+
userCredentials(creds),
|
|
9578
|
+
"POST",
|
|
9579
|
+
`/api/v1/admin/customers/${orgId}/move-stores`,
|
|
9580
|
+
body5
|
|
9581
|
+
);
|
|
9582
|
+
}
|
|
9191
9583
|
|
|
9192
9584
|
// src/context.ts
|
|
9193
9585
|
var import_node_readline = __toESM(require("node:readline"));
|
|
@@ -9220,6 +9612,16 @@ function resolveSelection(tree, input) {
|
|
|
9220
9612
|
const shop = shopValue ? match(shopValue, org.shops, "shop") : void 0;
|
|
9221
9613
|
return { orgId: org.id, ...shop ? { shopId: shop.id } : {} };
|
|
9222
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
|
+
}
|
|
9223
9625
|
function ask(question) {
|
|
9224
9626
|
const rl = import_node_readline.default.createInterface({ input: process.stdin, output: process.stderr });
|
|
9225
9627
|
return new Promise(
|
|
@@ -9232,8 +9634,23 @@ function ask(question) {
|
|
|
9232
9634
|
async function pickContext(tree) {
|
|
9233
9635
|
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
9234
9636
|
throw new CliError(
|
|
9235
|
-
"Interactive context selection requires a TTY. Pass
|
|
9236
|
-
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
|
+
}
|
|
9237
9654
|
);
|
|
9238
9655
|
let org = tree.orgs.length === 1 ? tree.orgs[0] : void 0;
|
|
9239
9656
|
if (!org) {
|
|
@@ -9353,7 +9770,7 @@ function parseFormBody(body5) {
|
|
|
9353
9770
|
return new URLSearchParams(body5);
|
|
9354
9771
|
}
|
|
9355
9772
|
async function waitForBrowserLogin(baseUrl, userMode = false) {
|
|
9356
|
-
const state =
|
|
9773
|
+
const state = import_node_crypto4.default.randomBytes(24).toString("base64url");
|
|
9357
9774
|
const clientName = import_node_os6.default.hostname() || "Local machine";
|
|
9358
9775
|
return new Promise((resolve2, reject) => {
|
|
9359
9776
|
let settled = false;
|
|
@@ -9399,10 +9816,14 @@ async function waitForBrowserLogin(baseUrl, userMode = false) {
|
|
|
9399
9816
|
const returnedState = params.get("state");
|
|
9400
9817
|
const apiKey = params.get("apiKey");
|
|
9401
9818
|
const error = params.get("error");
|
|
9819
|
+
const code = params.get("code");
|
|
9402
9820
|
if (error) {
|
|
9403
9821
|
res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
|
|
9404
9822
|
res.end("<h1>Apex CLI login failed</h1><p>You can close this window.</p>");
|
|
9405
|
-
|
|
9823
|
+
const details = code ? { code } : void 0;
|
|
9824
|
+
finish(
|
|
9825
|
+
details ? new CliError(error, classifyApiError(400, details), details) : new Error(error)
|
|
9826
|
+
);
|
|
9406
9827
|
return;
|
|
9407
9828
|
}
|
|
9408
9829
|
if (returnedState !== state) {
|
|
@@ -9486,6 +9907,7 @@ var loginCommand = new Command("login").description("Authenticate with Apex").op
|
|
|
9486
9907
|
try {
|
|
9487
9908
|
apiKey = await waitForBrowserLogin(baseUrl, true);
|
|
9488
9909
|
} catch (err) {
|
|
9910
|
+
if (err instanceof CliError) throw err;
|
|
9489
9911
|
fail(this, err instanceof Error ? err.message : "Browser login failed.");
|
|
9490
9912
|
}
|
|
9491
9913
|
}
|
|
@@ -9658,6 +10080,14 @@ Context: ${current.orgId ?? "none"}${current.shopId ? ` / ${current.shopId}` : "
|
|
|
9658
10080
|
|
|
9659
10081
|
// src/commands/use.ts
|
|
9660
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
|
+
);
|
|
9661
10091
|
const creds = requireUserCredentials();
|
|
9662
10092
|
const response = await getAccountContext(creds);
|
|
9663
10093
|
if (!response.ok || !response.data)
|
|
@@ -9667,9 +10097,32 @@ var useCommand = new Command("use").description("Set the current Apex organizati
|
|
|
9667
10097
|
classifyApiError(response.status, response.errorDetails),
|
|
9668
10098
|
response.errorDetails
|
|
9669
10099
|
);
|
|
9670
|
-
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);
|
|
9671
10101
|
const stored = getStoredCredentials();
|
|
9672
|
-
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
|
+
}
|
|
9673
10126
|
saveCredentials({ ...stored, context: selected2 });
|
|
9674
10127
|
setEnvelopeContext({
|
|
9675
10128
|
credential: "user",
|
|
@@ -9685,7 +10138,7 @@ var useCommand = new Command("use").description("Set the current Apex organizati
|
|
|
9685
10138
|
|
|
9686
10139
|
// src/commands/experiments.ts
|
|
9687
10140
|
init_source();
|
|
9688
|
-
var
|
|
10141
|
+
var import_node_crypto6 = __toESM(require("node:crypto"));
|
|
9689
10142
|
var import_node_fs8 = __toESM(require("node:fs"));
|
|
9690
10143
|
var import_node_path6 = __toESM(require("node:path"));
|
|
9691
10144
|
|
|
@@ -9777,13 +10230,18 @@ function buildQaPreviewUrl(experiment2, options = {}) {
|
|
|
9777
10230
|
}
|
|
9778
10231
|
|
|
9779
10232
|
// src/lib/qa-preview-url.ts
|
|
9780
|
-
function buildQaPreviewUrl2(storeUrl, experimentId, variationId) {
|
|
9781
|
-
const url = buildQaPreviewUrl(
|
|
9782
|
-
|
|
9783
|
-
|
|
9784
|
-
|
|
9785
|
-
|
|
10233
|
+
function buildQaPreviewUrl2(storeUrl, experimentId, variationId, qaSessionId) {
|
|
10234
|
+
const url = buildQaPreviewUrl(
|
|
10235
|
+
{
|
|
10236
|
+
id: experimentId,
|
|
10237
|
+
store_url: storeUrl,
|
|
10238
|
+
variations: [{ id: variationId, is_control: false }]
|
|
10239
|
+
},
|
|
10240
|
+
{ qaSessionId }
|
|
10241
|
+
);
|
|
9786
10242
|
if (!url) throw new TypeError("Invalid QA preview URL");
|
|
10243
|
+
if (qaSessionId && url.searchParams.get("drip_qa_session") !== qaSessionId)
|
|
10244
|
+
url.searchParams.set("drip_qa_session", qaSessionId);
|
|
9787
10245
|
return url;
|
|
9788
10246
|
}
|
|
9789
10247
|
|
|
@@ -9995,7 +10453,7 @@ function deriveStoreUrl(patterns) {
|
|
|
9995
10453
|
}
|
|
9996
10454
|
|
|
9997
10455
|
// src/lib/asset-upload.ts
|
|
9998
|
-
var
|
|
10456
|
+
var import_node_crypto5 = __toESM(require("node:crypto"));
|
|
9999
10457
|
var import_node_fs7 = __toESM(require("node:fs"));
|
|
10000
10458
|
var import_node_path5 = __toESM(require("node:path"));
|
|
10001
10459
|
var INLINE_DATA_URI_MAX_CHARS = 4096;
|
|
@@ -10020,7 +10478,7 @@ var EXTENSION_BY_MIME = {
|
|
|
10020
10478
|
"image/avif": ".avif"
|
|
10021
10479
|
};
|
|
10022
10480
|
function contentHash(bytes) {
|
|
10023
|
-
return
|
|
10481
|
+
return import_node_crypto5.default.createHash("sha256").update(bytes).digest("hex");
|
|
10024
10482
|
}
|
|
10025
10483
|
function deliveryUrl(baseUrl, asset) {
|
|
10026
10484
|
const returnedUrl = asset.deliveryUrl ?? asset.delivery_url ?? asset.url;
|
|
@@ -10181,91 +10639,859 @@ function createAssetReferenceRewriter(options) {
|
|
|
10181
10639
|
return { rewrite, rewriteUnknown };
|
|
10182
10640
|
}
|
|
10183
10641
|
|
|
10184
|
-
// src/
|
|
10185
|
-
|
|
10186
|
-
|
|
10187
|
-
|
|
10188
|
-
|
|
10189
|
-
|
|
10190
|
-
|
|
10191
|
-
|
|
10192
|
-
|
|
10193
|
-
|
|
10194
|
-
|
|
10195
|
-
|
|
10196
|
-
|
|
10197
|
-
|
|
10198
|
-
|
|
10199
|
-
|
|
10200
|
-
|
|
10201
|
-
|
|
10202
|
-
|
|
10203
|
-
|
|
10204
|
-
|
|
10205
|
-
|
|
10206
|
-
|
|
10207
|
-
|
|
10208
|
-
|
|
10209
|
-
|
|
10210
|
-
|
|
10211
|
-
|
|
10212
|
-
|
|
10213
|
-
|
|
10214
|
-
|
|
10215
|
-
|
|
10216
|
-
|
|
10217
|
-
|
|
10218
|
-
|
|
10219
|
-
|
|
10220
|
-
|
|
10221
|
-
|
|
10222
|
-
|
|
10223
|
-
|
|
10224
|
-
|
|
10225
|
-
|
|
10226
|
-
|
|
10227
|
-
|
|
10228
|
-
|
|
10229
|
-
|
|
10230
|
-
|
|
10231
|
-
|
|
10232
|
-
|
|
10233
|
-
|
|
10234
|
-
|
|
10235
|
-
|
|
10236
|
-
|
|
10237
|
-
|
|
10238
|
-
|
|
10239
|
-
|
|
10240
|
-
|
|
10241
|
-
|
|
10242
|
-
|
|
10243
|
-
|
|
10244
|
-
|
|
10245
|
-
|
|
10246
|
-
|
|
10247
|
-
|
|
10248
|
-
|
|
10249
|
-
|
|
10250
|
-
|
|
10251
|
-
|
|
10252
|
-
|
|
10253
|
-
|
|
10254
|
-
|
|
10255
|
-
|
|
10256
|
-
|
|
10257
|
-
|
|
10258
|
-
|
|
10259
|
-
|
|
10260
|
-
|
|
10261
|
-
|
|
10262
|
-
|
|
10263
|
-
|
|
10264
|
-
|
|
10265
|
-
|
|
10266
|
-
|
|
10267
|
-
|
|
10268
|
-
|
|
10642
|
+
// src/generated/experiment-filesystem.schema.json
|
|
10643
|
+
var experiment_filesystem_schema_default = {
|
|
10644
|
+
$id: "https://api.drip-apex.com/schemas/experiment-filesystem-v1.json",
|
|
10645
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
10646
|
+
files: {
|
|
10647
|
+
"experiment.json": {
|
|
10648
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
10649
|
+
additionalProperties: false,
|
|
10650
|
+
properties: {
|
|
10651
|
+
autoStopOverride: {
|
|
10652
|
+
anyOf: [
|
|
10653
|
+
{},
|
|
10654
|
+
{
|
|
10655
|
+
type: "null"
|
|
10656
|
+
}
|
|
10657
|
+
]
|
|
10658
|
+
},
|
|
10659
|
+
countingType: {
|
|
10660
|
+
anyOf: [
|
|
10661
|
+
{
|
|
10662
|
+
enum: ["standard", "setup_test", "aa_test", "non_program"],
|
|
10663
|
+
type: "string"
|
|
10664
|
+
},
|
|
10665
|
+
{
|
|
10666
|
+
type: "null"
|
|
10667
|
+
}
|
|
10668
|
+
]
|
|
10669
|
+
},
|
|
10670
|
+
description: {
|
|
10671
|
+
anyOf: [
|
|
10672
|
+
{
|
|
10673
|
+
type: "string"
|
|
10674
|
+
},
|
|
10675
|
+
{
|
|
10676
|
+
type: "null"
|
|
10677
|
+
}
|
|
10678
|
+
]
|
|
10679
|
+
},
|
|
10680
|
+
environmentKey: {
|
|
10681
|
+
type: "string"
|
|
10682
|
+
},
|
|
10683
|
+
experimentType: {
|
|
10684
|
+
enum: ["mutation", "redirect"],
|
|
10685
|
+
type: "string"
|
|
10686
|
+
},
|
|
10687
|
+
id: {
|
|
10688
|
+
anyOf: [
|
|
10689
|
+
{
|
|
10690
|
+
type: "string"
|
|
10691
|
+
},
|
|
10692
|
+
{
|
|
10693
|
+
type: "null"
|
|
10694
|
+
}
|
|
10695
|
+
]
|
|
10696
|
+
},
|
|
10697
|
+
name: {
|
|
10698
|
+
type: "string"
|
|
10699
|
+
},
|
|
10700
|
+
schemaVersion: {
|
|
10701
|
+
const: 1,
|
|
10702
|
+
type: "number"
|
|
10703
|
+
},
|
|
10704
|
+
sequentialTestingEnabled: {
|
|
10705
|
+
type: "boolean"
|
|
10706
|
+
},
|
|
10707
|
+
significanceLevel: {
|
|
10708
|
+
type: "number"
|
|
10709
|
+
},
|
|
10710
|
+
slug: {
|
|
10711
|
+
anyOf: [
|
|
10712
|
+
{
|
|
10713
|
+
type: "string"
|
|
10714
|
+
},
|
|
10715
|
+
{
|
|
10716
|
+
type: "null"
|
|
10717
|
+
}
|
|
10718
|
+
]
|
|
10719
|
+
},
|
|
10720
|
+
statisticalPower: {
|
|
10721
|
+
type: "number"
|
|
10722
|
+
},
|
|
10723
|
+
storeUrl: {
|
|
10724
|
+
anyOf: [
|
|
10725
|
+
{
|
|
10726
|
+
type: "string"
|
|
10727
|
+
},
|
|
10728
|
+
{
|
|
10729
|
+
type: "null"
|
|
10730
|
+
}
|
|
10731
|
+
]
|
|
10732
|
+
},
|
|
10733
|
+
testKey: {
|
|
10734
|
+
anyOf: [
|
|
10735
|
+
{
|
|
10736
|
+
type: "string"
|
|
10737
|
+
},
|
|
10738
|
+
{
|
|
10739
|
+
type: "null"
|
|
10740
|
+
}
|
|
10741
|
+
]
|
|
10742
|
+
},
|
|
10743
|
+
trafficAllocation: {
|
|
10744
|
+
maximum: 1,
|
|
10745
|
+
minimum: 0,
|
|
10746
|
+
type: "number"
|
|
10747
|
+
}
|
|
10748
|
+
},
|
|
10749
|
+
required: ["schemaVersion"],
|
|
10750
|
+
type: "object"
|
|
10751
|
+
},
|
|
10752
|
+
"experiment/index.css": {
|
|
10753
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
10754
|
+
contentMediaType: "text/css",
|
|
10755
|
+
type: "string"
|
|
10756
|
+
},
|
|
10757
|
+
"experiment/index.js": {
|
|
10758
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
10759
|
+
contentMediaType: "text/javascript",
|
|
10760
|
+
type: "string"
|
|
10761
|
+
},
|
|
10762
|
+
"experiment/reset.js": {
|
|
10763
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
10764
|
+
contentMediaType: "text/javascript",
|
|
10765
|
+
type: "string"
|
|
10766
|
+
},
|
|
10767
|
+
"launch.json": {
|
|
10768
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
10769
|
+
additionalProperties: false,
|
|
10770
|
+
properties: {
|
|
10771
|
+
endedAt: {
|
|
10772
|
+
anyOf: [
|
|
10773
|
+
{
|
|
10774
|
+
type: "string"
|
|
10775
|
+
},
|
|
10776
|
+
{
|
|
10777
|
+
type: "null"
|
|
10778
|
+
}
|
|
10779
|
+
]
|
|
10780
|
+
},
|
|
10781
|
+
launchApproval: {
|
|
10782
|
+
anyOf: [
|
|
10783
|
+
{
|
|
10784
|
+
additionalProperties: false,
|
|
10785
|
+
properties: {
|
|
10786
|
+
decidedAt: {
|
|
10787
|
+
anyOf: [
|
|
10788
|
+
{
|
|
10789
|
+
type: "string"
|
|
10790
|
+
},
|
|
10791
|
+
{
|
|
10792
|
+
type: "null"
|
|
10793
|
+
}
|
|
10794
|
+
]
|
|
10795
|
+
},
|
|
10796
|
+
note: {
|
|
10797
|
+
anyOf: [
|
|
10798
|
+
{
|
|
10799
|
+
type: "string"
|
|
10800
|
+
},
|
|
10801
|
+
{
|
|
10802
|
+
type: "null"
|
|
10803
|
+
}
|
|
10804
|
+
]
|
|
10805
|
+
},
|
|
10806
|
+
status: {
|
|
10807
|
+
type: "string"
|
|
10808
|
+
}
|
|
10809
|
+
},
|
|
10810
|
+
required: ["status", "decidedAt", "note"],
|
|
10811
|
+
type: "object"
|
|
10812
|
+
},
|
|
10813
|
+
{
|
|
10814
|
+
type: "null"
|
|
10815
|
+
}
|
|
10816
|
+
]
|
|
10817
|
+
},
|
|
10818
|
+
launchGate: {
|
|
10819
|
+
additionalProperties: false,
|
|
10820
|
+
properties: {
|
|
10821
|
+
qaApproved: {
|
|
10822
|
+
type: "boolean"
|
|
10823
|
+
},
|
|
10824
|
+
treatmentArtifactsApproved: {
|
|
10825
|
+
type: "boolean"
|
|
10826
|
+
}
|
|
10827
|
+
},
|
|
10828
|
+
required: ["qaApproved", "treatmentArtifactsApproved"],
|
|
10829
|
+
type: "object"
|
|
10830
|
+
},
|
|
10831
|
+
readonly: {
|
|
10832
|
+
const: true,
|
|
10833
|
+
type: "boolean"
|
|
10834
|
+
},
|
|
10835
|
+
scheduledEndAt: {
|
|
10836
|
+
anyOf: [
|
|
10837
|
+
{
|
|
10838
|
+
type: "string"
|
|
10839
|
+
},
|
|
10840
|
+
{
|
|
10841
|
+
type: "null"
|
|
10842
|
+
}
|
|
10843
|
+
]
|
|
10844
|
+
},
|
|
10845
|
+
scheduledStartAt: {
|
|
10846
|
+
anyOf: [
|
|
10847
|
+
{
|
|
10848
|
+
type: "string"
|
|
10849
|
+
},
|
|
10850
|
+
{
|
|
10851
|
+
type: "null"
|
|
10852
|
+
}
|
|
10853
|
+
]
|
|
10854
|
+
},
|
|
10855
|
+
schemaVersion: {
|
|
10856
|
+
const: 1,
|
|
10857
|
+
type: "number"
|
|
10858
|
+
},
|
|
10859
|
+
startedAt: {
|
|
10860
|
+
anyOf: [
|
|
10861
|
+
{
|
|
10862
|
+
type: "string"
|
|
10863
|
+
},
|
|
10864
|
+
{
|
|
10865
|
+
type: "null"
|
|
10866
|
+
}
|
|
10867
|
+
]
|
|
10868
|
+
},
|
|
10869
|
+
status: {
|
|
10870
|
+
type: "string"
|
|
10871
|
+
}
|
|
10872
|
+
},
|
|
10873
|
+
readOnly: true,
|
|
10874
|
+
required: [
|
|
10875
|
+
"schemaVersion",
|
|
10876
|
+
"readonly",
|
|
10877
|
+
"status",
|
|
10878
|
+
"scheduledStartAt",
|
|
10879
|
+
"scheduledEndAt",
|
|
10880
|
+
"startedAt",
|
|
10881
|
+
"endedAt",
|
|
10882
|
+
"launchApproval",
|
|
10883
|
+
"launchGate"
|
|
10884
|
+
],
|
|
10885
|
+
type: "object"
|
|
10886
|
+
},
|
|
10887
|
+
"metrics.json": {
|
|
10888
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
10889
|
+
additionalProperties: false,
|
|
10890
|
+
properties: {
|
|
10891
|
+
goals: {
|
|
10892
|
+
items: {
|
|
10893
|
+
additionalProperties: false,
|
|
10894
|
+
properties: {
|
|
10895
|
+
capture: {
|
|
10896
|
+
anyOf: [
|
|
10897
|
+
{},
|
|
10898
|
+
{
|
|
10899
|
+
type: "null"
|
|
10900
|
+
}
|
|
10901
|
+
]
|
|
10902
|
+
},
|
|
10903
|
+
countOnce: {
|
|
10904
|
+
type: "boolean"
|
|
10905
|
+
},
|
|
10906
|
+
eventName: {
|
|
10907
|
+
anyOf: [
|
|
10908
|
+
{
|
|
10909
|
+
type: "string"
|
|
10910
|
+
},
|
|
10911
|
+
{
|
|
10912
|
+
type: "null"
|
|
10913
|
+
}
|
|
10914
|
+
]
|
|
10915
|
+
},
|
|
10916
|
+
filter: {
|
|
10917
|
+
anyOf: [
|
|
10918
|
+
{},
|
|
10919
|
+
{
|
|
10920
|
+
type: "null"
|
|
10921
|
+
}
|
|
10922
|
+
]
|
|
10923
|
+
},
|
|
10924
|
+
id: {
|
|
10925
|
+
anyOf: [
|
|
10926
|
+
{
|
|
10927
|
+
type: "string"
|
|
10928
|
+
},
|
|
10929
|
+
{
|
|
10930
|
+
type: "null"
|
|
10931
|
+
}
|
|
10932
|
+
]
|
|
10933
|
+
},
|
|
10934
|
+
isPrimary: {
|
|
10935
|
+
type: "boolean"
|
|
10936
|
+
},
|
|
10937
|
+
measurement: {},
|
|
10938
|
+
name: {
|
|
10939
|
+
type: "string"
|
|
10940
|
+
},
|
|
10941
|
+
pageId: {
|
|
10942
|
+
anyOf: [
|
|
10943
|
+
{
|
|
10944
|
+
type: "string"
|
|
10945
|
+
},
|
|
10946
|
+
{
|
|
10947
|
+
type: "null"
|
|
10948
|
+
}
|
|
10949
|
+
]
|
|
10950
|
+
},
|
|
10951
|
+
selector: {
|
|
10952
|
+
anyOf: [
|
|
10953
|
+
{
|
|
10954
|
+
type: "string"
|
|
10955
|
+
},
|
|
10956
|
+
{
|
|
10957
|
+
type: "null"
|
|
10958
|
+
}
|
|
10959
|
+
]
|
|
10960
|
+
},
|
|
10961
|
+
textMatch: {
|
|
10962
|
+
anyOf: [
|
|
10963
|
+
{
|
|
10964
|
+
type: "string"
|
|
10965
|
+
},
|
|
10966
|
+
{
|
|
10967
|
+
type: "null"
|
|
10968
|
+
}
|
|
10969
|
+
]
|
|
10970
|
+
},
|
|
10971
|
+
type: {
|
|
10972
|
+
enum: ["click", "pageview", "custom", "revenue"],
|
|
10973
|
+
type: "string"
|
|
10974
|
+
},
|
|
10975
|
+
urlPattern: {
|
|
10976
|
+
anyOf: [
|
|
10977
|
+
{
|
|
10978
|
+
type: "string"
|
|
10979
|
+
},
|
|
10980
|
+
{
|
|
10981
|
+
type: "null"
|
|
10982
|
+
}
|
|
10983
|
+
]
|
|
10984
|
+
},
|
|
10985
|
+
valueProperty: {
|
|
10986
|
+
anyOf: [
|
|
10987
|
+
{
|
|
10988
|
+
type: "string"
|
|
10989
|
+
},
|
|
10990
|
+
{
|
|
10991
|
+
type: "null"
|
|
10992
|
+
}
|
|
10993
|
+
]
|
|
10994
|
+
},
|
|
10995
|
+
valueUnit: {
|
|
10996
|
+
anyOf: [
|
|
10997
|
+
{
|
|
10998
|
+
type: "string"
|
|
10999
|
+
},
|
|
11000
|
+
{
|
|
11001
|
+
type: "null"
|
|
11002
|
+
}
|
|
11003
|
+
]
|
|
11004
|
+
}
|
|
11005
|
+
},
|
|
11006
|
+
required: ["name", "type"],
|
|
11007
|
+
type: "object"
|
|
11008
|
+
},
|
|
11009
|
+
type: "array"
|
|
11010
|
+
},
|
|
11011
|
+
primaryGoalId: {
|
|
11012
|
+
anyOf: [
|
|
11013
|
+
{
|
|
11014
|
+
type: "string"
|
|
11015
|
+
},
|
|
11016
|
+
{
|
|
11017
|
+
type: "null"
|
|
11018
|
+
}
|
|
11019
|
+
]
|
|
11020
|
+
},
|
|
11021
|
+
schemaVersion: {
|
|
11022
|
+
const: 1,
|
|
11023
|
+
type: "number"
|
|
11024
|
+
}
|
|
11025
|
+
},
|
|
11026
|
+
required: ["schemaVersion", "primaryGoalId", "goals"],
|
|
11027
|
+
type: "object"
|
|
11028
|
+
},
|
|
11029
|
+
"qa.json": {
|
|
11030
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
11031
|
+
additionalProperties: false,
|
|
11032
|
+
properties: {
|
|
11033
|
+
latestRun: {
|
|
11034
|
+
anyOf: [
|
|
11035
|
+
{
|
|
11036
|
+
additionalProperties: false,
|
|
11037
|
+
properties: {
|
|
11038
|
+
approvedAt: {
|
|
11039
|
+
anyOf: [
|
|
11040
|
+
{
|
|
11041
|
+
type: "string"
|
|
11042
|
+
},
|
|
11043
|
+
{
|
|
11044
|
+
type: "null"
|
|
11045
|
+
}
|
|
11046
|
+
]
|
|
11047
|
+
},
|
|
11048
|
+
completedAt: {
|
|
11049
|
+
anyOf: [
|
|
11050
|
+
{
|
|
11051
|
+
type: "string"
|
|
11052
|
+
},
|
|
11053
|
+
{
|
|
11054
|
+
type: "null"
|
|
11055
|
+
}
|
|
11056
|
+
]
|
|
11057
|
+
},
|
|
11058
|
+
id: {
|
|
11059
|
+
type: "string"
|
|
11060
|
+
},
|
|
11061
|
+
source: {
|
|
11062
|
+
type: "string"
|
|
11063
|
+
},
|
|
11064
|
+
status: {
|
|
11065
|
+
type: "string"
|
|
11066
|
+
},
|
|
11067
|
+
summary: {
|
|
11068
|
+
anyOf: [
|
|
11069
|
+
{},
|
|
11070
|
+
{
|
|
11071
|
+
type: "null"
|
|
11072
|
+
}
|
|
11073
|
+
]
|
|
11074
|
+
}
|
|
11075
|
+
},
|
|
11076
|
+
required: ["id", "status", "source", "approvedAt", "completedAt", "summary"],
|
|
11077
|
+
type: "object"
|
|
11078
|
+
},
|
|
11079
|
+
{
|
|
11080
|
+
type: "null"
|
|
11081
|
+
}
|
|
11082
|
+
]
|
|
11083
|
+
},
|
|
11084
|
+
qaApprovedAt: {
|
|
11085
|
+
anyOf: [
|
|
11086
|
+
{
|
|
11087
|
+
type: "string"
|
|
11088
|
+
},
|
|
11089
|
+
{
|
|
11090
|
+
type: "null"
|
|
11091
|
+
}
|
|
11092
|
+
]
|
|
11093
|
+
},
|
|
11094
|
+
qaApprovedBy: {
|
|
11095
|
+
anyOf: [
|
|
11096
|
+
{
|
|
11097
|
+
type: "string"
|
|
11098
|
+
},
|
|
11099
|
+
{
|
|
11100
|
+
type: "null"
|
|
11101
|
+
}
|
|
11102
|
+
]
|
|
11103
|
+
},
|
|
11104
|
+
readonly: {
|
|
11105
|
+
const: true,
|
|
11106
|
+
type: "boolean"
|
|
11107
|
+
},
|
|
11108
|
+
schemaVersion: {
|
|
11109
|
+
const: 1,
|
|
11110
|
+
type: "number"
|
|
11111
|
+
}
|
|
11112
|
+
},
|
|
11113
|
+
readOnly: true,
|
|
11114
|
+
required: ["schemaVersion", "readonly", "qaApprovedAt", "qaApprovedBy", "latestRun"],
|
|
11115
|
+
type: "object"
|
|
11116
|
+
},
|
|
11117
|
+
"targeting.json": {
|
|
11118
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
11119
|
+
additionalProperties: {},
|
|
11120
|
+
properties: {
|
|
11121
|
+
authorUrl: {
|
|
11122
|
+
items: {
|
|
11123
|
+
additionalProperties: false,
|
|
11124
|
+
properties: {
|
|
11125
|
+
include: {
|
|
11126
|
+
type: "boolean"
|
|
11127
|
+
},
|
|
11128
|
+
match_type: {
|
|
11129
|
+
enum: ["exact", "contains", "wildcard", "regex"],
|
|
11130
|
+
type: "string"
|
|
11131
|
+
},
|
|
11132
|
+
matchType: {
|
|
11133
|
+
enum: ["exact", "contains", "wildcard", "regex"],
|
|
11134
|
+
type: "string"
|
|
11135
|
+
},
|
|
11136
|
+
pattern: {
|
|
11137
|
+
minLength: 1,
|
|
11138
|
+
type: "string"
|
|
11139
|
+
},
|
|
11140
|
+
type: {
|
|
11141
|
+
enum: ["regex", "simple"],
|
|
11142
|
+
type: "string"
|
|
11143
|
+
}
|
|
11144
|
+
},
|
|
11145
|
+
required: ["type", "pattern"],
|
|
11146
|
+
type: "object"
|
|
11147
|
+
},
|
|
11148
|
+
type: "array"
|
|
11149
|
+
},
|
|
11150
|
+
custom: {},
|
|
11151
|
+
device: {},
|
|
11152
|
+
deviceType: {
|
|
11153
|
+
type: "string"
|
|
11154
|
+
},
|
|
11155
|
+
lifecycle: {},
|
|
11156
|
+
pageRules: {
|
|
11157
|
+
items: {},
|
|
11158
|
+
type: "array"
|
|
11159
|
+
},
|
|
11160
|
+
pages: {
|
|
11161
|
+
items: {},
|
|
11162
|
+
type: "array"
|
|
11163
|
+
},
|
|
11164
|
+
segments: {
|
|
11165
|
+
items: {},
|
|
11166
|
+
type: "array"
|
|
11167
|
+
},
|
|
11168
|
+
url: {
|
|
11169
|
+
items: {
|
|
11170
|
+
additionalProperties: false,
|
|
11171
|
+
properties: {
|
|
11172
|
+
include: {
|
|
11173
|
+
type: "boolean"
|
|
11174
|
+
},
|
|
11175
|
+
match_type: {
|
|
11176
|
+
enum: ["exact", "contains", "wildcard", "regex"],
|
|
11177
|
+
type: "string"
|
|
11178
|
+
},
|
|
11179
|
+
matchType: {
|
|
11180
|
+
enum: ["exact", "contains", "wildcard", "regex"],
|
|
11181
|
+
type: "string"
|
|
11182
|
+
},
|
|
11183
|
+
pattern: {
|
|
11184
|
+
minLength: 1,
|
|
11185
|
+
type: "string"
|
|
11186
|
+
},
|
|
11187
|
+
type: {
|
|
11188
|
+
enum: ["regex", "simple"],
|
|
11189
|
+
type: "string"
|
|
11190
|
+
}
|
|
11191
|
+
},
|
|
11192
|
+
required: ["type", "pattern"],
|
|
11193
|
+
type: "object"
|
|
11194
|
+
},
|
|
11195
|
+
type: "array"
|
|
11196
|
+
}
|
|
11197
|
+
},
|
|
11198
|
+
type: "object"
|
|
11199
|
+
},
|
|
11200
|
+
"variations/*/anti-flicker.json": {
|
|
11201
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
11202
|
+
items: {
|
|
11203
|
+
type: "string"
|
|
11204
|
+
},
|
|
11205
|
+
type: "array"
|
|
11206
|
+
},
|
|
11207
|
+
"variations/*/index.css": {
|
|
11208
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
11209
|
+
contentMediaType: "text/css",
|
|
11210
|
+
type: "string"
|
|
11211
|
+
},
|
|
11212
|
+
"variations/*/index.js": {
|
|
11213
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
11214
|
+
contentMediaType: "text/javascript",
|
|
11215
|
+
type: "string"
|
|
11216
|
+
},
|
|
11217
|
+
"variations/*/mutations.json": {
|
|
11218
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
11219
|
+
items: {
|
|
11220
|
+
additionalProperties: {},
|
|
11221
|
+
anyOf: [
|
|
11222
|
+
{
|
|
11223
|
+
required: ["selector"]
|
|
11224
|
+
},
|
|
11225
|
+
{
|
|
11226
|
+
properties: {
|
|
11227
|
+
action: {
|
|
11228
|
+
enum: [
|
|
11229
|
+
"priceTest",
|
|
11230
|
+
"shippingTest",
|
|
11231
|
+
"offerTest",
|
|
11232
|
+
"postPurchaseOffer",
|
|
11233
|
+
"checkoutBlock"
|
|
11234
|
+
]
|
|
11235
|
+
}
|
|
11236
|
+
},
|
|
11237
|
+
required: ["action"]
|
|
11238
|
+
}
|
|
11239
|
+
],
|
|
11240
|
+
properties: {
|
|
11241
|
+
action: {
|
|
11242
|
+
type: "string"
|
|
11243
|
+
},
|
|
11244
|
+
selector: {
|
|
11245
|
+
minLength: 1,
|
|
11246
|
+
type: "string"
|
|
11247
|
+
},
|
|
11248
|
+
type: {
|
|
11249
|
+
type: "string"
|
|
11250
|
+
}
|
|
11251
|
+
},
|
|
11252
|
+
type: "object"
|
|
11253
|
+
},
|
|
11254
|
+
type: "array"
|
|
11255
|
+
},
|
|
11256
|
+
"variations/*/reset.js": {
|
|
11257
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
11258
|
+
contentMediaType: "text/javascript",
|
|
11259
|
+
type: "string"
|
|
11260
|
+
},
|
|
11261
|
+
"variations/*/url-blocks.json": {
|
|
11262
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
11263
|
+
items: {},
|
|
11264
|
+
type: "array"
|
|
11265
|
+
},
|
|
11266
|
+
"variations/*/variation.json": {
|
|
11267
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
11268
|
+
additionalProperties: false,
|
|
11269
|
+
properties: {
|
|
11270
|
+
artifactReview: {
|
|
11271
|
+
additionalProperties: false,
|
|
11272
|
+
properties: {
|
|
11273
|
+
hash: {
|
|
11274
|
+
anyOf: [
|
|
11275
|
+
{
|
|
11276
|
+
type: "string"
|
|
11277
|
+
},
|
|
11278
|
+
{
|
|
11279
|
+
type: "null"
|
|
11280
|
+
}
|
|
11281
|
+
]
|
|
11282
|
+
},
|
|
11283
|
+
reviewedAt: {
|
|
11284
|
+
anyOf: [
|
|
11285
|
+
{
|
|
11286
|
+
type: "string"
|
|
11287
|
+
},
|
|
11288
|
+
{
|
|
11289
|
+
type: "null"
|
|
11290
|
+
}
|
|
11291
|
+
]
|
|
11292
|
+
},
|
|
11293
|
+
reviewedBy: {
|
|
11294
|
+
anyOf: [
|
|
11295
|
+
{
|
|
11296
|
+
type: "string"
|
|
11297
|
+
},
|
|
11298
|
+
{
|
|
11299
|
+
type: "null"
|
|
11300
|
+
}
|
|
11301
|
+
]
|
|
11302
|
+
},
|
|
11303
|
+
source: {
|
|
11304
|
+
anyOf: [
|
|
11305
|
+
{
|
|
11306
|
+
type: "string"
|
|
11307
|
+
},
|
|
11308
|
+
{
|
|
11309
|
+
type: "null"
|
|
11310
|
+
}
|
|
11311
|
+
]
|
|
11312
|
+
},
|
|
11313
|
+
status: {
|
|
11314
|
+
type: "string"
|
|
11315
|
+
}
|
|
11316
|
+
},
|
|
11317
|
+
required: ["status"],
|
|
11318
|
+
type: "object"
|
|
11319
|
+
},
|
|
11320
|
+
codeDeliveryMode: {
|
|
11321
|
+
enum: ["inline", "external"],
|
|
11322
|
+
type: "string"
|
|
11323
|
+
},
|
|
11324
|
+
cssArtifactId: {
|
|
11325
|
+
anyOf: [
|
|
11326
|
+
{
|
|
11327
|
+
type: "string"
|
|
11328
|
+
},
|
|
11329
|
+
{
|
|
11330
|
+
type: "null"
|
|
11331
|
+
}
|
|
11332
|
+
]
|
|
11333
|
+
},
|
|
11334
|
+
id: {
|
|
11335
|
+
anyOf: [
|
|
11336
|
+
{
|
|
11337
|
+
type: "string"
|
|
11338
|
+
},
|
|
11339
|
+
{
|
|
11340
|
+
type: "null"
|
|
11341
|
+
}
|
|
11342
|
+
]
|
|
11343
|
+
},
|
|
11344
|
+
isControl: {
|
|
11345
|
+
type: "boolean"
|
|
11346
|
+
},
|
|
11347
|
+
jsArtifactId: {
|
|
11348
|
+
anyOf: [
|
|
11349
|
+
{
|
|
11350
|
+
type: "string"
|
|
11351
|
+
},
|
|
11352
|
+
{
|
|
11353
|
+
type: "null"
|
|
11354
|
+
}
|
|
11355
|
+
]
|
|
11356
|
+
},
|
|
11357
|
+
name: {
|
|
11358
|
+
type: "string"
|
|
11359
|
+
},
|
|
11360
|
+
redirectUrl: {
|
|
11361
|
+
anyOf: [
|
|
11362
|
+
{
|
|
11363
|
+
type: "string"
|
|
11364
|
+
},
|
|
11365
|
+
{
|
|
11366
|
+
type: "null"
|
|
11367
|
+
}
|
|
11368
|
+
]
|
|
11369
|
+
},
|
|
11370
|
+
schemaVersion: {
|
|
11371
|
+
const: 1,
|
|
11372
|
+
type: "number"
|
|
11373
|
+
},
|
|
11374
|
+
slug: {
|
|
11375
|
+
type: "string"
|
|
11376
|
+
},
|
|
11377
|
+
weight: {
|
|
11378
|
+
maximum: 1,
|
|
11379
|
+
minimum: 0,
|
|
11380
|
+
type: "number"
|
|
11381
|
+
}
|
|
11382
|
+
},
|
|
11383
|
+
required: ["schemaVersion"],
|
|
11384
|
+
type: "object"
|
|
11385
|
+
}
|
|
11386
|
+
},
|
|
11387
|
+
schemaVersion: 1,
|
|
11388
|
+
source: "experiment-filesystem"
|
|
11389
|
+
};
|
|
11390
|
+
|
|
11391
|
+
// src/commands/experiments.ts
|
|
11392
|
+
function formatDate(iso) {
|
|
11393
|
+
return new Date(iso).toLocaleDateString("en-US", {
|
|
11394
|
+
month: "short",
|
|
11395
|
+
day: "numeric",
|
|
11396
|
+
year: "numeric"
|
|
11397
|
+
});
|
|
11398
|
+
}
|
|
11399
|
+
function printExperiment(exp, dashboardUrl = null) {
|
|
11400
|
+
console.log();
|
|
11401
|
+
console.log(source_default.bold(exp.name));
|
|
11402
|
+
if (dashboardUrl) console.log(` Dashboard: ${dashboardUrl}`);
|
|
11403
|
+
console.log(` ID: ${source_default.dim(exp.id)}`);
|
|
11404
|
+
if (exp.slug) console.log(` Slug: ${source_default.cyan(exp.slug)}`);
|
|
11405
|
+
console.log(` Status: ${statusBadge(exp.status)}`);
|
|
11406
|
+
console.log(` Classification: ${exp.counting_type}`);
|
|
11407
|
+
console.log(` Traffic: ${(exp.traffic_allocation * 100).toFixed(0)}%`);
|
|
11408
|
+
console.log(` QA approved: ${exp.qa_approved_at ?? "none"}`);
|
|
11409
|
+
console.log(` Scheduled: ${exp.scheduled_start_at ?? "none"}`);
|
|
11410
|
+
console.log(` Started: ${exp.started_at ?? "none"}`);
|
|
11411
|
+
if (exp.targeting !== void 0) {
|
|
11412
|
+
console.log(` Targeting: ${effectivePageTargetingLabel(exp.targeting)}`);
|
|
11413
|
+
const device = deviceLabel(exp.targeting);
|
|
11414
|
+
if (device) console.log(` Device: ${device}`);
|
|
11415
|
+
}
|
|
11416
|
+
console.log(` Created: ${formatDate(exp.created_at)}`);
|
|
11417
|
+
if (exp.linked_idea) {
|
|
11418
|
+
const label = exp.linked_idea.key ? `${exp.linked_idea.key}: ` : "";
|
|
11419
|
+
const stage = exp.linked_idea.stage ? ` (${exp.linked_idea.stage})` : "";
|
|
11420
|
+
console.log(` Idea: ${label}${exp.linked_idea.title}${stage}`);
|
|
11421
|
+
}
|
|
11422
|
+
if (exp.variations && exp.variations.length > 0) {
|
|
11423
|
+
console.log(` Variations:`);
|
|
11424
|
+
for (const v of exp.variations) {
|
|
11425
|
+
const tag = v.is_control ? source_default.dim(" (control)") : "";
|
|
11426
|
+
console.log(` - ${v.name}${tag} ${source_default.dim(`weight=${(v.weight * 100).toFixed(0)}%`)}`);
|
|
11427
|
+
}
|
|
11428
|
+
}
|
|
11429
|
+
console.log();
|
|
11430
|
+
}
|
|
11431
|
+
function formatPct(value, digits = 1) {
|
|
11432
|
+
return typeof value === "number" && Number.isFinite(value) ? `${value.toFixed(digits)}%` : "-";
|
|
11433
|
+
}
|
|
11434
|
+
function confidenceBasisLabel(value) {
|
|
11435
|
+
if (value === "fixed_horizon") return "fixed-horizon";
|
|
11436
|
+
if (value === "sequential") return "always-valid";
|
|
11437
|
+
return "unknown";
|
|
11438
|
+
}
|
|
11439
|
+
function statisticsStateLabel(value) {
|
|
11440
|
+
return value === "untrustworthy" ? "verdict withheld" : String(value ?? "unknown");
|
|
11441
|
+
}
|
|
11442
|
+
function formatRevenue(value, currency) {
|
|
11443
|
+
if (value === null || value === void 0) return "\u2014";
|
|
11444
|
+
if (!currency || typeof value !== "number" || !Number.isFinite(value)) return String(value);
|
|
11445
|
+
try {
|
|
11446
|
+
return new Intl.NumberFormat("en-US", {
|
|
11447
|
+
style: "currency",
|
|
11448
|
+
currency: currency.trim().toUpperCase()
|
|
11449
|
+
}).format(value);
|
|
11450
|
+
} catch {
|
|
11451
|
+
return String(value);
|
|
11452
|
+
}
|
|
11453
|
+
}
|
|
11454
|
+
function audienceLine(segmentApplied) {
|
|
11455
|
+
if (!segmentApplied || typeof segmentApplied !== "object") return null;
|
|
11456
|
+
const { requested, applied, reason: reason2 } = segmentApplied;
|
|
11457
|
+
if (!requested) return null;
|
|
11458
|
+
const label = `${requested.mode === "exclude" ? "excluding " : ""}${String(requested.dim)}=${String(requested.value)}`;
|
|
11459
|
+
return applied === true ? label : `all visitors (${label} not applied: ${typeof reason2 === "string" ? reason2 : "unknown reason"})`;
|
|
11460
|
+
}
|
|
11461
|
+
function printExperimentResults(results2, currency) {
|
|
11462
|
+
const summary = summarizeResults(results2);
|
|
11463
|
+
const { experiment: experiment2, totals, decision, verdictWithheld, statisticsState, presentedOutcome } = summary;
|
|
11464
|
+
const outcomeProvenance = summary.outcome.provenance === "manager_decided" ? " (manager decided)" : "";
|
|
11465
|
+
console.log();
|
|
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
|
+
}
|
|
11477
|
+
console.log(` Status: ${statusBadge(String(experiment2.status ?? "unknown"))}`);
|
|
11478
|
+
const audience = audienceLine(results2.segment_applied);
|
|
11479
|
+
if (audience) console.log(` Audience: ${audience}`);
|
|
11480
|
+
console.log(` Visitors: ${String(totals.visitors ?? 0)}`);
|
|
11481
|
+
console.log(` Conversions: ${String(totals.primaryConversions ?? 0)}`);
|
|
11482
|
+
console.log(` Revenue: ${formatRevenue(totals.revenue, currency)}`);
|
|
11483
|
+
console.log(` Outcome: ${statisticsStateLabel(presentedOutcome)}${outcomeProvenance}`);
|
|
11484
|
+
const resultsWeekdays = summary.resultsWeekdays;
|
|
11485
|
+
if (resultsWeekdays && Array.isArray(resultsWeekdays.days)) {
|
|
11486
|
+
const labels = {
|
|
11487
|
+
monday: "Mon",
|
|
11488
|
+
tuesday: "Tue",
|
|
11489
|
+
wednesday: "Wed",
|
|
11490
|
+
thursday: "Thu",
|
|
11491
|
+
friday: "Fri",
|
|
11492
|
+
saturday: "Sat",
|
|
11493
|
+
sunday: "Sun"
|
|
11494
|
+
};
|
|
10269
11495
|
console.log(
|
|
10270
11496
|
` Judged on: ${resultsWeekdays.days.map((day) => labels[String(day)] ?? day).join(", ")} (${String(resultsWeekdays.timezone)})`
|
|
10271
11497
|
);
|
|
@@ -10314,18 +11540,11 @@ function printExperimentResults(results2, currency) {
|
|
|
10314
11540
|
}
|
|
10315
11541
|
console.log();
|
|
10316
11542
|
}
|
|
11543
|
+
var filesystemPaths = Object.keys(experiment_filesystem_schema_default.files);
|
|
10317
11544
|
var EXPERIMENT_FILESYSTEM_PATH_SCHEMA = {
|
|
10318
|
-
topLevel:
|
|
10319
|
-
experiment:
|
|
10320
|
-
variation:
|
|
10321
|
-
"variation.json",
|
|
10322
|
-
"mutations.json",
|
|
10323
|
-
"url-blocks.json",
|
|
10324
|
-
"index.css",
|
|
10325
|
-
"index.js",
|
|
10326
|
-
"reset.js",
|
|
10327
|
-
"anti-flicker.json"
|
|
10328
|
-
]
|
|
11545
|
+
topLevel: filesystemPaths.filter((entry) => !entry.includes("/")),
|
|
11546
|
+
experiment: filesystemPaths.filter((entry) => entry.startsWith("experiment/")).map((entry) => entry.slice("experiment/".length)),
|
|
11547
|
+
variation: filesystemPaths.filter((entry) => entry.startsWith("variations/*/")).map((entry) => entry.slice("variations/*/".length))
|
|
10329
11548
|
};
|
|
10330
11549
|
var SERVER_OWNED_EXPERIMENT_FILES = /* @__PURE__ */ new Set(["qa.json", "launch.json"]);
|
|
10331
11550
|
var escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -10334,7 +11553,7 @@ var FILESYSTEM_FILE_RE = new RegExp(
|
|
|
10334
11553
|
`^(?:${pathAlternation(EXPERIMENT_FILESYSTEM_PATH_SCHEMA.topLevel)}|experiment/(?:${pathAlternation(EXPERIMENT_FILESYSTEM_PATH_SCHEMA.experiment)})|variations/[^/]+/(?:${pathAlternation(EXPERIMENT_FILESYSTEM_PATH_SCHEMA.variation)}))$`
|
|
10335
11554
|
);
|
|
10336
11555
|
function sha256Text(value) {
|
|
10337
|
-
return
|
|
11556
|
+
return import_node_crypto6.default.createHash("sha256").update(value).digest("hex");
|
|
10338
11557
|
}
|
|
10339
11558
|
function folderPath(folder) {
|
|
10340
11559
|
return import_node_path6.default.resolve(process.cwd(), folder);
|
|
@@ -10453,7 +11672,7 @@ async function rewriteExperimentFilesystemAssets(input) {
|
|
|
10453
11672
|
baseUrl: input.baseUrl,
|
|
10454
11673
|
enabled: input.enabled,
|
|
10455
11674
|
upload: async (file) => {
|
|
10456
|
-
const hash =
|
|
11675
|
+
const hash = import_node_crypto6.default.createHash("sha256").update(file.bytes).digest("hex");
|
|
10457
11676
|
const cached = input.assets[hash];
|
|
10458
11677
|
if (cached) return cached;
|
|
10459
11678
|
if (input.upload === null) return null;
|
|
@@ -10777,20 +11996,31 @@ var list = new Command("list").alias("ls").description(
|
|
|
10777
11996
|
"List experiments in summary view by default; use --full for details or --fields for selected columns (archived experiments are hidden by default)"
|
|
10778
11997
|
).option(
|
|
10779
11998
|
"-s, --status <status>",
|
|
10780
|
-
"Filter by status (draft|awaiting_client_approval|changes_requested|client_approved|qa|ready_to_launch|running|paused|completed)"
|
|
11999
|
+
"Filter by status (draft|awaiting_client_approval|changes_requested|client_approved|qa|ready_to_launch|running|paused|completed) (applied server-side)"
|
|
12000
|
+
).option(
|
|
12001
|
+
"--url <absolute-url>",
|
|
12002
|
+
"Filter by effective storefront URL targeting (applied server-side)"
|
|
10781
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(
|
|
10782
12004
|
"--fields <list>",
|
|
10783
|
-
"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)"
|
|
10784
12006
|
).action(async function action3(opts) {
|
|
10785
12007
|
const creds = requireCredentials();
|
|
10786
12008
|
const spinner = await createSpinner("Fetching experiments...");
|
|
10787
|
-
const
|
|
12009
|
+
const listDefault = () => listExperiments(creds, {
|
|
10788
12010
|
status: opts.status,
|
|
10789
12011
|
limit: parseInt(opts.limit, 10),
|
|
10790
12012
|
includeArchived: opts.includeArchived,
|
|
10791
12013
|
view: opts.full ? "full" : "summary",
|
|
10792
12014
|
fields: opts.fields
|
|
10793
12015
|
});
|
|
12016
|
+
const res = opts.url !== void 0 ? await listExperiments(creds, {
|
|
12017
|
+
status: opts.status,
|
|
12018
|
+
limit: parseInt(opts.limit, 10),
|
|
12019
|
+
includeArchived: opts.includeArchived,
|
|
12020
|
+
view: opts.full ? "full" : "summary",
|
|
12021
|
+
fields: opts.fields,
|
|
12022
|
+
url: opts.url
|
|
12023
|
+
}) : await listDefault();
|
|
10794
12024
|
if (!res.ok) {
|
|
10795
12025
|
spinner?.fail("Failed to fetch experiments");
|
|
10796
12026
|
fail(this, res.error, classifyStatus(res.status), res.errorDetails);
|
|
@@ -10928,7 +12158,13 @@ ${JSON.stringify(data.order, null, 2)}`);
|
|
|
10928
12158
|
console.log(`
|
|
10929
12159
|
Definitions: ${data.definitions_url}`);
|
|
10930
12160
|
}
|
|
10931
|
-
var results = new Command("results").description("Read experiment results and timeseries by ID or slug").argument("<id-or-slug>", EXPERIMENT_REFERENCE_DESCRIPTION).option("--currency <code>", "Currency code for revenue formatting").option("--explain", "Explain raw-event exclusions and residuals").option("--goal <id>", "Goal ID or event key for --explain").option("--order <key>", "Order label trail for --explain").option("--samples", "Show sample identifiers with --explain").action(async function action7(idOrSlug, opts) {
|
|
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) {
|
|
12162
|
+
if (opts.explain && (opts.segment !== void 0 || opts.excludeSegment !== void 0))
|
|
12163
|
+
fail(
|
|
12164
|
+
this,
|
|
12165
|
+
"--segment and --exclude-segment are only available for results, without --explain",
|
|
12166
|
+
EXIT_CODES.validation
|
|
12167
|
+
);
|
|
10932
12168
|
if (!opts.explain && (opts.goal || opts.order || opts.samples))
|
|
10933
12169
|
fail(this, "--goal, --order and --samples require --explain", EXIT_CODES.validation);
|
|
10934
12170
|
const creds = requireCredentials();
|
|
@@ -10939,12 +12175,38 @@ var results = new Command("results").description("Read experiment results and ti
|
|
|
10939
12175
|
spinner?.fail(`Experiment "${idOrSlug}" not found`);
|
|
10940
12176
|
fail(this, `Experiment "${idOrSlug}" not found.`, EXIT_CODES.validation);
|
|
10941
12177
|
}
|
|
10942
|
-
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, {
|
|
12184
|
+
currency: opts.currency,
|
|
12185
|
+
segment: opts.segment,
|
|
12186
|
+
exclude_segment: opts.excludeSegment
|
|
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
|
+
}
|
|
10943
12205
|
if (!res.ok || !res.data) {
|
|
10944
12206
|
spinner?.fail("Failed to fetch experiment results");
|
|
10945
12207
|
fail(
|
|
10946
12208
|
this,
|
|
10947
|
-
res.error ?? "Failed to fetch experiment results"
|
|
12209
|
+
`${res.error ?? "Failed to fetch experiment results"}${isResultsComputing(res) ? "; increase --wait to keep retrying" : ""}`,
|
|
10948
12210
|
classifyStatus(res.status),
|
|
10949
12211
|
res.errorDetails
|
|
10950
12212
|
);
|
|
@@ -10961,6 +12223,11 @@ var results = new Command("results").description("Read experiment results and ti
|
|
|
10961
12223
|
fail(this, error instanceof Error ? error.message : String(error));
|
|
10962
12224
|
}
|
|
10963
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
|
+
}
|
|
10964
12231
|
var DAY_NAMES = {
|
|
10965
12232
|
mon: "monday",
|
|
10966
12233
|
monday: "monday",
|
|
@@ -11213,7 +12480,7 @@ var create = new Command("create").description(
|
|
|
11213
12480
|
[]
|
|
11214
12481
|
).option(
|
|
11215
12482
|
"--goal <goal-id|new:fields>",
|
|
11216
|
-
"Goal id or new:type=...,selector=...,name=... (repeatable)",
|
|
12483
|
+
"Goal id or new:type=...,selector=...,name=... (repeatable; omitted uses the shop default main goal)",
|
|
11217
12484
|
collectCreateOption,
|
|
11218
12485
|
[]
|
|
11219
12486
|
).option(
|
|
@@ -11632,6 +12899,7 @@ var pullFilesystem = new Command("pull").description("Pull an experiment as an e
|
|
|
11632
12899
|
output(
|
|
11633
12900
|
this,
|
|
11634
12901
|
{
|
|
12902
|
+
schemaVersion: 1,
|
|
11635
12903
|
folder,
|
|
11636
12904
|
experimentId: exp.id,
|
|
11637
12905
|
files: res.data.files.map((file) => import_node_path6.default.join(folder, file.path))
|
|
@@ -12034,7 +13302,45 @@ var applyFilesystem = new Command("apply").option(
|
|
|
12034
13302
|
acceptQaInvalidation = true;
|
|
12035
13303
|
}
|
|
12036
13304
|
const spinner = process.stderr.isTTY ? await createSpinner("Applying experiment filesystem...") : null;
|
|
12037
|
-
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({
|
|
12038
13344
|
files,
|
|
12039
13345
|
accept_qa_invalidation: acceptQaInvalidation,
|
|
12040
13346
|
...opts.confirmTargetingReplace ? { confirmTargetingReplace: true } : {}
|
|
@@ -12055,7 +13361,7 @@ var applyFilesystem = new Command("apply").option(
|
|
|
12055
13361
|
output(this, { aborted: true }, () => console.log("Aborted."));
|
|
12056
13362
|
return;
|
|
12057
13363
|
}
|
|
12058
|
-
res = await
|
|
13364
|
+
res = await applyWithVerification({
|
|
12059
13365
|
files,
|
|
12060
13366
|
accept_qa_invalidation: acceptQaInvalidation,
|
|
12061
13367
|
confirmTargetingReplace: true
|
|
@@ -12077,7 +13383,8 @@ var applyFilesystem = new Command("apply").option(
|
|
|
12077
13383
|
plan: res.data?.plan
|
|
12078
13384
|
});
|
|
12079
13385
|
}
|
|
12080
|
-
spinner?.
|
|
13386
|
+
if (publicationUnconfirmed) spinner?.stop();
|
|
13387
|
+
else spinner?.succeed("Applied experiment filesystem");
|
|
12081
13388
|
const synced = opts.sync ? await refreshExperimentFilesystemFolderIfNeeded(creds, folder, session.experimentId) : false;
|
|
12082
13389
|
if (!synced) {
|
|
12083
13390
|
await refreshServerOwnedFilesIfNeeded(
|
|
@@ -12101,6 +13408,11 @@ var applyFilesystem = new Command("apply").option(
|
|
|
12101
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.");
|
|
12102
13409
|
const previewResult = await getQaPreviewUrls(creds, session.experimentId);
|
|
12103
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
|
+
};
|
|
12104
13416
|
output(
|
|
12105
13417
|
this,
|
|
12106
13418
|
{
|
|
@@ -12109,14 +13421,30 @@ var applyFilesystem = new Command("apply").option(
|
|
|
12109
13421
|
applied: true,
|
|
12110
13422
|
plan: planRes.data,
|
|
12111
13423
|
requiredAcknowledgment: requiredQaAcknowledgment(planRes.data),
|
|
12112
|
-
result: res.data,
|
|
13424
|
+
result: verifiedAfterTimeout ? null : res.data,
|
|
12113
13425
|
synced,
|
|
12114
13426
|
dashboardUrl,
|
|
12115
|
-
receipt: res.data?.receipt ?? null,
|
|
12116
|
-
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
|
+
} : {}
|
|
12117
13436
|
},
|
|
12118
13437
|
() => {
|
|
12119
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
|
+
}
|
|
12120
13448
|
const receipt = res.data?.receipt;
|
|
12121
13449
|
if (receipt) {
|
|
12122
13450
|
console.log("\nReceipt");
|
|
@@ -12277,6 +13605,9 @@ var qaApprove = new Command("approve").description("Approve QA and move the expe
|
|
|
12277
13605
|
"[folder-or-id]",
|
|
12278
13606
|
"Experiment filesystem folder, ID, slug, or test key (for example BIO-085)",
|
|
12279
13607
|
"."
|
|
13608
|
+
).option(
|
|
13609
|
+
"--acknowledge-zero-traffic",
|
|
13610
|
+
"Acknowledge variations that receive no traffic for this split (Apex staff only)"
|
|
12280
13611
|
).action(async function action19(target) {
|
|
12281
13612
|
const creds = requireCredentials();
|
|
12282
13613
|
const spinner = await createSpinner("Approving QA...");
|
|
@@ -12284,7 +13615,40 @@ var qaApprove = new Command("approve").description("Approve QA and move the expe
|
|
|
12284
13615
|
const resolved = await resolveExperimentForFilesystemOrId(target);
|
|
12285
13616
|
const before = await resolveExperiment(resolved.experimentId);
|
|
12286
13617
|
if (!before) fail(this, "Experiment not found.", EXIT_CODES.validation);
|
|
12287
|
-
|
|
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
|
+
}
|
|
12288
13652
|
if (!res.ok)
|
|
12289
13653
|
throw new CliError(
|
|
12290
13654
|
res.error ?? "Failed to approve QA",
|
|
@@ -12460,7 +13824,55 @@ var qaEvidence = new Command("evidence").description("Attach screenshot jobs or
|
|
|
12460
13824
|
throw error;
|
|
12461
13825
|
}
|
|
12462
13826
|
});
|
|
12463
|
-
var qa = new Command("qa").description("Manage experiment QA runs from the CLI").addCommand(qaStatus).addCommand(
|
|
13827
|
+
var qa = new Command("qa").description("Manage experiment QA runs from the CLI").addCommand(qaStatus).addCommand(
|
|
13828
|
+
new Command("events").description("List QA-tagged events for an experiment session").argument("<id-or-slug>", EXPERIMENT_REFERENCE_DESCRIPTION).option("--session <id>", "QA session id (defaults to the latest QA run)").option("--minutes <n>", "Lookback window in minutes", "60").option("--type <type>", "Event type: all, goal, or custom", "all").action(async function(target, opts) {
|
|
13829
|
+
const minutes = Number(opts.minutes);
|
|
13830
|
+
if (!Number.isInteger(minutes) || minutes < 5 || minutes > 1440)
|
|
13831
|
+
fail(this, "--minutes must be an integer between 5 and 1440", EXIT_CODES.validation);
|
|
13832
|
+
if (!["all", "goal", "custom"].includes(opts.type))
|
|
13833
|
+
fail(this, "--type must be all, goal, or custom", EXIT_CODES.validation);
|
|
13834
|
+
const creds = requireCredentials();
|
|
13835
|
+
const experiment2 = { id: (await resolveExperimentForFilesystemOrId(target)).experimentId };
|
|
13836
|
+
let session = opts.session;
|
|
13837
|
+
if (!session) {
|
|
13838
|
+
const status4 = await getExperimentQaRun(creds, experiment2.id);
|
|
13839
|
+
if (!status4.ok || !status4.data)
|
|
13840
|
+
throw new CliError(
|
|
13841
|
+
status4.error ?? "Failed to fetch QA run",
|
|
13842
|
+
classifyStatus(status4.status),
|
|
13843
|
+
status4.errorDetails
|
|
13844
|
+
);
|
|
13845
|
+
session = status4.data.run.id;
|
|
13846
|
+
}
|
|
13847
|
+
const response = await getExperimentQaEvents(creds, experiment2.id, {
|
|
13848
|
+
session,
|
|
13849
|
+
minutes,
|
|
13850
|
+
type: opts.type
|
|
13851
|
+
});
|
|
13852
|
+
if (!response.ok || !response.data)
|
|
13853
|
+
throw new CliError(
|
|
13854
|
+
response.error ?? "Failed to fetch QA events",
|
|
13855
|
+
classifyStatus(response.status),
|
|
13856
|
+
response.errorDetails
|
|
13857
|
+
);
|
|
13858
|
+
output(
|
|
13859
|
+
this,
|
|
13860
|
+
response.data,
|
|
13861
|
+
() => printTable(
|
|
13862
|
+
["TYPE", "EVENT", "VARIATION", "SESSION", "COUNT", "LAST SEEN", "LAST URL"],
|
|
13863
|
+
response.data.data.map((row) => [
|
|
13864
|
+
row.event_type === "goal" ? "goal" : "custom",
|
|
13865
|
+
row.event_name || row.goal_id,
|
|
13866
|
+
row.variation_id,
|
|
13867
|
+
row.qa_session_id,
|
|
13868
|
+
String(row.events),
|
|
13869
|
+
row.last_event_at ?? "\u2014",
|
|
13870
|
+
row.last_url ?? "\u2014"
|
|
13871
|
+
])
|
|
13872
|
+
)
|
|
13873
|
+
);
|
|
13874
|
+
})
|
|
13875
|
+
).addCommand(qaCheck).addCommand(qaNotes).addCommand(qaApprove).addCommand(qaReopen).addCommand(qaEvidence);
|
|
12464
13876
|
var SCREENSHOT_DEVICES = [
|
|
12465
13877
|
"desktop_chrome",
|
|
12466
13878
|
"iphone_15_pro",
|
|
@@ -12541,7 +13953,7 @@ var screenshots = new Command("screenshots").description(
|
|
|
12541
13953
|
Math.min(resumeDelayMs, Math.max(0, resumeDeadline - screenshotNow()))
|
|
12542
13954
|
)
|
|
12543
13955
|
);
|
|
12544
|
-
resumeDelayMs = Math.min(Math.round(resumeDelayMs * 1.6),
|
|
13956
|
+
resumeDelayMs = Math.min(Math.round(resumeDelayMs * 1.6), 4e3);
|
|
12545
13957
|
continue;
|
|
12546
13958
|
}
|
|
12547
13959
|
if (!existing.ok && isTransientResumeStatus(existing.status) && attempt <= 3 && screenshotNow() < resumeDeadline) {
|
|
@@ -12551,7 +13963,7 @@ var screenshots = new Command("screenshots").description(
|
|
|
12551
13963
|
Math.min(resumeDelayMs, Math.max(0, resumeDeadline - screenshotNow()))
|
|
12552
13964
|
)
|
|
12553
13965
|
);
|
|
12554
|
-
resumeDelayMs = Math.min(Math.round(resumeDelayMs * 1.6),
|
|
13966
|
+
resumeDelayMs = Math.min(Math.round(resumeDelayMs * 1.6), 4e3);
|
|
12555
13967
|
continue;
|
|
12556
13968
|
}
|
|
12557
13969
|
break;
|
|
@@ -12705,7 +14117,7 @@ var screenshots = new Command("screenshots").description(
|
|
|
12705
14117
|
);
|
|
12706
14118
|
consecutiveTransientFailures += 1;
|
|
12707
14119
|
if (consecutiveTransientFailures > 3) throw error;
|
|
12708
|
-
delayMs = Math.min(Math.round(delayMs * 1.6),
|
|
14120
|
+
delayMs = Math.min(Math.round(delayMs * 1.6), 4e3);
|
|
12709
14121
|
continue;
|
|
12710
14122
|
}
|
|
12711
14123
|
const polledToTerminalState = polled.ok && polled.data && polled.data.status !== "queued" && polled.data.status !== "processing";
|
|
@@ -12730,11 +14142,11 @@ var screenshots = new Command("screenshots").description(
|
|
|
12730
14142
|
polled.errorDetails
|
|
12731
14143
|
);
|
|
12732
14144
|
}
|
|
12733
|
-
delayMs = Math.min(Math.round(delayMs * 1.6),
|
|
14145
|
+
delayMs = Math.min(Math.round(delayMs * 1.6), 4e3);
|
|
12734
14146
|
continue;
|
|
12735
14147
|
}
|
|
12736
14148
|
consecutiveTransientFailures = 0;
|
|
12737
|
-
delayMs = Math.min(Math.round(delayMs * 1.6),
|
|
14149
|
+
delayMs = Math.min(Math.round(delayMs * 1.6), 4e3);
|
|
12738
14150
|
}
|
|
12739
14151
|
if (opts.attachQa !== false) {
|
|
12740
14152
|
const attached = await attachExperimentQaEvidence(creds, experiment2.id, {
|
|
@@ -13112,6 +14524,8 @@ init_source();
|
|
|
13112
14524
|
async function signedPreviewFromReadiness(creds, experimentId, variationId, targetUrl) {
|
|
13113
14525
|
try {
|
|
13114
14526
|
const client = createHttpClient(creds, {
|
|
14527
|
+
onResponse: recordApiResponse2,
|
|
14528
|
+
observeNetworkErrors: true,
|
|
13115
14529
|
onNetworkError: (info) => {
|
|
13116
14530
|
throw new Error(info.cause);
|
|
13117
14531
|
}
|
|
@@ -13246,9 +14660,21 @@ var previewCommand = new Command("preview").description(
|
|
|
13246
14660
|
'Experiment is missing "store_url". Set it on the experiment or pass --store-url <url>.'
|
|
13247
14661
|
);
|
|
13248
14662
|
}
|
|
14663
|
+
const snapshot = await getExperimentFilesystem(creds, exp.id).catch(() => null);
|
|
14664
|
+
let qaRun;
|
|
14665
|
+
if (snapshot?.ok && snapshot.data) {
|
|
14666
|
+
const qaFile = snapshot.data.files.find((file) => file.path === "qa.json");
|
|
14667
|
+
if (qaFile) {
|
|
14668
|
+
try {
|
|
14669
|
+
const run3 = JSON.parse(qaFile.content)?.latestRun;
|
|
14670
|
+
if (run3 && typeof run3.id === "string" && run3.id.trim()) qaRun = run3;
|
|
14671
|
+
} catch {
|
|
14672
|
+
}
|
|
14673
|
+
}
|
|
14674
|
+
}
|
|
13249
14675
|
let previewUrl;
|
|
13250
14676
|
try {
|
|
13251
|
-
previewUrl = buildQaPreviewUrl2(storeUrl, exp.id, variation.id);
|
|
14677
|
+
previewUrl = buildQaPreviewUrl2(storeUrl, exp.id, variation.id, qaRun?.id);
|
|
13252
14678
|
} catch {
|
|
13253
14679
|
fail(
|
|
13254
14680
|
previewCommand,
|
|
@@ -13257,6 +14683,10 @@ var previewCommand = new Command("preview").description(
|
|
|
13257
14683
|
}
|
|
13258
14684
|
const signed = await signedPreviewFromReadiness(creds, exp.id, variation.id, storeUrl);
|
|
13259
14685
|
const selectedPreviewUrl = signed?.signed_preview_url ?? previewUrl.toString();
|
|
14686
|
+
if (signed?.signed_preview_url) {
|
|
14687
|
+
const signedSessionId = signed.signed_preview_qa_session_id;
|
|
14688
|
+
qaRun = signedSessionId ? qaRun?.id === signedSessionId ? qaRun : { id: signedSessionId } : void 0;
|
|
14689
|
+
}
|
|
13260
14690
|
const warning = signed?.signed_preview_url ? null : "Test images may not load on this unsigned link. Open Apex \u2192 QA for a signed link.";
|
|
13261
14691
|
if (warning) console.error(warning);
|
|
13262
14692
|
output(
|
|
@@ -13267,6 +14697,7 @@ var previewCommand = new Command("preview").description(
|
|
|
13267
14697
|
previewUrl: selectedPreviewUrl,
|
|
13268
14698
|
expiresAt: signed?.signed_preview_expires_at ?? null,
|
|
13269
14699
|
...warning ? { warning } : {},
|
|
14700
|
+
...qaRun ? { qa_run: qaRun, qa_session_id: qaRun.id } : {},
|
|
13270
14701
|
...!opts?.storeUrl && exp.preview_target ? { previewTarget: exp.preview_target } : {}
|
|
13271
14702
|
},
|
|
13272
14703
|
() => {
|
|
@@ -13338,8 +14769,7 @@ function loadHarnessConfig(folder) {
|
|
|
13338
14769
|
experimentName: experiment2.name ?? id,
|
|
13339
14770
|
variationSlug: slugify(variation, "variant-a"),
|
|
13340
14771
|
variationName: `Variant ${variation}`,
|
|
13341
|
-
targetUrl
|
|
13342
|
-
antiFlickerSelectors: []
|
|
14772
|
+
targetUrl
|
|
13343
14773
|
}
|
|
13344
14774
|
};
|
|
13345
14775
|
}
|
|
@@ -13423,14 +14853,15 @@ async function createSession(folder) {
|
|
|
13423
14853
|
if (!apex.targetUrl) {
|
|
13424
14854
|
throw new Error("Missing targetUrl. Run `apex harness init` and set targetUrl.");
|
|
13425
14855
|
}
|
|
14856
|
+
const resetJs = cleanupResetJs(experiment2);
|
|
13426
14857
|
const res = await createHarnessSession(creds, {
|
|
13427
14858
|
experimentSlug: apex.experimentSlug,
|
|
13428
14859
|
experimentName: apex.experimentName,
|
|
13429
14860
|
variationSlug: apex.variationSlug,
|
|
13430
14861
|
variationName: apex.variationName,
|
|
13431
14862
|
targetUrl: apex.targetUrl,
|
|
13432
|
-
resetJs:
|
|
13433
|
-
antiFlickerSelectors: apex.antiFlickerSelectors
|
|
14863
|
+
...typeof resetJs === "string" ? { resetJs } : {},
|
|
14864
|
+
...Array.isArray(apex.antiFlickerSelectors) ? { antiFlickerSelectors: apex.antiFlickerSelectors } : {}
|
|
13434
14865
|
});
|
|
13435
14866
|
if (!res.ok)
|
|
13436
14867
|
throw new CliError(
|
|
@@ -13558,7 +14989,7 @@ harnessCommand.command("publish").argument("<folder>", "AB Test Harness experime
|
|
|
13558
14989
|
|
|
13559
14990
|
// src/commands/operator.ts
|
|
13560
14991
|
init_source();
|
|
13561
|
-
var
|
|
14992
|
+
var import_node_crypto7 = __toESM(require("node:crypto"));
|
|
13562
14993
|
var import_node_fs11 = __toESM(require("node:fs"));
|
|
13563
14994
|
var import_node_path9 = __toESM(require("node:path"));
|
|
13564
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)$/;
|
|
@@ -13603,8 +15034,13 @@ function formatWorkspaceBlockingIssues(value) {
|
|
|
13603
15034
|
return lines.join("\n");
|
|
13604
15035
|
}).join("\n");
|
|
13605
15036
|
}
|
|
15037
|
+
function formatHarnessRefusal(message, details) {
|
|
15038
|
+
const issues = formatWorkspaceBlockingIssues(details);
|
|
15039
|
+
return issues ? `${message}
|
|
15040
|
+
${issues}` : message;
|
|
15041
|
+
}
|
|
13606
15042
|
function sha256Text2(value) {
|
|
13607
|
-
return
|
|
15043
|
+
return import_node_crypto7.default.createHash("sha256").update(value).digest("hex");
|
|
13608
15044
|
}
|
|
13609
15045
|
function byteLength(value) {
|
|
13610
15046
|
return Buffer.byteLength(value, "utf8");
|
|
@@ -13797,12 +15233,11 @@ async function createExactHarnessSession(folder, session) {
|
|
|
13797
15233
|
experimentName: expRes.data.name,
|
|
13798
15234
|
variationSlug: config.variationSlug ?? variation.slug ?? slugify2(variation.name, "variation"),
|
|
13799
15235
|
variationName: variation.name,
|
|
13800
|
-
targetUrl
|
|
13801
|
-
antiFlickerSelectors: []
|
|
15236
|
+
targetUrl
|
|
13802
15237
|
});
|
|
13803
15238
|
if (!res.ok || !res.data)
|
|
13804
15239
|
throw new CliError(
|
|
13805
|
-
res.error ?? "Failed to create harness session",
|
|
15240
|
+
formatHarnessRefusal(res.error ?? "Failed to create harness session", res.errorDetails),
|
|
13806
15241
|
classifyStatus(res.status),
|
|
13807
15242
|
res.errorDetails
|
|
13808
15243
|
);
|
|
@@ -13894,7 +15329,15 @@ async function runPublish(folderArg, opts = {}) {
|
|
|
13894
15329
|
const harnessSession = await createExactHarnessSession(folder, session);
|
|
13895
15330
|
const version = await uploadCurrentArtifactVersion(folder, harnessSession.sessionId);
|
|
13896
15331
|
const published = await publishHarnessSession(creds, harnessSession.sessionId);
|
|
13897
|
-
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
|
+
);
|
|
13898
15341
|
external = {
|
|
13899
15342
|
sessionId: harnessSession.sessionId,
|
|
13900
15343
|
version: version.version,
|
|
@@ -14541,10 +15984,81 @@ trackingCommand.command("latency").description("Measure forced-preview versus na
|
|
|
14541
15984
|
|
|
14542
15985
|
// src/commands/events.ts
|
|
14543
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
|
+
}
|
|
14544
16013
|
var eventsCommand = new Command("events").description("Export raw tracking events");
|
|
14545
|
-
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) => {
|
|
14546
16020
|
if (!Number.isFinite(Date.parse(opts.from)) || !Number.isFinite(Date.parse(opts.to))) {
|
|
14547
|
-
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
|
+
);
|
|
14548
16062
|
}
|
|
14549
16063
|
if (!["exclude", "include", "only"].includes(opts.qa)) {
|
|
14550
16064
|
fail(command, "--qa must be exclude, include, or only", EXIT_CODES.validation);
|
|
@@ -14573,140 +16087,161 @@ eventsCommand.command("export").description("Export every raw event in an absolu
|
|
|
14573
16087
|
fail(command, "--json-summary requires --out for the event rows", EXIT_CODES.validation);
|
|
14574
16088
|
}
|
|
14575
16089
|
const output2 = opts.out ? import_node_fs14.default.createWriteStream(opts.out, { flags: "wx" }) : null;
|
|
14576
|
-
let outputError = null;
|
|
14577
|
-
output2?.on("error", (error) => {
|
|
14578
|
-
outputError = error;
|
|
14579
|
-
});
|
|
14580
|
-
if (output2) {
|
|
14581
|
-
try {
|
|
14582
|
-
await new Promise((resolve2, reject) => {
|
|
14583
|
-
output2.once("open", () => resolve2());
|
|
14584
|
-
output2.once("error", reject);
|
|
14585
|
-
});
|
|
14586
|
-
} catch (error) {
|
|
14587
|
-
|
|
14588
|
-
command,
|
|
14589
|
-
`Cannot write ${opts.out}: ${error instanceof Error ? error.message : String(error)}`,
|
|
14590
|
-
EXIT_CODES.general,
|
|
14591
|
-
{ code: "local_file_error" }
|
|
14592
|
-
);
|
|
14593
|
-
}
|
|
14594
|
-
}
|
|
14595
|
-
const writeLine = async (line) => {
|
|
14596
|
-
const stream = output2 ?? process.stdout;
|
|
14597
|
-
const accepted = output2 ? output2.write(line) : writeRawCommandOutput(line);
|
|
14598
|
-
if (accepted) return;
|
|
14599
|
-
await new Promise((resolve2, reject) => {
|
|
14600
|
-
const onDrain = () => {
|
|
14601
|
-
stream.off("error", onError);
|
|
14602
|
-
resolve2();
|
|
14603
|
-
};
|
|
14604
|
-
const onError = (error) => {
|
|
14605
|
-
stream.off("drain", onDrain);
|
|
14606
|
-
reject(error);
|
|
14607
|
-
};
|
|
14608
|
-
stream.once("drain", onDrain);
|
|
14609
|
-
stream.once("error", onError);
|
|
14610
|
-
});
|
|
14611
|
-
};
|
|
14612
|
-
let cursor;
|
|
14613
|
-
let written = 0;
|
|
14614
|
-
let pages = 0;
|
|
14615
|
-
let totals;
|
|
14616
|
-
do {
|
|
14617
|
-
const response = await exportEventsPage(requireCredentials(), {
|
|
14618
|
-
from: opts.from,
|
|
14619
|
-
to: opts.to,
|
|
14620
|
-
experiment: opts.experiment,
|
|
14621
|
-
eventTypes: opts.eventType?.split(",").map((value) => value.trim()).filter(Boolean),
|
|
14622
|
-
qa: opts.qa,
|
|
14623
|
-
limit,
|
|
14624
|
-
cursor
|
|
14625
|
-
});
|
|
14626
|
-
if (!response.ok || !response.data) {
|
|
14627
|
-
output2?.destroy();
|
|
16090
|
+
let outputError = null;
|
|
16091
|
+
output2?.on("error", (error) => {
|
|
16092
|
+
outputError = error;
|
|
16093
|
+
});
|
|
16094
|
+
if (output2) {
|
|
16095
|
+
try {
|
|
16096
|
+
await new Promise((resolve2, reject) => {
|
|
16097
|
+
output2.once("open", () => resolve2());
|
|
16098
|
+
output2.once("error", reject);
|
|
16099
|
+
});
|
|
16100
|
+
} catch (error) {
|
|
16101
|
+
const exists = error?.code === "EEXIST";
|
|
14628
16102
|
fail(
|
|
14629
16103
|
command,
|
|
14630
|
-
|
|
14631
|
-
|
|
14632
|
-
|
|
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 }
|
|
14633
16107
|
);
|
|
14634
16108
|
}
|
|
14635
|
-
|
|
14636
|
-
|
|
14637
|
-
|
|
14638
|
-
|
|
14639
|
-
|
|
14640
|
-
|
|
14641
|
-
|
|
16109
|
+
}
|
|
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) {
|
|
16143
|
+
fail(
|
|
16144
|
+
command,
|
|
16145
|
+
response.error ?? "Event export page failed",
|
|
16146
|
+
classifyStatus(response.status),
|
|
16147
|
+
response.errorDetails
|
|
16148
|
+
);
|
|
16149
|
+
}
|
|
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
|
+
);
|
|
14642
16172
|
}
|
|
14643
|
-
|
|
14644
|
-
|
|
14645
|
-
|
|
14646
|
-
|
|
16173
|
+
written += 1;
|
|
16174
|
+
}
|
|
16175
|
+
pages += 1;
|
|
16176
|
+
cursor = page.next_cursor ?? void 0;
|
|
16177
|
+
} while (cursor);
|
|
16178
|
+
if (output2) {
|
|
14647
16179
|
try {
|
|
14648
|
-
await
|
|
14649
|
-
|
|
16180
|
+
await new Promise((resolve2, reject) => {
|
|
16181
|
+
if (outputError) return reject(outputError);
|
|
16182
|
+
output2.once("error", reject);
|
|
16183
|
+
output2.end(resolve2);
|
|
16184
|
+
});
|
|
14650
16185
|
} catch (error) {
|
|
14651
16186
|
fail(
|
|
14652
16187
|
command,
|
|
14653
|
-
`Cannot write ${opts.out
|
|
16188
|
+
`Cannot write ${opts.out}: ${error instanceof Error ? error.message : String(error)}`,
|
|
14654
16189
|
EXIT_CODES.general,
|
|
14655
16190
|
{ code: "local_file_error" }
|
|
14656
16191
|
);
|
|
14657
16192
|
}
|
|
14658
|
-
|
|
14659
|
-
|
|
14660
|
-
|
|
14661
|
-
|
|
14662
|
-
|
|
14663
|
-
|
|
14664
|
-
|
|
14665
|
-
|
|
14666
|
-
|
|
14667
|
-
output2.once("error", reject);
|
|
14668
|
-
output2.end(resolve2);
|
|
14669
|
-
});
|
|
14670
|
-
} catch (error) {
|
|
14671
|
-
fail(
|
|
14672
|
-
command,
|
|
14673
|
-
`Cannot write ${opts.out}: ${error instanceof Error ? error.message : String(error)}`,
|
|
14674
|
-
EXIT_CODES.general,
|
|
14675
|
-
{ code: "local_file_error" }
|
|
14676
|
-
);
|
|
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
|
+
);
|
|
14677
16202
|
}
|
|
14678
|
-
|
|
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)
|
|
14679
16211
|
fail(
|
|
14680
16212
|
command,
|
|
14681
|
-
|
|
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.",
|
|
14682
16214
|
EXIT_CODES.general,
|
|
14683
16215
|
{
|
|
14684
|
-
|
|
16216
|
+
reason: "export_count_mismatch",
|
|
16217
|
+
...summary
|
|
14685
16218
|
}
|
|
14686
16219
|
);
|
|
14687
|
-
|
|
14688
|
-
|
|
14689
|
-
|
|
14690
|
-
events_written: written,
|
|
14691
|
-
pages,
|
|
14692
|
-
totals: totals ?? null,
|
|
14693
|
-
count_matches: matches
|
|
14694
|
-
};
|
|
14695
|
-
if (!matches)
|
|
14696
|
-
fail(
|
|
14697
|
-
command,
|
|
14698
|
-
"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.",
|
|
14699
|
-
EXIT_CODES.general,
|
|
14700
|
-
{
|
|
14701
|
-
reason: "export_count_mismatch",
|
|
14702
|
-
...summary
|
|
14703
|
-
}
|
|
14704
|
-
);
|
|
14705
|
-
if (summaryOnStdout) writeEnvelope(summary, "apex events export");
|
|
14706
|
-
process.stderr.write(
|
|
14707
|
-
`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}.
|
|
14708
16223
|
`
|
|
14709
|
-
|
|
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
|
+
}
|
|
14710
16245
|
});
|
|
14711
16246
|
|
|
14712
16247
|
// src/commands/goals.ts
|
|
@@ -14757,6 +16292,10 @@ function sdkPageviewGoalMatch(urlPattern, href) {
|
|
|
14757
16292
|
var INVENTORY_PAGE_SIZE = 100;
|
|
14758
16293
|
var GOALS_PAGE_SIZE = 100;
|
|
14759
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." }];
|
|
14760
16299
|
var NON_QUANTIFIABLE = /* @__PURE__ */ new Set(["(", "|", "^", "$"]);
|
|
14761
16300
|
function urlPatternDependence(pattern) {
|
|
14762
16301
|
let fragment = false;
|
|
@@ -14899,6 +16438,58 @@ function parseCapture(command, value) {
|
|
|
14899
16438
|
function cleanBody(input) {
|
|
14900
16439
|
return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== void 0));
|
|
14901
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
|
+
}
|
|
14902
16493
|
function printGoal(goal) {
|
|
14903
16494
|
console.log();
|
|
14904
16495
|
console.log(source_default.bold(goal.name));
|
|
@@ -14911,6 +16502,7 @@ function printGoal(goal) {
|
|
|
14911
16502
|
if (goal.text_match) console.log(` Text match: ${goal.text_match}`);
|
|
14912
16503
|
if (goal.url_pattern) console.log(` URL: ${goal.url_pattern}`);
|
|
14913
16504
|
if (goal.event_name) console.log(` Event: ${goal.event_name}`);
|
|
16505
|
+
if (goal.page_id) console.log(` Page: ${goal.page_id}`);
|
|
14914
16506
|
console.log(` Count once: ${goal.count_once ? "yes" : "no"}`);
|
|
14915
16507
|
console.log();
|
|
14916
16508
|
}
|
|
@@ -14938,8 +16530,22 @@ var list2 = new Command("list").alias("ls").description("List goals").option("-n
|
|
|
14938
16530
|
);
|
|
14939
16531
|
});
|
|
14940
16532
|
});
|
|
14941
|
-
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) {
|
|
14942
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;
|
|
14943
16549
|
const body5 = cleanBody({
|
|
14944
16550
|
name: opts.name,
|
|
14945
16551
|
type: opts.type,
|
|
@@ -14950,6 +16556,7 @@ var create2 = new Command("create").description("Create a goal").requiredOption(
|
|
|
14950
16556
|
text_match: opts.textMatch,
|
|
14951
16557
|
url_pattern: opts.urlPattern,
|
|
14952
16558
|
event_name: opts.eventName,
|
|
16559
|
+
page_id: pageId,
|
|
14953
16560
|
count_once: opts.countOnce === true,
|
|
14954
16561
|
capture: parseCapture(this, typeof opts.capture === "string" ? opts.capture : void 0)
|
|
14955
16562
|
});
|
|
@@ -14967,8 +16574,9 @@ var create2 = new Command("create").description("Create a goal").requiredOption(
|
|
|
14967
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(
|
|
14968
16575
|
"--value-property <property>",
|
|
14969
16576
|
"Numeric event property for value goals; pass null to clear"
|
|
14970
|
-
).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) {
|
|
14971
16578
|
const creds = requireCredentials();
|
|
16579
|
+
const pageId = opts.page === "null" ? null : typeof opts.page === "string" ? await resolveGoalPageId(this, creds, opts.page) : void 0;
|
|
14972
16580
|
const body5 = cleanBody({
|
|
14973
16581
|
name: opts.name,
|
|
14974
16582
|
type: opts.type,
|
|
@@ -14979,6 +16587,7 @@ var update2 = new Command("update").description("Update a goal").argument("<id>"
|
|
|
14979
16587
|
text_match: opts.textMatch,
|
|
14980
16588
|
url_pattern: opts.urlPattern,
|
|
14981
16589
|
event_name: opts.eventName,
|
|
16590
|
+
page_id: pageId,
|
|
14982
16591
|
count_once: opts.countOnce,
|
|
14983
16592
|
capture: parseCapture(this, typeof opts.capture === "string" ? opts.capture : void 0)
|
|
14984
16593
|
});
|
|
@@ -16623,13 +18232,13 @@ var NAMED_HTML_ENTITIES = {
|
|
|
16623
18232
|
nbsp: "\xA0"
|
|
16624
18233
|
};
|
|
16625
18234
|
function decodeHtmlEntities(value) {
|
|
16626
|
-
return value.replace(/&(#[0-9]+|#[xX][0-9a-fA-F]+|[a-zA-Z]+);/g, (match2,
|
|
16627
|
-
if (
|
|
16628
|
-
const hex =
|
|
16629
|
-
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);
|
|
16630
18239
|
return Number.isInteger(code) && code >= 0 && code <= 1114111 ? String.fromCodePoint(code) : match2;
|
|
16631
18240
|
}
|
|
16632
|
-
return NAMED_HTML_ENTITIES[
|
|
18241
|
+
return NAMED_HTML_ENTITIES[entity2.toLowerCase()] ?? match2;
|
|
16633
18242
|
});
|
|
16634
18243
|
}
|
|
16635
18244
|
function printSettings(settings2) {
|
|
@@ -17399,9 +19008,6 @@ function resolveWorkerBase(options) {
|
|
|
17399
19008
|
const raw = options.worker ?? process.env.DRIP_WORKER_URL ?? PRODUCTION_WORKER_BASE_URL;
|
|
17400
19009
|
return raw.trim().replace(/\/+$/, "");
|
|
17401
19010
|
}
|
|
17402
|
-
function resolveShopId(options) {
|
|
17403
|
-
return (options.shopId ?? process.env.DRIP_SHOP_ID ?? "").trim() || null;
|
|
17404
|
-
}
|
|
17405
19011
|
function servedConfigUrl(workerBase, shopId) {
|
|
17406
19012
|
return `${workerBase}/config?shopId=${encodeURIComponent(shopId)}&env=${PRODUCTION_ENVIRONMENT_KEY}`;
|
|
17407
19013
|
}
|
|
@@ -17574,7 +19180,8 @@ async function readGoals(creds) {
|
|
|
17574
19180
|
return { goals };
|
|
17575
19181
|
}
|
|
17576
19182
|
async function runDoctor(options = {}) {
|
|
17577
|
-
const
|
|
19183
|
+
const resolved = resolveCheckShop(options.shopId, process.env.DRIP_SHOP_ID);
|
|
19184
|
+
const { creds } = resolved;
|
|
17578
19185
|
if (options.check === "purchase_capture") {
|
|
17579
19186
|
const domainsRes2 = await getInstallDomains(creds);
|
|
17580
19187
|
if (!domainsRes2.ok || !domainsRes2.data)
|
|
@@ -17584,7 +19191,9 @@ async function runDoctor(options = {}) {
|
|
|
17584
19191
|
classifyStatus(domainsRes2.status),
|
|
17585
19192
|
domainsRes2.errorDetails
|
|
17586
19193
|
);
|
|
17587
|
-
|
|
19194
|
+
if (resolved.shopId)
|
|
19195
|
+
assertResponseShop(domainsRes2.data.shop_id, resolved.shopId, creds.credentialKind);
|
|
19196
|
+
const shopId2 = resolved.shopId ?? domainsRes2.data.shop_id;
|
|
17588
19197
|
const servedResult2 = shopId2 ? await fetchServedConfig(resolveWorkerBase(options), shopId2) : {};
|
|
17589
19198
|
const readTracking = async (minutes) => {
|
|
17590
19199
|
try {
|
|
@@ -17635,6 +19244,7 @@ async function runDoctor(options = {}) {
|
|
|
17635
19244
|
apiFailureOf(res)
|
|
17636
19245
|
);
|
|
17637
19246
|
}
|
|
19247
|
+
assertResponseShop(domainsRes.data.shop_id, resolved.shopId, creds.credentialKind);
|
|
17638
19248
|
for (const [label, res] of [
|
|
17639
19249
|
["url inventory", inventory],
|
|
17640
19250
|
["goals", goals]
|
|
@@ -17678,7 +19288,7 @@ async function runDoctor(options = {}) {
|
|
|
17678
19288
|
withCaveat(devPollutionCheck(eventHosts, envsRes.data, unparsedRows), inventoryCaveat)
|
|
17679
19289
|
);
|
|
17680
19290
|
const primary = hostOnly(domainsRes.data.domain) ?? eventHosts[0];
|
|
17681
|
-
const shopId =
|
|
19291
|
+
const shopId = resolved.shopId ?? domainsRes.data.shop_id;
|
|
17682
19292
|
const workerBase = resolveWorkerBase(options);
|
|
17683
19293
|
const [page, servedResult] = await Promise.all([
|
|
17684
19294
|
primary ? fetchPrimaryHtml(primary) : Promise.resolve({}),
|
|
@@ -17899,7 +19509,7 @@ function consentRateCheck(items) {
|
|
|
17899
19509
|
}
|
|
17900
19510
|
var doctorCommand = new Command("doctor").description("Check shop configuration against observed analytics").option(
|
|
17901
19511
|
"--shop-id <uuid>",
|
|
17902
|
-
"
|
|
19512
|
+
"Apex shop ID to check; API requests use it too (DRIP_SHOP_ID is the fallback when no shop is selected)"
|
|
17903
19513
|
).option("--check <id>", "Run one check (for example purchase_capture)").option(
|
|
17904
19514
|
"--worker <url>",
|
|
17905
19515
|
`Apex Worker base URL; defaults to DRIP_WORKER_URL or ${PRODUCTION_WORKER_BASE_URL}`
|
|
@@ -17908,7 +19518,7 @@ var doctorCommand = new Command("doctor").description("Check shop configuration
|
|
|
17908
19518
|
if (options.check && data.checks.length === 0)
|
|
17909
19519
|
fail(this, `Unknown doctor check: ${options.check}`, EXIT_CODES.validation);
|
|
17910
19520
|
output(this, data, () => printDoctorReport(data.checks));
|
|
17911
|
-
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;
|
|
17912
19522
|
});
|
|
17913
19523
|
function printDoctorReport(checks) {
|
|
17914
19524
|
printTable(
|
|
@@ -18092,7 +19702,7 @@ Examples:
|
|
|
18092
19702
|
);
|
|
18093
19703
|
|
|
18094
19704
|
// src/commands/install.ts
|
|
18095
|
-
var
|
|
19705
|
+
var import_promises3 = require("node:dns/promises");
|
|
18096
19706
|
var import_node_http2 = require("node:http");
|
|
18097
19707
|
var import_node_https = require("node:https");
|
|
18098
19708
|
var import_node_net = require("node:net");
|
|
@@ -18150,7 +19760,7 @@ var testResolver;
|
|
|
18150
19760
|
var testFetch;
|
|
18151
19761
|
async function resolveHost(hostname) {
|
|
18152
19762
|
const mocked = testResolver?.(hostname) ?? null;
|
|
18153
|
-
const addresses = mocked ?? await (0,
|
|
19763
|
+
const addresses = mocked ?? await (0, import_promises3.lookup)(hostname, { all: true });
|
|
18154
19764
|
return addresses.map((entry) => entry.address);
|
|
18155
19765
|
}
|
|
18156
19766
|
async function validateUrl(raw, allowedHosts) {
|
|
@@ -18630,16 +20240,16 @@ async function checkServedConfig(configUrl, workerBaseUrl, workerBaseSource) {
|
|
|
18630
20240
|
};
|
|
18631
20241
|
}
|
|
18632
20242
|
var installCommand = new Command("install").description("Check Apex storefront installation").command("check").description(
|
|
18633
|
-
"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"
|
|
18634
20244
|
).option("--url <url>", "Check one explicit bound storefront URL").option(
|
|
18635
20245
|
"--shop-id <id>",
|
|
18636
|
-
"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)"
|
|
18637
20247
|
).option(
|
|
18638
20248
|
"--worker-url <url>",
|
|
18639
20249
|
"Apex Worker base URL that serves /config (defaults to the URL in the served snippet, then DRIP_WORKER_URL, then the production worker)"
|
|
18640
20250
|
).option("--environment <key>", "Environment key to request from the worker", "production").action(async function action65(opts) {
|
|
18641
|
-
const
|
|
18642
|
-
const
|
|
20251
|
+
const resolved = resolveCheckShop(opts.shopId, process.env.DRIP_SHOP_ID);
|
|
20252
|
+
const { creds, shopId, overriddenShopId } = resolved;
|
|
18643
20253
|
if (!shopId)
|
|
18644
20254
|
fail(
|
|
18645
20255
|
this,
|
|
@@ -18647,6 +20257,10 @@ var installCommand = new Command("install").description("Check Apex storefront i
|
|
|
18647
20257
|
EXIT_CODES.validation,
|
|
18648
20258
|
{ field: "shop-id" }
|
|
18649
20259
|
);
|
|
20260
|
+
if (overriddenShopId)
|
|
20261
|
+
console.error(
|
|
20262
|
+
`Checking shop ${shopId} from --shop-id instead of the selected shop ${overriddenShopId}.`
|
|
20263
|
+
);
|
|
18650
20264
|
const domains = await getInstallDomains(creds);
|
|
18651
20265
|
if (!domains.ok || !domains.data)
|
|
18652
20266
|
fail(
|
|
@@ -18655,6 +20269,7 @@ var installCommand = new Command("install").description("Check Apex storefront i
|
|
|
18655
20269
|
classifyStatus(domains.status),
|
|
18656
20270
|
domains.errorDetails
|
|
18657
20271
|
);
|
|
20272
|
+
assertResponseShop(domains.data.shop_id, shopId, creds.credentialKind);
|
|
18658
20273
|
const mockStoreOrigins = cliTestSetting("STORE_ORIGINS");
|
|
18659
20274
|
const storeOrigins = cliTestSetting("API") === "1" && mockStoreOrigins ? mockStoreOrigins.split(",") : domains.data.storeOrigins;
|
|
18660
20275
|
const origins = opts.url ? [opts.url] : storeOrigins;
|
|
@@ -18686,7 +20301,16 @@ var installCommand = new Command("install").description("Check Apex storefront i
|
|
|
18686
20301
|
});
|
|
18687
20302
|
continue;
|
|
18688
20303
|
}
|
|
18689
|
-
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
|
+
}
|
|
18690
20314
|
if (error instanceof BodyDecodeError) {
|
|
18691
20315
|
pages.push({
|
|
18692
20316
|
url,
|
|
@@ -18723,10 +20347,12 @@ var installCommand = new Command("install").description("Check Apex storefront i
|
|
|
18723
20347
|
);
|
|
18724
20348
|
let tracking;
|
|
18725
20349
|
let trackingExitCode = null;
|
|
20350
|
+
let trackingApiFailure;
|
|
18726
20351
|
try {
|
|
18727
20352
|
const result = await verifyTracking(creds);
|
|
18728
20353
|
if (!result.ok || !result.data) {
|
|
18729
20354
|
trackingExitCode = classifyStatus(result.status);
|
|
20355
|
+
trackingApiFailure = apiFailureOf(result);
|
|
18730
20356
|
tracking = {
|
|
18731
20357
|
status: "FAIL",
|
|
18732
20358
|
message: result.error ?? "Failed to verify tracking",
|
|
@@ -18750,6 +20376,11 @@ var installCommand = new Command("install").description("Check Apex storefront i
|
|
|
18750
20376
|
if (servedConfig.status !== "PASS") warnings.push(servedConfig.message);
|
|
18751
20377
|
if (tracking.status === "WARN") warnings.push(tracking.message);
|
|
18752
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
|
+
];
|
|
18753
20384
|
const data = { ok: !failed, pages, warnings, servedConfig, tracking };
|
|
18754
20385
|
const renderReport = () => {
|
|
18755
20386
|
printTable(
|
|
@@ -18781,12 +20412,24 @@ var installCommand = new Command("install").description("Check Apex storefront i
|
|
|
18781
20412
|
};
|
|
18782
20413
|
if (failed) {
|
|
18783
20414
|
if (!shouldUseJson(this)) renderReport();
|
|
18784
|
-
|
|
18785
|
-
|
|
18786
|
-
|
|
18787
|
-
|
|
18788
|
-
|
|
18789
|
-
|
|
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
|
+
});
|
|
18790
20433
|
}
|
|
18791
20434
|
output(this, data, renderReport);
|
|
18792
20435
|
}).parent;
|
|
@@ -19383,60 +21026,312 @@ function detail4(x) {
|
|
|
19383
21026
|
console.log(` State: ${x.status}`);
|
|
19384
21027
|
console.log();
|
|
19385
21028
|
}
|
|
19386
|
-
var list9 = new Command("list").alias("ls").description("List personalizations").option("-n, --limit <n>", "Max results", "50").action(async function(o) {
|
|
19387
|
-
const r = await listManagedDeliveries(requireCredentials(), kind4, Number.parseInt(o.limit, 10));
|
|
19388
|
-
if (!r.ok || !r.data)
|
|
19389
|
-
fail(this, r.error ?? "Failed to list", classifyStatus(r.status), r.errorDetails);
|
|
19390
|
-
output(
|
|
19391
|
-
this,
|
|
19392
|
-
r.data,
|
|
19393
|
-
() => printTable(
|
|
19394
|
-
["KEY/NAME", "STATE", "UPDATED"],
|
|
19395
|
-
r.data.data.map((x) => [x.key || x.name, x.status, x.updated_at ?? "-"]),
|
|
19396
|
-
[36, 12, 28]
|
|
19397
|
-
)
|
|
19398
|
-
);
|
|
19399
|
-
});
|
|
19400
|
-
var get8 = new Command("get").description("Get a personalization").argument("<id>").action(async function(id) {
|
|
19401
|
-
const r = await getManagedDelivery(requireCredentials(), kind4, id);
|
|
19402
|
-
if (!r.ok || !r.data)
|
|
19403
|
-
fail(this, r.error ?? "Failed to get", classifyStatus(r.status), r.errorDetails);
|
|
19404
|
-
output(this, r.data, () => detail4(r.data));
|
|
19405
|
-
});
|
|
19406
|
-
function write4(name) {
|
|
19407
|
-
const c = new Command(name).description(
|
|
19408
|
-
`${name === "create" ? "Create" : "Update"} a personalization`
|
|
19409
|
-
);
|
|
19410
|
-
if (name === "update") c.argument("<id>");
|
|
19411
|
-
c.argument("[json]", "Inline JSON, @file, or file path").option("--file <path>", "JSON body file").option("--key <key>").option("--name <name>").option("--description <description>");
|
|
19412
|
-
c.action(async function(...args) {
|
|
19413
|
-
const o = this.opts();
|
|
19414
|
-
const id = name === "update" ? String(args[0]) : void 0;
|
|
19415
|
-
const value = o.file ? `@${o.file}` : String(args[name === "update" ? 1 : 0] ?? "") || void 0;
|
|
19416
|
-
const b = body2(this, value, o);
|
|
19417
|
-
if (name === "update" && !Object.keys(b).length)
|
|
19418
|
-
fail(this, "Provide at least one field to update.", EXIT_CODES.validation);
|
|
19419
|
-
const r = name === "create" ? await createManagedDelivery(requireCredentials(), kind4, b) : await updateManagedDelivery(requireCredentials(), kind4, id, b);
|
|
19420
|
-
if (!r.ok || !r.data)
|
|
19421
|
-
fail(this, r.error ?? `Failed to ${name}`, classifyStatus(r.status), r.errorDetails);
|
|
19422
|
-
output(this, r.data, () => detail4(r.data));
|
|
19423
|
-
});
|
|
19424
|
-
return c;
|
|
21029
|
+
var list9 = new Command("list").alias("ls").description("List personalizations").option("-n, --limit <n>", "Max results", "50").action(async function(o) {
|
|
21030
|
+
const r = await listManagedDeliveries(requireCredentials(), kind4, Number.parseInt(o.limit, 10));
|
|
21031
|
+
if (!r.ok || !r.data)
|
|
21032
|
+
fail(this, r.error ?? "Failed to list", classifyStatus(r.status), r.errorDetails);
|
|
21033
|
+
output(
|
|
21034
|
+
this,
|
|
21035
|
+
r.data,
|
|
21036
|
+
() => printTable(
|
|
21037
|
+
["KEY/NAME", "STATE", "UPDATED"],
|
|
21038
|
+
r.data.data.map((x) => [x.key || x.name, x.status, x.updated_at ?? "-"]),
|
|
21039
|
+
[36, 12, 28]
|
|
21040
|
+
)
|
|
21041
|
+
);
|
|
21042
|
+
});
|
|
21043
|
+
var get8 = new Command("get").description("Get a personalization").argument("<id>").action(async function(id) {
|
|
21044
|
+
const r = await getManagedDelivery(requireCredentials(), kind4, id);
|
|
21045
|
+
if (!r.ok || !r.data)
|
|
21046
|
+
fail(this, r.error ?? "Failed to get", classifyStatus(r.status), r.errorDetails);
|
|
21047
|
+
output(this, r.data, () => detail4(r.data));
|
|
21048
|
+
});
|
|
21049
|
+
function write4(name) {
|
|
21050
|
+
const c = new Command(name).description(
|
|
21051
|
+
`${name === "create" ? "Create" : "Update"} a personalization`
|
|
21052
|
+
);
|
|
21053
|
+
if (name === "update") c.argument("<id>");
|
|
21054
|
+
c.argument("[json]", "Inline JSON, @file, or file path").option("--file <path>", "JSON body file").option("--key <key>").option("--name <name>").option("--description <description>");
|
|
21055
|
+
c.action(async function(...args) {
|
|
21056
|
+
const o = this.opts();
|
|
21057
|
+
const id = name === "update" ? String(args[0]) : void 0;
|
|
21058
|
+
const value = o.file ? `@${o.file}` : String(args[name === "update" ? 1 : 0] ?? "") || void 0;
|
|
21059
|
+
const b = body2(this, value, o);
|
|
21060
|
+
if (name === "update" && !Object.keys(b).length)
|
|
21061
|
+
fail(this, "Provide at least one field to update.", EXIT_CODES.validation);
|
|
21062
|
+
const r = name === "create" ? await createManagedDelivery(requireCredentials(), kind4, b) : await updateManagedDelivery(requireCredentials(), kind4, id, b);
|
|
21063
|
+
if (!r.ok || !r.data)
|
|
21064
|
+
fail(this, r.error ?? `Failed to ${name}`, classifyStatus(r.status), r.errorDetails);
|
|
21065
|
+
output(this, r.data, () => detail4(r.data));
|
|
21066
|
+
});
|
|
21067
|
+
return c;
|
|
21068
|
+
}
|
|
21069
|
+
function lifecycle2(action78) {
|
|
21070
|
+
return new Command(action78).description(`${action78[0].toUpperCase()}${action78.slice(1)} a personalization`).argument("<id>").action(async function(id) {
|
|
21071
|
+
const r = await transitionManagedDelivery(requireCredentials(), kind4, id, action78);
|
|
21072
|
+
if (!r.ok || !r.data)
|
|
21073
|
+
fail(this, r.error ?? `Failed to ${action78}`, classifyStatus(r.status), r.errorDetails);
|
|
21074
|
+
output(this, r.data, () => detail4(r.data));
|
|
21075
|
+
});
|
|
21076
|
+
}
|
|
21077
|
+
var personalizationsCommand = new Command("personalizations").description("Manage personalizations").addCommand(list9).addCommand(get8).addCommand(write4("create")).addCommand(write4("update")).addCommand(lifecycle2("activate")).addCommand(lifecycle2("pause")).addCommand(lifecycle2("archive"));
|
|
21078
|
+
|
|
21079
|
+
// src/commands/targeting-resources.ts
|
|
21080
|
+
init_source();
|
|
21081
|
+
var import_node_fs23 = __toESM(require("node:fs"));
|
|
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
|
+
};
|
|
19425
21290
|
}
|
|
19426
|
-
function
|
|
19427
|
-
return new Command(
|
|
19428
|
-
const
|
|
19429
|
-
|
|
19430
|
-
|
|
19431
|
-
|
|
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
|
+
});
|
|
19432
21308
|
});
|
|
19433
21309
|
}
|
|
19434
|
-
var personalizationsCommand = new Command("personalizations").description("Manage personalizations").addCommand(list9).addCommand(get8).addCommand(write4("create")).addCommand(write4("update")).addCommand(lifecycle2("activate")).addCommand(lifecycle2("pause")).addCommand(lifecycle2("archive"));
|
|
19435
21310
|
|
|
19436
21311
|
// src/commands/targeting-resources.ts
|
|
19437
|
-
|
|
19438
|
-
|
|
19439
|
-
|
|
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
|
+
}
|
|
19440
21335
|
function body3(command, value) {
|
|
19441
21336
|
if (!value) return {};
|
|
19442
21337
|
const source = value.startsWith("@") ? value.slice(1) : value;
|
|
@@ -19514,7 +21409,10 @@ function targetingResourceCommand(kind5) {
|
|
|
19514
21409
|
`${action78 === "create" ? "Create" : "Update"} a ${singular3}`
|
|
19515
21410
|
);
|
|
19516
21411
|
if (action78 === "update") command.argument("<id>");
|
|
19517
|
-
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) {
|
|
19518
21416
|
const opts = this.opts();
|
|
19519
21417
|
const id = action78 === "update" ? String(args[0]) : void 0;
|
|
19520
21418
|
const value = opts.file ? `@${opts.file}` : String(args[action78 === "update" ? 1 : 0] ?? "") || void 0;
|
|
@@ -19533,10 +21431,11 @@ function targetingResourceCommand(kind5) {
|
|
|
19533
21431
|
this,
|
|
19534
21432
|
response.error ?? `Failed to ${action78}`,
|
|
19535
21433
|
classifyStatus(response.status),
|
|
19536
|
-
response.errorDetails
|
|
21434
|
+
exclusionFailureDetails(kind5, action78, response.status, response.errorDetails)
|
|
19537
21435
|
);
|
|
19538
21436
|
output(this, response.data, () => console.log(source_default.green(`${action78}d ${singular3}`)));
|
|
19539
21437
|
});
|
|
21438
|
+
if (kind5 === "exclusion-groups") withBodyHelp(command, action78);
|
|
19540
21439
|
root.addCommand(command);
|
|
19541
21440
|
}
|
|
19542
21441
|
const deleteCommand = new Command("delete").description(`Delete a ${singular3}`).argument("<id>");
|
|
@@ -19575,9 +21474,9 @@ function targetingResourceCommand(kind5) {
|
|
|
19575
21474
|
output(this, response.data ?? null, () => console.log(source_default.green(`Deleted ${singular3}`)));
|
|
19576
21475
|
})
|
|
19577
21476
|
);
|
|
19578
|
-
if (kind5 === "exclusion-groups")
|
|
21477
|
+
if (kind5 === "exclusion-groups") {
|
|
19579
21478
|
root.addCommand(
|
|
19580
|
-
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) {
|
|
19581
21480
|
const response = await requestTargetingResource(
|
|
19582
21481
|
requireCredentials(),
|
|
19583
21482
|
"PUT",
|
|
@@ -19590,11 +21489,18 @@ function targetingResourceCommand(kind5) {
|
|
|
19590
21489
|
this,
|
|
19591
21490
|
response.error ?? "Failed to replace members",
|
|
19592
21491
|
classifyStatus(response.status),
|
|
19593
|
-
|
|
21492
|
+
exclusionFailureDetails(
|
|
21493
|
+
kind5,
|
|
21494
|
+
"replace-members",
|
|
21495
|
+
response.status,
|
|
21496
|
+
response.errorDetails
|
|
21497
|
+
)
|
|
19594
21498
|
);
|
|
19595
21499
|
output(this, response.data, () => console.log(source_default.green("Replaced members")));
|
|
19596
21500
|
})
|
|
19597
21501
|
);
|
|
21502
|
+
withBodyHelp(root.commands.at(-1), "replace-members");
|
|
21503
|
+
}
|
|
19598
21504
|
return root;
|
|
19599
21505
|
}
|
|
19600
21506
|
var pagesCommand = targetingResourceCommand("pages");
|
|
@@ -19683,7 +21589,7 @@ var test2 = new Command("test").description("Evaluate experiment URL targeting f
|
|
|
19683
21589
|
var targetingCommand = new Command("targeting").description("Test experiment targeting").addCommand(test2);
|
|
19684
21590
|
|
|
19685
21591
|
// src/commands/sos.ts
|
|
19686
|
-
var
|
|
21592
|
+
var import_node_crypto8 = require("node:crypto");
|
|
19687
21593
|
init_source();
|
|
19688
21594
|
var import_node_fs24 = __toESM(require("node:fs"));
|
|
19689
21595
|
var import_node_path21 = __toESM(require("node:path"));
|
|
@@ -19794,7 +21700,7 @@ var create4 = new Command("create").description("Create an Apex SOS ticket").opt
|
|
|
19794
21700
|
const bytes = file.bytes;
|
|
19795
21701
|
await uploadSosAttachment(grant.signed_url, file.content_type, bytes);
|
|
19796
21702
|
attachmentPaths.push(grant.path);
|
|
19797
|
-
attachmentDigests.push((0,
|
|
21703
|
+
attachmentDigests.push((0, import_node_crypto8.createHash)("sha256").update(bytes).digest("hex"));
|
|
19798
21704
|
}
|
|
19799
21705
|
}
|
|
19800
21706
|
const description = readText(opts.description) ?? descriptionArg ?? `Apex CLI ${opts.type} SOS request${opts.loom ? ` with Loom evidence: ${opts.loom}` : "."}`;
|
|
@@ -20107,54 +22013,6 @@ var shopsCommand = new Command("shops").description("Manage organization shops")
|
|
|
20107
22013
|
settings("auto-stop", "Read or update auto-stop defaults", { on: "enable", off: "disable" })
|
|
20108
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);
|
|
20109
22015
|
|
|
20110
|
-
// src/commands/schema.ts
|
|
20111
|
-
function optionSchema(option) {
|
|
20112
|
-
return {
|
|
20113
|
-
flags: option.flags,
|
|
20114
|
-
...option.argChoices ? { choices: option.argChoices } : {},
|
|
20115
|
-
description: option.description,
|
|
20116
|
-
required: option.required,
|
|
20117
|
-
optional: option.optional,
|
|
20118
|
-
mandatory: Boolean(option.mandatory),
|
|
20119
|
-
variadic: option.variadic,
|
|
20120
|
-
...option.defaultValue === void 0 ? {} : { defaultValue: option.defaultValue }
|
|
20121
|
-
};
|
|
20122
|
-
}
|
|
20123
|
-
function commandSchema(command) {
|
|
20124
|
-
return {
|
|
20125
|
-
name: command.name(),
|
|
20126
|
-
aliases: command.aliases(),
|
|
20127
|
-
description: command.description(),
|
|
20128
|
-
usage: command.usage(),
|
|
20129
|
-
arguments: command.registeredArguments.map((arg) => ({
|
|
20130
|
-
name: arg.name(),
|
|
20131
|
-
required: arg.required,
|
|
20132
|
-
variadic: arg.variadic,
|
|
20133
|
-
description: arg.description,
|
|
20134
|
-
...arg.defaultValue === void 0 ? {} : { defaultValue: arg.defaultValue }
|
|
20135
|
-
})),
|
|
20136
|
-
options: command.options.map(optionSchema),
|
|
20137
|
-
commands: command.commands.map(commandSchema)
|
|
20138
|
-
};
|
|
20139
|
-
}
|
|
20140
|
-
function createSchemaCommand(program3) {
|
|
20141
|
-
return new Command("schema").description("Print the current Apex CLI command, flag, and mandatory-option schema").option("--json", "Print JSON schema").action(function action78() {
|
|
20142
|
-
const schema = {
|
|
20143
|
-
schemaVersion: 2,
|
|
20144
|
-
contract: {
|
|
20145
|
-
optionValueArity: "Options' required and optional fields describe whether the option's value is required or optional when the flag is given.",
|
|
20146
|
-
mandatoryOptions: "An option's mandatory field is true only when the flag itself must be passed.",
|
|
20147
|
-
requiredArguments: "An argument's required field means the positional is mandatory."
|
|
20148
|
-
},
|
|
20149
|
-
...commandSchema(program3)
|
|
20150
|
-
};
|
|
20151
|
-
output(this, schema, () => {
|
|
20152
|
-
process.stdout.write(`${JSON.stringify(schema, null, 2)}
|
|
20153
|
-
`);
|
|
20154
|
-
});
|
|
20155
|
-
});
|
|
20156
|
-
}
|
|
20157
|
-
|
|
20158
22016
|
// src/lib/gtm-miner.ts
|
|
20159
22017
|
function extractContainerReferences(html) {
|
|
20160
22018
|
const results2 = /* @__PURE__ */ new Map();
|
|
@@ -20787,8 +22645,15 @@ var skillsCommand = new Command("skills").description("Print or install the Apex
|
|
|
20787
22645
|
});
|
|
20788
22646
|
});
|
|
20789
22647
|
|
|
22648
|
+
// ../../ops/org-split/move-plan.mjs
|
|
22649
|
+
var uuid = /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/;
|
|
22650
|
+
function validateMoveInput(shopIds, toOrgId) {
|
|
22651
|
+
if (typeof toOrgId !== "string" || !uuid.test(toOrgId) || !Array.isArray(shopIds) || !shopIds.length || shopIds.some((id) => typeof id !== "string" || !uuid.test(id))) throw new Error("Choose valid store and destination UUIDs");
|
|
22652
|
+
return { shopIds: [...new Set(shopIds.map((id) => id.toLowerCase()))].sort(), toOrgId: toOrgId.toLowerCase() };
|
|
22653
|
+
}
|
|
22654
|
+
|
|
20790
22655
|
// src/commands/admin.ts
|
|
20791
|
-
var
|
|
22656
|
+
var uuid2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
20792
22657
|
function switchValue(value) {
|
|
20793
22658
|
if (value === "on") return true;
|
|
20794
22659
|
if (value === "off") return false;
|
|
@@ -20915,7 +22780,7 @@ var create6 = new Command("create").description("Create an internal, demo or dog
|
|
|
20915
22780
|
fail(this, "--name must be 1 to 60 characters", 3);
|
|
20916
22781
|
if (opts.reason.trim().length < 8 || opts.reason.trim().length > 500)
|
|
20917
22782
|
fail(this, "--reason must be 8 to 500 characters", 3);
|
|
20918
|
-
if (opts.claimShop && !
|
|
22783
|
+
if (opts.claimShop && !uuid2.test(opts.claimShop)) fail(this, "--claim-shop must be a UUID", 3);
|
|
20919
22784
|
if (opts.claimShop && (opts.domain || opts.platform))
|
|
20920
22785
|
fail(this, "Use --claim-shop or --domain with --platform", 3);
|
|
20921
22786
|
if (!!opts.domain !== !!opts.platform)
|
|
@@ -20936,7 +22801,7 @@ var create6 = new Command("create").description("Create an internal, demo or dog
|
|
|
20936
22801
|
fail(this, "--owner requires a store for the invitation", 3);
|
|
20937
22802
|
if (opts.plan !== "self_managed" && (!opts.consultant || !opts.consultantTitle?.trim()))
|
|
20938
22803
|
fail(this, "Managed plans require --consultant and --consultant-title", 3);
|
|
20939
|
-
if (opts.consultant && !
|
|
22804
|
+
if (opts.consultant && !uuid2.test(opts.consultant))
|
|
20940
22805
|
fail(this, "--consultant must be a platform access UUID", 3);
|
|
20941
22806
|
if (!!opts.consultant !== !!opts.consultantTitle?.trim())
|
|
20942
22807
|
fail(this, "Provide --consultant and --consultant-title together", 3);
|
|
@@ -21001,7 +22866,7 @@ var create6 = new Command("create").description("Create an internal, demo or dog
|
|
|
21001
22866
|
});
|
|
21002
22867
|
});
|
|
21003
22868
|
var setCategory = new Command("set-category").description("Change an existing account's category with an audit reason").argument("<org-id>", "Organization UUID").requiredOption("--category <category>", "internal, demo, dogfood or customer").requiredOption("--reason <text>", "Audit reason, 8 to 500 characters").option("--confirm", "Confirm category change").option("--json", "Output JSON").action(async function(orgId, opts) {
|
|
21004
|
-
if (!
|
|
22869
|
+
if (!uuid2.test(orgId)) fail(this, "<org-id> must be a UUID", 3);
|
|
21005
22870
|
if (!["internal", "demo", "dogfood", "customer"].includes(opts.category))
|
|
21006
22871
|
fail(this, "--category must be internal, demo, dogfood or customer", 3);
|
|
21007
22872
|
const reason2 = opts.reason.trim();
|
|
@@ -21032,14 +22897,137 @@ var setCategory = new Command("set-category").description("Change an existing ac
|
|
|
21032
22897
|
printTable(["RESULT"], [[`Customer ${result.customerId} moved to ${result.category}`]]);
|
|
21033
22898
|
});
|
|
21034
22899
|
});
|
|
21035
|
-
var
|
|
22900
|
+
var setManagedBy = new Command("set-managed-by").description("Assign or clear a managing agency with an audit reason").argument("<org-id>", "Organization UUID").option("--by <org-id>", "Managing agency UUID").option("--clear", "Clear the managing agency").requiredOption("--reason <text>", "Audit reason, 8 to 500 characters").option("--confirm", "Confirm manager change").option("--json", "Output JSON").action(async function(orgId, opts) {
|
|
22901
|
+
if (!uuid2.test(orgId)) fail(this, "<org-id> must be a UUID", 3);
|
|
22902
|
+
if (!!opts.by === !!opts.clear) fail(this, "Use exactly one of --by or --clear", 3);
|
|
22903
|
+
if (opts.by && !uuid2.test(opts.by)) fail(this, "--by must be a UUID", 3);
|
|
22904
|
+
const reason2 = opts.reason.trim();
|
|
22905
|
+
if (reason2.length < 8 || reason2.length > 500)
|
|
22906
|
+
fail(this, "--reason must be 8 to 500 characters", 3);
|
|
22907
|
+
if (!opts.confirm) {
|
|
22908
|
+
const quote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
22909
|
+
confirmationRequired(
|
|
22910
|
+
this,
|
|
22911
|
+
"--confirm is required to change a managing agency",
|
|
22912
|
+
`apex admin customers set-managed-by ${orgId} ${opts.by ? `--by ${opts.by}` : "--clear"} --reason ${quote(reason2)} --confirm`
|
|
22913
|
+
);
|
|
22914
|
+
}
|
|
22915
|
+
const res = await setAdminCustomerManagedBy(requireUserCredentials(), orgId, {
|
|
22916
|
+
managed_by_org_id: opts.by ?? null,
|
|
22917
|
+
reason: reason2
|
|
22918
|
+
});
|
|
22919
|
+
if (!res.ok || !res.data)
|
|
22920
|
+
fail(
|
|
22921
|
+
this,
|
|
22922
|
+
res.error ?? "Failed to change managing agency",
|
|
22923
|
+
classifyStatus(res.status),
|
|
22924
|
+
res.errorDetails
|
|
22925
|
+
);
|
|
22926
|
+
output(
|
|
22927
|
+
this,
|
|
22928
|
+
res.data,
|
|
22929
|
+
() => printTable(
|
|
22930
|
+
["CUSTOMER", "MANAGING AGENCY"],
|
|
22931
|
+
[[res.data.data.customerId, res.data.data.managed_by_org_id ?? "None"]]
|
|
22932
|
+
)
|
|
22933
|
+
);
|
|
22934
|
+
});
|
|
22935
|
+
var setWorkspaceType = new Command("set-workspace-type").description("Change a workspace type with an audit reason").argument("<org-id>", "Organization UUID").requiredOption("--type <company|agency>", "Workspace type").requiredOption("--reason <text>", "Audit reason, 8 to 500 characters").option("--confirm", "Confirm workspace type change").option("--json", "Output JSON").action(async function(orgId, opts) {
|
|
22936
|
+
if (!uuid2.test(orgId)) fail(this, "<org-id> must be a UUID", 3);
|
|
22937
|
+
if (opts.type !== "company" && opts.type !== "agency")
|
|
22938
|
+
fail(this, "--type must be company or agency", 3);
|
|
22939
|
+
const reason2 = opts.reason.trim();
|
|
22940
|
+
if (reason2.length < 8 || reason2.length > 500)
|
|
22941
|
+
fail(this, "--reason must be 8 to 500 characters", 3);
|
|
22942
|
+
if (!opts.confirm) {
|
|
22943
|
+
const quote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
22944
|
+
confirmationRequired(
|
|
22945
|
+
this,
|
|
22946
|
+
"--confirm is required to change a workspace type",
|
|
22947
|
+
`apex admin customers set-workspace-type ${orgId} --type ${opts.type} --reason ${quote(reason2)} --confirm`
|
|
22948
|
+
);
|
|
22949
|
+
}
|
|
22950
|
+
const res = await setAdminCustomerWorkspaceType(requireUserCredentials(), orgId, {
|
|
22951
|
+
workspace_type: opts.type,
|
|
22952
|
+
reason: reason2
|
|
22953
|
+
});
|
|
22954
|
+
if (!res.ok || !res.data)
|
|
22955
|
+
fail(
|
|
22956
|
+
this,
|
|
22957
|
+
res.error ?? "Failed to change workspace type",
|
|
22958
|
+
classifyStatus(res.status),
|
|
22959
|
+
res.errorDetails
|
|
22960
|
+
);
|
|
22961
|
+
output(
|
|
22962
|
+
this,
|
|
22963
|
+
res.data,
|
|
22964
|
+
() => printTable(
|
|
22965
|
+
["CUSTOMER", "WORKSPACE TYPE"],
|
|
22966
|
+
[[res.data.data.customerId, res.data.data.workspace_type]]
|
|
22967
|
+
)
|
|
22968
|
+
);
|
|
22969
|
+
});
|
|
22970
|
+
function storeMoveRequest(opts) {
|
|
22971
|
+
const { shopIds, toOrgId } = validateMoveInput(opts.shop, opts.to);
|
|
22972
|
+
const reason2 = opts.reason.trim();
|
|
22973
|
+
if (reason2.length < 8 || reason2.length > 500)
|
|
22974
|
+
throw new Error("--reason must be 8 to 500 characters");
|
|
22975
|
+
return {
|
|
22976
|
+
toOrgId,
|
|
22977
|
+
body: {
|
|
22978
|
+
shop_ids: shopIds,
|
|
22979
|
+
reason: reason2,
|
|
22980
|
+
keep_grants: !!opts.keepGrants,
|
|
22981
|
+
dry_run: !opts.confirm
|
|
22982
|
+
}
|
|
22983
|
+
};
|
|
22984
|
+
}
|
|
22985
|
+
var moveStores = new Command("move-stores").description("Preview or confirm an audited set of store moves").requiredOption("--to <org-id>", "Destination organization UUID").requiredOption(
|
|
22986
|
+
"--shop <shop-id>",
|
|
22987
|
+
"Store UUID, repeat for each store",
|
|
22988
|
+
(value, previous) => [...previous, value],
|
|
22989
|
+
[]
|
|
22990
|
+
).requiredOption("--reason <text>", "Audit reason, 8 to 500 characters").option("--keep-grants", "Preserve explicit store roles and overrides").option("--confirm", "Perform the move; otherwise return a read-only plan").option("--json", "Output JSON").action(async function(opts) {
|
|
22991
|
+
let request2;
|
|
22992
|
+
try {
|
|
22993
|
+
request2 = storeMoveRequest(opts);
|
|
22994
|
+
} catch (error) {
|
|
22995
|
+
fail(this, error instanceof Error ? error.message : "Invalid move request", 3);
|
|
22996
|
+
}
|
|
22997
|
+
const res = await moveAdminCustomerStores(
|
|
22998
|
+
requireUserCredentials(),
|
|
22999
|
+
request2.toOrgId,
|
|
23000
|
+
request2.body
|
|
23001
|
+
);
|
|
23002
|
+
if (!res.ok || !res.data)
|
|
23003
|
+
fail(
|
|
23004
|
+
this,
|
|
23005
|
+
res.error ?? "Failed to move stores",
|
|
23006
|
+
classifyStatus(res.status),
|
|
23007
|
+
res.errorDetails
|
|
23008
|
+
);
|
|
23009
|
+
output(this, res.data, () => {
|
|
23010
|
+
printTable(
|
|
23011
|
+
["FIELD", "VALUE"],
|
|
23012
|
+
Object.entries(res.data.data).map(([key, value]) => [key, JSON.stringify(value)])
|
|
23013
|
+
);
|
|
23014
|
+
});
|
|
23015
|
+
});
|
|
23016
|
+
var customers = new Command("customers").description("Create engineering accounts and manage existing account categories").addCommand(create6).addCommand(setCategory).addCommand(setManagedBy).addCommand(setWorkspaceType).addCommand(moveStores);
|
|
21036
23017
|
var adminCommand = new Command("admin").description("Operate allowlisted platform controls").addCommand(settingsGet).addCommand(quarantine).addCommand(shops).addCommand(customers);
|
|
21037
23018
|
|
|
21038
23019
|
// src/commands/membership.ts
|
|
21039
23020
|
init_source();
|
|
21040
23021
|
async function run2(c, p, label) {
|
|
21041
23022
|
const r = await p;
|
|
21042
|
-
if (!r.ok)
|
|
23023
|
+
if (!r.ok)
|
|
23024
|
+
fail(
|
|
23025
|
+
c,
|
|
23026
|
+
r.error ?? `Failed to ${label}`,
|
|
23027
|
+
classifyStatus(r.status),
|
|
23028
|
+
r.errorDetails,
|
|
23029
|
+
apiFailureOf(r)
|
|
23030
|
+
);
|
|
21043
23031
|
output(c, r.data ?? {}, () => process.stdout.write(`${JSON.stringify(r.data, null, 2)}
|
|
21044
23032
|
`));
|
|
21045
23033
|
}
|
|
@@ -21059,7 +23047,7 @@ async function confirm(c, force, label, retry) {
|
|
|
21059
23047
|
}
|
|
21060
23048
|
return true;
|
|
21061
23049
|
}
|
|
21062
|
-
var orgCommand = new Command("org").description("Manage the current organization");
|
|
23050
|
+
var orgCommand = new Command("org").alias("orgs").description("Manage the current organization");
|
|
21063
23051
|
orgCommand.command("get").action(async function() {
|
|
21064
23052
|
await run2(this, getOrg(requireUserCredentials()), "get organization");
|
|
21065
23053
|
});
|
|
@@ -21075,7 +23063,27 @@ orgCommand.command("update").option("--name <name>").option("--slug <slug>").opt
|
|
|
21075
23063
|
);
|
|
21076
23064
|
});
|
|
21077
23065
|
orgCommand.command("members").action(async function() {
|
|
21078
|
-
await
|
|
23066
|
+
const r = await listOrgMembers(requireUserCredentials());
|
|
23067
|
+
if (!r.ok)
|
|
23068
|
+
fail(
|
|
23069
|
+
this,
|
|
23070
|
+
r.error ?? "Failed to list members",
|
|
23071
|
+
classifyStatus(r.status),
|
|
23072
|
+
r.errorDetails,
|
|
23073
|
+
apiFailureOf(r)
|
|
23074
|
+
);
|
|
23075
|
+
const rows = r.data?.data ?? [];
|
|
23076
|
+
output(this, r.data ?? {}, () => {
|
|
23077
|
+
printTable(
|
|
23078
|
+
["ID", "MEMBER", "ROLE", "SUSPENDED"],
|
|
23079
|
+
rows.map((row) => [
|
|
23080
|
+
String(row.id),
|
|
23081
|
+
String(row.email ?? row.name ?? "-"),
|
|
23082
|
+
String(row.role),
|
|
23083
|
+
String(row.suspended_at ?? "-")
|
|
23084
|
+
])
|
|
23085
|
+
);
|
|
23086
|
+
});
|
|
21079
23087
|
});
|
|
21080
23088
|
orgCommand.command("invite").argument("<email>").option("--role <role>", "Alias for --org-role").option("--org-role <role>", "Organization role", "member").addOption(
|
|
21081
23089
|
new Option(
|
|
@@ -21112,6 +23120,26 @@ orgCommand.command("set-role").argument("<memberId>").argument("<role>").option(
|
|
|
21112
23120
|
"set role"
|
|
21113
23121
|
);
|
|
21114
23122
|
});
|
|
23123
|
+
orgCommand.command("suspend-member").argument("<memberId>").option("--reason <text>").option("-f, --force").action(async function(id, o) {
|
|
23124
|
+
if (!await confirm(
|
|
23125
|
+
this,
|
|
23126
|
+
o.force,
|
|
23127
|
+
`Suspend organization member ${id}`,
|
|
23128
|
+
`apex org suspend-member ${id} --force`
|
|
23129
|
+
))
|
|
23130
|
+
return;
|
|
23131
|
+
await run2(this, suspendOrgMember(requireUserCredentials(), id, o.reason), "suspend member");
|
|
23132
|
+
});
|
|
23133
|
+
orgCommand.command("reinstate-member").argument("<memberId>").option("-f, --force").action(async function(id, o) {
|
|
23134
|
+
if (!await confirm(
|
|
23135
|
+
this,
|
|
23136
|
+
o.force,
|
|
23137
|
+
`Reinstate organization member ${id}`,
|
|
23138
|
+
`apex org reinstate-member ${id} --force`
|
|
23139
|
+
))
|
|
23140
|
+
return;
|
|
23141
|
+
await run2(this, reinstateOrgMember(requireUserCredentials(), id), "reinstate member");
|
|
23142
|
+
});
|
|
21115
23143
|
orgCommand.command("remove-member").argument("<memberId>").option("-f, --force").action(async function(id, o) {
|
|
21116
23144
|
if (!await confirm(
|
|
21117
23145
|
this,
|
|
@@ -21122,23 +23150,38 @@ orgCommand.command("remove-member").argument("<memberId>").option("-f, --force")
|
|
|
21122
23150
|
return;
|
|
21123
23151
|
await run2(this, removeOrgMember(requireUserCredentials(), id), "remove member");
|
|
21124
23152
|
});
|
|
21125
|
-
var teamCommand = new Command("team").description("Manage a shop team");
|
|
21126
|
-
|
|
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);
|
|
21127
23168
|
await run2(this, listTeamMembers(requireUserCredentials(), o.shop), "list team");
|
|
21128
23169
|
});
|
|
21129
|
-
teamCommand.command("invite").argument("<email>").
|
|
23170
|
+
teamCommand.command("invite").argument("<email>").option("--shop <id>", teamShopDescription).addOption(
|
|
21130
23171
|
new Option(
|
|
21131
23172
|
"--role <role>",
|
|
21132
23173
|
"Role: editor (create and run tests, manage settings, invite people) or client_viewer (view tests, results and pipeline)"
|
|
21133
23174
|
).choices(["editor", "client_viewer"]).makeOptionMandatory()
|
|
21134
23175
|
).action(async function(email, o) {
|
|
23176
|
+
o.shop = teamShop(this, o.shop);
|
|
21135
23177
|
await run2(
|
|
21136
23178
|
this,
|
|
21137
23179
|
inviteTeam(requireUserCredentials(), email, o.shop, o.role),
|
|
21138
23180
|
"invite team member"
|
|
21139
23181
|
);
|
|
21140
23182
|
});
|
|
21141
|
-
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);
|
|
21142
23185
|
await run2(
|
|
21143
23186
|
this,
|
|
21144
23187
|
revokeTeamInvite(requireUserCredentials(), id, o.shop),
|
|
@@ -21150,10 +23193,12 @@ teamCommand.command("set-role").argument("<memberId>").addArgument(
|
|
|
21150
23193
|
"<role>",
|
|
21151
23194
|
"Role: editor (create and run tests, manage settings, invite people) or client_viewer (view tests, results and pipeline)"
|
|
21152
23195
|
).choices(["editor", "client_viewer"])
|
|
21153
|
-
).
|
|
23196
|
+
).option("--shop <id>", teamShopDescription).action(async function(id, role, o) {
|
|
23197
|
+
o.shop = teamShop(this, o.shop);
|
|
21154
23198
|
await run2(this, setTeamMember(requireUserCredentials(), id, o.shop, role), "set role");
|
|
21155
23199
|
});
|
|
21156
|
-
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);
|
|
21157
23202
|
if (!await confirm(
|
|
21158
23203
|
this,
|
|
21159
23204
|
o.force,
|
|
@@ -21164,6 +23209,38 @@ teamCommand.command("remove").argument("<memberId>").requiredOption("--shop <id>
|
|
|
21164
23209
|
await run2(this, removeTeamMember(requireUserCredentials(), id, o.shop), "remove member");
|
|
21165
23210
|
});
|
|
21166
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
|
+
|
|
21167
23244
|
// src/commands/account.ts
|
|
21168
23245
|
function userCredentials2() {
|
|
21169
23246
|
return requireUserCredentials();
|
|
@@ -21769,7 +23846,7 @@ var import_node_fs29 = __toESM(require("node:fs"));
|
|
|
21769
23846
|
var import_node_path26 = __toESM(require("node:path"));
|
|
21770
23847
|
|
|
21771
23848
|
// src/commands/landing-pages.ts
|
|
21772
|
-
var
|
|
23849
|
+
var import_node_crypto9 = __toESM(require("node:crypto"));
|
|
21773
23850
|
var import_node_fs28 = __toESM(require("node:fs"));
|
|
21774
23851
|
var import_node_path25 = __toESM(require("node:path"));
|
|
21775
23852
|
function apiFailure(command, response, fallback2) {
|
|
@@ -21783,7 +23860,7 @@ function apiFailure(command, response, fallback2) {
|
|
|
21783
23860
|
}
|
|
21784
23861
|
}
|
|
21785
23862
|
function sha256(body5) {
|
|
21786
|
-
return
|
|
23863
|
+
return import_node_crypto9.default.createHash("sha256").update(body5).digest("hex");
|
|
21787
23864
|
}
|
|
21788
23865
|
function markerPath(folder) {
|
|
21789
23866
|
return import_node_path25.default.join(folder, LANDING_PAGE_CHECKOUT_MARKER);
|
|
@@ -22532,7 +24609,10 @@ function addJsonOption(command) {
|
|
|
22532
24609
|
if (!command.options.some((option) => option.long === "--shop"))
|
|
22533
24610
|
command.option("--shop <id>", "Use a shop for this invocation");
|
|
22534
24611
|
}
|
|
22535
|
-
command.exitOverride((err) =>
|
|
24612
|
+
command.exitOverride((err) => {
|
|
24613
|
+
recordCommanderExit(err, command);
|
|
24614
|
+
interceptCliError(err, command);
|
|
24615
|
+
});
|
|
22536
24616
|
for (const child of command.commands) addJsonOption(child);
|
|
22537
24617
|
}
|
|
22538
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);
|
|
@@ -22588,7 +24668,9 @@ program2.addCommand(skillsCommand);
|
|
|
22588
24668
|
program2.addCommand(adminCommand, { hidden: true });
|
|
22589
24669
|
program2.addCommand(orgCommand);
|
|
22590
24670
|
program2.addCommand(teamCommand);
|
|
24671
|
+
program2.addCommand(upgradeCommand);
|
|
22591
24672
|
addJsonOption(program2);
|
|
24673
|
+
registerTelemetryProgram(program2);
|
|
22592
24674
|
var optionTerminatorIndex = process.argv.indexOf("--");
|
|
22593
24675
|
var optionArgv = optionTerminatorIndex === -1 ? process.argv : process.argv.slice(0, optionTerminatorIndex);
|
|
22594
24676
|
if (optionArgv.includes("--json")) program2.setOptionValue("json", true);
|