@drip-apex/apex-cli 0.15.3 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +17 -10
- package/README.md +47 -23
- package/dist/cli.js +2892 -351
- package/package.json +1 -1
- package/skills/apex-cli/SKILL.md +7 -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.
|
|
7426
|
+
version: "0.16.0",
|
|
7329
7427
|
license: "MIT",
|
|
7330
7428
|
description: "Apex CLI for managing experiments, approved roadmap ideas, and draft workspaces.",
|
|
7331
7429
|
private: false,
|
|
@@ -7383,7 +7481,9 @@ var EXIT_CODES = {
|
|
|
7383
7481
|
auth: 2,
|
|
7384
7482
|
validation: 3,
|
|
7385
7483
|
confirmationRequired: 4,
|
|
7386
|
-
conflict: 5
|
|
7484
|
+
conflict: 5,
|
|
7485
|
+
// A check command ran and reported a definite failing verdict.
|
|
7486
|
+
checkFailed: 6
|
|
7387
7487
|
};
|
|
7388
7488
|
var MAX_RECORDED_API_FAILURES = 32;
|
|
7389
7489
|
var recentApiFailures = [];
|
|
@@ -7495,7 +7595,13 @@ function configureRuntime(command) {
|
|
|
7495
7595
|
};
|
|
7496
7596
|
}
|
|
7497
7597
|
function writeJsonDocument(envelope) {
|
|
7498
|
-
const
|
|
7598
|
+
const update6 = currentUpdateState(
|
|
7599
|
+
CLI_VERSION,
|
|
7600
|
+
envelope.ok ? void 0 : minimumFromError(envelope.error, envelope.error.reason)
|
|
7601
|
+
);
|
|
7602
|
+
const warnings = update6 ? [updateWarning(update6)] : [];
|
|
7603
|
+
writeUpdateNotice(update6);
|
|
7604
|
+
const text2 = `${JSON.stringify({ ...envelope, ...update6 ? { update: update6 } : {}, ...warnings.length ? { warnings } : {} }, null, 2)}
|
|
7499
7605
|
`;
|
|
7500
7606
|
if (wroteEnvelope) {
|
|
7501
7607
|
process.stderr.write(text2);
|
|
@@ -7600,6 +7706,7 @@ var liveSpinners = /* @__PURE__ */ new Set();
|
|
|
7600
7706
|
var exitFallback;
|
|
7601
7707
|
function drainAndExit(code) {
|
|
7602
7708
|
for (const spinner of liveSpinners) spinner.stop();
|
|
7709
|
+
writePendingUpdateNotice();
|
|
7603
7710
|
process.exitCode = code;
|
|
7604
7711
|
if (exitFallback) clearTimeout(exitFallback);
|
|
7605
7712
|
exitFallback = setTimeout(() => process.exit(code), 500);
|
|
@@ -7638,13 +7745,35 @@ async function createSpinner(message) {
|
|
|
7638
7745
|
}
|
|
7639
7746
|
return spinner.start();
|
|
7640
7747
|
}
|
|
7641
|
-
var updateNotice;
|
|
7642
7748
|
async function maybeCheckForUpdate() {
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
|
|
7749
|
+
await checkForUpdate(CLI_VERSION);
|
|
7750
|
+
}
|
|
7751
|
+
var wroteUpdateNotice = false;
|
|
7752
|
+
function updateWarning(update6) {
|
|
7753
|
+
return update6.required ? {
|
|
7754
|
+
code: "cli_upgrade_required",
|
|
7755
|
+
message: `Apex CLI ${update6.current} is below the minimum supported version ${update6.minimum}. Run: ${UPGRADE_COMMAND} (or apex upgrade), then retry.`
|
|
7756
|
+
} : {
|
|
7757
|
+
code: "cli_update_available",
|
|
7758
|
+
message: `Apex CLI ${update6.latest} is available (current ${update6.current}). Run: ${UPGRADE_COMMAND}`
|
|
7759
|
+
};
|
|
7760
|
+
}
|
|
7761
|
+
function writeUpdateNotice(update6) {
|
|
7762
|
+
if (!update6 || wroteUpdateNotice) return;
|
|
7763
|
+
wroteUpdateNotice = true;
|
|
7764
|
+
process.stderr.write(`${updateWarning(update6).message}
|
|
7646
7765
|
`);
|
|
7647
7766
|
}
|
|
7767
|
+
function writePendingUpdateNotice() {
|
|
7768
|
+
writeUpdateNotice(currentUpdateState(CLI_VERSION));
|
|
7769
|
+
}
|
|
7770
|
+
process.on?.("beforeExit", writePendingUpdateNotice);
|
|
7771
|
+
function minimumFromError(error, reason2) {
|
|
7772
|
+
if (reason2 !== "cli_upgrade_required") return;
|
|
7773
|
+
const api = error.api?.body;
|
|
7774
|
+
const value = error.details?.minimum ?? api?.error?.details?.minimum;
|
|
7775
|
+
return typeof value === "string" ? value : void 0;
|
|
7776
|
+
}
|
|
7648
7777
|
async function handleCliError(err, command) {
|
|
7649
7778
|
if (err && typeof err === "object" && "exitCode" in err && err.exitCode === 0) {
|
|
7650
7779
|
flushFallbackEnvelope();
|
|
@@ -7676,6 +7805,9 @@ async function handleCliError(err, command) {
|
|
|
7676
7805
|
process.stderr.write(`Retry: ${String(cliError.details.retry)}
|
|
7677
7806
|
`);
|
|
7678
7807
|
}
|
|
7808
|
+
writeUpdateNotice(
|
|
7809
|
+
currentUpdateState(CLI_VERSION, minimumFromError(cliError, errorReason(cliError)))
|
|
7810
|
+
);
|
|
7679
7811
|
}
|
|
7680
7812
|
await finishInvocation({ exitCode: cliError.exitCode, error: err });
|
|
7681
7813
|
drainAndExit(cliError.exitCode);
|
|
@@ -7687,9 +7819,20 @@ function errorReason(error) {
|
|
|
7687
7819
|
const body5 = error.api?.body;
|
|
7688
7820
|
const code = body5?.code ?? body5?.error?.code ?? (error.api ? details?.code : void 0);
|
|
7689
7821
|
if (typeof code === "string" && code) return code;
|
|
7690
|
-
if ([
|
|
7691
|
-
|
|
7692
|
-
|
|
7822
|
+
if ([
|
|
7823
|
+
"shop_context_required",
|
|
7824
|
+
"shop_context_mismatch",
|
|
7825
|
+
"install_check_failed",
|
|
7826
|
+
"local_file_error",
|
|
7827
|
+
"validation_failed",
|
|
7828
|
+
"member_suspended",
|
|
7829
|
+
"upgrade_failed",
|
|
7830
|
+
"conflicting_context_input",
|
|
7831
|
+
"context_not_persistable",
|
|
7832
|
+
"context_selection_required",
|
|
7833
|
+
"invalid_window",
|
|
7834
|
+
"output_exists"
|
|
7835
|
+
].includes(String(details?.code)))
|
|
7693
7836
|
return String(details?.code);
|
|
7694
7837
|
const status4 = error.api?.status;
|
|
7695
7838
|
if (status4 === 401) return "auth_required";
|
|
@@ -7705,6 +7848,14 @@ function errorReason(error) {
|
|
|
7705
7848
|
function errorNextSteps(error) {
|
|
7706
7849
|
const reason2 = errorReason(error);
|
|
7707
7850
|
const steps = [];
|
|
7851
|
+
if (reason2 === "cli_upgrade_required")
|
|
7852
|
+
steps.push(
|
|
7853
|
+
{
|
|
7854
|
+
command: UPGRADE_COMMAND,
|
|
7855
|
+
why: "This CLI version is below the server's minimum for changes; upgrade, then retry the same command."
|
|
7856
|
+
},
|
|
7857
|
+
{ command: "apex upgrade", why: "Runs the same install for you." }
|
|
7858
|
+
);
|
|
7708
7859
|
if (reason2 === "member_suspended")
|
|
7709
7860
|
return [
|
|
7710
7861
|
{
|
|
@@ -7712,6 +7863,14 @@ function errorNextSteps(error) {
|
|
|
7712
7863
|
why: "Ask an organization owner or admin to reinstate your membership."
|
|
7713
7864
|
}
|
|
7714
7865
|
];
|
|
7866
|
+
if (reason2 === "feature_disabled") {
|
|
7867
|
+
const label = typeof error.details?.label === "string" ? error.details.label : "this feature";
|
|
7868
|
+
const setting = typeof error.details?.setting === "string" ? ` (shop setting ${error.details.setting})` : "";
|
|
7869
|
+
steps.push({
|
|
7870
|
+
command: "ask your Apex contact",
|
|
7871
|
+
why: `Turn on ${label} for this shop${setting}.`
|
|
7872
|
+
});
|
|
7873
|
+
}
|
|
7715
7874
|
if (reason2 === "auth_required" || error.api?.status === 401) {
|
|
7716
7875
|
steps.push({ command: "apex login", why: "Authenticate with a valid API key." });
|
|
7717
7876
|
if (!error.api)
|
|
@@ -7751,8 +7910,10 @@ function errorNextSteps(error) {
|
|
|
7751
7910
|
const hints = error.details?.next_steps;
|
|
7752
7911
|
if (Array.isArray(hints))
|
|
7753
7912
|
for (const hint of hints) {
|
|
7754
|
-
if (hint && typeof hint.command === "string" && typeof hint.why === "string")
|
|
7755
|
-
steps.
|
|
7913
|
+
if (hint && typeof hint.command === "string" && typeof hint.why === "string") {
|
|
7914
|
+
if (!steps.some((step) => step.command === hint.command))
|
|
7915
|
+
steps.push({ command: hint.command, why: hint.why });
|
|
7916
|
+
}
|
|
7756
7917
|
}
|
|
7757
7918
|
if (reason2 === "validation_failed" && error.details?.plan)
|
|
7758
7919
|
steps.push({
|
|
@@ -7869,7 +8030,7 @@ function clearCredentials() {
|
|
|
7869
8030
|
} catch {
|
|
7870
8031
|
}
|
|
7871
8032
|
}
|
|
7872
|
-
function requireCredentials() {
|
|
8033
|
+
function requireCredentials(fallbackShopId) {
|
|
7873
8034
|
const creds = getCredentials();
|
|
7874
8035
|
if (!creds) {
|
|
7875
8036
|
throw new CliError(
|
|
@@ -7878,6 +8039,7 @@ function requireCredentials() {
|
|
|
7878
8039
|
);
|
|
7879
8040
|
}
|
|
7880
8041
|
const context = resolveContext(creds, void 0, defaultCredentialKind(creds));
|
|
8042
|
+
if (!context.shopId) context.shopId = fallbackShopId?.trim() || void 0;
|
|
7881
8043
|
if (defaultCredentialKind(creds) === "user") {
|
|
7882
8044
|
setEnvelopeContext({
|
|
7883
8045
|
credential: "user",
|
|
@@ -7917,6 +8079,53 @@ function requireCredentials() {
|
|
|
7917
8079
|
});
|
|
7918
8080
|
return { ...creds, credentialKind: "shop", resolvedContext: context };
|
|
7919
8081
|
}
|
|
8082
|
+
function resolveCheckShop(explicitShopId, fallbackShopId) {
|
|
8083
|
+
const explicit = explicitShopId?.trim() || null;
|
|
8084
|
+
const fallback2 = fallbackShopId?.trim() || null;
|
|
8085
|
+
const creds = requireCredentials(explicit ?? fallback2 ?? void 0);
|
|
8086
|
+
const selectedFlag = argvOption("shop")?.trim() || null;
|
|
8087
|
+
if (explicit && selectedFlag && explicit.toLowerCase() !== selectedFlag.toLowerCase()) {
|
|
8088
|
+
throw new CliError(
|
|
8089
|
+
`--shop-id ${explicit} does not match --shop ${selectedFlag}; pass the same shop to both or drop one.`,
|
|
8090
|
+
EXIT_CODES.validation,
|
|
8091
|
+
{
|
|
8092
|
+
code: "shop_context_mismatch",
|
|
8093
|
+
field: "shop-id",
|
|
8094
|
+
requested_shop_id: explicit,
|
|
8095
|
+
selected_shop_id: selectedFlag
|
|
8096
|
+
}
|
|
8097
|
+
);
|
|
8098
|
+
}
|
|
8099
|
+
const previous = creds.resolvedContext?.shopId?.trim() || null;
|
|
8100
|
+
const shopId = explicit ?? previous ?? fallback2;
|
|
8101
|
+
if (creds.credentialKind !== "user" || !shopId) return { creds, shopId };
|
|
8102
|
+
const resolvedContext = { ...creds.resolvedContext, shopId };
|
|
8103
|
+
setEnvelopeContext({
|
|
8104
|
+
credential: "user",
|
|
8105
|
+
org_id: resolvedContext.orgId ?? null,
|
|
8106
|
+
shop_id: shopId
|
|
8107
|
+
});
|
|
8108
|
+
return {
|
|
8109
|
+
creds: { ...creds, resolvedContext },
|
|
8110
|
+
shopId,
|
|
8111
|
+
...previous && previous.toLowerCase() !== shopId.toLowerCase() ? { overriddenShopId: previous } : {}
|
|
8112
|
+
};
|
|
8113
|
+
}
|
|
8114
|
+
function assertResponseShop(apiShopId, requestedShopId, credentialKind) {
|
|
8115
|
+
const api = apiShopId?.trim();
|
|
8116
|
+
const requested = requestedShopId?.trim();
|
|
8117
|
+
if (!requested || api && api.toLowerCase() === requested.toLowerCase()) return;
|
|
8118
|
+
throw new CliError(
|
|
8119
|
+
!api ? `The API returned no shop identity; cannot verify that it answered for the requested shop ${requested}.` : credentialKind === "shop" ? `This API key is bound to shop ${api}, not the requested shop ${requested}; use a key for that shop or drop --shop-id.` : `The API answered for shop ${api}, not the requested shop ${requested}.`,
|
|
8120
|
+
EXIT_CODES.validation,
|
|
8121
|
+
{
|
|
8122
|
+
code: "shop_context_mismatch",
|
|
8123
|
+
field: "shop-id",
|
|
8124
|
+
requested_shop_id: requested,
|
|
8125
|
+
api_shop_id: api || null
|
|
8126
|
+
}
|
|
8127
|
+
);
|
|
8128
|
+
}
|
|
7920
8129
|
function requireUserCredentials() {
|
|
7921
8130
|
const creds = getCredentials();
|
|
7922
8131
|
const environmentUserKey = creds?.credentialKind === "user" ? creds.apiKey : void 0;
|
|
@@ -7977,7 +8186,65 @@ var invocation;
|
|
|
7977
8186
|
var firstError;
|
|
7978
8187
|
var apiCalls = 0;
|
|
7979
8188
|
var finished = false;
|
|
8189
|
+
var resolvedPath = "";
|
|
8190
|
+
var helpPath;
|
|
8191
|
+
var attempted;
|
|
8192
|
+
var apiCallTimings = [];
|
|
8193
|
+
var entity;
|
|
8194
|
+
function registerTelemetryProgram(program3) {
|
|
8195
|
+
program3.hook("preAction", (_command, actionCommand) => {
|
|
8196
|
+
resolvedPath = telemetryCommandPath(actionCommand);
|
|
8197
|
+
});
|
|
8198
|
+
const registerHelp = (command) => {
|
|
8199
|
+
command.on("beforeHelp", () => {
|
|
8200
|
+
helpPath = telemetryCommandPath(command);
|
|
8201
|
+
});
|
|
8202
|
+
for (const child of command.commands) registerHelp(child);
|
|
8203
|
+
};
|
|
8204
|
+
registerHelp(program3);
|
|
8205
|
+
}
|
|
8206
|
+
function telemetryCommandPath(command) {
|
|
8207
|
+
return commandPath(command).replace(/^apex(?:\s+|$)/, "");
|
|
8208
|
+
}
|
|
8209
|
+
function recordCommanderExit(error, command) {
|
|
8210
|
+
resolvedPath = telemetryCommandPath(command);
|
|
8211
|
+
attempted = error.code === "commander.unknownCommand" && command.args[0] ? [resolvedPath, redactAttempted(command.args[0])].filter(Boolean).join(" ").slice(0, 200) : resolvedPath || void 0;
|
|
8212
|
+
}
|
|
8213
|
+
var agentHosts = /* @__PURE__ */ new Set([
|
|
8214
|
+
"claude-code",
|
|
8215
|
+
"codex",
|
|
8216
|
+
"cursor",
|
|
8217
|
+
"grok",
|
|
8218
|
+
"gemini",
|
|
8219
|
+
"copilot",
|
|
8220
|
+
"other"
|
|
8221
|
+
]);
|
|
8222
|
+
function detectAgentHost(env2, tty2) {
|
|
8223
|
+
if (env2.APEX_AGENT_HOST && agentHosts.has(env2.APEX_AGENT_HOST)) return env2.APEX_AGENT_HOST;
|
|
8224
|
+
if (env2.CLAUDECODE === "1") return "claude-code";
|
|
8225
|
+
if (env2.CODEX_THREAD_ID || env2.CODEX_SANDBOX) return "codex";
|
|
8226
|
+
if (env2.CURSOR_AGENT || env2.CURSOR_TRACE_ID) return "cursor";
|
|
8227
|
+
if (Object.keys(env2).some((name) => name.startsWith("GROK_"))) return "grok";
|
|
8228
|
+
if (env2.AI_AGENT) {
|
|
8229
|
+
const token = env2.AI_AGENT.match(/^[a-z-]+/)?.[0];
|
|
8230
|
+
return token && agentHosts.has(token) ? token : "other";
|
|
8231
|
+
}
|
|
8232
|
+
return tty2 ? "terminal" : void 0;
|
|
8233
|
+
}
|
|
8234
|
+
function detectSessionId(env2) {
|
|
8235
|
+
if (/^[A-Za-z0-9._:-]{1,128}$/.test(env2.APEX_SESSION_ID ?? "")) return env2.APEX_SESSION_ID;
|
|
8236
|
+
const hostId = env2.CLAUDE_CODE_SESSION_ID || env2.CODEX_THREAD_ID;
|
|
8237
|
+
return hostId ? `h_${(0, import_node_crypto2.createHash)("sha256").update(hostId).digest("hex").slice(0, 32)}` : void 0;
|
|
8238
|
+
}
|
|
7980
8239
|
function beginInvocation(command, context = {}) {
|
|
8240
|
+
resolvedPath = telemetryCommandPath(command);
|
|
8241
|
+
helpPath = void 0;
|
|
8242
|
+
attempted = void 0;
|
|
8243
|
+
firstError = void 0;
|
|
8244
|
+
apiCalls = 0;
|
|
8245
|
+
finished = false;
|
|
8246
|
+
apiCallTimings.length = 0;
|
|
8247
|
+
entity = void 0;
|
|
7981
8248
|
const names = /* @__PURE__ */ new Set();
|
|
7982
8249
|
for (let current = command; current; current = current.parent) {
|
|
7983
8250
|
for (const option of current.options) {
|
|
@@ -8030,7 +8297,9 @@ var staticRouteSegments = /* @__PURE__ */ new Set([
|
|
|
8030
8297
|
"duplicate",
|
|
8031
8298
|
"entry-exit",
|
|
8032
8299
|
"environments",
|
|
8300
|
+
"events",
|
|
8033
8301
|
"evidence",
|
|
8302
|
+
"export",
|
|
8034
8303
|
"exclusion-groups",
|
|
8035
8304
|
"experiments",
|
|
8036
8305
|
"external-pixels",
|
|
@@ -8124,6 +8393,24 @@ function normalizeRoute(path26) {
|
|
|
8124
8393
|
}
|
|
8125
8394
|
function recordApiResponse2(response) {
|
|
8126
8395
|
apiCalls++;
|
|
8396
|
+
if (apiCallTimings.length < 25)
|
|
8397
|
+
apiCallTimings.push({
|
|
8398
|
+
method: response.method,
|
|
8399
|
+
route: normalizeRoute(response.path),
|
|
8400
|
+
status: response.status,
|
|
8401
|
+
duration_ms: response.durationMs,
|
|
8402
|
+
...response.serverTiming ? { server_timing: response.serverTiming } : {}
|
|
8403
|
+
});
|
|
8404
|
+
if (!entity) {
|
|
8405
|
+
const match2 = response.path.match(
|
|
8406
|
+
/^\/api\/v1\/(experiments|landing-pages)\/([0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12})(?=\/|\?|$)/i
|
|
8407
|
+
);
|
|
8408
|
+
if (match2)
|
|
8409
|
+
entity = {
|
|
8410
|
+
entity_type: match2[1] === "experiments" ? "experiment" : "landing_page",
|
|
8411
|
+
entity_id: match2[2]
|
|
8412
|
+
};
|
|
8413
|
+
}
|
|
8127
8414
|
if (response.status >= 400 && !firstError)
|
|
8128
8415
|
firstError = { ...response, path: normalizeRoute(response.path) };
|
|
8129
8416
|
}
|
|
@@ -8133,20 +8420,62 @@ function classifyError(exitCode, error, status4) {
|
|
|
8133
8420
|
if (detail5.code === "commander.unknownCommand") return "unknown_command";
|
|
8134
8421
|
if (detail5.code?.startsWith("commander.")) return "validation";
|
|
8135
8422
|
if (exitCode === 4) return "confirmation_required";
|
|
8423
|
+
if (exitCode === EXIT_CODES.checkFailed) return "check_failed";
|
|
8136
8424
|
if (status4 === 401 || status4 === 403 || exitCode === 2) return "auth";
|
|
8137
|
-
if (status4 === 404) return "not_found";
|
|
8425
|
+
if (status4 === 404 || status4 === 410) return "not_found";
|
|
8138
8426
|
if (status4 === 429) return "rate_limited";
|
|
8139
8427
|
if (status4 === 409 || exitCode === 5) return "conflict";
|
|
8140
8428
|
if (status4 && status4 >= 500) return "server";
|
|
8141
8429
|
if (status4 === 400 || status4 === 422 || exitCode === 3) return "validation";
|
|
8430
|
+
if (detail5.details?.reason === "timeout" || detail5.details?.reason === "connect_timeout" || detail5.name === "TimeoutError")
|
|
8431
|
+
return "timeout";
|
|
8142
8432
|
if (Boolean(
|
|
8143
|
-
detail5.details?.reason && ["network", "dns", "
|
|
8144
|
-
|
|
8145
|
-
)
|
|
8146
|
-
) || detail5.details?.code === "network_error" || detail5.name === "TimeoutError")
|
|
8433
|
+
detail5.details?.reason && ["network", "dns", "tls", "refused", "reset", "unknown"].includes(detail5.details.reason)
|
|
8434
|
+
) || detail5.details?.code === "network_error")
|
|
8147
8435
|
return "network";
|
|
8148
8436
|
return "internal";
|
|
8149
8437
|
}
|
|
8438
|
+
function redactAttempted(token) {
|
|
8439
|
+
return /^(?:apx_|sk_|pk_|rk_|ghp_|gho_|xox|Bearer)/.test(token) || /^[A-Za-z0-9_-]{24,}$/.test(token) || /:\/\/|[/\\@=]/.test(token) || token.length > 40 ? "(redacted)" : token;
|
|
8440
|
+
}
|
|
8441
|
+
function normalizeErrorCode(value, exitCode) {
|
|
8442
|
+
const normalized = value.toLowerCase();
|
|
8443
|
+
return normalized.length <= 64 && /^[a-z0-9._-]+$/.test(normalized) ? normalized : `exit_${exitCode}`;
|
|
8444
|
+
}
|
|
8445
|
+
function invocationErrorCode(exitCode, error, commanderExit) {
|
|
8446
|
+
if (exitCode === 0) return void 0;
|
|
8447
|
+
let value = firstError?.errorCode;
|
|
8448
|
+
if (!value && firstError)
|
|
8449
|
+
value = firstError.status === 410 ? "gone" : `http_${firstError.status}`;
|
|
8450
|
+
const detail5 = error && typeof error === "object" ? error : {};
|
|
8451
|
+
if (!value && typeof detail5.details?.reason === "string" && !detail5.api) {
|
|
8452
|
+
const reason2 = detail5.details.reason.toLowerCase();
|
|
8453
|
+
value = [
|
|
8454
|
+
"network",
|
|
8455
|
+
"dns",
|
|
8456
|
+
"timeout",
|
|
8457
|
+
"connect_timeout",
|
|
8458
|
+
"tls",
|
|
8459
|
+
"refused",
|
|
8460
|
+
"reset",
|
|
8461
|
+
"unknown"
|
|
8462
|
+
].includes(reason2) ? `network_${reason2}` : reason2 || void 0;
|
|
8463
|
+
}
|
|
8464
|
+
if (!value && commanderExit && typeof detail5.code === "string")
|
|
8465
|
+
value = detail5.code.slice("commander.".length).replace(/([a-z])([A-Z])/g, "$1_$2");
|
|
8466
|
+
if (!value) {
|
|
8467
|
+
const apiCode = detail5.api?.body?.code ?? detail5.api?.body?.error?.code ?? detail5.details?.code;
|
|
8468
|
+
if (typeof apiCode === "string" && apiCode) value = apiCode;
|
|
8469
|
+
else if (detail5.api?.status === 401 || exitCode === 2) value = "auth_required";
|
|
8470
|
+
else if (detail5.api?.status === 403) value = "auth_forbidden";
|
|
8471
|
+
else if (detail5.api?.status === 404) value = "not_found";
|
|
8472
|
+
else if (detail5.api?.status === 429) value = "rate_limited";
|
|
8473
|
+
else if (detail5.api?.status === 409 || exitCode === 5) value = "conflict";
|
|
8474
|
+
else if (exitCode === 4) value = "confirmation_required";
|
|
8475
|
+
else if (exitCode === 3) value = "validation_failed";
|
|
8476
|
+
}
|
|
8477
|
+
return normalizeErrorCode(value ?? `exit_${exitCode}`, exitCode);
|
|
8478
|
+
}
|
|
8150
8479
|
async function finishInvocation({
|
|
8151
8480
|
exitCode: rawExitCode,
|
|
8152
8481
|
error
|
|
@@ -8168,10 +8497,8 @@ async function finishInvocation({
|
|
|
8168
8497
|
const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
|
|
8169
8498
|
const commanderExit = code.startsWith("commander.");
|
|
8170
8499
|
const parse3 = commanderExit && exitCode !== 0;
|
|
8171
|
-
|
|
8172
|
-
const
|
|
8173
|
-
if (attempted === "login" || attempted === "logout") return;
|
|
8174
|
-
const command = parse3 ? "(parse)" : invocation?.command ?? (commanderExit && exitCode === 0 ? attempted ?? (code === "commander.version" ? "(version)" : "(help)") : void 0);
|
|
8500
|
+
if (resolvedPath === "login" || resolvedPath === "logout") return;
|
|
8501
|
+
const command = parse3 ? "(parse)" : invocation?.command ?? (commanderExit && exitCode === 0 ? code === "commander.version" ? "(version)" : ["(help)", helpPath].filter(Boolean).join(" ") : void 0);
|
|
8175
8502
|
if (!command || command === "login" || command === "logout") return;
|
|
8176
8503
|
if (!getClientIdentity()) setClientIdentity({ client: "apex-cli", version: CLI_VERSION });
|
|
8177
8504
|
const payload = {
|
|
@@ -8185,8 +8512,12 @@ async function finishInvocation({
|
|
|
8185
8512
|
exit_code: exitCode,
|
|
8186
8513
|
error_class: classifyError(exitCode, error, firstError?.status),
|
|
8187
8514
|
api_calls: apiCalls,
|
|
8515
|
+
...apiCallTimings.length ? { api_call_timings: [...apiCallTimings] } : {},
|
|
8516
|
+
...entity,
|
|
8517
|
+
...detectAgentHost(process.env, process.stdout.isTTY === true) ? { agent_host: detectAgentHost(process.env, process.stdout.isTTY === true) } : {},
|
|
8518
|
+
...detectSessionId(process.env) ? { session_id: detectSessionId(process.env) } : {},
|
|
8188
8519
|
first_error_status: firstError?.status,
|
|
8189
|
-
first_error_code:
|
|
8520
|
+
first_error_code: invocationErrorCode(exitCode, error, commanderExit),
|
|
8190
8521
|
first_error_request_id: firstError?.requestId,
|
|
8191
8522
|
first_error_route: firstError?.path,
|
|
8192
8523
|
duration_ms: Math.max(0, Math.round(performance.now() - started))
|
|
@@ -8207,13 +8538,13 @@ init_source();
|
|
|
8207
8538
|
|
|
8208
8539
|
// src/commands/login.ts
|
|
8209
8540
|
init_source();
|
|
8210
|
-
var
|
|
8541
|
+
var import_node_crypto4 = __toESM(require("node:crypto"));
|
|
8211
8542
|
var import_node_http = __toESM(require("node:http"));
|
|
8212
8543
|
var import_node_os6 = __toESM(require("node:os"));
|
|
8213
8544
|
var import_node_readline2 = __toESM(require("node:readline"));
|
|
8214
8545
|
|
|
8215
8546
|
// src/api.ts
|
|
8216
|
-
var
|
|
8547
|
+
var import_node_crypto3 = require("node:crypto");
|
|
8217
8548
|
|
|
8218
8549
|
// ../api-client/src/landing-pages.ts
|
|
8219
8550
|
var LANDING_PAGE_MANIFEST_FILE = "apex-landing-page.json";
|
|
@@ -8378,7 +8709,11 @@ function sharedClient(creds) {
|
|
|
8378
8709
|
let client = sharedClients.get(creds);
|
|
8379
8710
|
if (!client) {
|
|
8380
8711
|
client = createHttpClient(creds, {
|
|
8381
|
-
onResponse:
|
|
8712
|
+
onResponse: (observation) => {
|
|
8713
|
+
recordApiResponse2(observation);
|
|
8714
|
+
recordServerCliPolicy?.(observation);
|
|
8715
|
+
},
|
|
8716
|
+
observeNetworkErrors: true,
|
|
8382
8717
|
transport: testTransport,
|
|
8383
8718
|
testTransportVersion: () => testTransport ? process.env[["APEX", "CLI", "MOCK", "API", "VERSION"].join("_")] : void 0,
|
|
8384
8719
|
onNetworkError: (info) => {
|
|
@@ -8428,8 +8763,14 @@ function sharedClient(creds) {
|
|
|
8428
8763
|
}
|
|
8429
8764
|
return client;
|
|
8430
8765
|
}
|
|
8431
|
-
async function request(creds, method, path26, body5, requestTimeoutMs) {
|
|
8432
|
-
const response = await sharedClient(creds).request(
|
|
8766
|
+
async function request(creds, method, path26, body5, requestTimeoutMs, options) {
|
|
8767
|
+
const response = await sharedClient(creds).request(
|
|
8768
|
+
method,
|
|
8769
|
+
path26,
|
|
8770
|
+
body5,
|
|
8771
|
+
requestTimeoutMs,
|
|
8772
|
+
options
|
|
8773
|
+
);
|
|
8433
8774
|
recordApiResponse(response);
|
|
8434
8775
|
return response;
|
|
8435
8776
|
}
|
|
@@ -8534,6 +8875,7 @@ function planExperimentFilesystem(creds, id, body5) {
|
|
|
8534
8875
|
body5
|
|
8535
8876
|
);
|
|
8536
8877
|
}
|
|
8878
|
+
var APPLY_TIMEOUT_MS = 9e4;
|
|
8537
8879
|
function applyExperimentFilesystem(creds, id, body5) {
|
|
8538
8880
|
return request(
|
|
8539
8881
|
creds,
|
|
@@ -8543,7 +8885,9 @@ function applyExperimentFilesystem(creds, id, body5) {
|
|
|
8543
8885
|
files: body5.files,
|
|
8544
8886
|
...body5.accept_qa_invalidation !== void 0 ? { accept_qa_invalidation: body5.accept_qa_invalidation } : {},
|
|
8545
8887
|
...body5.confirmTargetingReplace === true ? { confirmTargetingReplace: true } : {}
|
|
8546
|
-
}
|
|
8888
|
+
},
|
|
8889
|
+
void 0,
|
|
8890
|
+
{ minTimeoutMs: APPLY_TIMEOUT_MS }
|
|
8547
8891
|
);
|
|
8548
8892
|
}
|
|
8549
8893
|
function getExperimentQaRun(creds, id) {
|
|
@@ -8560,6 +8904,23 @@ function getExperimentQaEvents(creds, id, options = {}) {
|
|
|
8560
8904
|
`/api/v1/experiments/${id}/qa/events${params.size ? `?${params}` : ""}`
|
|
8561
8905
|
);
|
|
8562
8906
|
}
|
|
8907
|
+
async function runExperimentCommerceProbes(creds, id, kinds) {
|
|
8908
|
+
const response = await request(
|
|
8909
|
+
creds,
|
|
8910
|
+
"POST",
|
|
8911
|
+
`/api/v1/experiments/${id}/qa/commerce-probes`,
|
|
8912
|
+
kinds?.length ? { kinds } : {}
|
|
8913
|
+
);
|
|
8914
|
+
return { ...response, data: response.data?.data };
|
|
8915
|
+
}
|
|
8916
|
+
async function getExperimentCommerceProbes(creds, id) {
|
|
8917
|
+
const response = await request(
|
|
8918
|
+
creds,
|
|
8919
|
+
"GET",
|
|
8920
|
+
`/api/v1/experiments/${id}/qa/commerce-probes`
|
|
8921
|
+
);
|
|
8922
|
+
return { ...response, data: response.data?.data };
|
|
8923
|
+
}
|
|
8563
8924
|
function updateExperimentQaRun(creds, id, body5) {
|
|
8564
8925
|
return request(creds, "PATCH", `/api/v1/experiments/${id}/qa/run`, body5);
|
|
8565
8926
|
}
|
|
@@ -8569,8 +8930,9 @@ function attachExperimentQaEvidence(creds, id, body5) {
|
|
|
8569
8930
|
function uploadExperimentQaEvidence(creds, id, form) {
|
|
8570
8931
|
return requestForm(creds, "POST", `/api/v1/experiments/${id}/qa/evidence`, form);
|
|
8571
8932
|
}
|
|
8572
|
-
function approveExperimentQa(creds, id) {
|
|
8573
|
-
return request(creds, "PATCH", `/api/v1/experiments/${id}/qa`);
|
|
8933
|
+
function approveExperimentQa(creds, id, acknowledgement) {
|
|
8934
|
+
if (!acknowledgement) return request(creds, "PATCH", `/api/v1/experiments/${id}/qa`);
|
|
8935
|
+
return request(creds, "PATCH", `/api/v1/experiments/${id}/qa`, acknowledgement);
|
|
8574
8936
|
}
|
|
8575
8937
|
function reopenExperimentQa(creds, id) {
|
|
8576
8938
|
return request(creds, "DELETE", `/api/v1/experiments/${id}/qa`);
|
|
@@ -8770,7 +9132,7 @@ function testTargeting(creds, url, opts) {
|
|
|
8770
9132
|
return request(creds, "GET", `/api/v1/targeting/test?${params.toString()}`);
|
|
8771
9133
|
}
|
|
8772
9134
|
function createSos(creds, body5, options = {}) {
|
|
8773
|
-
const idempotency_key = body5.idempotency_key ?? (0,
|
|
9135
|
+
const idempotency_key = body5.idempotency_key ?? (0, import_node_crypto3.createHash)("sha256").update(
|
|
8774
9136
|
[
|
|
8775
9137
|
body5.type,
|
|
8776
9138
|
body5.experiment_id ?? "",
|
|
@@ -9267,6 +9629,16 @@ function resolveSelection(tree, input) {
|
|
|
9267
9629
|
const shop = shopValue ? match(shopValue, org.shops, "shop") : void 0;
|
|
9268
9630
|
return { orgId: org.id, ...shop ? { shopId: shop.id } : {} };
|
|
9269
9631
|
}
|
|
9632
|
+
function resolveFlagSelection(tree, values) {
|
|
9633
|
+
if (values.org) {
|
|
9634
|
+
const org = match(values.org, tree.orgs, "organization");
|
|
9635
|
+
const shop2 = values.shop ? match(values.shop, org.shops, "shop") : void 0;
|
|
9636
|
+
return { orgId: org.id, ...shop2 ? { shopId: shop2.id } : {} };
|
|
9637
|
+
}
|
|
9638
|
+
const shops2 = tree.orgs.flatMap((org) => org.shops.map((shop2) => ({ ...shop2, orgId: org.id })));
|
|
9639
|
+
const shop = match(values.shop, shops2, "shop");
|
|
9640
|
+
return { orgId: shop.orgId, shopId: shop.id };
|
|
9641
|
+
}
|
|
9270
9642
|
function ask(question) {
|
|
9271
9643
|
const rl = import_node_readline.default.createInterface({ input: process.stdin, output: process.stderr });
|
|
9272
9644
|
return new Promise(
|
|
@@ -9279,8 +9651,23 @@ function ask(question) {
|
|
|
9279
9651
|
async function pickContext(tree) {
|
|
9280
9652
|
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
9281
9653
|
throw new CliError(
|
|
9282
|
-
"Interactive context selection requires a TTY. Pass
|
|
9283
|
-
EXIT_CODES.validation
|
|
9654
|
+
"Interactive context selection requires a TTY. Pass <org>[/<shop>] to apex use, or use --org/--shop.",
|
|
9655
|
+
EXIT_CODES.validation,
|
|
9656
|
+
{
|
|
9657
|
+
code: "context_selection_required",
|
|
9658
|
+
candidates: tree.orgs.slice(0, 20).map((org2) => ({
|
|
9659
|
+
org_id: org2.id,
|
|
9660
|
+
org_name: org2.name,
|
|
9661
|
+
shops: org2.shops.slice(0, 20).map((shop2) => ({ id: shop2.id, name: shop2.name }))
|
|
9662
|
+
})),
|
|
9663
|
+
truncated: tree.orgs.length > 20 || tree.orgs.some((org2) => org2.shops.length > 20),
|
|
9664
|
+
next_steps: [
|
|
9665
|
+
{
|
|
9666
|
+
command: "apex use <org-id>/<shop-id>",
|
|
9667
|
+
why: "Choose one of error.details.candidates."
|
|
9668
|
+
}
|
|
9669
|
+
]
|
|
9670
|
+
}
|
|
9284
9671
|
);
|
|
9285
9672
|
let org = tree.orgs.length === 1 ? tree.orgs[0] : void 0;
|
|
9286
9673
|
if (!org) {
|
|
@@ -9400,7 +9787,7 @@ function parseFormBody(body5) {
|
|
|
9400
9787
|
return new URLSearchParams(body5);
|
|
9401
9788
|
}
|
|
9402
9789
|
async function waitForBrowserLogin(baseUrl, userMode = false) {
|
|
9403
|
-
const state =
|
|
9790
|
+
const state = import_node_crypto4.default.randomBytes(24).toString("base64url");
|
|
9404
9791
|
const clientName = import_node_os6.default.hostname() || "Local machine";
|
|
9405
9792
|
return new Promise((resolve2, reject) => {
|
|
9406
9793
|
let settled = false;
|
|
@@ -9710,6 +10097,14 @@ Context: ${current.orgId ?? "none"}${current.shopId ? ` / ${current.shopId}` : "
|
|
|
9710
10097
|
|
|
9711
10098
|
// src/commands/use.ts
|
|
9712
10099
|
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) {
|
|
10100
|
+
const flags = this.optsWithGlobals();
|
|
10101
|
+
if (input && (flags.org || flags.shop))
|
|
10102
|
+
fail(
|
|
10103
|
+
this,
|
|
10104
|
+
"Pass the context either as <org>[/<shop>] or with --org/--shop, not both.",
|
|
10105
|
+
EXIT_CODES.validation,
|
|
10106
|
+
{ code: "conflicting_context_input" }
|
|
10107
|
+
);
|
|
9713
10108
|
const creds = requireUserCredentials();
|
|
9714
10109
|
const response = await getAccountContext(creds);
|
|
9715
10110
|
if (!response.ok || !response.data)
|
|
@@ -9719,9 +10114,32 @@ var useCommand = new Command("use").description("Set the current Apex organizati
|
|
|
9719
10114
|
classifyApiError(response.status, response.errorDetails),
|
|
9720
10115
|
response.errorDetails
|
|
9721
10116
|
);
|
|
9722
|
-
const selected2 = input ? resolveSelection(response.data.data, input) : await pickContext(response.data.data);
|
|
10117
|
+
const selected2 = input ? resolveSelection(response.data.data, input) : flags.org || flags.shop ? resolveFlagSelection(response.data.data, flags) : await pickContext(response.data.data);
|
|
9723
10118
|
const stored = getStoredCredentials();
|
|
9724
|
-
if (!stored)
|
|
10119
|
+
if (!stored) {
|
|
10120
|
+
const env2 = {
|
|
10121
|
+
DRIP_ORG: selected2.orgId,
|
|
10122
|
+
...selected2.shopId ? { DRIP_SHOP: selected2.shopId } : {}
|
|
10123
|
+
};
|
|
10124
|
+
const exportCommand = `export DRIP_ORG=${selected2.orgId}${selected2.shopId ? ` DRIP_SHOP=${selected2.shopId}` : " && unset DRIP_SHOP"}`;
|
|
10125
|
+
fail(
|
|
10126
|
+
this,
|
|
10127
|
+
`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.`,
|
|
10128
|
+
EXIT_CODES.validation,
|
|
10129
|
+
{
|
|
10130
|
+
code: "context_not_persistable",
|
|
10131
|
+
org_id: selected2.orgId,
|
|
10132
|
+
shop_id: selected2.shopId ?? null,
|
|
10133
|
+
env: env2,
|
|
10134
|
+
next_steps: [
|
|
10135
|
+
{
|
|
10136
|
+
command: exportCommand,
|
|
10137
|
+
why: "Selects this context for every later apex command in this environment."
|
|
10138
|
+
}
|
|
10139
|
+
]
|
|
10140
|
+
}
|
|
10141
|
+
);
|
|
10142
|
+
}
|
|
9725
10143
|
saveCredentials({ ...stored, context: selected2 });
|
|
9726
10144
|
setEnvelopeContext({
|
|
9727
10145
|
credential: "user",
|
|
@@ -9737,7 +10155,7 @@ var useCommand = new Command("use").description("Set the current Apex organizati
|
|
|
9737
10155
|
|
|
9738
10156
|
// src/commands/experiments.ts
|
|
9739
10157
|
init_source();
|
|
9740
|
-
var
|
|
10158
|
+
var import_node_crypto6 = __toESM(require("node:crypto"));
|
|
9741
10159
|
var import_node_fs8 = __toESM(require("node:fs"));
|
|
9742
10160
|
var import_node_path6 = __toESM(require("node:path"));
|
|
9743
10161
|
|
|
@@ -9979,14 +10397,17 @@ function summarizeResults(payload) {
|
|
|
9979
10397
|
const decision = record(data.decision);
|
|
9980
10398
|
const outcome = record(data.effective_outcome);
|
|
9981
10399
|
const commerce = record(data.commerce);
|
|
10400
|
+
const commerceResults = data.commerce_results == null ? null : record(data.commerce_results);
|
|
9982
10401
|
const rawArms = Array.isArray(data.arms) ? data.arms.map(record) : [];
|
|
9983
10402
|
const degradedFlags = {
|
|
9984
10403
|
explicit: data.degraded === true,
|
|
9985
10404
|
commerceUnavailable: commerce.unavailable === true,
|
|
10405
|
+
commerceResultsDegraded: commerceResults?.degraded === true,
|
|
9986
10406
|
armMetricsMissing: rawArms.some((arm) => arm.primaryConversions == null || arm.primaryRate == null)
|
|
9987
10407
|
};
|
|
9988
10408
|
const missing = /* @__PURE__ */ new Set();
|
|
9989
10409
|
if (degradedFlags.commerceUnavailable) missing.add("commerce orders");
|
|
10410
|
+
if (degradedFlags.commerceResultsDegraded) missing.add("commerce profit");
|
|
9990
10411
|
const controlRate = rawArms.find((arm) => arm.isControl === true)?.primaryRate;
|
|
9991
10412
|
const treatmentCount = rawArms.filter((arm) => arm.isControl !== true).length;
|
|
9992
10413
|
const plannedDays = typeof data.planned_days === "number" && Number.isFinite(data.planned_days) && data.planned_days > 0 ? Math.max(1, Math.round(data.planned_days)) : null;
|
|
@@ -10033,7 +10454,8 @@ function summarizeResults(payload) {
|
|
|
10033
10454
|
})) : [],
|
|
10034
10455
|
metrics: Array.isArray(data.metrics) ? data.metrics.map(record) : [],
|
|
10035
10456
|
resultsWeekdays: data.results_weekdays,
|
|
10036
|
-
confidenceIndication
|
|
10457
|
+
confidenceIndication,
|
|
10458
|
+
commerceResults
|
|
10037
10459
|
};
|
|
10038
10460
|
}
|
|
10039
10461
|
|
|
@@ -10052,7 +10474,7 @@ function deriveStoreUrl(patterns) {
|
|
|
10052
10474
|
}
|
|
10053
10475
|
|
|
10054
10476
|
// src/lib/asset-upload.ts
|
|
10055
|
-
var
|
|
10477
|
+
var import_node_crypto5 = __toESM(require("node:crypto"));
|
|
10056
10478
|
var import_node_fs7 = __toESM(require("node:fs"));
|
|
10057
10479
|
var import_node_path5 = __toESM(require("node:path"));
|
|
10058
10480
|
var INLINE_DATA_URI_MAX_CHARS = 4096;
|
|
@@ -10077,7 +10499,7 @@ var EXTENSION_BY_MIME = {
|
|
|
10077
10499
|
"image/avif": ".avif"
|
|
10078
10500
|
};
|
|
10079
10501
|
function contentHash(bytes) {
|
|
10080
|
-
return
|
|
10502
|
+
return import_node_crypto5.default.createHash("sha256").update(bytes).digest("hex");
|
|
10081
10503
|
}
|
|
10082
10504
|
function deliveryUrl(baseUrl, asset) {
|
|
10083
10505
|
const returnedUrl = asset.deliveryUrl ?? asset.delivery_url ?? asset.url;
|
|
@@ -10243,6 +10665,1425 @@ var experiment_filesystem_schema_default = {
|
|
|
10243
10665
|
$id: "https://api.drip-apex.com/schemas/experiment-filesystem-v1.json",
|
|
10244
10666
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
10245
10667
|
files: {
|
|
10668
|
+
"commerce/offer.json": {
|
|
10669
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
10670
|
+
oneOf: [
|
|
10671
|
+
{
|
|
10672
|
+
additionalProperties: false,
|
|
10673
|
+
properties: {
|
|
10674
|
+
arms: {
|
|
10675
|
+
items: {
|
|
10676
|
+
anyOf: [
|
|
10677
|
+
{
|
|
10678
|
+
additionalProperties: false,
|
|
10679
|
+
properties: {
|
|
10680
|
+
sameAsControl: {
|
|
10681
|
+
const: true,
|
|
10682
|
+
type: "boolean"
|
|
10683
|
+
},
|
|
10684
|
+
variation: {
|
|
10685
|
+
minLength: 1,
|
|
10686
|
+
type: "string"
|
|
10687
|
+
}
|
|
10688
|
+
},
|
|
10689
|
+
required: ["variation", "sameAsControl"],
|
|
10690
|
+
type: "object"
|
|
10691
|
+
},
|
|
10692
|
+
{
|
|
10693
|
+
additionalProperties: false,
|
|
10694
|
+
properties: {
|
|
10695
|
+
control: {
|
|
10696
|
+
const: true,
|
|
10697
|
+
type: "boolean"
|
|
10698
|
+
},
|
|
10699
|
+
key: {
|
|
10700
|
+
maxLength: 24,
|
|
10701
|
+
minLength: 1,
|
|
10702
|
+
pattern: "^[a-z0-9-]+$",
|
|
10703
|
+
type: "string"
|
|
10704
|
+
},
|
|
10705
|
+
label: {
|
|
10706
|
+
maxLength: 60,
|
|
10707
|
+
minLength: 1,
|
|
10708
|
+
type: "string"
|
|
10709
|
+
},
|
|
10710
|
+
percentOff: {
|
|
10711
|
+
anyOf: [
|
|
10712
|
+
{
|
|
10713
|
+
maximum: 90,
|
|
10714
|
+
minimum: 1,
|
|
10715
|
+
type: "integer"
|
|
10716
|
+
},
|
|
10717
|
+
{
|
|
10718
|
+
type: "null"
|
|
10719
|
+
}
|
|
10720
|
+
]
|
|
10721
|
+
},
|
|
10722
|
+
variation: {
|
|
10723
|
+
minLength: 1,
|
|
10724
|
+
type: "string"
|
|
10725
|
+
}
|
|
10726
|
+
},
|
|
10727
|
+
required: ["key", "label", "percentOff", "variation"],
|
|
10728
|
+
type: "object"
|
|
10729
|
+
}
|
|
10730
|
+
]
|
|
10731
|
+
},
|
|
10732
|
+
minItems: 1,
|
|
10733
|
+
type: "array"
|
|
10734
|
+
},
|
|
10735
|
+
kind: {
|
|
10736
|
+
const: "offer",
|
|
10737
|
+
type: "string"
|
|
10738
|
+
},
|
|
10739
|
+
lever: {
|
|
10740
|
+
additionalProperties: false,
|
|
10741
|
+
properties: {
|
|
10742
|
+
catalogSource: {
|
|
10743
|
+
const: "live",
|
|
10744
|
+
type: "string"
|
|
10745
|
+
},
|
|
10746
|
+
currencyCode: {
|
|
10747
|
+
maxLength: 3,
|
|
10748
|
+
minLength: 3,
|
|
10749
|
+
pattern: "^[A-Z]{3}$",
|
|
10750
|
+
type: "string"
|
|
10751
|
+
},
|
|
10752
|
+
offerType: {
|
|
10753
|
+
const: "discount_depth",
|
|
10754
|
+
type: "string"
|
|
10755
|
+
},
|
|
10756
|
+
target: {
|
|
10757
|
+
additionalProperties: false,
|
|
10758
|
+
properties: {
|
|
10759
|
+
productGids: {
|
|
10760
|
+
default: [],
|
|
10761
|
+
items: {
|
|
10762
|
+
pattern: "^gid:\\/\\/shopify\\/Product\\/\\d+$",
|
|
10763
|
+
type: "string"
|
|
10764
|
+
},
|
|
10765
|
+
maxItems: 100,
|
|
10766
|
+
type: "array"
|
|
10767
|
+
},
|
|
10768
|
+
variantGids: {
|
|
10769
|
+
default: [],
|
|
10770
|
+
items: {
|
|
10771
|
+
pattern: "^gid:\\/\\/shopify\\/ProductVariant\\/\\d+$",
|
|
10772
|
+
type: "string"
|
|
10773
|
+
},
|
|
10774
|
+
maxItems: 100,
|
|
10775
|
+
type: "array"
|
|
10776
|
+
}
|
|
10777
|
+
},
|
|
10778
|
+
type: "object"
|
|
10779
|
+
}
|
|
10780
|
+
},
|
|
10781
|
+
required: ["offerType", "catalogSource", "currencyCode", "target"],
|
|
10782
|
+
type: "object"
|
|
10783
|
+
},
|
|
10784
|
+
schemaVersion: {
|
|
10785
|
+
const: 1,
|
|
10786
|
+
type: "number"
|
|
10787
|
+
},
|
|
10788
|
+
serverOwned: {
|
|
10789
|
+
additionalProperties: false,
|
|
10790
|
+
properties: {
|
|
10791
|
+
carriers: {
|
|
10792
|
+
additionalProperties: {},
|
|
10793
|
+
propertyNames: {
|
|
10794
|
+
type: "string"
|
|
10795
|
+
},
|
|
10796
|
+
type: "object"
|
|
10797
|
+
},
|
|
10798
|
+
readonly: {
|
|
10799
|
+
const: true,
|
|
10800
|
+
type: "boolean"
|
|
10801
|
+
}
|
|
10802
|
+
},
|
|
10803
|
+
readOnly: true,
|
|
10804
|
+
required: ["readonly", "carriers"],
|
|
10805
|
+
type: "object"
|
|
10806
|
+
}
|
|
10807
|
+
},
|
|
10808
|
+
required: ["schemaVersion", "kind", "lever", "arms"],
|
|
10809
|
+
type: "object"
|
|
10810
|
+
},
|
|
10811
|
+
{
|
|
10812
|
+
additionalProperties: false,
|
|
10813
|
+
properties: {
|
|
10814
|
+
arms: {
|
|
10815
|
+
items: {
|
|
10816
|
+
anyOf: [
|
|
10817
|
+
{
|
|
10818
|
+
additionalProperties: false,
|
|
10819
|
+
properties: {
|
|
10820
|
+
sameAsControl: {
|
|
10821
|
+
const: true,
|
|
10822
|
+
type: "boolean"
|
|
10823
|
+
},
|
|
10824
|
+
variation: {
|
|
10825
|
+
minLength: 1,
|
|
10826
|
+
type: "string"
|
|
10827
|
+
}
|
|
10828
|
+
},
|
|
10829
|
+
required: ["variation", "sameAsControl"],
|
|
10830
|
+
type: "object"
|
|
10831
|
+
},
|
|
10832
|
+
{
|
|
10833
|
+
additionalProperties: false,
|
|
10834
|
+
properties: {
|
|
10835
|
+
control: {
|
|
10836
|
+
const: true,
|
|
10837
|
+
type: "boolean"
|
|
10838
|
+
},
|
|
10839
|
+
key: {
|
|
10840
|
+
maxLength: 24,
|
|
10841
|
+
minLength: 1,
|
|
10842
|
+
pattern: "^[a-z0-9-]+$",
|
|
10843
|
+
type: "string"
|
|
10844
|
+
},
|
|
10845
|
+
label: {
|
|
10846
|
+
maxLength: 60,
|
|
10847
|
+
minLength: 1,
|
|
10848
|
+
type: "string"
|
|
10849
|
+
},
|
|
10850
|
+
threshold: {
|
|
10851
|
+
anyOf: [
|
|
10852
|
+
{
|
|
10853
|
+
exclusiveMinimum: 0,
|
|
10854
|
+
type: "number"
|
|
10855
|
+
},
|
|
10856
|
+
{
|
|
10857
|
+
type: "null"
|
|
10858
|
+
}
|
|
10859
|
+
]
|
|
10860
|
+
},
|
|
10861
|
+
tiers: {
|
|
10862
|
+
items: {
|
|
10863
|
+
additionalProperties: false,
|
|
10864
|
+
properties: {
|
|
10865
|
+
giftChoiceVariantGids: {
|
|
10866
|
+
items: {
|
|
10867
|
+
pattern: "^gid:\\/\\/shopify\\/ProductVariant\\/\\d+$",
|
|
10868
|
+
type: "string"
|
|
10869
|
+
},
|
|
10870
|
+
maxItems: 4,
|
|
10871
|
+
minItems: 2,
|
|
10872
|
+
type: "array"
|
|
10873
|
+
},
|
|
10874
|
+
giftVariantGid: {
|
|
10875
|
+
pattern: "^gid:\\/\\/shopify\\/ProductVariant\\/\\d+$",
|
|
10876
|
+
type: "string"
|
|
10877
|
+
},
|
|
10878
|
+
threshold: {
|
|
10879
|
+
exclusiveMinimum: 0,
|
|
10880
|
+
type: "number"
|
|
10881
|
+
}
|
|
10882
|
+
},
|
|
10883
|
+
required: ["threshold", "giftVariantGid"],
|
|
10884
|
+
type: "object"
|
|
10885
|
+
},
|
|
10886
|
+
maxItems: 3,
|
|
10887
|
+
minItems: 1,
|
|
10888
|
+
type: "array"
|
|
10889
|
+
},
|
|
10890
|
+
variation: {
|
|
10891
|
+
minLength: 1,
|
|
10892
|
+
type: "string"
|
|
10893
|
+
}
|
|
10894
|
+
},
|
|
10895
|
+
required: ["key", "label", "variation"],
|
|
10896
|
+
type: "object"
|
|
10897
|
+
}
|
|
10898
|
+
]
|
|
10899
|
+
},
|
|
10900
|
+
minItems: 1,
|
|
10901
|
+
type: "array"
|
|
10902
|
+
},
|
|
10903
|
+
kind: {
|
|
10904
|
+
const: "offer",
|
|
10905
|
+
type: "string"
|
|
10906
|
+
},
|
|
10907
|
+
lever: {
|
|
10908
|
+
additionalProperties: false,
|
|
10909
|
+
properties: {
|
|
10910
|
+
catalogSource: {
|
|
10911
|
+
const: "live",
|
|
10912
|
+
type: "string"
|
|
10913
|
+
},
|
|
10914
|
+
currencyCode: {
|
|
10915
|
+
maxLength: 3,
|
|
10916
|
+
minLength: 3,
|
|
10917
|
+
pattern: "^[A-Z]{3}$",
|
|
10918
|
+
type: "string"
|
|
10919
|
+
},
|
|
10920
|
+
giftVariantGid: {
|
|
10921
|
+
pattern: "^gid:\\/\\/shopify\\/ProductVariant\\/\\d+$",
|
|
10922
|
+
type: "string"
|
|
10923
|
+
},
|
|
10924
|
+
offerType: {
|
|
10925
|
+
const: "gwp_threshold",
|
|
10926
|
+
type: "string"
|
|
10927
|
+
}
|
|
10928
|
+
},
|
|
10929
|
+
required: ["offerType", "catalogSource", "currencyCode"],
|
|
10930
|
+
type: "object"
|
|
10931
|
+
},
|
|
10932
|
+
schemaVersion: {
|
|
10933
|
+
const: 1,
|
|
10934
|
+
type: "number"
|
|
10935
|
+
},
|
|
10936
|
+
serverOwned: {
|
|
10937
|
+
additionalProperties: false,
|
|
10938
|
+
properties: {
|
|
10939
|
+
carriers: {
|
|
10940
|
+
additionalProperties: {},
|
|
10941
|
+
propertyNames: {
|
|
10942
|
+
type: "string"
|
|
10943
|
+
},
|
|
10944
|
+
type: "object"
|
|
10945
|
+
},
|
|
10946
|
+
readonly: {
|
|
10947
|
+
const: true,
|
|
10948
|
+
type: "boolean"
|
|
10949
|
+
}
|
|
10950
|
+
},
|
|
10951
|
+
readOnly: true,
|
|
10952
|
+
required: ["readonly", "carriers"],
|
|
10953
|
+
type: "object"
|
|
10954
|
+
}
|
|
10955
|
+
},
|
|
10956
|
+
required: ["schemaVersion", "kind", "lever", "arms"],
|
|
10957
|
+
type: "object"
|
|
10958
|
+
},
|
|
10959
|
+
{
|
|
10960
|
+
additionalProperties: false,
|
|
10961
|
+
properties: {
|
|
10962
|
+
arms: {
|
|
10963
|
+
items: {
|
|
10964
|
+
anyOf: [
|
|
10965
|
+
{
|
|
10966
|
+
additionalProperties: false,
|
|
10967
|
+
properties: {
|
|
10968
|
+
sameAsControl: {
|
|
10969
|
+
const: true,
|
|
10970
|
+
type: "boolean"
|
|
10971
|
+
},
|
|
10972
|
+
variation: {
|
|
10973
|
+
minLength: 1,
|
|
10974
|
+
type: "string"
|
|
10975
|
+
}
|
|
10976
|
+
},
|
|
10977
|
+
required: ["variation", "sameAsControl"],
|
|
10978
|
+
type: "object"
|
|
10979
|
+
},
|
|
10980
|
+
{
|
|
10981
|
+
additionalProperties: false,
|
|
10982
|
+
properties: {
|
|
10983
|
+
control: {
|
|
10984
|
+
const: true,
|
|
10985
|
+
type: "boolean"
|
|
10986
|
+
},
|
|
10987
|
+
key: {
|
|
10988
|
+
maxLength: 24,
|
|
10989
|
+
minLength: 1,
|
|
10990
|
+
pattern: "^[a-z0-9-]+$",
|
|
10991
|
+
type: "string"
|
|
10992
|
+
},
|
|
10993
|
+
label: {
|
|
10994
|
+
maxLength: 60,
|
|
10995
|
+
minLength: 1,
|
|
10996
|
+
type: "string"
|
|
10997
|
+
},
|
|
10998
|
+
minimumQuantity: {
|
|
10999
|
+
anyOf: [
|
|
11000
|
+
{
|
|
11001
|
+
maximum: 9007199254740991,
|
|
11002
|
+
minimum: 2,
|
|
11003
|
+
type: "integer"
|
|
11004
|
+
},
|
|
11005
|
+
{
|
|
11006
|
+
type: "null"
|
|
11007
|
+
}
|
|
11008
|
+
]
|
|
11009
|
+
},
|
|
11010
|
+
percentOff: {
|
|
11011
|
+
anyOf: [
|
|
11012
|
+
{
|
|
11013
|
+
maximum: 90,
|
|
11014
|
+
minimum: 1,
|
|
11015
|
+
type: "integer"
|
|
11016
|
+
},
|
|
11017
|
+
{
|
|
11018
|
+
type: "null"
|
|
11019
|
+
}
|
|
11020
|
+
]
|
|
11021
|
+
},
|
|
11022
|
+
variation: {
|
|
11023
|
+
minLength: 1,
|
|
11024
|
+
type: "string"
|
|
11025
|
+
}
|
|
11026
|
+
},
|
|
11027
|
+
required: ["key", "label", "minimumQuantity", "percentOff", "variation"],
|
|
11028
|
+
type: "object"
|
|
11029
|
+
}
|
|
11030
|
+
]
|
|
11031
|
+
},
|
|
11032
|
+
minItems: 1,
|
|
11033
|
+
type: "array"
|
|
11034
|
+
},
|
|
11035
|
+
kind: {
|
|
11036
|
+
const: "offer",
|
|
11037
|
+
type: "string"
|
|
11038
|
+
},
|
|
11039
|
+
lever: {
|
|
11040
|
+
additionalProperties: false,
|
|
11041
|
+
properties: {
|
|
11042
|
+
catalogSource: {
|
|
11043
|
+
const: "live",
|
|
11044
|
+
type: "string"
|
|
11045
|
+
},
|
|
11046
|
+
currencyCode: {
|
|
11047
|
+
maxLength: 3,
|
|
11048
|
+
minLength: 3,
|
|
11049
|
+
pattern: "^[A-Z]{3}$",
|
|
11050
|
+
type: "string"
|
|
11051
|
+
},
|
|
11052
|
+
offerType: {
|
|
11053
|
+
const: "quantity_breaks",
|
|
11054
|
+
type: "string"
|
|
11055
|
+
},
|
|
11056
|
+
target: {
|
|
11057
|
+
additionalProperties: false,
|
|
11058
|
+
properties: {
|
|
11059
|
+
productGids: {
|
|
11060
|
+
default: [],
|
|
11061
|
+
items: {
|
|
11062
|
+
pattern: "^gid:\\/\\/shopify\\/Product\\/\\d+$",
|
|
11063
|
+
type: "string"
|
|
11064
|
+
},
|
|
11065
|
+
maxItems: 100,
|
|
11066
|
+
type: "array"
|
|
11067
|
+
},
|
|
11068
|
+
variantGids: {
|
|
11069
|
+
default: [],
|
|
11070
|
+
items: {
|
|
11071
|
+
pattern: "^gid:\\/\\/shopify\\/ProductVariant\\/\\d+$",
|
|
11072
|
+
type: "string"
|
|
11073
|
+
},
|
|
11074
|
+
maxItems: 100,
|
|
11075
|
+
type: "array"
|
|
11076
|
+
}
|
|
11077
|
+
},
|
|
11078
|
+
type: "object"
|
|
11079
|
+
}
|
|
11080
|
+
},
|
|
11081
|
+
required: ["offerType", "catalogSource", "currencyCode", "target"],
|
|
11082
|
+
type: "object"
|
|
11083
|
+
},
|
|
11084
|
+
schemaVersion: {
|
|
11085
|
+
const: 1,
|
|
11086
|
+
type: "number"
|
|
11087
|
+
},
|
|
11088
|
+
serverOwned: {
|
|
11089
|
+
additionalProperties: false,
|
|
11090
|
+
properties: {
|
|
11091
|
+
carriers: {
|
|
11092
|
+
additionalProperties: {},
|
|
11093
|
+
propertyNames: {
|
|
11094
|
+
type: "string"
|
|
11095
|
+
},
|
|
11096
|
+
type: "object"
|
|
11097
|
+
},
|
|
11098
|
+
readonly: {
|
|
11099
|
+
const: true,
|
|
11100
|
+
type: "boolean"
|
|
11101
|
+
}
|
|
11102
|
+
},
|
|
11103
|
+
readOnly: true,
|
|
11104
|
+
required: ["readonly", "carriers"],
|
|
11105
|
+
type: "object"
|
|
11106
|
+
}
|
|
11107
|
+
},
|
|
11108
|
+
required: ["schemaVersion", "kind", "lever", "arms"],
|
|
11109
|
+
type: "object"
|
|
11110
|
+
},
|
|
11111
|
+
{
|
|
11112
|
+
additionalProperties: false,
|
|
11113
|
+
properties: {
|
|
11114
|
+
carriers: {
|
|
11115
|
+
additionalProperties: {
|
|
11116
|
+
items: {},
|
|
11117
|
+
type: "array"
|
|
11118
|
+
},
|
|
11119
|
+
propertyNames: {
|
|
11120
|
+
type: "string"
|
|
11121
|
+
},
|
|
11122
|
+
type: "object"
|
|
11123
|
+
},
|
|
11124
|
+
kind: {
|
|
11125
|
+
const: "offer",
|
|
11126
|
+
type: "string"
|
|
11127
|
+
},
|
|
11128
|
+
readonly: {
|
|
11129
|
+
const: true,
|
|
11130
|
+
type: "boolean"
|
|
11131
|
+
},
|
|
11132
|
+
reason: {
|
|
11133
|
+
type: "string"
|
|
11134
|
+
},
|
|
11135
|
+
schemaVersion: {
|
|
11136
|
+
const: 1,
|
|
11137
|
+
type: "number"
|
|
11138
|
+
}
|
|
11139
|
+
},
|
|
11140
|
+
required: ["schemaVersion", "kind", "readonly", "reason", "carriers"],
|
|
11141
|
+
type: "object"
|
|
11142
|
+
}
|
|
11143
|
+
]
|
|
11144
|
+
},
|
|
11145
|
+
"commerce/post-purchase.json": {
|
|
11146
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
11147
|
+
oneOf: [
|
|
11148
|
+
{
|
|
11149
|
+
additionalProperties: false,
|
|
11150
|
+
properties: {
|
|
11151
|
+
arms: {
|
|
11152
|
+
items: {
|
|
11153
|
+
anyOf: [
|
|
11154
|
+
{
|
|
11155
|
+
additionalProperties: false,
|
|
11156
|
+
properties: {
|
|
11157
|
+
sameAsControl: {
|
|
11158
|
+
const: true,
|
|
11159
|
+
type: "boolean"
|
|
11160
|
+
},
|
|
11161
|
+
variation: {
|
|
11162
|
+
minLength: 1,
|
|
11163
|
+
type: "string"
|
|
11164
|
+
}
|
|
11165
|
+
},
|
|
11166
|
+
required: ["variation", "sameAsControl"],
|
|
11167
|
+
type: "object"
|
|
11168
|
+
},
|
|
11169
|
+
{
|
|
11170
|
+
additionalProperties: false,
|
|
11171
|
+
properties: {
|
|
11172
|
+
control: {
|
|
11173
|
+
const: true,
|
|
11174
|
+
type: "boolean"
|
|
11175
|
+
},
|
|
11176
|
+
key: {
|
|
11177
|
+
maxLength: 80,
|
|
11178
|
+
minLength: 1,
|
|
11179
|
+
type: "string"
|
|
11180
|
+
},
|
|
11181
|
+
kind: {
|
|
11182
|
+
const: "control",
|
|
11183
|
+
type: "string"
|
|
11184
|
+
},
|
|
11185
|
+
variation: {
|
|
11186
|
+
minLength: 1,
|
|
11187
|
+
type: "string"
|
|
11188
|
+
}
|
|
11189
|
+
},
|
|
11190
|
+
required: ["kind", "key", "variation"],
|
|
11191
|
+
type: "object"
|
|
11192
|
+
},
|
|
11193
|
+
{
|
|
11194
|
+
additionalProperties: false,
|
|
11195
|
+
properties: {
|
|
11196
|
+
acceptLabel: {
|
|
11197
|
+
maxLength: 60,
|
|
11198
|
+
minLength: 1,
|
|
11199
|
+
type: "string"
|
|
11200
|
+
},
|
|
11201
|
+
control: {
|
|
11202
|
+
const: true,
|
|
11203
|
+
type: "boolean"
|
|
11204
|
+
},
|
|
11205
|
+
declineLabel: {
|
|
11206
|
+
maxLength: 60,
|
|
11207
|
+
minLength: 1,
|
|
11208
|
+
type: "string"
|
|
11209
|
+
},
|
|
11210
|
+
description: {
|
|
11211
|
+
maxLength: 500,
|
|
11212
|
+
type: "string"
|
|
11213
|
+
},
|
|
11214
|
+
discount: {
|
|
11215
|
+
oneOf: [
|
|
11216
|
+
{
|
|
11217
|
+
additionalProperties: false,
|
|
11218
|
+
properties: {
|
|
11219
|
+
kind: {
|
|
11220
|
+
const: "percent",
|
|
11221
|
+
type: "string"
|
|
11222
|
+
},
|
|
11223
|
+
percentOff: {
|
|
11224
|
+
maximum: 90,
|
|
11225
|
+
minimum: 1,
|
|
11226
|
+
type: "integer"
|
|
11227
|
+
}
|
|
11228
|
+
},
|
|
11229
|
+
required: ["kind", "percentOff"],
|
|
11230
|
+
type: "object"
|
|
11231
|
+
},
|
|
11232
|
+
{
|
|
11233
|
+
additionalProperties: false,
|
|
11234
|
+
properties: {
|
|
11235
|
+
amount: {
|
|
11236
|
+
exclusiveMinimum: 0,
|
|
11237
|
+
type: "number"
|
|
11238
|
+
},
|
|
11239
|
+
kind: {
|
|
11240
|
+
const: "fixed_price",
|
|
11241
|
+
type: "string"
|
|
11242
|
+
}
|
|
11243
|
+
},
|
|
11244
|
+
required: ["kind", "amount"],
|
|
11245
|
+
type: "object"
|
|
11246
|
+
}
|
|
11247
|
+
]
|
|
11248
|
+
},
|
|
11249
|
+
headline: {
|
|
11250
|
+
maxLength: 160,
|
|
11251
|
+
minLength: 1,
|
|
11252
|
+
type: "string"
|
|
11253
|
+
},
|
|
11254
|
+
key: {
|
|
11255
|
+
maxLength: 80,
|
|
11256
|
+
minLength: 1,
|
|
11257
|
+
type: "string"
|
|
11258
|
+
},
|
|
11259
|
+
kind: {
|
|
11260
|
+
const: "offer",
|
|
11261
|
+
type: "string"
|
|
11262
|
+
},
|
|
11263
|
+
productVariantGid: {
|
|
11264
|
+
pattern: "^gid:\\/\\/shopify\\/ProductVariant\\/\\d+$",
|
|
11265
|
+
type: "string"
|
|
11266
|
+
},
|
|
11267
|
+
productVariantTitle: {
|
|
11268
|
+
maxLength: 255,
|
|
11269
|
+
minLength: 1,
|
|
11270
|
+
type: "string"
|
|
11271
|
+
},
|
|
11272
|
+
quantity: {
|
|
11273
|
+
const: 1,
|
|
11274
|
+
type: "number"
|
|
11275
|
+
},
|
|
11276
|
+
variation: {
|
|
11277
|
+
minLength: 1,
|
|
11278
|
+
type: "string"
|
|
11279
|
+
}
|
|
11280
|
+
},
|
|
11281
|
+
required: [
|
|
11282
|
+
"kind",
|
|
11283
|
+
"key",
|
|
11284
|
+
"productVariantGid",
|
|
11285
|
+
"quantity",
|
|
11286
|
+
"discount",
|
|
11287
|
+
"variation"
|
|
11288
|
+
],
|
|
11289
|
+
type: "object"
|
|
11290
|
+
}
|
|
11291
|
+
]
|
|
11292
|
+
},
|
|
11293
|
+
minItems: 1,
|
|
11294
|
+
type: "array"
|
|
11295
|
+
},
|
|
11296
|
+
kind: {
|
|
11297
|
+
const: "post-purchase",
|
|
11298
|
+
type: "string"
|
|
11299
|
+
},
|
|
11300
|
+
lever: {
|
|
11301
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
11302
|
+
additionalProperties: false,
|
|
11303
|
+
properties: {
|
|
11304
|
+
currencyCode: {
|
|
11305
|
+
maxLength: 3,
|
|
11306
|
+
minLength: 3,
|
|
11307
|
+
pattern: "^[A-Z]{3}$",
|
|
11308
|
+
type: "string"
|
|
11309
|
+
}
|
|
11310
|
+
},
|
|
11311
|
+
required: ["currencyCode"],
|
|
11312
|
+
type: "object"
|
|
11313
|
+
},
|
|
11314
|
+
schemaVersion: {
|
|
11315
|
+
const: 1,
|
|
11316
|
+
type: "number"
|
|
11317
|
+
},
|
|
11318
|
+
serverOwned: {
|
|
11319
|
+
additionalProperties: false,
|
|
11320
|
+
properties: {
|
|
11321
|
+
carriers: {
|
|
11322
|
+
additionalProperties: {},
|
|
11323
|
+
propertyNames: {
|
|
11324
|
+
type: "string"
|
|
11325
|
+
},
|
|
11326
|
+
type: "object"
|
|
11327
|
+
},
|
|
11328
|
+
readonly: {
|
|
11329
|
+
const: true,
|
|
11330
|
+
type: "boolean"
|
|
11331
|
+
}
|
|
11332
|
+
},
|
|
11333
|
+
readOnly: true,
|
|
11334
|
+
required: ["readonly", "carriers"],
|
|
11335
|
+
type: "object"
|
|
11336
|
+
}
|
|
11337
|
+
},
|
|
11338
|
+
required: ["schemaVersion", "kind", "lever", "arms"],
|
|
11339
|
+
type: "object"
|
|
11340
|
+
},
|
|
11341
|
+
{
|
|
11342
|
+
additionalProperties: false,
|
|
11343
|
+
properties: {
|
|
11344
|
+
carriers: {
|
|
11345
|
+
additionalProperties: {
|
|
11346
|
+
items: {},
|
|
11347
|
+
type: "array"
|
|
11348
|
+
},
|
|
11349
|
+
propertyNames: {
|
|
11350
|
+
type: "string"
|
|
11351
|
+
},
|
|
11352
|
+
type: "object"
|
|
11353
|
+
},
|
|
11354
|
+
kind: {
|
|
11355
|
+
const: "post-purchase",
|
|
11356
|
+
type: "string"
|
|
11357
|
+
},
|
|
11358
|
+
readonly: {
|
|
11359
|
+
const: true,
|
|
11360
|
+
type: "boolean"
|
|
11361
|
+
},
|
|
11362
|
+
reason: {
|
|
11363
|
+
type: "string"
|
|
11364
|
+
},
|
|
11365
|
+
schemaVersion: {
|
|
11366
|
+
const: 1,
|
|
11367
|
+
type: "number"
|
|
11368
|
+
}
|
|
11369
|
+
},
|
|
11370
|
+
required: ["schemaVersion", "kind", "readonly", "reason", "carriers"],
|
|
11371
|
+
type: "object"
|
|
11372
|
+
}
|
|
11373
|
+
]
|
|
11374
|
+
},
|
|
11375
|
+
"commerce/price.json": {
|
|
11376
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
11377
|
+
oneOf: [
|
|
11378
|
+
{
|
|
11379
|
+
additionalProperties: false,
|
|
11380
|
+
properties: {
|
|
11381
|
+
arms: {
|
|
11382
|
+
items: {
|
|
11383
|
+
anyOf: [
|
|
11384
|
+
{
|
|
11385
|
+
additionalProperties: false,
|
|
11386
|
+
properties: {
|
|
11387
|
+
control: {
|
|
11388
|
+
const: true,
|
|
11389
|
+
type: "boolean"
|
|
11390
|
+
},
|
|
11391
|
+
variation: {
|
|
11392
|
+
minLength: 1,
|
|
11393
|
+
type: "string"
|
|
11394
|
+
}
|
|
11395
|
+
},
|
|
11396
|
+
required: ["variation", "control"],
|
|
11397
|
+
type: "object"
|
|
11398
|
+
},
|
|
11399
|
+
{
|
|
11400
|
+
additionalProperties: false,
|
|
11401
|
+
properties: {
|
|
11402
|
+
sameAsControl: {
|
|
11403
|
+
const: true,
|
|
11404
|
+
type: "boolean"
|
|
11405
|
+
},
|
|
11406
|
+
variation: {
|
|
11407
|
+
minLength: 1,
|
|
11408
|
+
type: "string"
|
|
11409
|
+
}
|
|
11410
|
+
},
|
|
11411
|
+
required: ["variation", "sameAsControl"],
|
|
11412
|
+
type: "object"
|
|
11413
|
+
},
|
|
11414
|
+
{
|
|
11415
|
+
additionalProperties: false,
|
|
11416
|
+
properties: {
|
|
11417
|
+
key: {
|
|
11418
|
+
maxLength: 24,
|
|
11419
|
+
minLength: 1,
|
|
11420
|
+
pattern: "^[a-z0-9-]+$",
|
|
11421
|
+
type: "string"
|
|
11422
|
+
},
|
|
11423
|
+
label: {
|
|
11424
|
+
maxLength: 60,
|
|
11425
|
+
minLength: 1,
|
|
11426
|
+
type: "string"
|
|
11427
|
+
},
|
|
11428
|
+
prices: {
|
|
11429
|
+
additionalProperties: {
|
|
11430
|
+
additionalProperties: false,
|
|
11431
|
+
properties: {
|
|
11432
|
+
compareAt: {
|
|
11433
|
+
oneOf: [
|
|
11434
|
+
{
|
|
11435
|
+
additionalProperties: false,
|
|
11436
|
+
properties: {
|
|
11437
|
+
amount: {
|
|
11438
|
+
exclusiveMinimum: 0,
|
|
11439
|
+
type: "number"
|
|
11440
|
+
},
|
|
11441
|
+
mode: {
|
|
11442
|
+
const: "set",
|
|
11443
|
+
type: "string"
|
|
11444
|
+
}
|
|
11445
|
+
},
|
|
11446
|
+
required: ["mode", "amount"],
|
|
11447
|
+
type: "object"
|
|
11448
|
+
},
|
|
11449
|
+
{
|
|
11450
|
+
additionalProperties: false,
|
|
11451
|
+
properties: {
|
|
11452
|
+
mode: {
|
|
11453
|
+
const: "clear",
|
|
11454
|
+
type: "string"
|
|
11455
|
+
}
|
|
11456
|
+
},
|
|
11457
|
+
required: ["mode"],
|
|
11458
|
+
type: "object"
|
|
11459
|
+
}
|
|
11460
|
+
]
|
|
11461
|
+
},
|
|
11462
|
+
price: {
|
|
11463
|
+
anyOf: [
|
|
11464
|
+
{
|
|
11465
|
+
exclusiveMinimum: 0,
|
|
11466
|
+
type: "number"
|
|
11467
|
+
},
|
|
11468
|
+
{
|
|
11469
|
+
type: "null"
|
|
11470
|
+
}
|
|
11471
|
+
]
|
|
11472
|
+
}
|
|
11473
|
+
},
|
|
11474
|
+
required: ["price"],
|
|
11475
|
+
type: "object"
|
|
11476
|
+
},
|
|
11477
|
+
type: "object"
|
|
11478
|
+
},
|
|
11479
|
+
type: {
|
|
11480
|
+
const: "fixed",
|
|
11481
|
+
default: "fixed",
|
|
11482
|
+
type: "string"
|
|
11483
|
+
},
|
|
11484
|
+
variation: {
|
|
11485
|
+
minLength: 1,
|
|
11486
|
+
type: "string"
|
|
11487
|
+
}
|
|
11488
|
+
},
|
|
11489
|
+
required: ["key", "label", "variation", "prices"],
|
|
11490
|
+
type: "object"
|
|
11491
|
+
},
|
|
11492
|
+
{
|
|
11493
|
+
additionalProperties: false,
|
|
11494
|
+
properties: {
|
|
11495
|
+
key: {
|
|
11496
|
+
maxLength: 24,
|
|
11497
|
+
minLength: 1,
|
|
11498
|
+
pattern: "^[a-z0-9-]+$",
|
|
11499
|
+
type: "string"
|
|
11500
|
+
},
|
|
11501
|
+
label: {
|
|
11502
|
+
maxLength: 60,
|
|
11503
|
+
minLength: 1,
|
|
11504
|
+
type: "string"
|
|
11505
|
+
},
|
|
11506
|
+
percentDelta: {
|
|
11507
|
+
maximum: -1,
|
|
11508
|
+
minimum: -90,
|
|
11509
|
+
type: "integer"
|
|
11510
|
+
},
|
|
11511
|
+
prices: {
|
|
11512
|
+
additionalProperties: {
|
|
11513
|
+
additionalProperties: false,
|
|
11514
|
+
properties: {
|
|
11515
|
+
compareAt: {
|
|
11516
|
+
oneOf: [
|
|
11517
|
+
{
|
|
11518
|
+
additionalProperties: false,
|
|
11519
|
+
properties: {
|
|
11520
|
+
amount: {
|
|
11521
|
+
exclusiveMinimum: 0,
|
|
11522
|
+
type: "number"
|
|
11523
|
+
},
|
|
11524
|
+
mode: {
|
|
11525
|
+
const: "set",
|
|
11526
|
+
type: "string"
|
|
11527
|
+
}
|
|
11528
|
+
},
|
|
11529
|
+
required: ["mode", "amount"],
|
|
11530
|
+
type: "object"
|
|
11531
|
+
},
|
|
11532
|
+
{
|
|
11533
|
+
additionalProperties: false,
|
|
11534
|
+
properties: {
|
|
11535
|
+
mode: {
|
|
11536
|
+
const: "clear",
|
|
11537
|
+
type: "string"
|
|
11538
|
+
}
|
|
11539
|
+
},
|
|
11540
|
+
required: ["mode"],
|
|
11541
|
+
type: "object"
|
|
11542
|
+
}
|
|
11543
|
+
]
|
|
11544
|
+
},
|
|
11545
|
+
price: {
|
|
11546
|
+
anyOf: [
|
|
11547
|
+
{
|
|
11548
|
+
exclusiveMinimum: 0,
|
|
11549
|
+
type: "number"
|
|
11550
|
+
},
|
|
11551
|
+
{
|
|
11552
|
+
type: "null"
|
|
11553
|
+
}
|
|
11554
|
+
]
|
|
11555
|
+
}
|
|
11556
|
+
},
|
|
11557
|
+
required: ["price"],
|
|
11558
|
+
type: "object"
|
|
11559
|
+
},
|
|
11560
|
+
type: "object"
|
|
11561
|
+
},
|
|
11562
|
+
type: {
|
|
11563
|
+
const: "percent",
|
|
11564
|
+
type: "string"
|
|
11565
|
+
},
|
|
11566
|
+
variation: {
|
|
11567
|
+
minLength: 1,
|
|
11568
|
+
type: "string"
|
|
11569
|
+
}
|
|
11570
|
+
},
|
|
11571
|
+
required: ["key", "label", "type", "percentDelta", "variation", "prices"],
|
|
11572
|
+
type: "object"
|
|
11573
|
+
},
|
|
11574
|
+
{
|
|
11575
|
+
additionalProperties: false,
|
|
11576
|
+
properties: {
|
|
11577
|
+
key: {
|
|
11578
|
+
maxLength: 24,
|
|
11579
|
+
minLength: 1,
|
|
11580
|
+
pattern: "^[a-z0-9-]+$",
|
|
11581
|
+
type: "string"
|
|
11582
|
+
},
|
|
11583
|
+
label: {
|
|
11584
|
+
maxLength: 60,
|
|
11585
|
+
minLength: 1,
|
|
11586
|
+
type: "string"
|
|
11587
|
+
},
|
|
11588
|
+
prices: {
|
|
11589
|
+
additionalProperties: {
|
|
11590
|
+
additionalProperties: false,
|
|
11591
|
+
properties: {
|
|
11592
|
+
compareAt: {
|
|
11593
|
+
oneOf: [
|
|
11594
|
+
{
|
|
11595
|
+
additionalProperties: false,
|
|
11596
|
+
properties: {
|
|
11597
|
+
amount: {
|
|
11598
|
+
exclusiveMinimum: 0,
|
|
11599
|
+
type: "number"
|
|
11600
|
+
},
|
|
11601
|
+
mode: {
|
|
11602
|
+
const: "set",
|
|
11603
|
+
type: "string"
|
|
11604
|
+
}
|
|
11605
|
+
},
|
|
11606
|
+
required: ["mode", "amount"],
|
|
11607
|
+
type: "object"
|
|
11608
|
+
},
|
|
11609
|
+
{
|
|
11610
|
+
additionalProperties: false,
|
|
11611
|
+
properties: {
|
|
11612
|
+
mode: {
|
|
11613
|
+
const: "clear",
|
|
11614
|
+
type: "string"
|
|
11615
|
+
}
|
|
11616
|
+
},
|
|
11617
|
+
required: ["mode"],
|
|
11618
|
+
type: "object"
|
|
11619
|
+
}
|
|
11620
|
+
]
|
|
11621
|
+
},
|
|
11622
|
+
price: {
|
|
11623
|
+
anyOf: [
|
|
11624
|
+
{
|
|
11625
|
+
exclusiveMinimum: 0,
|
|
11626
|
+
type: "number"
|
|
11627
|
+
},
|
|
11628
|
+
{
|
|
11629
|
+
type: "null"
|
|
11630
|
+
}
|
|
11631
|
+
]
|
|
11632
|
+
}
|
|
11633
|
+
},
|
|
11634
|
+
required: ["price"],
|
|
11635
|
+
type: "object"
|
|
11636
|
+
},
|
|
11637
|
+
type: "object"
|
|
11638
|
+
},
|
|
11639
|
+
type: {
|
|
11640
|
+
const: "compare-at",
|
|
11641
|
+
type: "string"
|
|
11642
|
+
},
|
|
11643
|
+
variation: {
|
|
11644
|
+
minLength: 1,
|
|
11645
|
+
type: "string"
|
|
11646
|
+
}
|
|
11647
|
+
},
|
|
11648
|
+
required: ["key", "label", "type", "variation", "prices"],
|
|
11649
|
+
type: "object"
|
|
11650
|
+
}
|
|
11651
|
+
]
|
|
11652
|
+
},
|
|
11653
|
+
minItems: 1,
|
|
11654
|
+
type: "array"
|
|
11655
|
+
},
|
|
11656
|
+
kind: {
|
|
11657
|
+
const: "price",
|
|
11658
|
+
type: "string"
|
|
11659
|
+
},
|
|
11660
|
+
lever: {
|
|
11661
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
11662
|
+
additionalProperties: false,
|
|
11663
|
+
properties: {
|
|
11664
|
+
catalogSource: {
|
|
11665
|
+
anyOf: [
|
|
11666
|
+
{
|
|
11667
|
+
const: "sample",
|
|
11668
|
+
type: "string"
|
|
11669
|
+
},
|
|
11670
|
+
{
|
|
11671
|
+
const: "live",
|
|
11672
|
+
type: "string"
|
|
11673
|
+
}
|
|
11674
|
+
]
|
|
11675
|
+
},
|
|
11676
|
+
currencyCode: {
|
|
11677
|
+
maxLength: 3,
|
|
11678
|
+
minLength: 3,
|
|
11679
|
+
pattern: "^[A-Z]{3}$",
|
|
11680
|
+
type: "string"
|
|
11681
|
+
},
|
|
11682
|
+
productId: {
|
|
11683
|
+
maxLength: 120,
|
|
11684
|
+
minLength: 1,
|
|
11685
|
+
type: "string"
|
|
11686
|
+
},
|
|
11687
|
+
productTitle: {
|
|
11688
|
+
maxLength: 255,
|
|
11689
|
+
minLength: 1,
|
|
11690
|
+
type: "string"
|
|
11691
|
+
},
|
|
11692
|
+
variants: {
|
|
11693
|
+
items: {
|
|
11694
|
+
additionalProperties: false,
|
|
11695
|
+
properties: {
|
|
11696
|
+
controlPrice: {
|
|
11697
|
+
minimum: 0,
|
|
11698
|
+
type: "number"
|
|
11699
|
+
},
|
|
11700
|
+
sku: {
|
|
11701
|
+
maxLength: 120,
|
|
11702
|
+
type: "string"
|
|
11703
|
+
},
|
|
11704
|
+
title: {
|
|
11705
|
+
maxLength: 255,
|
|
11706
|
+
minLength: 1,
|
|
11707
|
+
type: "string"
|
|
11708
|
+
},
|
|
11709
|
+
variantId: {
|
|
11710
|
+
maxLength: 120,
|
|
11711
|
+
minLength: 1,
|
|
11712
|
+
type: "string"
|
|
11713
|
+
}
|
|
11714
|
+
},
|
|
11715
|
+
required: ["variantId", "title", "controlPrice"],
|
|
11716
|
+
type: "object"
|
|
11717
|
+
},
|
|
11718
|
+
minItems: 1,
|
|
11719
|
+
type: "array"
|
|
11720
|
+
}
|
|
11721
|
+
},
|
|
11722
|
+
required: [
|
|
11723
|
+
"catalogSource",
|
|
11724
|
+
"productId",
|
|
11725
|
+
"productTitle",
|
|
11726
|
+
"currencyCode",
|
|
11727
|
+
"variants"
|
|
11728
|
+
],
|
|
11729
|
+
type: "object"
|
|
11730
|
+
},
|
|
11731
|
+
schemaVersion: {
|
|
11732
|
+
const: 1,
|
|
11733
|
+
type: "number"
|
|
11734
|
+
},
|
|
11735
|
+
serverOwned: {
|
|
11736
|
+
additionalProperties: false,
|
|
11737
|
+
properties: {
|
|
11738
|
+
carriers: {
|
|
11739
|
+
additionalProperties: {},
|
|
11740
|
+
propertyNames: {
|
|
11741
|
+
type: "string"
|
|
11742
|
+
},
|
|
11743
|
+
type: "object"
|
|
11744
|
+
},
|
|
11745
|
+
readonly: {
|
|
11746
|
+
const: true,
|
|
11747
|
+
type: "boolean"
|
|
11748
|
+
}
|
|
11749
|
+
},
|
|
11750
|
+
readOnly: true,
|
|
11751
|
+
required: ["readonly", "carriers"],
|
|
11752
|
+
type: "object"
|
|
11753
|
+
}
|
|
11754
|
+
},
|
|
11755
|
+
required: ["schemaVersion", "kind", "lever", "arms"],
|
|
11756
|
+
type: "object"
|
|
11757
|
+
},
|
|
11758
|
+
{
|
|
11759
|
+
additionalProperties: false,
|
|
11760
|
+
properties: {
|
|
11761
|
+
carriers: {
|
|
11762
|
+
additionalProperties: {
|
|
11763
|
+
items: {},
|
|
11764
|
+
type: "array"
|
|
11765
|
+
},
|
|
11766
|
+
propertyNames: {
|
|
11767
|
+
type: "string"
|
|
11768
|
+
},
|
|
11769
|
+
type: "object"
|
|
11770
|
+
},
|
|
11771
|
+
kind: {
|
|
11772
|
+
const: "price",
|
|
11773
|
+
type: "string"
|
|
11774
|
+
},
|
|
11775
|
+
readonly: {
|
|
11776
|
+
const: true,
|
|
11777
|
+
type: "boolean"
|
|
11778
|
+
},
|
|
11779
|
+
reason: {
|
|
11780
|
+
type: "string"
|
|
11781
|
+
},
|
|
11782
|
+
schemaVersion: {
|
|
11783
|
+
const: 1,
|
|
11784
|
+
type: "number"
|
|
11785
|
+
}
|
|
11786
|
+
},
|
|
11787
|
+
required: ["schemaVersion", "kind", "readonly", "reason", "carriers"],
|
|
11788
|
+
type: "object"
|
|
11789
|
+
}
|
|
11790
|
+
]
|
|
11791
|
+
},
|
|
11792
|
+
"commerce/shipping.json": {
|
|
11793
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
11794
|
+
oneOf: [
|
|
11795
|
+
{
|
|
11796
|
+
additionalProperties: false,
|
|
11797
|
+
properties: {
|
|
11798
|
+
arms: {
|
|
11799
|
+
items: {
|
|
11800
|
+
anyOf: [
|
|
11801
|
+
{
|
|
11802
|
+
additionalProperties: false,
|
|
11803
|
+
properties: {
|
|
11804
|
+
sameAsControl: {
|
|
11805
|
+
const: true,
|
|
11806
|
+
type: "boolean"
|
|
11807
|
+
},
|
|
11808
|
+
variation: {
|
|
11809
|
+
minLength: 1,
|
|
11810
|
+
type: "string"
|
|
11811
|
+
}
|
|
11812
|
+
},
|
|
11813
|
+
required: ["variation", "sameAsControl"],
|
|
11814
|
+
type: "object"
|
|
11815
|
+
},
|
|
11816
|
+
{
|
|
11817
|
+
additionalProperties: false,
|
|
11818
|
+
properties: {
|
|
11819
|
+
bannerCopy: {
|
|
11820
|
+
default: "",
|
|
11821
|
+
maxLength: 200,
|
|
11822
|
+
type: "string"
|
|
11823
|
+
},
|
|
11824
|
+
control: {
|
|
11825
|
+
const: true,
|
|
11826
|
+
type: "boolean"
|
|
11827
|
+
},
|
|
11828
|
+
flatRate: {
|
|
11829
|
+
anyOf: [
|
|
11830
|
+
{
|
|
11831
|
+
minimum: 0,
|
|
11832
|
+
type: "number"
|
|
11833
|
+
},
|
|
11834
|
+
{
|
|
11835
|
+
type: "null"
|
|
11836
|
+
}
|
|
11837
|
+
],
|
|
11838
|
+
default: null
|
|
11839
|
+
},
|
|
11840
|
+
key: {
|
|
11841
|
+
maxLength: 24,
|
|
11842
|
+
minLength: 1,
|
|
11843
|
+
pattern: "^[a-z0-9-]+$",
|
|
11844
|
+
type: "string"
|
|
11845
|
+
},
|
|
11846
|
+
label: {
|
|
11847
|
+
maxLength: 60,
|
|
11848
|
+
minLength: 1,
|
|
11849
|
+
type: "string"
|
|
11850
|
+
},
|
|
11851
|
+
percentOff: {
|
|
11852
|
+
anyOf: [
|
|
11853
|
+
{
|
|
11854
|
+
maximum: 100,
|
|
11855
|
+
minimum: 1,
|
|
11856
|
+
type: "integer"
|
|
11857
|
+
},
|
|
11858
|
+
{
|
|
11859
|
+
type: "null"
|
|
11860
|
+
}
|
|
11861
|
+
],
|
|
11862
|
+
default: null
|
|
11863
|
+
},
|
|
11864
|
+
progressCopy: {
|
|
11865
|
+
maxLength: 200,
|
|
11866
|
+
type: "string"
|
|
11867
|
+
},
|
|
11868
|
+
reachedCopy: {
|
|
11869
|
+
maxLength: 200,
|
|
11870
|
+
type: "string"
|
|
11871
|
+
},
|
|
11872
|
+
target: {
|
|
11873
|
+
additionalProperties: false,
|
|
11874
|
+
default: {
|
|
11875
|
+
productGids: [],
|
|
11876
|
+
variantGids: []
|
|
11877
|
+
},
|
|
11878
|
+
properties: {
|
|
11879
|
+
productGids: {
|
|
11880
|
+
default: [],
|
|
11881
|
+
items: {
|
|
11882
|
+
pattern: "^gid:\\/\\/shopify\\/Product\\/\\d+$",
|
|
11883
|
+
type: "string"
|
|
11884
|
+
},
|
|
11885
|
+
maxItems: 100,
|
|
11886
|
+
type: "array"
|
|
11887
|
+
},
|
|
11888
|
+
variantGids: {
|
|
11889
|
+
default: [],
|
|
11890
|
+
items: {
|
|
11891
|
+
pattern: "^gid:\\/\\/shopify\\/ProductVariant\\/\\d+$",
|
|
11892
|
+
type: "string"
|
|
11893
|
+
},
|
|
11894
|
+
maxItems: 100,
|
|
11895
|
+
type: "array"
|
|
11896
|
+
}
|
|
11897
|
+
},
|
|
11898
|
+
type: "object"
|
|
11899
|
+
},
|
|
11900
|
+
threshold: {
|
|
11901
|
+
anyOf: [
|
|
11902
|
+
{
|
|
11903
|
+
minimum: 0,
|
|
11904
|
+
type: "number"
|
|
11905
|
+
},
|
|
11906
|
+
{
|
|
11907
|
+
type: "null"
|
|
11908
|
+
}
|
|
11909
|
+
]
|
|
11910
|
+
},
|
|
11911
|
+
treatment: {
|
|
11912
|
+
enum: [
|
|
11913
|
+
"progress_bar",
|
|
11914
|
+
"banner",
|
|
11915
|
+
"free_shipping",
|
|
11916
|
+
"flat_rate",
|
|
11917
|
+
"percent_off",
|
|
11918
|
+
"none"
|
|
11919
|
+
],
|
|
11920
|
+
type: "string"
|
|
11921
|
+
},
|
|
11922
|
+
variation: {
|
|
11923
|
+
minLength: 1,
|
|
11924
|
+
type: "string"
|
|
11925
|
+
}
|
|
11926
|
+
},
|
|
11927
|
+
required: [
|
|
11928
|
+
"key",
|
|
11929
|
+
"label",
|
|
11930
|
+
"threshold",
|
|
11931
|
+
"treatment",
|
|
11932
|
+
"progressCopy",
|
|
11933
|
+
"reachedCopy",
|
|
11934
|
+
"variation"
|
|
11935
|
+
],
|
|
11936
|
+
type: "object"
|
|
11937
|
+
}
|
|
11938
|
+
]
|
|
11939
|
+
},
|
|
11940
|
+
minItems: 1,
|
|
11941
|
+
type: "array"
|
|
11942
|
+
},
|
|
11943
|
+
kind: {
|
|
11944
|
+
const: "shipping",
|
|
11945
|
+
type: "string"
|
|
11946
|
+
},
|
|
11947
|
+
lever: {
|
|
11948
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
11949
|
+
additionalProperties: false,
|
|
11950
|
+
properties: {
|
|
11951
|
+
catalogSource: {
|
|
11952
|
+
anyOf: [
|
|
11953
|
+
{
|
|
11954
|
+
const: "sample",
|
|
11955
|
+
type: "string"
|
|
11956
|
+
},
|
|
11957
|
+
{
|
|
11958
|
+
const: "live",
|
|
11959
|
+
type: "string"
|
|
11960
|
+
}
|
|
11961
|
+
]
|
|
11962
|
+
},
|
|
11963
|
+
currencyCode: {
|
|
11964
|
+
maxLength: 3,
|
|
11965
|
+
minLength: 3,
|
|
11966
|
+
pattern: "^[A-Z]{3}$",
|
|
11967
|
+
type: "string"
|
|
11968
|
+
},
|
|
11969
|
+
enforcement: {
|
|
11970
|
+
default: "none",
|
|
11971
|
+
enum: ["none", "free_shipping"],
|
|
11972
|
+
type: "string"
|
|
11973
|
+
},
|
|
11974
|
+
enforcementPolicy: {
|
|
11975
|
+
additionalProperties: false,
|
|
11976
|
+
properties: {
|
|
11977
|
+
collisionPolicy: {
|
|
11978
|
+
const: "block_on_existing_shipping_discount",
|
|
11979
|
+
type: "string"
|
|
11980
|
+
},
|
|
11981
|
+
expressServiceHandles: {
|
|
11982
|
+
default: [],
|
|
11983
|
+
items: {
|
|
11984
|
+
maxLength: 255,
|
|
11985
|
+
minLength: 1,
|
|
11986
|
+
pattern: "^[a-z0-9][a-z0-9-]*$",
|
|
11987
|
+
type: "string"
|
|
11988
|
+
},
|
|
11989
|
+
maxItems: 50,
|
|
11990
|
+
type: "array"
|
|
11991
|
+
},
|
|
11992
|
+
maximumSubsidy: {
|
|
11993
|
+
exclusiveMinimum: 0,
|
|
11994
|
+
type: "number"
|
|
11995
|
+
},
|
|
11996
|
+
standardServiceHandles: {
|
|
11997
|
+
items: {
|
|
11998
|
+
maxLength: 255,
|
|
11999
|
+
minLength: 1,
|
|
12000
|
+
pattern: "^[a-z0-9][a-z0-9-]*$",
|
|
12001
|
+
type: "string"
|
|
12002
|
+
},
|
|
12003
|
+
maxItems: 50,
|
|
12004
|
+
minItems: 1,
|
|
12005
|
+
type: "array"
|
|
12006
|
+
}
|
|
12007
|
+
},
|
|
12008
|
+
required: ["standardServiceHandles", "maximumSubsidy", "collisionPolicy"],
|
|
12009
|
+
type: "object"
|
|
12010
|
+
},
|
|
12011
|
+
liveFreeShippingThreshold: {
|
|
12012
|
+
anyOf: [
|
|
12013
|
+
{
|
|
12014
|
+
minimum: 0,
|
|
12015
|
+
type: "number"
|
|
12016
|
+
},
|
|
12017
|
+
{
|
|
12018
|
+
type: "null"
|
|
12019
|
+
}
|
|
12020
|
+
]
|
|
12021
|
+
}
|
|
12022
|
+
},
|
|
12023
|
+
required: ["catalogSource", "currencyCode", "liveFreeShippingThreshold"],
|
|
12024
|
+
type: "object"
|
|
12025
|
+
},
|
|
12026
|
+
schemaVersion: {
|
|
12027
|
+
const: 1,
|
|
12028
|
+
type: "number"
|
|
12029
|
+
},
|
|
12030
|
+
serverOwned: {
|
|
12031
|
+
additionalProperties: false,
|
|
12032
|
+
properties: {
|
|
12033
|
+
carriers: {
|
|
12034
|
+
additionalProperties: {},
|
|
12035
|
+
propertyNames: {
|
|
12036
|
+
type: "string"
|
|
12037
|
+
},
|
|
12038
|
+
type: "object"
|
|
12039
|
+
},
|
|
12040
|
+
readonly: {
|
|
12041
|
+
const: true,
|
|
12042
|
+
type: "boolean"
|
|
12043
|
+
}
|
|
12044
|
+
},
|
|
12045
|
+
readOnly: true,
|
|
12046
|
+
required: ["readonly", "carriers"],
|
|
12047
|
+
type: "object"
|
|
12048
|
+
}
|
|
12049
|
+
},
|
|
12050
|
+
required: ["schemaVersion", "kind", "lever", "arms"],
|
|
12051
|
+
type: "object"
|
|
12052
|
+
},
|
|
12053
|
+
{
|
|
12054
|
+
additionalProperties: false,
|
|
12055
|
+
properties: {
|
|
12056
|
+
carriers: {
|
|
12057
|
+
additionalProperties: {
|
|
12058
|
+
items: {},
|
|
12059
|
+
type: "array"
|
|
12060
|
+
},
|
|
12061
|
+
propertyNames: {
|
|
12062
|
+
type: "string"
|
|
12063
|
+
},
|
|
12064
|
+
type: "object"
|
|
12065
|
+
},
|
|
12066
|
+
kind: {
|
|
12067
|
+
const: "shipping",
|
|
12068
|
+
type: "string"
|
|
12069
|
+
},
|
|
12070
|
+
readonly: {
|
|
12071
|
+
const: true,
|
|
12072
|
+
type: "boolean"
|
|
12073
|
+
},
|
|
12074
|
+
reason: {
|
|
12075
|
+
type: "string"
|
|
12076
|
+
},
|
|
12077
|
+
schemaVersion: {
|
|
12078
|
+
const: 1,
|
|
12079
|
+
type: "number"
|
|
12080
|
+
}
|
|
12081
|
+
},
|
|
12082
|
+
required: ["schemaVersion", "kind", "readonly", "reason", "carriers"],
|
|
12083
|
+
type: "object"
|
|
12084
|
+
}
|
|
12085
|
+
]
|
|
12086
|
+
},
|
|
10246
12087
|
"experiment.json": {
|
|
10247
12088
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
10248
12089
|
additionalProperties: false,
|
|
@@ -10824,13 +12665,7 @@ var experiment_filesystem_schema_default = {
|
|
|
10824
12665
|
{
|
|
10825
12666
|
properties: {
|
|
10826
12667
|
action: {
|
|
10827
|
-
enum: [
|
|
10828
|
-
"priceTest",
|
|
10829
|
-
"shippingTest",
|
|
10830
|
-
"offerTest",
|
|
10831
|
-
"postPurchaseOffer",
|
|
10832
|
-
"checkoutBlock"
|
|
10833
|
-
]
|
|
12668
|
+
enum: ["checkoutBlock"]
|
|
10834
12669
|
}
|
|
10835
12670
|
},
|
|
10836
12671
|
required: ["action"]
|
|
@@ -11063,6 +12898,16 @@ function printExperimentResults(results2, currency) {
|
|
|
11063
12898
|
const outcomeProvenance = summary.outcome.provenance === "manager_decided" ? " (manager decided)" : "";
|
|
11064
12899
|
console.log();
|
|
11065
12900
|
console.log(source_default.bold(String(experiment2.name ?? experiment2.id ?? "Experiment results")));
|
|
12901
|
+
if (typeof results2.computed_at === "string") {
|
|
12902
|
+
const ageSeconds = Math.max(
|
|
12903
|
+
0,
|
|
12904
|
+
Math.round((Date.now() - Date.parse(results2.computed_at)) / 1e3)
|
|
12905
|
+
);
|
|
12906
|
+
const age = ageSeconds < 60 ? `${ageSeconds}s ago` : ageSeconds < 3600 ? `${Math.floor(ageSeconds / 60)}m ago` : `${Math.floor(ageSeconds / 3600)}h ago`;
|
|
12907
|
+
console.log(
|
|
12908
|
+
` Computed: ${results2.computed_at} (${age}, from ${String(results2.results_source ?? "unknown")})`
|
|
12909
|
+
);
|
|
12910
|
+
}
|
|
11066
12911
|
console.log(` Status: ${statusBadge(String(experiment2.status ?? "unknown"))}`);
|
|
11067
12912
|
const audience = audienceLine(results2.segment_applied);
|
|
11068
12913
|
if (audience) console.log(` Audience: ${audience}`);
|
|
@@ -11111,6 +12956,49 @@ function printExperimentResults(results2, currency) {
|
|
|
11111
12956
|
[32, 8, 6, 8, 10]
|
|
11112
12957
|
);
|
|
11113
12958
|
}
|
|
12959
|
+
const commerceResults = summary.commerceResults;
|
|
12960
|
+
if (commerceResults) {
|
|
12961
|
+
console.log();
|
|
12962
|
+
console.log(source_default.bold("Profit per visitor (CM1)"));
|
|
12963
|
+
if (commerceResults.degraded || !commerceResults.plane) {
|
|
12964
|
+
console.log(` Unavailable: ${String(commerceResults.reason ?? "unknown")}`);
|
|
12965
|
+
} else {
|
|
12966
|
+
const plane = commerceResults.plane;
|
|
12967
|
+
const commerceArms = Array.isArray(plane.arms) ? plane.arms : [];
|
|
12968
|
+
printTable(
|
|
12969
|
+
["ARM", "VISITORS", "ORDERS", "CM1/VISITOR", "DELTA (95% CI)", "STATE"],
|
|
12970
|
+
commerceArms.map((arm) => {
|
|
12971
|
+
const deltas = arm.deltaVsControl;
|
|
12972
|
+
const cm = deltas?.cm;
|
|
12973
|
+
const ci = cm?.ci95;
|
|
12974
|
+
const count2 = (value) => typeof value === "number" && Number.isFinite(value) ? String(value) : "-";
|
|
12975
|
+
const money = (value) => typeof value === "number" ? formatRevenue(value, String(plane.currency ?? currency ?? "")) : "-";
|
|
12976
|
+
return [
|
|
12977
|
+
String(arm.name ?? arm.variationId ?? "-"),
|
|
12978
|
+
// Fail closed: a missing count is unavailable, never a measured 0.
|
|
12979
|
+
count2(arm.visitors),
|
|
12980
|
+
count2(arm.orders),
|
|
12981
|
+
money(arm.cmPerVisitor),
|
|
12982
|
+
cm ? `${money(cm.abs)}${ci ? ` (${money(ci.lower)} to ${money(ci.upper)})` : ""}` : "-",
|
|
12983
|
+
String(cm?.state ?? "control")
|
|
12984
|
+
];
|
|
12985
|
+
})
|
|
12986
|
+
);
|
|
12987
|
+
const oec = plane.oec;
|
|
12988
|
+
const coverage = plane.coverage;
|
|
12989
|
+
console.log(` Verdict: ${String(oec?.verdict ?? "unknown")}`);
|
|
12990
|
+
console.log(` ${String(oec?.plainLanguage ?? "")}`);
|
|
12991
|
+
console.log(
|
|
12992
|
+
` Cost coverage: ${typeof coverage?.costCoverage === "number" ? `${(coverage.costCoverage * 100).toFixed(1)}%` : "-"}`
|
|
12993
|
+
);
|
|
12994
|
+
const health = plane.health;
|
|
12995
|
+
for (const [name, check] of Object.entries(health ?? {})) {
|
|
12996
|
+
if (name === "trustworthy" || typeof check !== "object" || check === null || check.ok !== false)
|
|
12997
|
+
continue;
|
|
12998
|
+
console.log(` Health: ${name} failed`);
|
|
12999
|
+
}
|
|
13000
|
+
}
|
|
13001
|
+
}
|
|
11114
13002
|
const metrics = summary.metrics;
|
|
11115
13003
|
if (metrics.length > 0) {
|
|
11116
13004
|
console.log();
|
|
@@ -11133,16 +13021,17 @@ var filesystemPaths = Object.keys(experiment_filesystem_schema_default.files);
|
|
|
11133
13021
|
var EXPERIMENT_FILESYSTEM_PATH_SCHEMA = {
|
|
11134
13022
|
topLevel: filesystemPaths.filter((entry) => !entry.includes("/")),
|
|
11135
13023
|
experiment: filesystemPaths.filter((entry) => entry.startsWith("experiment/")).map((entry) => entry.slice("experiment/".length)),
|
|
13024
|
+
commerce: filesystemPaths.filter((entry) => entry.startsWith("commerce/")).map((entry) => entry.slice("commerce/".length)),
|
|
11136
13025
|
variation: filesystemPaths.filter((entry) => entry.startsWith("variations/*/")).map((entry) => entry.slice("variations/*/".length))
|
|
11137
13026
|
};
|
|
11138
13027
|
var SERVER_OWNED_EXPERIMENT_FILES = /* @__PURE__ */ new Set(["qa.json", "launch.json"]);
|
|
11139
13028
|
var escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
11140
13029
|
var pathAlternation = (paths) => paths.map(escapeRegex).join("|");
|
|
11141
13030
|
var FILESYSTEM_FILE_RE = new RegExp(
|
|
11142
|
-
`^(?:${pathAlternation(EXPERIMENT_FILESYSTEM_PATH_SCHEMA.topLevel)}|experiment/(?:${pathAlternation(EXPERIMENT_FILESYSTEM_PATH_SCHEMA.experiment)})|variations/[^/]+/(?:${pathAlternation(EXPERIMENT_FILESYSTEM_PATH_SCHEMA.variation)}))$`
|
|
13031
|
+
`^(?:${pathAlternation(EXPERIMENT_FILESYSTEM_PATH_SCHEMA.topLevel)}|experiment/(?:${pathAlternation(EXPERIMENT_FILESYSTEM_PATH_SCHEMA.experiment)})|commerce/(?:${pathAlternation(EXPERIMENT_FILESYSTEM_PATH_SCHEMA.commerce)})|variations/[^/]+/(?:${pathAlternation(EXPERIMENT_FILESYSTEM_PATH_SCHEMA.variation)}))$`
|
|
11143
13032
|
);
|
|
11144
13033
|
function sha256Text(value) {
|
|
11145
|
-
return
|
|
13034
|
+
return import_node_crypto6.default.createHash("sha256").update(value).digest("hex");
|
|
11146
13035
|
}
|
|
11147
13036
|
function folderPath(folder) {
|
|
11148
13037
|
return import_node_path6.default.resolve(process.cwd(), folder);
|
|
@@ -11239,6 +13128,13 @@ function listExperimentFilesystemFiles(folder, session) {
|
|
|
11239
13128
|
const content = import_node_fs8.default.readFileSync(absPath, "utf8");
|
|
11240
13129
|
files.push({ path: relativePath, content, sha256: sha256Text(content) });
|
|
11241
13130
|
}
|
|
13131
|
+
for (const fileName of EXPERIMENT_FILESYSTEM_PATH_SCHEMA.commerce) {
|
|
13132
|
+
const relativePath = `commerce/${fileName}`;
|
|
13133
|
+
const absPath = import_node_path6.default.join(folder, relativePath);
|
|
13134
|
+
if (!import_node_fs8.default.existsSync(absPath)) continue;
|
|
13135
|
+
const content = import_node_fs8.default.readFileSync(absPath, "utf8");
|
|
13136
|
+
files.push({ path: relativePath, content, sha256: sha256Text(content) });
|
|
13137
|
+
}
|
|
11242
13138
|
const variationsRoot = import_node_path6.default.join(folder, "variations");
|
|
11243
13139
|
if (import_node_fs8.default.existsSync(variationsRoot)) {
|
|
11244
13140
|
for (const variationDir of import_node_fs8.default.readdirSync(variationsRoot, { withFileTypes: true })) {
|
|
@@ -11261,7 +13157,7 @@ async function rewriteExperimentFilesystemAssets(input) {
|
|
|
11261
13157
|
baseUrl: input.baseUrl,
|
|
11262
13158
|
enabled: input.enabled,
|
|
11263
13159
|
upload: async (file) => {
|
|
11264
|
-
const hash =
|
|
13160
|
+
const hash = import_node_crypto6.default.createHash("sha256").update(file.bytes).digest("hex");
|
|
11265
13161
|
const cached = input.assets[hash];
|
|
11266
13162
|
if (cached) return cached;
|
|
11267
13163
|
if (input.upload === null) return null;
|
|
@@ -11591,7 +13487,7 @@ var list = new Command("list").alias("ls").description(
|
|
|
11591
13487
|
"Filter by effective storefront URL targeting (applied server-side)"
|
|
11592
13488
|
).option("-n, --limit <n>", "Max results", "20").option("--include-archived", "Include archived experiments").option("--full", "Return full experiment details instead of the default summary").option(
|
|
11593
13489
|
"--fields <list>",
|
|
11594
|
-
"Comma-separated fields to return and table columns in order (JSON always keeps id)"
|
|
13490
|
+
"Comma-separated fields to return and table columns in order (JSON always keeps id; fields outside the summary switch to full details automatically)"
|
|
11595
13491
|
).action(async function action3(opts) {
|
|
11596
13492
|
const creds = requireCredentials();
|
|
11597
13493
|
const spinner = await createSpinner("Fetching experiments...");
|
|
@@ -11747,7 +13643,7 @@ ${JSON.stringify(data.order, null, 2)}`);
|
|
|
11747
13643
|
console.log(`
|
|
11748
13644
|
Definitions: ${data.definitions_url}`);
|
|
11749
13645
|
}
|
|
11750
|
-
var results = new Command("results").description("Read experiment results and timeseries by ID or slug").argument("<id-or-slug>", EXPERIMENT_REFERENCE_DESCRIPTION).option("--currency <code>", "Currency code for revenue formatting").option("--segment <dim:value>", "Include one audience segment").option("--exclude-segment <dim:value>", "Exclude one audience segment (include wins)").option("--explain", "Explain raw-event exclusions and residuals").option("--goal <id>", "Goal ID or event key for --explain").option("--order <key>", "Order label trail for --explain").option("--samples", "Show sample identifiers with --explain").action(async function action7(idOrSlug, opts) {
|
|
13646
|
+
var results = new Command("results").description("Read experiment results and timeseries by ID or slug").argument("<id-or-slug>", EXPERIMENT_REFERENCE_DESCRIPTION).option("--currency <code>", "Currency code for revenue formatting").option("--segment <dim:value>", "Include one audience segment").option("--exclude-segment <dim:value>", "Exclude one audience segment (include wins)").option("--explain", "Explain raw-event exclusions and residuals").option("--goal <id>", "Goal ID or event key for --explain").option("--order <key>", "Order label trail for --explain").option("--samples", "Show sample identifiers with --explain").option("--wait <seconds>", "Wait for a cold server-side results build", "120").action(async function action7(idOrSlug, opts) {
|
|
11751
13647
|
if (opts.explain && (opts.segment !== void 0 || opts.excludeSegment !== void 0))
|
|
11752
13648
|
fail(
|
|
11753
13649
|
this,
|
|
@@ -11764,16 +13660,38 @@ var results = new Command("results").description("Read experiment results and ti
|
|
|
11764
13660
|
spinner?.fail(`Experiment "${idOrSlug}" not found`);
|
|
11765
13661
|
fail(this, `Experiment "${idOrSlug}" not found.`, EXIT_CODES.validation);
|
|
11766
13662
|
}
|
|
11767
|
-
const
|
|
13663
|
+
const waitSeconds = Number(opts.wait ?? "120");
|
|
13664
|
+
if (!Number.isFinite(waitSeconds) || waitSeconds < 0) {
|
|
13665
|
+
fail(this, "--wait must be a non-negative number of seconds", EXIT_CODES.validation);
|
|
13666
|
+
}
|
|
13667
|
+
const startedWaitingAt = Date.now();
|
|
13668
|
+
let res = opts.explain ? await getExperimentResultsExplain(creds, exp.id, opts) : await getExperimentResults(creds, exp.id, {
|
|
11768
13669
|
currency: opts.currency,
|
|
11769
13670
|
segment: opts.segment,
|
|
11770
13671
|
exclude_segment: opts.excludeSegment
|
|
11771
13672
|
});
|
|
13673
|
+
while (!opts.explain && isResultsComputing(res)) {
|
|
13674
|
+
const elapsedSeconds = (Date.now() - startedWaitingAt) / 1e3;
|
|
13675
|
+
const remainingSeconds = waitSeconds - elapsedSeconds;
|
|
13676
|
+
if (remainingSeconds <= 0) break;
|
|
13677
|
+
const requestedDelay = Number(
|
|
13678
|
+
res.retryAfter ?? res.errorDetails?.retry_after_seconds ?? 15
|
|
13679
|
+
);
|
|
13680
|
+
const delaySeconds = Math.min(30, Math.max(0, requestedDelay), remainingSeconds);
|
|
13681
|
+
if (!shouldUseJson(this))
|
|
13682
|
+
console.error(`Results are computing on the server; retrying in ${delaySeconds} s`);
|
|
13683
|
+
await new Promise((resolve2) => setTimeout(resolve2, delaySeconds * 1e3));
|
|
13684
|
+
res = await getExperimentResults(creds, exp.id, {
|
|
13685
|
+
currency: opts.currency,
|
|
13686
|
+
segment: opts.segment,
|
|
13687
|
+
exclude_segment: opts.excludeSegment
|
|
13688
|
+
});
|
|
13689
|
+
}
|
|
11772
13690
|
if (!res.ok || !res.data) {
|
|
11773
13691
|
spinner?.fail("Failed to fetch experiment results");
|
|
11774
13692
|
fail(
|
|
11775
13693
|
this,
|
|
11776
|
-
res.error ?? "Failed to fetch experiment results"
|
|
13694
|
+
`${res.error ?? "Failed to fetch experiment results"}${isResultsComputing(res) ? "; increase --wait to keep retrying" : ""}`,
|
|
11777
13695
|
classifyStatus(res.status),
|
|
11778
13696
|
res.errorDetails
|
|
11779
13697
|
);
|
|
@@ -11790,6 +13708,11 @@ var results = new Command("results").description("Read experiment results and ti
|
|
|
11790
13708
|
fail(this, error instanceof Error ? error.message : String(error));
|
|
11791
13709
|
}
|
|
11792
13710
|
});
|
|
13711
|
+
function isResultsComputing(res) {
|
|
13712
|
+
if (res.status !== 503) return false;
|
|
13713
|
+
const body5 = res.data;
|
|
13714
|
+
return body5?.error?.code === "results_computing" || res.errorDetails?.code === "results_computing";
|
|
13715
|
+
}
|
|
11793
13716
|
var DAY_NAMES = {
|
|
11794
13717
|
mon: "monday",
|
|
11795
13718
|
monday: "monday",
|
|
@@ -12864,7 +14787,45 @@ var applyFilesystem = new Command("apply").option(
|
|
|
12864
14787
|
acceptQaInvalidation = true;
|
|
12865
14788
|
}
|
|
12866
14789
|
const spinner = process.stderr.isTTY ? await createSpinner("Applying experiment filesystem...") : null;
|
|
12867
|
-
let
|
|
14790
|
+
let verifiedAfterTimeout = false;
|
|
14791
|
+
let verifiedTimeoutMs;
|
|
14792
|
+
let publicationUnconfirmed = false;
|
|
14793
|
+
const applyWithVerification = async (body5) => {
|
|
14794
|
+
try {
|
|
14795
|
+
return await applyExperimentFilesystem(creds, session.experimentId, body5);
|
|
14796
|
+
} catch (error) {
|
|
14797
|
+
if (!(error instanceof CliError) || error.details?.reason !== "timeout" && error.details?.reason !== "reset")
|
|
14798
|
+
throw error;
|
|
14799
|
+
const reason2 = error.details.reason;
|
|
14800
|
+
const timeoutMs = typeof error.details.timeoutMs === "number" ? error.details.timeoutMs : APPLY_TIMEOUT_MS;
|
|
14801
|
+
const verification = await planExperimentFilesystem(creds, session.experimentId, {
|
|
14802
|
+
files
|
|
14803
|
+
}).catch(() => null);
|
|
14804
|
+
if (verification?.ok && verification.data?.ok === true && verification.data.changes.length === 0) {
|
|
14805
|
+
verifiedAfterTimeout = true;
|
|
14806
|
+
verifiedTimeoutMs = timeoutMs;
|
|
14807
|
+
publicationUnconfirmed = verification.data.qaApproval?.status !== "draft";
|
|
14808
|
+
return {
|
|
14809
|
+
ok: true,
|
|
14810
|
+
status: 200,
|
|
14811
|
+
data: { applied: true, plan: verification.data }
|
|
14812
|
+
};
|
|
14813
|
+
}
|
|
14814
|
+
const rerun = `apex experiments apply ${shellQuote(folderArg)}`;
|
|
14815
|
+
throw new CliError(
|
|
14816
|
+
`Apply did not confirm within ${timeoutMs / 1e3}s and the server does not yet hold these changes; re-run ${rerun}`,
|
|
14817
|
+
EXIT_CODES.general,
|
|
14818
|
+
{
|
|
14819
|
+
code: "apply_unconfirmed",
|
|
14820
|
+
reason: reason2,
|
|
14821
|
+
timeoutMs,
|
|
14822
|
+
pendingChanges: verification?.ok && verification.data ? verification.data.changes.length : null,
|
|
14823
|
+
next_steps: [{ command: rerun, why: "Re-run the unconfirmed apply." }]
|
|
14824
|
+
}
|
|
14825
|
+
);
|
|
14826
|
+
}
|
|
14827
|
+
};
|
|
14828
|
+
let res = await applyWithVerification({
|
|
12868
14829
|
files,
|
|
12869
14830
|
accept_qa_invalidation: acceptQaInvalidation,
|
|
12870
14831
|
...opts.confirmTargetingReplace ? { confirmTargetingReplace: true } : {}
|
|
@@ -12885,7 +14846,7 @@ var applyFilesystem = new Command("apply").option(
|
|
|
12885
14846
|
output(this, { aborted: true }, () => console.log("Aborted."));
|
|
12886
14847
|
return;
|
|
12887
14848
|
}
|
|
12888
|
-
res = await
|
|
14849
|
+
res = await applyWithVerification({
|
|
12889
14850
|
files,
|
|
12890
14851
|
accept_qa_invalidation: acceptQaInvalidation,
|
|
12891
14852
|
confirmTargetingReplace: true
|
|
@@ -12907,7 +14868,8 @@ var applyFilesystem = new Command("apply").option(
|
|
|
12907
14868
|
plan: res.data?.plan
|
|
12908
14869
|
});
|
|
12909
14870
|
}
|
|
12910
|
-
spinner?.
|
|
14871
|
+
if (publicationUnconfirmed) spinner?.stop();
|
|
14872
|
+
else spinner?.succeed("Applied experiment filesystem");
|
|
12911
14873
|
const synced = opts.sync ? await refreshExperimentFilesystemFolderIfNeeded(creds, folder, session.experimentId) : false;
|
|
12912
14874
|
if (!synced) {
|
|
12913
14875
|
await refreshServerOwnedFilesIfNeeded(
|
|
@@ -12931,6 +14893,11 @@ var applyFilesystem = new Command("apply").option(
|
|
|
12931
14893
|
const syncMessage = synced ? "Local files were synced." : opts.sync ? "Local file sync failed; retry --sync or apex experiments pull." : "Local authored files were left unchanged; use --sync or apex experiments pull to refresh." + (res.data?.receipt?.files ? "" : " No receipt file digests; authored-file manifest entries left unchanged.");
|
|
12932
14894
|
const previewResult = await getQaPreviewUrls(creds, session.experimentId);
|
|
12933
14895
|
const { previewUrls, dashboardUrl } = previewResult;
|
|
14896
|
+
const publicationWarning = "Storefront publication is unconfirmed; visitors may still receive the previous treatment.";
|
|
14897
|
+
const publicationNextStep = {
|
|
14898
|
+
command: `apex experiments status ${shellQuote(folderArg)} --json`,
|
|
14899
|
+
why: "Check experiment status before handover; persisted files do not confirm storefront publication."
|
|
14900
|
+
};
|
|
12934
14901
|
output(
|
|
12935
14902
|
this,
|
|
12936
14903
|
{
|
|
@@ -12939,14 +14906,30 @@ var applyFilesystem = new Command("apply").option(
|
|
|
12939
14906
|
applied: true,
|
|
12940
14907
|
plan: planRes.data,
|
|
12941
14908
|
requiredAcknowledgment: requiredQaAcknowledgment(planRes.data),
|
|
12942
|
-
result: res.data,
|
|
14909
|
+
result: verifiedAfterTimeout ? null : res.data,
|
|
12943
14910
|
synced,
|
|
12944
14911
|
dashboardUrl,
|
|
12945
|
-
receipt: res.data?.receipt ?? null,
|
|
12946
|
-
previewUrls
|
|
14912
|
+
receipt: verifiedAfterTimeout ? null : res.data?.receipt ?? null,
|
|
14913
|
+
previewUrls,
|
|
14914
|
+
...verifiedAfterTimeout ? { verifiedAfterTimeout: true, timeoutMs: verifiedTimeoutMs } : {},
|
|
14915
|
+
...publicationUnconfirmed ? {
|
|
14916
|
+
persisted: true,
|
|
14917
|
+
publication: "unconfirmed",
|
|
14918
|
+
warnings: [publicationWarning],
|
|
14919
|
+
next_steps: [publicationNextStep]
|
|
14920
|
+
} : {}
|
|
12947
14921
|
},
|
|
12948
14922
|
() => {
|
|
12949
14923
|
printFilesystemPlan(planRes.data);
|
|
14924
|
+
if (verifiedAfterTimeout) {
|
|
14925
|
+
console.log(
|
|
14926
|
+
`${publicationUnconfirmed ? "Apply saved" : "Apply confirmed"}: the server already holds these changes (the response timed out after ${verifiedTimeoutMs / 1e3}s).`
|
|
14927
|
+
);
|
|
14928
|
+
}
|
|
14929
|
+
if (publicationUnconfirmed) {
|
|
14930
|
+
console.log(`Warning: ${publicationWarning}`);
|
|
14931
|
+
console.log(`Next: ${publicationNextStep.command} (${publicationNextStep.why})`);
|
|
14932
|
+
}
|
|
12950
14933
|
const receipt = res.data?.receipt;
|
|
12951
14934
|
if (receipt) {
|
|
12952
14935
|
console.log("\nReceipt");
|
|
@@ -13107,6 +15090,9 @@ var qaApprove = new Command("approve").description("Approve QA and move the expe
|
|
|
13107
15090
|
"[folder-or-id]",
|
|
13108
15091
|
"Experiment filesystem folder, ID, slug, or test key (for example BIO-085)",
|
|
13109
15092
|
"."
|
|
15093
|
+
).option(
|
|
15094
|
+
"--acknowledge-zero-traffic",
|
|
15095
|
+
"Acknowledge variations that receive no traffic for this split (Apex staff only)"
|
|
13110
15096
|
).action(async function action19(target) {
|
|
13111
15097
|
const creds = requireCredentials();
|
|
13112
15098
|
const spinner = await createSpinner("Approving QA...");
|
|
@@ -13114,7 +15100,40 @@ var qaApprove = new Command("approve").description("Approve QA and move the expe
|
|
|
13114
15100
|
const resolved = await resolveExperimentForFilesystemOrId(target);
|
|
13115
15101
|
const before = await resolveExperiment(resolved.experimentId);
|
|
13116
15102
|
if (!before) fail(this, "Experiment not found.", EXIT_CODES.validation);
|
|
13117
|
-
|
|
15103
|
+
let res = await approveExperimentQa(creds, resolved.experimentId);
|
|
15104
|
+
const details = res.errorDetails;
|
|
15105
|
+
if (!res.ok && res.status === 409 && details?.code === "degenerate_weights_ack_required") {
|
|
15106
|
+
if (!this.opts().acknowledgeZeroTraffic) {
|
|
15107
|
+
throw new CliError(
|
|
15108
|
+
`${res.error ?? "QA approval requires acknowledgement"} Rerun with --acknowledge-zero-traffic.`,
|
|
15109
|
+
classifyStatus(res.status),
|
|
15110
|
+
details
|
|
15111
|
+
);
|
|
15112
|
+
}
|
|
15113
|
+
if (details.internal_only === true) {
|
|
15114
|
+
throw new CliError(
|
|
15115
|
+
res.error ?? "Only Apex Admin or Internal users can acknowledge zero traffic.",
|
|
15116
|
+
classifyStatus(res.status),
|
|
15117
|
+
details
|
|
15118
|
+
);
|
|
15119
|
+
}
|
|
15120
|
+
if (typeof details.split_hash === "string" && details.split_hash.length > 0) {
|
|
15121
|
+
const weights = Array.isArray(details.variation_weights) ? details.variation_weights : [];
|
|
15122
|
+
for (const value of weights) {
|
|
15123
|
+
if (value && typeof value === "object") {
|
|
15124
|
+
const variation = value;
|
|
15125
|
+
process.stderr.write(
|
|
15126
|
+
`${variation.name ?? variation.id ?? "Variation"}: ${(variation.weight ?? 0) * 100}%${variation.weight === 0 ? " (no traffic)" : ""}
|
|
15127
|
+
`
|
|
15128
|
+
);
|
|
15129
|
+
}
|
|
15130
|
+
}
|
|
15131
|
+
res = await approveExperimentQa(creds, resolved.experimentId, {
|
|
15132
|
+
acknowledge_degenerate_weights: true,
|
|
15133
|
+
acknowledge_degenerate_weights_hash: details.split_hash
|
|
15134
|
+
});
|
|
15135
|
+
}
|
|
15136
|
+
}
|
|
13118
15137
|
if (!res.ok)
|
|
13119
15138
|
throw new CliError(
|
|
13120
15139
|
res.error ?? "Failed to approve QA",
|
|
@@ -13291,6 +15310,46 @@ var qaEvidence = new Command("evidence").description("Attach screenshot jobs or
|
|
|
13291
15310
|
}
|
|
13292
15311
|
});
|
|
13293
15312
|
var qa = new Command("qa").description("Manage experiment QA runs from the CLI").addCommand(qaStatus).addCommand(
|
|
15313
|
+
new Command("probe").description("Run or read saved commerce QA probes").argument("<folder-or-id>", EXPERIMENT_REFERENCE_DESCRIPTION).option("--kind <kind...>", "Probe kinds: price, shipping, post_purchase").option("--saved", "Read saved evidence without calling Shopify").action(async function(target, opts) {
|
|
15314
|
+
const kinds = opts.kind;
|
|
15315
|
+
if (kinds && (kinds.length > 3 || new Set(kinds).size !== kinds.length || kinds.some((kind5) => !["price", "shipping", "post_purchase"].includes(kind5))))
|
|
15316
|
+
fail(this, "--kind must be price, shipping, or post_purchase", EXIT_CODES.validation);
|
|
15317
|
+
const creds = requireCredentials();
|
|
15318
|
+
const { experimentId } = await resolveExperimentForFilesystemOrId(target);
|
|
15319
|
+
const response = opts.saved ? await getExperimentCommerceProbes(creds, experimentId) : await runExperimentCommerceProbes(creds, experimentId, kinds);
|
|
15320
|
+
if (!response.ok || !response.data)
|
|
15321
|
+
throw new CliError(
|
|
15322
|
+
response.error ?? "Failed to load commerce probes",
|
|
15323
|
+
classifyStatus(response.status),
|
|
15324
|
+
response.errorDetails
|
|
15325
|
+
);
|
|
15326
|
+
const data = opts.saved && kinds ? {
|
|
15327
|
+
...response.data,
|
|
15328
|
+
kinds: Object.fromEntries(
|
|
15329
|
+
Object.entries(response.data.kinds).filter(([kind5]) => kinds.includes(kind5))
|
|
15330
|
+
)
|
|
15331
|
+
} : response.data;
|
|
15332
|
+
output(
|
|
15333
|
+
this,
|
|
15334
|
+
data,
|
|
15335
|
+
() => printTable(
|
|
15336
|
+
["KIND", "STATE", "SUMMARY"],
|
|
15337
|
+
Object.entries(data.kinds).map(([kind5, value]) => [
|
|
15338
|
+
kind5,
|
|
15339
|
+
value.evidence.state,
|
|
15340
|
+
value.result?.summary ?? value.evidence.reasons[0] ?? "Saved evidence is current."
|
|
15341
|
+
])
|
|
15342
|
+
)
|
|
15343
|
+
);
|
|
15344
|
+
const states = Object.values(data.kinds).map((value) => value?.evidence.state);
|
|
15345
|
+
if (states.some((state) => state !== "pass")) {
|
|
15346
|
+
console.error(
|
|
15347
|
+
"Next: fix stale, pending, or failing evidence and run apex experiments qa probe again."
|
|
15348
|
+
);
|
|
15349
|
+
process.exitCode = states.includes("fail") ? EXIT_CODES.checkFailed : EXIT_CODES.validation;
|
|
15350
|
+
}
|
|
15351
|
+
})
|
|
15352
|
+
).addCommand(
|
|
13294
15353
|
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) {
|
|
13295
15354
|
const minutes = Number(opts.minutes);
|
|
13296
15355
|
if (!Number.isInteger(minutes) || minutes < 5 || minutes > 1440)
|
|
@@ -13419,7 +15478,7 @@ var screenshots = new Command("screenshots").description(
|
|
|
13419
15478
|
Math.min(resumeDelayMs, Math.max(0, resumeDeadline - screenshotNow()))
|
|
13420
15479
|
)
|
|
13421
15480
|
);
|
|
13422
|
-
resumeDelayMs = Math.min(Math.round(resumeDelayMs * 1.6),
|
|
15481
|
+
resumeDelayMs = Math.min(Math.round(resumeDelayMs * 1.6), 4e3);
|
|
13423
15482
|
continue;
|
|
13424
15483
|
}
|
|
13425
15484
|
if (!existing.ok && isTransientResumeStatus(existing.status) && attempt <= 3 && screenshotNow() < resumeDeadline) {
|
|
@@ -13429,7 +15488,7 @@ var screenshots = new Command("screenshots").description(
|
|
|
13429
15488
|
Math.min(resumeDelayMs, Math.max(0, resumeDeadline - screenshotNow()))
|
|
13430
15489
|
)
|
|
13431
15490
|
);
|
|
13432
|
-
resumeDelayMs = Math.min(Math.round(resumeDelayMs * 1.6),
|
|
15491
|
+
resumeDelayMs = Math.min(Math.round(resumeDelayMs * 1.6), 4e3);
|
|
13433
15492
|
continue;
|
|
13434
15493
|
}
|
|
13435
15494
|
break;
|
|
@@ -13583,7 +15642,7 @@ var screenshots = new Command("screenshots").description(
|
|
|
13583
15642
|
);
|
|
13584
15643
|
consecutiveTransientFailures += 1;
|
|
13585
15644
|
if (consecutiveTransientFailures > 3) throw error;
|
|
13586
|
-
delayMs = Math.min(Math.round(delayMs * 1.6),
|
|
15645
|
+
delayMs = Math.min(Math.round(delayMs * 1.6), 4e3);
|
|
13587
15646
|
continue;
|
|
13588
15647
|
}
|
|
13589
15648
|
const polledToTerminalState = polled.ok && polled.data && polled.data.status !== "queued" && polled.data.status !== "processing";
|
|
@@ -13608,11 +15667,11 @@ var screenshots = new Command("screenshots").description(
|
|
|
13608
15667
|
polled.errorDetails
|
|
13609
15668
|
);
|
|
13610
15669
|
}
|
|
13611
|
-
delayMs = Math.min(Math.round(delayMs * 1.6),
|
|
15670
|
+
delayMs = Math.min(Math.round(delayMs * 1.6), 4e3);
|
|
13612
15671
|
continue;
|
|
13613
15672
|
}
|
|
13614
15673
|
consecutiveTransientFailures = 0;
|
|
13615
|
-
delayMs = Math.min(Math.round(delayMs * 1.6),
|
|
15674
|
+
delayMs = Math.min(Math.round(delayMs * 1.6), 4e3);
|
|
13616
15675
|
}
|
|
13617
15676
|
if (opts.attachQa !== false) {
|
|
13618
15677
|
const attached = await attachExperimentQaEvidence(creds, experiment2.id, {
|
|
@@ -13990,6 +16049,8 @@ init_source();
|
|
|
13990
16049
|
async function signedPreviewFromReadiness(creds, experimentId, variationId, targetUrl) {
|
|
13991
16050
|
try {
|
|
13992
16051
|
const client = createHttpClient(creds, {
|
|
16052
|
+
onResponse: recordApiResponse2,
|
|
16053
|
+
observeNetworkErrors: true,
|
|
13993
16054
|
onNetworkError: (info) => {
|
|
13994
16055
|
throw new Error(info.cause);
|
|
13995
16056
|
}
|
|
@@ -14233,8 +16294,7 @@ function loadHarnessConfig(folder) {
|
|
|
14233
16294
|
experimentName: experiment2.name ?? id,
|
|
14234
16295
|
variationSlug: slugify(variation, "variant-a"),
|
|
14235
16296
|
variationName: `Variant ${variation}`,
|
|
14236
|
-
targetUrl
|
|
14237
|
-
antiFlickerSelectors: []
|
|
16297
|
+
targetUrl
|
|
14238
16298
|
}
|
|
14239
16299
|
};
|
|
14240
16300
|
}
|
|
@@ -14318,14 +16378,15 @@ async function createSession(folder) {
|
|
|
14318
16378
|
if (!apex.targetUrl) {
|
|
14319
16379
|
throw new Error("Missing targetUrl. Run `apex harness init` and set targetUrl.");
|
|
14320
16380
|
}
|
|
16381
|
+
const resetJs = cleanupResetJs(experiment2);
|
|
14321
16382
|
const res = await createHarnessSession(creds, {
|
|
14322
16383
|
experimentSlug: apex.experimentSlug,
|
|
14323
16384
|
experimentName: apex.experimentName,
|
|
14324
16385
|
variationSlug: apex.variationSlug,
|
|
14325
16386
|
variationName: apex.variationName,
|
|
14326
16387
|
targetUrl: apex.targetUrl,
|
|
14327
|
-
resetJs:
|
|
14328
|
-
antiFlickerSelectors: apex.antiFlickerSelectors
|
|
16388
|
+
...typeof resetJs === "string" ? { resetJs } : {},
|
|
16389
|
+
...Array.isArray(apex.antiFlickerSelectors) ? { antiFlickerSelectors: apex.antiFlickerSelectors } : {}
|
|
14329
16390
|
});
|
|
14330
16391
|
if (!res.ok)
|
|
14331
16392
|
throw new CliError(
|
|
@@ -14453,7 +16514,7 @@ harnessCommand.command("publish").argument("<folder>", "AB Test Harness experime
|
|
|
14453
16514
|
|
|
14454
16515
|
// src/commands/operator.ts
|
|
14455
16516
|
init_source();
|
|
14456
|
-
var
|
|
16517
|
+
var import_node_crypto7 = __toESM(require("node:crypto"));
|
|
14457
16518
|
var import_node_fs11 = __toESM(require("node:fs"));
|
|
14458
16519
|
var import_node_path9 = __toESM(require("node:path"));
|
|
14459
16520
|
var CANONICAL_FILE_RE = /^(src\/(?:index\.js|index\.scss|info\.js|mutations\.json)|experiment\.config\.json|metrics\.json|targeting\.json|dev-doc\.md|brand\.md|goals\/[a-zA-Z0-9_.-]+\.json|segments\/[a-zA-Z0-9_.-]+\.json|network\/[a-zA-Z0-9_.-]+\.recipe\.json)$/;
|
|
@@ -14498,8 +16559,13 @@ function formatWorkspaceBlockingIssues(value) {
|
|
|
14498
16559
|
return lines.join("\n");
|
|
14499
16560
|
}).join("\n");
|
|
14500
16561
|
}
|
|
16562
|
+
function formatHarnessRefusal(message, details) {
|
|
16563
|
+
const issues = formatWorkspaceBlockingIssues(details);
|
|
16564
|
+
return issues ? `${message}
|
|
16565
|
+
${issues}` : message;
|
|
16566
|
+
}
|
|
14501
16567
|
function sha256Text2(value) {
|
|
14502
|
-
return
|
|
16568
|
+
return import_node_crypto7.default.createHash("sha256").update(value).digest("hex");
|
|
14503
16569
|
}
|
|
14504
16570
|
function byteLength(value) {
|
|
14505
16571
|
return Buffer.byteLength(value, "utf8");
|
|
@@ -14692,12 +16758,11 @@ async function createExactHarnessSession(folder, session) {
|
|
|
14692
16758
|
experimentName: expRes.data.name,
|
|
14693
16759
|
variationSlug: config.variationSlug ?? variation.slug ?? slugify2(variation.name, "variation"),
|
|
14694
16760
|
variationName: variation.name,
|
|
14695
|
-
targetUrl
|
|
14696
|
-
antiFlickerSelectors: []
|
|
16761
|
+
targetUrl
|
|
14697
16762
|
});
|
|
14698
16763
|
if (!res.ok || !res.data)
|
|
14699
16764
|
throw new CliError(
|
|
14700
|
-
res.error ?? "Failed to create harness session",
|
|
16765
|
+
formatHarnessRefusal(res.error ?? "Failed to create harness session", res.errorDetails),
|
|
14701
16766
|
classifyStatus(res.status),
|
|
14702
16767
|
res.errorDetails
|
|
14703
16768
|
);
|
|
@@ -14789,7 +16854,15 @@ async function runPublish(folderArg, opts = {}) {
|
|
|
14789
16854
|
const harnessSession = await createExactHarnessSession(folder, session);
|
|
14790
16855
|
const version = await uploadCurrentArtifactVersion(folder, harnessSession.sessionId);
|
|
14791
16856
|
const published = await publishHarnessSession(creds, harnessSession.sessionId);
|
|
14792
|
-
if (!published.ok)
|
|
16857
|
+
if (!published.ok)
|
|
16858
|
+
throw new CliError(
|
|
16859
|
+
formatHarnessRefusal(
|
|
16860
|
+
published.error ?? "Failed to publish external artifacts",
|
|
16861
|
+
published.errorDetails
|
|
16862
|
+
),
|
|
16863
|
+
classifyStatus(published.status),
|
|
16864
|
+
published.errorDetails
|
|
16865
|
+
);
|
|
14793
16866
|
external = {
|
|
14794
16867
|
sessionId: harnessSession.sessionId,
|
|
14795
16868
|
version: version.version,
|
|
@@ -15436,10 +17509,81 @@ trackingCommand.command("latency").description("Measure forced-preview versus na
|
|
|
15436
17509
|
|
|
15437
17510
|
// src/commands/events.ts
|
|
15438
17511
|
var import_node_fs14 = __toESM(require("node:fs"));
|
|
17512
|
+
var maxWindowMs = 31 * 864e5;
|
|
17513
|
+
function correctedWindowStep(opts, nowMs) {
|
|
17514
|
+
const fromMs = Date.parse(opts.from);
|
|
17515
|
+
const endMs = Math.min(fromMs + maxWindowMs, nowMs);
|
|
17516
|
+
if (endMs <= fromMs) {
|
|
17517
|
+
return {
|
|
17518
|
+
command: "Choose a --from start before now.",
|
|
17519
|
+
why: "No valid export window exists from this start; --to must be after --from and at or before now."
|
|
17520
|
+
};
|
|
17521
|
+
}
|
|
17522
|
+
const quote = (value) => `'${value.replace(/'/g, "'\\''")}'`;
|
|
17523
|
+
const args = [
|
|
17524
|
+
"apex events export",
|
|
17525
|
+
`--from ${quote(opts.from)}`,
|
|
17526
|
+
`--to ${quote(new Date(endMs).toISOString())}`,
|
|
17527
|
+
`--out ${quote(opts.out ?? "events.ndjson")}`
|
|
17528
|
+
];
|
|
17529
|
+
if (opts.experiment !== void 0) args.push(`--experiment ${quote(opts.experiment)}`);
|
|
17530
|
+
if (opts.eventType !== void 0) args.push(`--event-type ${quote(opts.eventType)}`);
|
|
17531
|
+
args.push(`--qa ${quote(opts.qa)}`, `--limit ${quote(opts.limit)}`);
|
|
17532
|
+
if (opts.jsonSummary) args.push("--json-summary");
|
|
17533
|
+
return {
|
|
17534
|
+
command: args.join(" "),
|
|
17535
|
+
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."
|
|
17536
|
+
};
|
|
17537
|
+
}
|
|
15439
17538
|
var eventsCommand = new Command("events").description("Export raw tracking events");
|
|
15440
|
-
eventsCommand.command("export").description(
|
|
17539
|
+
eventsCommand.command("export").description(
|
|
17540
|
+
"Export every raw event in a window of at most 31 days that does not end in the future"
|
|
17541
|
+
).requiredOption(
|
|
17542
|
+
"--from <iso>",
|
|
17543
|
+
"Inclusive ISO 8601 window start; the window spans at most 31 days"
|
|
17544
|
+
).requiredOption("--to <iso>", "Exclusive ISO 8601 window end; not in the future").option("--experiment <id>", "Filter by experiment ID").option("--event-type <types>", "Comma-separated event types").option("--qa <scope>", "QA scope: exclude, include, or only", "include").option("--limit <count>", "Page size from 1 to 1000", "500").option("--out <path>", "Write event rows to a file instead of stdout").option("--json-summary", "Print a JSON summary on stdout; requires --out for the NDJSON rows").action(async (opts, command) => {
|
|
15441
17545
|
if (!Number.isFinite(Date.parse(opts.from)) || !Number.isFinite(Date.parse(opts.to))) {
|
|
15442
|
-
fail(command, "--from and --to must be ISO 8601 timestamps", EXIT_CODES.validation
|
|
17546
|
+
fail(command, "--from and --to must be ISO 8601 timestamps", EXIT_CODES.validation, {
|
|
17547
|
+
code: "invalid_window",
|
|
17548
|
+
reason: "invalid_timestamp"
|
|
17549
|
+
});
|
|
17550
|
+
}
|
|
17551
|
+
const fromMs = Date.parse(opts.from);
|
|
17552
|
+
const toMs = Date.parse(opts.to);
|
|
17553
|
+
if (toMs <= fromMs)
|
|
17554
|
+
fail(command, "--to must be after --from.", EXIT_CODES.validation, {
|
|
17555
|
+
code: "invalid_window",
|
|
17556
|
+
reason: "to_not_after_from"
|
|
17557
|
+
});
|
|
17558
|
+
const now = /* @__PURE__ */ new Date();
|
|
17559
|
+
if (toMs - fromMs > maxWindowMs) {
|
|
17560
|
+
const requestedDays = ((toMs - fromMs) / 864e5).toFixed(2);
|
|
17561
|
+
fail(
|
|
17562
|
+
command,
|
|
17563
|
+
`The export window is ${requestedDays} days; the maximum is 31. Export in windows of at most 31 days.`,
|
|
17564
|
+
EXIT_CODES.validation,
|
|
17565
|
+
{
|
|
17566
|
+
code: "invalid_window",
|
|
17567
|
+
reason: "window_too_long",
|
|
17568
|
+
max_days: 31,
|
|
17569
|
+
requested_days: requestedDays,
|
|
17570
|
+
next_steps: [correctedWindowStep(opts, now.getTime())]
|
|
17571
|
+
}
|
|
17572
|
+
);
|
|
17573
|
+
}
|
|
17574
|
+
if (toMs > now.getTime()) {
|
|
17575
|
+
const nowIso = now.toISOString();
|
|
17576
|
+
fail(
|
|
17577
|
+
command,
|
|
17578
|
+
`--to is in the future; use a time at or before now (${nowIso}).`,
|
|
17579
|
+
EXIT_CODES.validation,
|
|
17580
|
+
{
|
|
17581
|
+
code: "invalid_window",
|
|
17582
|
+
reason: "to_in_future",
|
|
17583
|
+
now: nowIso,
|
|
17584
|
+
next_steps: [correctedWindowStep(opts, now.getTime())]
|
|
17585
|
+
}
|
|
17586
|
+
);
|
|
15443
17587
|
}
|
|
15444
17588
|
if (!["exclude", "include", "only"].includes(opts.qa)) {
|
|
15445
17589
|
fail(command, "--qa must be exclude, include, or only", EXIT_CODES.validation);
|
|
@@ -15479,129 +17623,150 @@ eventsCommand.command("export").description("Export every raw event in an absolu
|
|
|
15479
17623
|
output2.once("error", reject);
|
|
15480
17624
|
});
|
|
15481
17625
|
} catch (error) {
|
|
17626
|
+
const exists = error?.code === "EEXIST";
|
|
15482
17627
|
fail(
|
|
15483
17628
|
command,
|
|
15484
|
-
`Cannot write ${opts.out}: ${error instanceof Error ? error.message : String(error)}`,
|
|
15485
|
-
EXIT_CODES.general,
|
|
15486
|
-
{ code: "local_file_error" }
|
|
17629
|
+
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)}`,
|
|
17630
|
+
exists ? EXIT_CODES.validation : EXIT_CODES.general,
|
|
17631
|
+
{ code: exists ? "output_exists" : "local_file_error", path: opts.out }
|
|
15487
17632
|
);
|
|
15488
17633
|
}
|
|
15489
17634
|
}
|
|
15490
|
-
|
|
15491
|
-
const
|
|
15492
|
-
|
|
15493
|
-
|
|
15494
|
-
|
|
15495
|
-
|
|
15496
|
-
|
|
15497
|
-
|
|
15498
|
-
|
|
15499
|
-
|
|
15500
|
-
|
|
15501
|
-
|
|
15502
|
-
|
|
15503
|
-
|
|
15504
|
-
|
|
15505
|
-
|
|
15506
|
-
|
|
15507
|
-
|
|
15508
|
-
|
|
15509
|
-
|
|
15510
|
-
|
|
15511
|
-
|
|
15512
|
-
|
|
15513
|
-
|
|
15514
|
-
|
|
15515
|
-
|
|
15516
|
-
|
|
15517
|
-
|
|
15518
|
-
|
|
15519
|
-
|
|
15520
|
-
|
|
15521
|
-
|
|
15522
|
-
|
|
15523
|
-
|
|
15524
|
-
|
|
15525
|
-
|
|
15526
|
-
|
|
15527
|
-
|
|
15528
|
-
|
|
15529
|
-
|
|
15530
|
-
|
|
15531
|
-
|
|
15532
|
-
|
|
15533
|
-
|
|
15534
|
-
|
|
15535
|
-
|
|
15536
|
-
|
|
17635
|
+
try {
|
|
17636
|
+
const writeLine = async (line) => {
|
|
17637
|
+
const stream = output2 ?? process.stdout;
|
|
17638
|
+
const accepted = output2 ? output2.write(line) : writeRawCommandOutput(line);
|
|
17639
|
+
if (accepted) return;
|
|
17640
|
+
await new Promise((resolve2, reject) => {
|
|
17641
|
+
const onDrain = () => {
|
|
17642
|
+
stream.off("error", onError);
|
|
17643
|
+
resolve2();
|
|
17644
|
+
};
|
|
17645
|
+
const onError = (error) => {
|
|
17646
|
+
stream.off("drain", onDrain);
|
|
17647
|
+
reject(error);
|
|
17648
|
+
};
|
|
17649
|
+
stream.once("drain", onDrain);
|
|
17650
|
+
stream.once("error", onError);
|
|
17651
|
+
});
|
|
17652
|
+
};
|
|
17653
|
+
let cursor;
|
|
17654
|
+
let written = 0;
|
|
17655
|
+
let pages = 0;
|
|
17656
|
+
let totals;
|
|
17657
|
+
do {
|
|
17658
|
+
const response = await exportEventsPage(requireCredentials(), {
|
|
17659
|
+
from: opts.from,
|
|
17660
|
+
to: opts.to,
|
|
17661
|
+
experiment: opts.experiment,
|
|
17662
|
+
eventTypes: opts.eventType?.split(",").map((value) => value.trim()).filter(Boolean),
|
|
17663
|
+
qa: opts.qa,
|
|
17664
|
+
limit,
|
|
17665
|
+
cursor
|
|
17666
|
+
});
|
|
17667
|
+
if (!response.ok || !response.data) {
|
|
17668
|
+
fail(
|
|
17669
|
+
command,
|
|
17670
|
+
response.error ?? "Event export page failed",
|
|
17671
|
+
classifyStatus(response.status),
|
|
17672
|
+
response.errorDetails
|
|
17673
|
+
);
|
|
17674
|
+
}
|
|
17675
|
+
if (outputError)
|
|
17676
|
+
fail(
|
|
17677
|
+
command,
|
|
17678
|
+
`Cannot write ${opts.out}: ${outputError.message}`,
|
|
17679
|
+
EXIT_CODES.general,
|
|
17680
|
+
{
|
|
17681
|
+
code: "local_file_error"
|
|
17682
|
+
}
|
|
17683
|
+
);
|
|
17684
|
+
const page = response.data.data;
|
|
17685
|
+
totals ??= page.totals;
|
|
17686
|
+
for (const event of page.events) {
|
|
17687
|
+
try {
|
|
17688
|
+
await writeLine(`${JSON.stringify(event)}
|
|
17689
|
+
`);
|
|
17690
|
+
} catch (error) {
|
|
17691
|
+
fail(
|
|
17692
|
+
command,
|
|
17693
|
+
`Cannot write ${opts.out ?? "stdout"}: ${error instanceof Error ? error.message : String(error)}`,
|
|
17694
|
+
EXIT_CODES.general,
|
|
17695
|
+
{ code: "local_file_error" }
|
|
17696
|
+
);
|
|
15537
17697
|
}
|
|
15538
|
-
|
|
15539
|
-
|
|
15540
|
-
|
|
15541
|
-
|
|
17698
|
+
written += 1;
|
|
17699
|
+
}
|
|
17700
|
+
pages += 1;
|
|
17701
|
+
cursor = page.next_cursor ?? void 0;
|
|
17702
|
+
} while (cursor);
|
|
17703
|
+
if (output2) {
|
|
15542
17704
|
try {
|
|
15543
|
-
await
|
|
15544
|
-
|
|
17705
|
+
await new Promise((resolve2, reject) => {
|
|
17706
|
+
if (outputError) return reject(outputError);
|
|
17707
|
+
output2.once("error", reject);
|
|
17708
|
+
output2.end(resolve2);
|
|
17709
|
+
});
|
|
15545
17710
|
} catch (error) {
|
|
15546
17711
|
fail(
|
|
15547
17712
|
command,
|
|
15548
|
-
`Cannot write ${opts.out
|
|
17713
|
+
`Cannot write ${opts.out}: ${error instanceof Error ? error.message : String(error)}`,
|
|
15549
17714
|
EXIT_CODES.general,
|
|
15550
17715
|
{ code: "local_file_error" }
|
|
15551
17716
|
);
|
|
15552
17717
|
}
|
|
15553
|
-
|
|
15554
|
-
|
|
15555
|
-
|
|
15556
|
-
|
|
15557
|
-
|
|
15558
|
-
|
|
15559
|
-
|
|
15560
|
-
|
|
15561
|
-
|
|
15562
|
-
output2.once("error", reject);
|
|
15563
|
-
output2.end(resolve2);
|
|
15564
|
-
});
|
|
15565
|
-
} catch (error) {
|
|
15566
|
-
fail(
|
|
15567
|
-
command,
|
|
15568
|
-
`Cannot write ${opts.out}: ${error instanceof Error ? error.message : String(error)}`,
|
|
15569
|
-
EXIT_CODES.general,
|
|
15570
|
-
{ code: "local_file_error" }
|
|
15571
|
-
);
|
|
17718
|
+
if (outputError)
|
|
17719
|
+
fail(
|
|
17720
|
+
command,
|
|
17721
|
+
`Cannot write ${opts.out}: ${outputError.message}`,
|
|
17722
|
+
EXIT_CODES.general,
|
|
17723
|
+
{
|
|
17724
|
+
code: "local_file_error"
|
|
17725
|
+
}
|
|
17726
|
+
);
|
|
15572
17727
|
}
|
|
15573
|
-
|
|
17728
|
+
const matches = totals ? written === totals.events : false;
|
|
17729
|
+
const summary = {
|
|
17730
|
+
events_written: written,
|
|
17731
|
+
pages,
|
|
17732
|
+
totals: totals ?? null,
|
|
17733
|
+
count_matches: matches
|
|
17734
|
+
};
|
|
17735
|
+
if (!matches)
|
|
15574
17736
|
fail(
|
|
15575
17737
|
command,
|
|
15576
|
-
|
|
17738
|
+
"Exported event count does not match the server total. Totals are counted when the first page is read, so events that arrive late for a window ending in the last few minutes, or rows sharing one timestamp and event ID, can cause a difference. Re-run for a window that ended at least 15 minutes ago.",
|
|
15577
17739
|
EXIT_CODES.general,
|
|
15578
17740
|
{
|
|
15579
|
-
|
|
17741
|
+
reason: "export_count_mismatch",
|
|
17742
|
+
...summary
|
|
15580
17743
|
}
|
|
15581
17744
|
);
|
|
15582
|
-
|
|
15583
|
-
|
|
15584
|
-
|
|
15585
|
-
events_written: written,
|
|
15586
|
-
pages,
|
|
15587
|
-
totals: totals ?? null,
|
|
15588
|
-
count_matches: matches
|
|
15589
|
-
};
|
|
15590
|
-
if (!matches)
|
|
15591
|
-
fail(
|
|
15592
|
-
command,
|
|
15593
|
-
"Exported event count does not match the server total. Totals are counted when the first page is read, so events that arrive late for a window ending in the last few minutes, or rows sharing one timestamp and event ID, can cause a difference. Re-run for a window that ended at least 15 minutes ago.",
|
|
15594
|
-
EXIT_CODES.general,
|
|
15595
|
-
{
|
|
15596
|
-
reason: "export_count_mismatch",
|
|
15597
|
-
...summary
|
|
15598
|
-
}
|
|
15599
|
-
);
|
|
15600
|
-
if (summaryOnStdout) writeEnvelope(summary, "apex events export");
|
|
15601
|
-
process.stderr.write(
|
|
15602
|
-
`Exported ${written} events across ${pages} page(s); total ${totals?.events ?? "unknown"}; count matches: ${matches}.
|
|
17745
|
+
if (summaryOnStdout) writeEnvelope(summary, "apex events export");
|
|
17746
|
+
process.stderr.write(
|
|
17747
|
+
`Exported ${written} events across ${pages} page(s); total ${totals?.events ?? "unknown"}; count matches: ${matches}.
|
|
15603
17748
|
`
|
|
15604
|
-
|
|
17749
|
+
);
|
|
17750
|
+
} catch (error) {
|
|
17751
|
+
if (output2 && opts.out) {
|
|
17752
|
+
output2.destroy();
|
|
17753
|
+
import_node_fs14.default.rmSync(opts.out, { force: true });
|
|
17754
|
+
const suffix = `The partial file ${opts.out} was removed.`;
|
|
17755
|
+
if (error instanceof CliError)
|
|
17756
|
+
throw new CliError(
|
|
17757
|
+
`${error.message} ${suffix}`,
|
|
17758
|
+
error.exitCode,
|
|
17759
|
+
{ ...error.details, partial_file_removed: true },
|
|
17760
|
+
error.api
|
|
17761
|
+
);
|
|
17762
|
+
throw new CliError(
|
|
17763
|
+
`${error instanceof Error ? error.message : String(error)} ${suffix}`,
|
|
17764
|
+
EXIT_CODES.general,
|
|
17765
|
+
{ code: "local_file_error", partial_file_removed: true }
|
|
17766
|
+
);
|
|
17767
|
+
}
|
|
17768
|
+
throw error;
|
|
17769
|
+
}
|
|
15605
17770
|
});
|
|
15606
17771
|
|
|
15607
17772
|
// src/commands/goals.ts
|
|
@@ -15652,6 +17817,10 @@ function sdkPageviewGoalMatch(urlPattern, href) {
|
|
|
15652
17817
|
var INVENTORY_PAGE_SIZE = 100;
|
|
15653
17818
|
var GOALS_PAGE_SIZE = 100;
|
|
15654
17819
|
var MAX_GOAL_PAGES = 100;
|
|
17820
|
+
var MAX_PAGE_LOOKUP_PAGES = 1e3;
|
|
17821
|
+
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;
|
|
17822
|
+
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)";
|
|
17823
|
+
var PAGE_NEXT_STEPS = [{ command: "apex pages list", why: "Find the page ID or key." }];
|
|
15655
17824
|
var NON_QUANTIFIABLE = /* @__PURE__ */ new Set(["(", "|", "^", "$"]);
|
|
15656
17825
|
function urlPatternDependence(pattern) {
|
|
15657
17826
|
let fragment = false;
|
|
@@ -15794,6 +17963,58 @@ function parseCapture(command, value) {
|
|
|
15794
17963
|
function cleanBody(input) {
|
|
15795
17964
|
return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== void 0));
|
|
15796
17965
|
}
|
|
17966
|
+
async function resolveGoalPageId(command, creds, value) {
|
|
17967
|
+
if (UUID_V1_TO_V8.test(value)) return value;
|
|
17968
|
+
let cursor;
|
|
17969
|
+
for (let page = 0; page < MAX_PAGE_LOOKUP_PAGES; page += 1) {
|
|
17970
|
+
const response = await requestTargetingResource(
|
|
17971
|
+
creds,
|
|
17972
|
+
"GET",
|
|
17973
|
+
"pages",
|
|
17974
|
+
void 0,
|
|
17975
|
+
void 0,
|
|
17976
|
+
100,
|
|
17977
|
+
cursor
|
|
17978
|
+
);
|
|
17979
|
+
if (!response.ok || !response.data) {
|
|
17980
|
+
if (response.status === 403) {
|
|
17981
|
+
fail(
|
|
17982
|
+
command,
|
|
17983
|
+
"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).",
|
|
17984
|
+
EXIT_CODES.auth,
|
|
17985
|
+
{
|
|
17986
|
+
code: "page_lookup_forbidden",
|
|
17987
|
+
next_steps: [
|
|
17988
|
+
{
|
|
17989
|
+
command: "apex pages list",
|
|
17990
|
+
why: "Use a key with pages:read to find the page ID, or get it from the dashboard; pass --page <page ID> instead."
|
|
17991
|
+
}
|
|
17992
|
+
]
|
|
17993
|
+
}
|
|
17994
|
+
);
|
|
17995
|
+
}
|
|
17996
|
+
fail(
|
|
17997
|
+
command,
|
|
17998
|
+
response.error ?? "Failed to list pages",
|
|
17999
|
+
classifyStatus(response.status),
|
|
18000
|
+
response.errorDetails
|
|
18001
|
+
);
|
|
18002
|
+
}
|
|
18003
|
+
if (!("data" in response.data) || !Array.isArray(response.data.data)) break;
|
|
18004
|
+
const pageData = response.data;
|
|
18005
|
+
const match2 = pageData.data.find((item) => item.key === value);
|
|
18006
|
+
if (match2) return match2.id;
|
|
18007
|
+
const next = pageData.pagination?.next_cursor ?? null;
|
|
18008
|
+
if (!pageData.pagination?.has_more || !next || next === cursor) break;
|
|
18009
|
+
cursor = next;
|
|
18010
|
+
}
|
|
18011
|
+
fail(
|
|
18012
|
+
command,
|
|
18013
|
+
`No active page with key ${value} in this shop; list pages with apex pages list`,
|
|
18014
|
+
EXIT_CODES.validation,
|
|
18015
|
+
{ code: "page_not_found", next_steps: PAGE_NEXT_STEPS }
|
|
18016
|
+
);
|
|
18017
|
+
}
|
|
15797
18018
|
function printGoal(goal) {
|
|
15798
18019
|
console.log();
|
|
15799
18020
|
console.log(source_default.bold(goal.name));
|
|
@@ -15806,6 +18027,7 @@ function printGoal(goal) {
|
|
|
15806
18027
|
if (goal.text_match) console.log(` Text match: ${goal.text_match}`);
|
|
15807
18028
|
if (goal.url_pattern) console.log(` URL: ${goal.url_pattern}`);
|
|
15808
18029
|
if (goal.event_name) console.log(` Event: ${goal.event_name}`);
|
|
18030
|
+
if (goal.page_id) console.log(` Page: ${goal.page_id}`);
|
|
15809
18031
|
console.log(` Count once: ${goal.count_once ? "yes" : "no"}`);
|
|
15810
18032
|
console.log();
|
|
15811
18033
|
}
|
|
@@ -15833,8 +18055,22 @@ var list2 = new Command("list").alias("ls").description("List goals").option("-n
|
|
|
15833
18055
|
);
|
|
15834
18056
|
});
|
|
15835
18057
|
});
|
|
15836
|
-
var create2 = new Command("create").description("Create a goal").requiredOption("--name <name>", "Goal name").requiredOption("--type <type>", "click|pageview|custom|revenue").option("--measurement <measurement>", "conversion|value", "conversion").option("--value-property <property>", "Numeric event property for value goals").option("--value-unit <unit>", "currency|number|duration_seconds|percent").option("--selector <selector>", "CSS selector for click goals").option("--text-match <text>", "Text match for click goals").option("--url-pattern <pattern>", "URL pattern for pageview goals").option("--event-name <name>", "Event name for custom goals").option("--count-once", "Count only once per visitor").option("--capture <json-or-path>", "Capture JSON array or @path").action(async function action35(opts) {
|
|
18058
|
+
var create2 = new Command("create").description("Create a goal").requiredOption("--name <name>", "Goal name").requiredOption("--type <type>", "click|pageview|custom|revenue").option("--measurement <measurement>", "conversion|value", "conversion").option("--value-property <property>", "Numeric event property for value goals").option("--value-unit <unit>", "currency|number|duration_seconds|percent").option("--selector <selector>", "CSS selector for click goals").option("--text-match <text>", "Text match for click goals").option("--url-pattern <pattern>", "URL pattern for pageview goals").option("--event-name <name>", "Event name for custom goals").option("--page <id-or-key>", PAGE_OPTION_DESCRIPTION).option("--count-once", "Count only once per visitor").option("--capture <json-or-path>", "Capture JSON array or @path").action(async function action35(opts) {
|
|
15837
18059
|
const creds = requireCredentials();
|
|
18060
|
+
if (opts.type === "click" && !opts.page) {
|
|
18061
|
+
fail(
|
|
18062
|
+
this,
|
|
18063
|
+
"Click goals need a page: pass --page <page id or key> (list pages with apex pages list)",
|
|
18064
|
+
EXIT_CODES.validation,
|
|
18065
|
+
{ code: "page_required", next_steps: PAGE_NEXT_STEPS }
|
|
18066
|
+
);
|
|
18067
|
+
}
|
|
18068
|
+
if ((opts.type === "custom" || opts.type === "revenue") && opts.page) {
|
|
18069
|
+
fail(this, `--page is not supported for ${opts.type} goals`, EXIT_CODES.validation, {
|
|
18070
|
+
code: "page_not_supported"
|
|
18071
|
+
});
|
|
18072
|
+
}
|
|
18073
|
+
const pageId = typeof opts.page === "string" ? await resolveGoalPageId(this, creds, opts.page) : void 0;
|
|
15838
18074
|
const body5 = cleanBody({
|
|
15839
18075
|
name: opts.name,
|
|
15840
18076
|
type: opts.type,
|
|
@@ -15845,6 +18081,7 @@ var create2 = new Command("create").description("Create a goal").requiredOption(
|
|
|
15845
18081
|
text_match: opts.textMatch,
|
|
15846
18082
|
url_pattern: opts.urlPattern,
|
|
15847
18083
|
event_name: opts.eventName,
|
|
18084
|
+
page_id: pageId,
|
|
15848
18085
|
count_once: opts.countOnce === true,
|
|
15849
18086
|
capture: parseCapture(this, typeof opts.capture === "string" ? opts.capture : void 0)
|
|
15850
18087
|
});
|
|
@@ -15862,8 +18099,9 @@ var create2 = new Command("create").description("Create a goal").requiredOption(
|
|
|
15862
18099
|
var update2 = new Command("update").description("Update a goal").argument("<id>", "Goal ID").option("--name <name>", "Goal name").option("--type <type>", "click|pageview|custom|revenue").option("--measurement <measurement>", "conversion|value").option(
|
|
15863
18100
|
"--value-property <property>",
|
|
15864
18101
|
"Numeric event property for value goals; pass null to clear"
|
|
15865
|
-
).option("--value-unit <unit>", "currency|number|duration_seconds|percent; pass null to clear").option("--selector <selector>", "CSS selector for click goals").option("--text-match <text>", "Text match for click goals").option("--url-pattern <pattern>", "URL pattern for pageview goals").option("--event-name <name>", "Event name for custom goals").option("--count-once", "Count only once per visitor").option("--no-count-once", "Do not count only once per visitor").option("--capture <json-or-path>", "Capture JSON array or @path").action(async function action36(id, opts) {
|
|
18102
|
+
).option("--value-unit <unit>", "currency|number|duration_seconds|percent; pass null to clear").option("--selector <selector>", "CSS selector for click goals").option("--text-match <text>", "Text match for click goals").option("--url-pattern <pattern>", "URL pattern for pageview goals").option("--event-name <name>", "Event name for custom goals").option("--page <id-or-key>", `${PAGE_OPTION_DESCRIPTION}; pass null to clear`).option("--count-once", "Count only once per visitor").option("--no-count-once", "Do not count only once per visitor").option("--capture <json-or-path>", "Capture JSON array or @path").action(async function action36(id, opts) {
|
|
15866
18103
|
const creds = requireCredentials();
|
|
18104
|
+
const pageId = opts.page === "null" ? null : typeof opts.page === "string" ? await resolveGoalPageId(this, creds, opts.page) : void 0;
|
|
15867
18105
|
const body5 = cleanBody({
|
|
15868
18106
|
name: opts.name,
|
|
15869
18107
|
type: opts.type,
|
|
@@ -15874,6 +18112,7 @@ var update2 = new Command("update").description("Update a goal").argument("<id>"
|
|
|
15874
18112
|
text_match: opts.textMatch,
|
|
15875
18113
|
url_pattern: opts.urlPattern,
|
|
15876
18114
|
event_name: opts.eventName,
|
|
18115
|
+
page_id: pageId,
|
|
15877
18116
|
count_once: opts.countOnce,
|
|
15878
18117
|
capture: parseCapture(this, typeof opts.capture === "string" ? opts.capture : void 0)
|
|
15879
18118
|
});
|
|
@@ -17518,13 +19757,13 @@ var NAMED_HTML_ENTITIES = {
|
|
|
17518
19757
|
nbsp: "\xA0"
|
|
17519
19758
|
};
|
|
17520
19759
|
function decodeHtmlEntities(value) {
|
|
17521
|
-
return value.replace(/&(#[0-9]+|#[xX][0-9a-fA-F]+|[a-zA-Z]+);/g, (match2,
|
|
17522
|
-
if (
|
|
17523
|
-
const hex =
|
|
17524
|
-
const code = Number.parseInt(hex ?
|
|
19760
|
+
return value.replace(/&(#[0-9]+|#[xX][0-9a-fA-F]+|[a-zA-Z]+);/g, (match2, entity2) => {
|
|
19761
|
+
if (entity2.startsWith("#")) {
|
|
19762
|
+
const hex = entity2[1] === "x" || entity2[1] === "X";
|
|
19763
|
+
const code = Number.parseInt(hex ? entity2.slice(2) : entity2.slice(1), hex ? 16 : 10);
|
|
17525
19764
|
return Number.isInteger(code) && code >= 0 && code <= 1114111 ? String.fromCodePoint(code) : match2;
|
|
17526
19765
|
}
|
|
17527
|
-
return NAMED_HTML_ENTITIES[
|
|
19766
|
+
return NAMED_HTML_ENTITIES[entity2.toLowerCase()] ?? match2;
|
|
17528
19767
|
});
|
|
17529
19768
|
}
|
|
17530
19769
|
function printSettings(settings2) {
|
|
@@ -18294,9 +20533,6 @@ function resolveWorkerBase(options) {
|
|
|
18294
20533
|
const raw = options.worker ?? process.env.DRIP_WORKER_URL ?? PRODUCTION_WORKER_BASE_URL;
|
|
18295
20534
|
return raw.trim().replace(/\/+$/, "");
|
|
18296
20535
|
}
|
|
18297
|
-
function resolveShopId(options) {
|
|
18298
|
-
return (options.shopId ?? process.env.DRIP_SHOP_ID ?? "").trim() || null;
|
|
18299
|
-
}
|
|
18300
20536
|
function servedConfigUrl(workerBase, shopId) {
|
|
18301
20537
|
return `${workerBase}/config?shopId=${encodeURIComponent(shopId)}&env=${PRODUCTION_ENVIRONMENT_KEY}`;
|
|
18302
20538
|
}
|
|
@@ -18469,7 +20705,8 @@ async function readGoals(creds) {
|
|
|
18469
20705
|
return { goals };
|
|
18470
20706
|
}
|
|
18471
20707
|
async function runDoctor(options = {}) {
|
|
18472
|
-
const
|
|
20708
|
+
const resolved = resolveCheckShop(options.shopId, process.env.DRIP_SHOP_ID);
|
|
20709
|
+
const { creds } = resolved;
|
|
18473
20710
|
if (options.check === "purchase_capture") {
|
|
18474
20711
|
const domainsRes2 = await getInstallDomains(creds);
|
|
18475
20712
|
if (!domainsRes2.ok || !domainsRes2.data)
|
|
@@ -18479,7 +20716,9 @@ async function runDoctor(options = {}) {
|
|
|
18479
20716
|
classifyStatus(domainsRes2.status),
|
|
18480
20717
|
domainsRes2.errorDetails
|
|
18481
20718
|
);
|
|
18482
|
-
|
|
20719
|
+
if (resolved.shopId)
|
|
20720
|
+
assertResponseShop(domainsRes2.data.shop_id, resolved.shopId, creds.credentialKind);
|
|
20721
|
+
const shopId2 = resolved.shopId ?? domainsRes2.data.shop_id;
|
|
18483
20722
|
const servedResult2 = shopId2 ? await fetchServedConfig(resolveWorkerBase(options), shopId2) : {};
|
|
18484
20723
|
const readTracking = async (minutes) => {
|
|
18485
20724
|
try {
|
|
@@ -18530,6 +20769,7 @@ async function runDoctor(options = {}) {
|
|
|
18530
20769
|
apiFailureOf(res)
|
|
18531
20770
|
);
|
|
18532
20771
|
}
|
|
20772
|
+
assertResponseShop(domainsRes.data.shop_id, resolved.shopId, creds.credentialKind);
|
|
18533
20773
|
for (const [label, res] of [
|
|
18534
20774
|
["url inventory", inventory],
|
|
18535
20775
|
["goals", goals]
|
|
@@ -18573,7 +20813,7 @@ async function runDoctor(options = {}) {
|
|
|
18573
20813
|
withCaveat(devPollutionCheck(eventHosts, envsRes.data, unparsedRows), inventoryCaveat)
|
|
18574
20814
|
);
|
|
18575
20815
|
const primary = hostOnly(domainsRes.data.domain) ?? eventHosts[0];
|
|
18576
|
-
const shopId =
|
|
20816
|
+
const shopId = resolved.shopId ?? domainsRes.data.shop_id;
|
|
18577
20817
|
const workerBase = resolveWorkerBase(options);
|
|
18578
20818
|
const [page, servedResult] = await Promise.all([
|
|
18579
20819
|
primary ? fetchPrimaryHtml(primary) : Promise.resolve({}),
|
|
@@ -18794,7 +21034,7 @@ function consentRateCheck(items) {
|
|
|
18794
21034
|
}
|
|
18795
21035
|
var doctorCommand = new Command("doctor").description("Check shop configuration against observed analytics").option(
|
|
18796
21036
|
"--shop-id <uuid>",
|
|
18797
|
-
"
|
|
21037
|
+
"Apex shop ID to check; API requests use it too (DRIP_SHOP_ID is the fallback when no shop is selected)"
|
|
18798
21038
|
).option("--check <id>", "Run one check (for example purchase_capture)").option(
|
|
18799
21039
|
"--worker <url>",
|
|
18800
21040
|
`Apex Worker base URL; defaults to DRIP_WORKER_URL or ${PRODUCTION_WORKER_BASE_URL}`
|
|
@@ -18803,7 +21043,7 @@ var doctorCommand = new Command("doctor").description("Check shop configuration
|
|
|
18803
21043
|
if (options.check && data.checks.length === 0)
|
|
18804
21044
|
fail(this, `Unknown doctor check: ${options.check}`, EXIT_CODES.validation);
|
|
18805
21045
|
output(this, data, () => printDoctorReport(data.checks));
|
|
18806
|
-
if (data.checks.some((c) => c.status === "FAIL")) process.exitCode = EXIT_CODES.
|
|
21046
|
+
if (data.checks.some((c) => c.status === "FAIL")) process.exitCode = EXIT_CODES.checkFailed;
|
|
18807
21047
|
});
|
|
18808
21048
|
function printDoctorReport(checks) {
|
|
18809
21049
|
printTable(
|
|
@@ -18987,7 +21227,7 @@ Examples:
|
|
|
18987
21227
|
);
|
|
18988
21228
|
|
|
18989
21229
|
// src/commands/install.ts
|
|
18990
|
-
var
|
|
21230
|
+
var import_promises3 = require("node:dns/promises");
|
|
18991
21231
|
var import_node_http2 = require("node:http");
|
|
18992
21232
|
var import_node_https = require("node:https");
|
|
18993
21233
|
var import_node_net = require("node:net");
|
|
@@ -19045,7 +21285,7 @@ var testResolver;
|
|
|
19045
21285
|
var testFetch;
|
|
19046
21286
|
async function resolveHost(hostname) {
|
|
19047
21287
|
const mocked = testResolver?.(hostname) ?? null;
|
|
19048
|
-
const addresses = mocked ?? await (0,
|
|
21288
|
+
const addresses = mocked ?? await (0, import_promises3.lookup)(hostname, { all: true });
|
|
19049
21289
|
return addresses.map((entry) => entry.address);
|
|
19050
21290
|
}
|
|
19051
21291
|
async function validateUrl(raw, allowedHosts) {
|
|
@@ -19525,16 +21765,16 @@ async function checkServedConfig(configUrl, workerBaseUrl, workerBaseSource) {
|
|
|
19525
21765
|
};
|
|
19526
21766
|
}
|
|
19527
21767
|
var installCommand = new Command("install").description("Check Apex storefront installation").command("check").description(
|
|
19528
|
-
"Verify snippet, served config, and recent tracking
|
|
21768
|
+
"Verify snippet, served config, and recent tracking for --shop-id (which also selects the shop for API requests), the selected shop, or DRIP_SHOP_ID"
|
|
19529
21769
|
).option("--url <url>", "Check one explicit bound storefront URL").option(
|
|
19530
21770
|
"--shop-id <id>",
|
|
19531
|
-
"Apex shop ID to verify (falls back to --shop/apex use, then DRIP_SHOP_ID)"
|
|
21771
|
+
"Apex shop ID to verify; API requests use it too (falls back to --shop/apex use, then DRIP_SHOP_ID)"
|
|
19532
21772
|
).option(
|
|
19533
21773
|
"--worker-url <url>",
|
|
19534
21774
|
"Apex Worker base URL that serves /config (defaults to the URL in the served snippet, then DRIP_WORKER_URL, then the production worker)"
|
|
19535
21775
|
).option("--environment <key>", "Environment key to request from the worker", "production").action(async function action65(opts) {
|
|
19536
|
-
const
|
|
19537
|
-
const
|
|
21776
|
+
const resolved = resolveCheckShop(opts.shopId, process.env.DRIP_SHOP_ID);
|
|
21777
|
+
const { creds, shopId, overriddenShopId } = resolved;
|
|
19538
21778
|
if (!shopId)
|
|
19539
21779
|
fail(
|
|
19540
21780
|
this,
|
|
@@ -19542,6 +21782,10 @@ var installCommand = new Command("install").description("Check Apex storefront i
|
|
|
19542
21782
|
EXIT_CODES.validation,
|
|
19543
21783
|
{ field: "shop-id" }
|
|
19544
21784
|
);
|
|
21785
|
+
if (overriddenShopId)
|
|
21786
|
+
console.error(
|
|
21787
|
+
`Checking shop ${shopId} from --shop-id instead of the selected shop ${overriddenShopId}.`
|
|
21788
|
+
);
|
|
19545
21789
|
const domains = await getInstallDomains(creds);
|
|
19546
21790
|
if (!domains.ok || !domains.data)
|
|
19547
21791
|
fail(
|
|
@@ -19550,6 +21794,7 @@ var installCommand = new Command("install").description("Check Apex storefront i
|
|
|
19550
21794
|
classifyStatus(domains.status),
|
|
19551
21795
|
domains.errorDetails
|
|
19552
21796
|
);
|
|
21797
|
+
assertResponseShop(domains.data.shop_id, shopId, creds.credentialKind);
|
|
19553
21798
|
const mockStoreOrigins = cliTestSetting("STORE_ORIGINS");
|
|
19554
21799
|
const storeOrigins = cliTestSetting("API") === "1" && mockStoreOrigins ? mockStoreOrigins.split(",") : domains.data.storeOrigins;
|
|
19555
21800
|
const origins = opts.url ? [opts.url] : storeOrigins;
|
|
@@ -19581,7 +21826,16 @@ var installCommand = new Command("install").description("Check Apex storefront i
|
|
|
19581
21826
|
});
|
|
19582
21827
|
continue;
|
|
19583
21828
|
}
|
|
19584
|
-
if (error instanceof UrlPolicyError)
|
|
21829
|
+
if (error instanceof UrlPolicyError) {
|
|
21830
|
+
const boundHosts = [...allowedHosts].sort();
|
|
21831
|
+
const unbound = error.message.includes("host is not bound to this shop");
|
|
21832
|
+
fail(
|
|
21833
|
+
this,
|
|
21834
|
+
unbound ? `${error.message} (shop ${shopId} is bound to: ${boundHosts.join(", ")})` : error.message,
|
|
21835
|
+
EXIT_CODES.validation,
|
|
21836
|
+
unbound ? { code: "url_not_bound", field: "url", shop_id: shopId, bound_hosts: boundHosts } : { field: "url" }
|
|
21837
|
+
);
|
|
21838
|
+
}
|
|
19585
21839
|
if (error instanceof BodyDecodeError) {
|
|
19586
21840
|
pages.push({
|
|
19587
21841
|
url,
|
|
@@ -19618,10 +21872,12 @@ var installCommand = new Command("install").description("Check Apex storefront i
|
|
|
19618
21872
|
);
|
|
19619
21873
|
let tracking;
|
|
19620
21874
|
let trackingExitCode = null;
|
|
21875
|
+
let trackingApiFailure;
|
|
19621
21876
|
try {
|
|
19622
21877
|
const result = await verifyTracking(creds);
|
|
19623
21878
|
if (!result.ok || !result.data) {
|
|
19624
21879
|
trackingExitCode = classifyStatus(result.status);
|
|
21880
|
+
trackingApiFailure = apiFailureOf(result);
|
|
19625
21881
|
tracking = {
|
|
19626
21882
|
status: "FAIL",
|
|
19627
21883
|
message: result.error ?? "Failed to verify tracking",
|
|
@@ -19645,6 +21901,11 @@ var installCommand = new Command("install").description("Check Apex storefront i
|
|
|
19645
21901
|
if (servedConfig.status !== "PASS") warnings.push(servedConfig.message);
|
|
19646
21902
|
if (tracking.status === "WARN") warnings.push(tracking.message);
|
|
19647
21903
|
const failed = pages.some((page) => page.status === "FAIL") || servedConfig.status === "FAIL" || tracking.status === "FAIL";
|
|
21904
|
+
const failedChecks = [
|
|
21905
|
+
...pages.filter((page) => page.status === "FAIL").map((page) => ({ check: "page", url: page.url, message: page.message })),
|
|
21906
|
+
...servedConfig.status === "FAIL" ? [{ check: "served_config", url: servedConfig.url, message: servedConfig.message }] : [],
|
|
21907
|
+
...tracking.status === "FAIL" ? [{ check: "tracking", message: tracking.message }] : []
|
|
21908
|
+
];
|
|
19648
21909
|
const data = { ok: !failed, pages, warnings, servedConfig, tracking };
|
|
19649
21910
|
const renderReport = () => {
|
|
19650
21911
|
printTable(
|
|
@@ -19676,12 +21937,24 @@ var installCommand = new Command("install").description("Check Apex storefront i
|
|
|
19676
21937
|
};
|
|
19677
21938
|
if (failed) {
|
|
19678
21939
|
if (!shouldUseJson(this)) renderReport();
|
|
19679
|
-
|
|
19680
|
-
|
|
19681
|
-
|
|
19682
|
-
|
|
19683
|
-
|
|
19684
|
-
|
|
21940
|
+
if (trackingExitCode !== null)
|
|
21941
|
+
fail(
|
|
21942
|
+
this,
|
|
21943
|
+
`Apex install check could not verify tracking: ${tracking.message}`,
|
|
21944
|
+
trackingExitCode,
|
|
21945
|
+
{ ...data, shop_id: shopId, failed_checks: failedChecks },
|
|
21946
|
+
trackingApiFailure
|
|
21947
|
+
);
|
|
21948
|
+
const summaries = failedChecks.map((entry) => {
|
|
21949
|
+
const label = entry.check === "page" && "url" in entry ? `page ${entry.url}` : entry.check === "served_config" ? "served config" : "tracking";
|
|
21950
|
+
return `${label}: ${entry.message.replace(/\.$/, "")}`;
|
|
21951
|
+
});
|
|
21952
|
+
fail(this, `Apex install check failed: ${summaries.join("; ")}`, EXIT_CODES.checkFailed, {
|
|
21953
|
+
...data,
|
|
21954
|
+
code: "install_check_failed",
|
|
21955
|
+
shop_id: shopId,
|
|
21956
|
+
failed_checks: failedChecks
|
|
21957
|
+
});
|
|
19685
21958
|
}
|
|
19686
21959
|
output(this, data, renderReport);
|
|
19687
21960
|
}).parent;
|
|
@@ -20332,6 +22605,258 @@ var personalizationsCommand = new Command("personalizations").description("Manag
|
|
|
20332
22605
|
init_source();
|
|
20333
22606
|
var import_node_fs23 = __toESM(require("node:fs"));
|
|
20334
22607
|
var import_node_path20 = __toESM(require("node:path"));
|
|
22608
|
+
|
|
22609
|
+
// src/generated/exclusion-group-body.schema.json
|
|
22610
|
+
var exclusion_group_body_schema_default = {
|
|
22611
|
+
create: {
|
|
22612
|
+
type: "object",
|
|
22613
|
+
additionalProperties: false,
|
|
22614
|
+
properties: {
|
|
22615
|
+
name: {
|
|
22616
|
+
type: "string",
|
|
22617
|
+
minLength: 1,
|
|
22618
|
+
maxLength: 120,
|
|
22619
|
+
description: "Group name, 1 to 120 characters."
|
|
22620
|
+
},
|
|
22621
|
+
description: {
|
|
22622
|
+
type: ["string", "null"]
|
|
22623
|
+
},
|
|
22624
|
+
members: {
|
|
22625
|
+
type: "array",
|
|
22626
|
+
minItems: 2,
|
|
22627
|
+
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.",
|
|
22628
|
+
items: {
|
|
22629
|
+
oneOf: [
|
|
22630
|
+
{
|
|
22631
|
+
type: "object",
|
|
22632
|
+
additionalProperties: false,
|
|
22633
|
+
properties: {
|
|
22634
|
+
experiment_id: {
|
|
22635
|
+
type: "string",
|
|
22636
|
+
format: "uuid",
|
|
22637
|
+
description: "Internal experiment UUID. Public ids and test keys are not resolved here."
|
|
22638
|
+
},
|
|
22639
|
+
traffic_share: {
|
|
22640
|
+
type: "number",
|
|
22641
|
+
minimum: 0,
|
|
22642
|
+
maximum: 1
|
|
22643
|
+
}
|
|
22644
|
+
},
|
|
22645
|
+
required: ["experiment_id", "traffic_share"]
|
|
22646
|
+
},
|
|
22647
|
+
{
|
|
22648
|
+
type: "object",
|
|
22649
|
+
additionalProperties: false,
|
|
22650
|
+
properties: {
|
|
22651
|
+
experimentId: {
|
|
22652
|
+
type: "string",
|
|
22653
|
+
format: "uuid",
|
|
22654
|
+
description: "Internal experiment UUID. Public ids and test keys are not resolved here."
|
|
22655
|
+
},
|
|
22656
|
+
share: {
|
|
22657
|
+
type: "number",
|
|
22658
|
+
minimum: 0,
|
|
22659
|
+
maximum: 100
|
|
22660
|
+
}
|
|
22661
|
+
},
|
|
22662
|
+
required: ["experimentId", "share"]
|
|
22663
|
+
}
|
|
22664
|
+
]
|
|
22665
|
+
}
|
|
22666
|
+
}
|
|
22667
|
+
},
|
|
22668
|
+
required: ["name", "members"]
|
|
22669
|
+
},
|
|
22670
|
+
update: {
|
|
22671
|
+
type: "object",
|
|
22672
|
+
additionalProperties: false,
|
|
22673
|
+
properties: {
|
|
22674
|
+
name: {
|
|
22675
|
+
type: "string",
|
|
22676
|
+
minLength: 1,
|
|
22677
|
+
maxLength: 120,
|
|
22678
|
+
description: "Group name, 1 to 120 characters."
|
|
22679
|
+
},
|
|
22680
|
+
description: {
|
|
22681
|
+
type: ["string", "null"]
|
|
22682
|
+
},
|
|
22683
|
+
members: {
|
|
22684
|
+
type: "array",
|
|
22685
|
+
minItems: 2,
|
|
22686
|
+
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.",
|
|
22687
|
+
items: {
|
|
22688
|
+
oneOf: [
|
|
22689
|
+
{
|
|
22690
|
+
type: "object",
|
|
22691
|
+
additionalProperties: false,
|
|
22692
|
+
properties: {
|
|
22693
|
+
experiment_id: {
|
|
22694
|
+
type: "string",
|
|
22695
|
+
format: "uuid",
|
|
22696
|
+
description: "Internal experiment UUID. Public ids and test keys are not resolved here."
|
|
22697
|
+
},
|
|
22698
|
+
traffic_share: {
|
|
22699
|
+
type: "number",
|
|
22700
|
+
minimum: 0,
|
|
22701
|
+
maximum: 1
|
|
22702
|
+
}
|
|
22703
|
+
},
|
|
22704
|
+
required: ["experiment_id", "traffic_share"]
|
|
22705
|
+
},
|
|
22706
|
+
{
|
|
22707
|
+
type: "object",
|
|
22708
|
+
additionalProperties: false,
|
|
22709
|
+
properties: {
|
|
22710
|
+
experimentId: {
|
|
22711
|
+
type: "string",
|
|
22712
|
+
format: "uuid",
|
|
22713
|
+
description: "Internal experiment UUID. Public ids and test keys are not resolved here."
|
|
22714
|
+
},
|
|
22715
|
+
share: {
|
|
22716
|
+
type: "number",
|
|
22717
|
+
minimum: 0,
|
|
22718
|
+
maximum: 100
|
|
22719
|
+
}
|
|
22720
|
+
},
|
|
22721
|
+
required: ["experimentId", "share"]
|
|
22722
|
+
}
|
|
22723
|
+
]
|
|
22724
|
+
}
|
|
22725
|
+
}
|
|
22726
|
+
}
|
|
22727
|
+
},
|
|
22728
|
+
"replace-members": {
|
|
22729
|
+
type: "object",
|
|
22730
|
+
additionalProperties: false,
|
|
22731
|
+
properties: {
|
|
22732
|
+
members: {
|
|
22733
|
+
type: "array",
|
|
22734
|
+
minItems: 2,
|
|
22735
|
+
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.",
|
|
22736
|
+
items: {
|
|
22737
|
+
oneOf: [
|
|
22738
|
+
{
|
|
22739
|
+
type: "object",
|
|
22740
|
+
additionalProperties: false,
|
|
22741
|
+
properties: {
|
|
22742
|
+
experiment_id: {
|
|
22743
|
+
type: "string",
|
|
22744
|
+
format: "uuid",
|
|
22745
|
+
description: "Internal experiment UUID. Public ids and test keys are not resolved here."
|
|
22746
|
+
},
|
|
22747
|
+
traffic_share: {
|
|
22748
|
+
type: "number",
|
|
22749
|
+
minimum: 0,
|
|
22750
|
+
maximum: 1
|
|
22751
|
+
}
|
|
22752
|
+
},
|
|
22753
|
+
required: ["experiment_id", "traffic_share"]
|
|
22754
|
+
},
|
|
22755
|
+
{
|
|
22756
|
+
type: "object",
|
|
22757
|
+
additionalProperties: false,
|
|
22758
|
+
properties: {
|
|
22759
|
+
experimentId: {
|
|
22760
|
+
type: "string",
|
|
22761
|
+
format: "uuid",
|
|
22762
|
+
description: "Internal experiment UUID. Public ids and test keys are not resolved here."
|
|
22763
|
+
},
|
|
22764
|
+
share: {
|
|
22765
|
+
type: "number",
|
|
22766
|
+
minimum: 0,
|
|
22767
|
+
maximum: 100
|
|
22768
|
+
}
|
|
22769
|
+
},
|
|
22770
|
+
required: ["experimentId", "share"]
|
|
22771
|
+
}
|
|
22772
|
+
]
|
|
22773
|
+
}
|
|
22774
|
+
}
|
|
22775
|
+
},
|
|
22776
|
+
required: ["members"]
|
|
22777
|
+
}
|
|
22778
|
+
};
|
|
22779
|
+
|
|
22780
|
+
// src/commands/schema.ts
|
|
22781
|
+
var commandBodySchemas = /* @__PURE__ */ new WeakMap();
|
|
22782
|
+
function setCommandBodySchema(command, schema) {
|
|
22783
|
+
commandBodySchemas.set(command, schema);
|
|
22784
|
+
return command;
|
|
22785
|
+
}
|
|
22786
|
+
function optionSchema(option) {
|
|
22787
|
+
return {
|
|
22788
|
+
flags: option.flags,
|
|
22789
|
+
...option.argChoices ? { choices: option.argChoices } : {},
|
|
22790
|
+
description: option.description,
|
|
22791
|
+
required: option.required,
|
|
22792
|
+
optional: option.optional,
|
|
22793
|
+
mandatory: Boolean(option.mandatory),
|
|
22794
|
+
variadic: option.variadic,
|
|
22795
|
+
...option.defaultValue === void 0 ? {} : { defaultValue: option.defaultValue }
|
|
22796
|
+
};
|
|
22797
|
+
}
|
|
22798
|
+
function commandSchema(command) {
|
|
22799
|
+
return {
|
|
22800
|
+
name: command.name(),
|
|
22801
|
+
aliases: command.aliases(),
|
|
22802
|
+
description: command.description(),
|
|
22803
|
+
usage: command.usage(),
|
|
22804
|
+
arguments: command.registeredArguments.map((arg) => ({
|
|
22805
|
+
name: arg.name(),
|
|
22806
|
+
required: arg.required,
|
|
22807
|
+
variadic: arg.variadic,
|
|
22808
|
+
description: arg.description,
|
|
22809
|
+
...arg.defaultValue === void 0 ? {} : { defaultValue: arg.defaultValue }
|
|
22810
|
+
})),
|
|
22811
|
+
options: command.options.map(optionSchema),
|
|
22812
|
+
...commandBodySchemas.has(command) ? { body: commandBodySchemas.get(command) } : {},
|
|
22813
|
+
commands: command.commands.map(commandSchema)
|
|
22814
|
+
};
|
|
22815
|
+
}
|
|
22816
|
+
function createSchemaCommand(program3) {
|
|
22817
|
+
return new Command("schema").description("Print the current Apex CLI command, flag, and mandatory-option schema").option("--json", "Print JSON schema").action(function action78() {
|
|
22818
|
+
const schema = {
|
|
22819
|
+
schemaVersion: 2,
|
|
22820
|
+
contract: {
|
|
22821
|
+
optionValueArity: "Options' required and optional fields describe whether the option's value is required or optional when the flag is given.",
|
|
22822
|
+
mandatoryOptions: "An option's mandatory field is true only when the flag itself must be passed.",
|
|
22823
|
+
requiredArguments: "An argument's required field means the positional is mandatory.",
|
|
22824
|
+
body: "A command's body field is the JSON Schema of the JSON document it sends."
|
|
22825
|
+
},
|
|
22826
|
+
...commandSchema(program3),
|
|
22827
|
+
files: experiment_filesystem_schema_default
|
|
22828
|
+
};
|
|
22829
|
+
output(this, schema, () => {
|
|
22830
|
+
process.stdout.write(`${JSON.stringify(schema, null, 2)}
|
|
22831
|
+
`);
|
|
22832
|
+
});
|
|
22833
|
+
});
|
|
22834
|
+
}
|
|
22835
|
+
|
|
22836
|
+
// src/commands/targeting-resources.ts
|
|
22837
|
+
var exclusionHelp = `Body shape: {"name":"...","members":[...]} (update fields are optional)
|
|
22838
|
+
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.
|
|
22839
|
+
Find experiment ids with: apex experiments list --fields id,name
|
|
22840
|
+
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}]}'
|
|
22841
|
+
Full JSON Schema: apex schema --json (this command's body key)`;
|
|
22842
|
+
function withBodyHelp(command, action78) {
|
|
22843
|
+
setCommandBodySchema(command, exclusion_group_body_schema_default[action78]);
|
|
22844
|
+
command.addHelpText("after", `
|
|
22845
|
+
${exclusionHelp}
|
|
22846
|
+
`);
|
|
22847
|
+
}
|
|
22848
|
+
function exclusionFailureDetails(kind5, action78, status4, details) {
|
|
22849
|
+
if (kind5 !== "exclusion-groups" || status4 !== 400 && status4 !== 422) return details;
|
|
22850
|
+
return {
|
|
22851
|
+
...details,
|
|
22852
|
+
next_steps: [
|
|
22853
|
+
{
|
|
22854
|
+
command: `apex exclusion-groups ${action78} --help`,
|
|
22855
|
+
why: "Shows the body shape, member rules and an example; error.details.issues names each failing field."
|
|
22856
|
+
}
|
|
22857
|
+
]
|
|
22858
|
+
};
|
|
22859
|
+
}
|
|
20335
22860
|
function body3(command, value) {
|
|
20336
22861
|
if (!value) return {};
|
|
20337
22862
|
const source = value.startsWith("@") ? value.slice(1) : value;
|
|
@@ -20409,7 +22934,10 @@ function targetingResourceCommand(kind5) {
|
|
|
20409
22934
|
`${action78 === "create" ? "Create" : "Update"} a ${singular3}`
|
|
20410
22935
|
);
|
|
20411
22936
|
if (action78 === "update") command.argument("<id>");
|
|
20412
|
-
command.argument(
|
|
22937
|
+
command.argument(
|
|
22938
|
+
"[json]",
|
|
22939
|
+
kind5 === "exclusion-groups" ? "Request body as inline JSON, @file, or a file path (shape below)" : "Inline JSON, @file, or file path"
|
|
22940
|
+
).option("--file <path>", "JSON body file").action(async function(...args) {
|
|
20413
22941
|
const opts = this.opts();
|
|
20414
22942
|
const id = action78 === "update" ? String(args[0]) : void 0;
|
|
20415
22943
|
const value = opts.file ? `@${opts.file}` : String(args[action78 === "update" ? 1 : 0] ?? "") || void 0;
|
|
@@ -20428,10 +22956,11 @@ function targetingResourceCommand(kind5) {
|
|
|
20428
22956
|
this,
|
|
20429
22957
|
response.error ?? `Failed to ${action78}`,
|
|
20430
22958
|
classifyStatus(response.status),
|
|
20431
|
-
response.errorDetails
|
|
22959
|
+
exclusionFailureDetails(kind5, action78, response.status, response.errorDetails)
|
|
20432
22960
|
);
|
|
20433
22961
|
output(this, response.data, () => console.log(source_default.green(`${action78}d ${singular3}`)));
|
|
20434
22962
|
});
|
|
22963
|
+
if (kind5 === "exclusion-groups") withBodyHelp(command, action78);
|
|
20435
22964
|
root.addCommand(command);
|
|
20436
22965
|
}
|
|
20437
22966
|
const deleteCommand = new Command("delete").description(`Delete a ${singular3}`).argument("<id>");
|
|
@@ -20470,9 +22999,9 @@ function targetingResourceCommand(kind5) {
|
|
|
20470
22999
|
output(this, response.data ?? null, () => console.log(source_default.green(`Deleted ${singular3}`)));
|
|
20471
23000
|
})
|
|
20472
23001
|
);
|
|
20473
|
-
if (kind5 === "exclusion-groups")
|
|
23002
|
+
if (kind5 === "exclusion-groups") {
|
|
20474
23003
|
root.addCommand(
|
|
20475
|
-
new Command("replace-members").description("Replace exclusion group members").argument("<id>").argument("<json>", "
|
|
23004
|
+
new Command("replace-members").description("Replace exclusion group members").argument("<id>").argument("<json>", "Request body as inline JSON, @file, or a file path (shape below)").action(async function(id, value) {
|
|
20476
23005
|
const response = await requestTargetingResource(
|
|
20477
23006
|
requireCredentials(),
|
|
20478
23007
|
"PUT",
|
|
@@ -20485,11 +23014,18 @@ function targetingResourceCommand(kind5) {
|
|
|
20485
23014
|
this,
|
|
20486
23015
|
response.error ?? "Failed to replace members",
|
|
20487
23016
|
classifyStatus(response.status),
|
|
20488
|
-
|
|
23017
|
+
exclusionFailureDetails(
|
|
23018
|
+
kind5,
|
|
23019
|
+
"replace-members",
|
|
23020
|
+
response.status,
|
|
23021
|
+
response.errorDetails
|
|
23022
|
+
)
|
|
20489
23023
|
);
|
|
20490
23024
|
output(this, response.data, () => console.log(source_default.green("Replaced members")));
|
|
20491
23025
|
})
|
|
20492
23026
|
);
|
|
23027
|
+
withBodyHelp(root.commands.at(-1), "replace-members");
|
|
23028
|
+
}
|
|
20493
23029
|
return root;
|
|
20494
23030
|
}
|
|
20495
23031
|
var pagesCommand = targetingResourceCommand("pages");
|
|
@@ -20578,7 +23114,7 @@ var test2 = new Command("test").description("Evaluate experiment URL targeting f
|
|
|
20578
23114
|
var targetingCommand = new Command("targeting").description("Test experiment targeting").addCommand(test2);
|
|
20579
23115
|
|
|
20580
23116
|
// src/commands/sos.ts
|
|
20581
|
-
var
|
|
23117
|
+
var import_node_crypto8 = require("node:crypto");
|
|
20582
23118
|
init_source();
|
|
20583
23119
|
var import_node_fs24 = __toESM(require("node:fs"));
|
|
20584
23120
|
var import_node_path21 = __toESM(require("node:path"));
|
|
@@ -20689,7 +23225,7 @@ var create4 = new Command("create").description("Create an Apex SOS ticket").opt
|
|
|
20689
23225
|
const bytes = file.bytes;
|
|
20690
23226
|
await uploadSosAttachment(grant.signed_url, file.content_type, bytes);
|
|
20691
23227
|
attachmentPaths.push(grant.path);
|
|
20692
|
-
attachmentDigests.push((0,
|
|
23228
|
+
attachmentDigests.push((0, import_node_crypto8.createHash)("sha256").update(bytes).digest("hex"));
|
|
20693
23229
|
}
|
|
20694
23230
|
}
|
|
20695
23231
|
const description = readText(opts.description) ?? descriptionArg ?? `Apex CLI ${opts.type} SOS request${opts.loom ? ` with Loom evidence: ${opts.loom}` : "."}`;
|
|
@@ -21002,55 +23538,6 @@ var shopsCommand = new Command("shops").description("Manage organization shops")
|
|
|
21002
23538
|
settings("auto-stop", "Read or update auto-stop defaults", { on: "enable", off: "disable" })
|
|
21003
23539
|
).addCommand(settings("testing-defaults", "Read or update testing defaults")).addCommand(settings("guardian", "Read or update guardian defaults", { on: "on", off: "off" })).addCommand(profile).addCommand(shopware);
|
|
21004
23540
|
|
|
21005
|
-
// src/commands/schema.ts
|
|
21006
|
-
function optionSchema(option) {
|
|
21007
|
-
return {
|
|
21008
|
-
flags: option.flags,
|
|
21009
|
-
...option.argChoices ? { choices: option.argChoices } : {},
|
|
21010
|
-
description: option.description,
|
|
21011
|
-
required: option.required,
|
|
21012
|
-
optional: option.optional,
|
|
21013
|
-
mandatory: Boolean(option.mandatory),
|
|
21014
|
-
variadic: option.variadic,
|
|
21015
|
-
...option.defaultValue === void 0 ? {} : { defaultValue: option.defaultValue }
|
|
21016
|
-
};
|
|
21017
|
-
}
|
|
21018
|
-
function commandSchema(command) {
|
|
21019
|
-
return {
|
|
21020
|
-
name: command.name(),
|
|
21021
|
-
aliases: command.aliases(),
|
|
21022
|
-
description: command.description(),
|
|
21023
|
-
usage: command.usage(),
|
|
21024
|
-
arguments: command.registeredArguments.map((arg) => ({
|
|
21025
|
-
name: arg.name(),
|
|
21026
|
-
required: arg.required,
|
|
21027
|
-
variadic: arg.variadic,
|
|
21028
|
-
description: arg.description,
|
|
21029
|
-
...arg.defaultValue === void 0 ? {} : { defaultValue: arg.defaultValue }
|
|
21030
|
-
})),
|
|
21031
|
-
options: command.options.map(optionSchema),
|
|
21032
|
-
commands: command.commands.map(commandSchema)
|
|
21033
|
-
};
|
|
21034
|
-
}
|
|
21035
|
-
function createSchemaCommand(program3) {
|
|
21036
|
-
return new Command("schema").description("Print the current Apex CLI command, flag, and mandatory-option schema").option("--json", "Print JSON schema").action(function action78() {
|
|
21037
|
-
const schema = {
|
|
21038
|
-
schemaVersion: 2,
|
|
21039
|
-
contract: {
|
|
21040
|
-
optionValueArity: "Options' required and optional fields describe whether the option's value is required or optional when the flag is given.",
|
|
21041
|
-
mandatoryOptions: "An option's mandatory field is true only when the flag itself must be passed.",
|
|
21042
|
-
requiredArguments: "An argument's required field means the positional is mandatory."
|
|
21043
|
-
},
|
|
21044
|
-
...commandSchema(program3),
|
|
21045
|
-
files: experiment_filesystem_schema_default
|
|
21046
|
-
};
|
|
21047
|
-
output(this, schema, () => {
|
|
21048
|
-
process.stdout.write(`${JSON.stringify(schema, null, 2)}
|
|
21049
|
-
`);
|
|
21050
|
-
});
|
|
21051
|
-
});
|
|
21052
|
-
}
|
|
21053
|
-
|
|
21054
23541
|
// src/lib/gtm-miner.ts
|
|
21055
23542
|
function extractContainerReferences(html) {
|
|
21056
23543
|
const results2 = /* @__PURE__ */ new Map();
|
|
@@ -22085,7 +24572,7 @@ async function confirm(c, force, label, retry) {
|
|
|
22085
24572
|
}
|
|
22086
24573
|
return true;
|
|
22087
24574
|
}
|
|
22088
|
-
var orgCommand = new Command("org").description("Manage the current organization");
|
|
24575
|
+
var orgCommand = new Command("org").alias("orgs").description("Manage the current organization");
|
|
22089
24576
|
orgCommand.command("get").action(async function() {
|
|
22090
24577
|
await run2(this, getOrg(requireUserCredentials()), "get organization");
|
|
22091
24578
|
});
|
|
@@ -22188,23 +24675,38 @@ orgCommand.command("remove-member").argument("<memberId>").option("-f, --force")
|
|
|
22188
24675
|
return;
|
|
22189
24676
|
await run2(this, removeOrgMember(requireUserCredentials(), id), "remove member");
|
|
22190
24677
|
});
|
|
22191
|
-
var teamCommand = new Command("team").description("Manage a shop team");
|
|
22192
|
-
|
|
24678
|
+
var teamCommand = new Command("team").alias("teams").description("Manage a shop team");
|
|
24679
|
+
var teamShopDescription = "Shop ID; defaults to the selected shop (--shop before the subcommand, DRIP_SHOP, or apex use)";
|
|
24680
|
+
function teamShop(command, explicit) {
|
|
24681
|
+
if (explicit) return explicit;
|
|
24682
|
+
const selected2 = requireUserCredentials().resolvedContext?.shopId;
|
|
24683
|
+
if (selected2) return selected2;
|
|
24684
|
+
fail(
|
|
24685
|
+
command,
|
|
24686
|
+
"apex team needs a shop. Pass --shop <id>, or select one with apex use <org>/<shop>.",
|
|
24687
|
+
3,
|
|
24688
|
+
{ code: "shop_context_required", retry: "apex use <org>/<shop>", flag: "--shop" }
|
|
24689
|
+
);
|
|
24690
|
+
}
|
|
24691
|
+
teamCommand.command("members").option("--shop <id>", teamShopDescription).action(async function(o) {
|
|
24692
|
+
o.shop = teamShop(this, o.shop);
|
|
22193
24693
|
await run2(this, listTeamMembers(requireUserCredentials(), o.shop), "list team");
|
|
22194
24694
|
});
|
|
22195
|
-
teamCommand.command("invite").argument("<email>").
|
|
24695
|
+
teamCommand.command("invite").argument("<email>").option("--shop <id>", teamShopDescription).addOption(
|
|
22196
24696
|
new Option(
|
|
22197
24697
|
"--role <role>",
|
|
22198
24698
|
"Role: editor (create and run tests, manage settings, invite people) or client_viewer (view tests, results and pipeline)"
|
|
22199
24699
|
).choices(["editor", "client_viewer"]).makeOptionMandatory()
|
|
22200
24700
|
).action(async function(email, o) {
|
|
24701
|
+
o.shop = teamShop(this, o.shop);
|
|
22201
24702
|
await run2(
|
|
22202
24703
|
this,
|
|
22203
24704
|
inviteTeam(requireUserCredentials(), email, o.shop, o.role),
|
|
22204
24705
|
"invite team member"
|
|
22205
24706
|
);
|
|
22206
24707
|
});
|
|
22207
|
-
teamCommand.command("revoke-invite").argument("<id>").
|
|
24708
|
+
teamCommand.command("revoke-invite").argument("<id>").option("--shop <id>", teamShopDescription).action(async function(id, o) {
|
|
24709
|
+
o.shop = teamShop(this, o.shop);
|
|
22208
24710
|
await run2(
|
|
22209
24711
|
this,
|
|
22210
24712
|
revokeTeamInvite(requireUserCredentials(), id, o.shop),
|
|
@@ -22216,10 +24718,12 @@ teamCommand.command("set-role").argument("<memberId>").addArgument(
|
|
|
22216
24718
|
"<role>",
|
|
22217
24719
|
"Role: editor (create and run tests, manage settings, invite people) or client_viewer (view tests, results and pipeline)"
|
|
22218
24720
|
).choices(["editor", "client_viewer"])
|
|
22219
|
-
).
|
|
24721
|
+
).option("--shop <id>", teamShopDescription).action(async function(id, role, o) {
|
|
24722
|
+
o.shop = teamShop(this, o.shop);
|
|
22220
24723
|
await run2(this, setTeamMember(requireUserCredentials(), id, o.shop, role), "set role");
|
|
22221
24724
|
});
|
|
22222
|
-
teamCommand.command("remove").argument("<memberId>").
|
|
24725
|
+
teamCommand.command("remove").argument("<memberId>").option("--shop <id>", teamShopDescription).option("-f, --force").action(async function(id, o) {
|
|
24726
|
+
o.shop = teamShop(this, o.shop);
|
|
22223
24727
|
if (!await confirm(
|
|
22224
24728
|
this,
|
|
22225
24729
|
o.force,
|
|
@@ -22230,6 +24734,38 @@ teamCommand.command("remove").argument("<memberId>").requiredOption("--shop <id>
|
|
|
22230
24734
|
await run2(this, removeTeamMember(requireUserCredentials(), id, o.shop), "remove member");
|
|
22231
24735
|
});
|
|
22232
24736
|
|
|
24737
|
+
// src/commands/upgrade.ts
|
|
24738
|
+
var import_node_child_process7 = require("node:child_process");
|
|
24739
|
+
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) {
|
|
24740
|
+
if (options.dryRun) {
|
|
24741
|
+
output(
|
|
24742
|
+
this,
|
|
24743
|
+
{ command: UPGRADE_COMMAND, ran: false, exit_code: null, previous_version: CLI_VERSION },
|
|
24744
|
+
() => console.log(UPGRADE_COMMAND)
|
|
24745
|
+
);
|
|
24746
|
+
return;
|
|
24747
|
+
}
|
|
24748
|
+
const npmBinary = process.env.APEX_NPM_BIN || (process.platform === "win32" ? "npm.cmd" : "npm");
|
|
24749
|
+
const json = shouldUseJson(this);
|
|
24750
|
+
const windows = process.platform === "win32";
|
|
24751
|
+
const command = windows ? `"${npmBinary}"` : npmBinary;
|
|
24752
|
+
const result = (0, import_node_child_process7.spawnSync)(command, ["install", "-g", "@drip-apex/apex-cli@latest"], {
|
|
24753
|
+
stdio: json ? ["inherit", 2, 2] : "inherit",
|
|
24754
|
+
shell: windows
|
|
24755
|
+
});
|
|
24756
|
+
if (result.error || result.status !== 0)
|
|
24757
|
+
fail(this, result.error?.message || "Apex CLI upgrade failed", EXIT_CODES.general, {
|
|
24758
|
+
code: "upgrade_failed",
|
|
24759
|
+
next_steps: [{ command: UPGRADE_COMMAND, why: "Run the install command manually." }]
|
|
24760
|
+
});
|
|
24761
|
+
clearUpdateCache();
|
|
24762
|
+
output(
|
|
24763
|
+
this,
|
|
24764
|
+
{ command: UPGRADE_COMMAND, ran: true, exit_code: 0, previous_version: CLI_VERSION },
|
|
24765
|
+
() => console.log("Apex CLI upgraded.")
|
|
24766
|
+
);
|
|
24767
|
+
});
|
|
24768
|
+
|
|
22233
24769
|
// src/commands/account.ts
|
|
22234
24770
|
function userCredentials2() {
|
|
22235
24771
|
return requireUserCredentials();
|
|
@@ -22835,7 +25371,7 @@ var import_node_fs29 = __toESM(require("node:fs"));
|
|
|
22835
25371
|
var import_node_path26 = __toESM(require("node:path"));
|
|
22836
25372
|
|
|
22837
25373
|
// src/commands/landing-pages.ts
|
|
22838
|
-
var
|
|
25374
|
+
var import_node_crypto9 = __toESM(require("node:crypto"));
|
|
22839
25375
|
var import_node_fs28 = __toESM(require("node:fs"));
|
|
22840
25376
|
var import_node_path25 = __toESM(require("node:path"));
|
|
22841
25377
|
function apiFailure(command, response, fallback2) {
|
|
@@ -22849,7 +25385,7 @@ function apiFailure(command, response, fallback2) {
|
|
|
22849
25385
|
}
|
|
22850
25386
|
}
|
|
22851
25387
|
function sha256(body5) {
|
|
22852
|
-
return
|
|
25388
|
+
return import_node_crypto9.default.createHash("sha256").update(body5).digest("hex");
|
|
22853
25389
|
}
|
|
22854
25390
|
function markerPath(folder) {
|
|
22855
25391
|
return import_node_path25.default.join(folder, LANDING_PAGE_CHECKOUT_MARKER);
|
|
@@ -23598,7 +26134,10 @@ function addJsonOption(command) {
|
|
|
23598
26134
|
if (!command.options.some((option) => option.long === "--shop"))
|
|
23599
26135
|
command.option("--shop <id>", "Use a shop for this invocation");
|
|
23600
26136
|
}
|
|
23601
|
-
command.exitOverride((err) =>
|
|
26137
|
+
command.exitOverride((err) => {
|
|
26138
|
+
recordCommanderExit(err, command);
|
|
26139
|
+
interceptCliError(err, command);
|
|
26140
|
+
});
|
|
23602
26141
|
for (const child of command.commands) addJsonOption(child);
|
|
23603
26142
|
}
|
|
23604
26143
|
var program2 = new Command().name("apex").description(source_default.bold("Apex CLI") + " \u2014 A/B testing experiments from the command line").version(CLI_VERSION).showSuggestionAfterError(true).showHelpAfterError(true);
|
|
@@ -23654,7 +26193,9 @@ program2.addCommand(skillsCommand);
|
|
|
23654
26193
|
program2.addCommand(adminCommand, { hidden: true });
|
|
23655
26194
|
program2.addCommand(orgCommand);
|
|
23656
26195
|
program2.addCommand(teamCommand);
|
|
26196
|
+
program2.addCommand(upgradeCommand);
|
|
23657
26197
|
addJsonOption(program2);
|
|
26198
|
+
registerTelemetryProgram(program2);
|
|
23658
26199
|
var optionTerminatorIndex = process.argv.indexOf("--");
|
|
23659
26200
|
var optionArgv = optionTerminatorIndex === -1 ? process.argv : process.argv.slice(0, optionTerminatorIndex);
|
|
23660
26201
|
if (optionArgv.includes("--json")) program2.setOptionValue("json", true);
|