@drip-apex/apex-cli 0.14.1 → 0.15.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/README.md +40 -3
- package/dist/cli.js +1390 -434
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1159,8 +1159,8 @@ var require_command = __commonJS({
|
|
|
1159
1159
|
"node_modules/commander/lib/command.js"(exports2) {
|
|
1160
1160
|
var EventEmitter = require("node:events").EventEmitter;
|
|
1161
1161
|
var childProcess2 = require("node:child_process");
|
|
1162
|
-
var
|
|
1163
|
-
var
|
|
1162
|
+
var path26 = require("node:path");
|
|
1163
|
+
var fs30 = require("node:fs");
|
|
1164
1164
|
var process16 = require("node:process");
|
|
1165
1165
|
var { Argument: Argument2, humanReadableArgName } = require_argument();
|
|
1166
1166
|
var { CommanderError: CommanderError2 } = require_error();
|
|
@@ -2141,7 +2141,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2141
2141
|
* @param {string} subcommandName
|
|
2142
2142
|
*/
|
|
2143
2143
|
_checkForMissingExecutable(executableFile, executableDir, subcommandName) {
|
|
2144
|
-
if (
|
|
2144
|
+
if (fs30.existsSync(executableFile)) return;
|
|
2145
2145
|
const executableDirMessage = executableDir ? `searched for local subcommand relative to directory '${executableDir}'` : "no directory for search for local subcommand, use .executableDir() to supply a custom directory";
|
|
2146
2146
|
const executableMissing = `'${executableFile}' does not exist
|
|
2147
2147
|
- if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
|
|
@@ -2159,11 +2159,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2159
2159
|
let launchWithNode = false;
|
|
2160
2160
|
const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
|
|
2161
2161
|
function findFile(baseDir, baseName) {
|
|
2162
|
-
const localBin =
|
|
2163
|
-
if (
|
|
2164
|
-
if (sourceExt.includes(
|
|
2162
|
+
const localBin = path26.resolve(baseDir, baseName);
|
|
2163
|
+
if (fs30.existsSync(localBin)) return localBin;
|
|
2164
|
+
if (sourceExt.includes(path26.extname(baseName))) return void 0;
|
|
2165
2165
|
const foundExt = sourceExt.find(
|
|
2166
|
-
(ext) =>
|
|
2166
|
+
(ext) => fs30.existsSync(`${localBin}${ext}`)
|
|
2167
2167
|
);
|
|
2168
2168
|
if (foundExt) return `${localBin}${foundExt}`;
|
|
2169
2169
|
return void 0;
|
|
@@ -2175,21 +2175,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2175
2175
|
if (this._scriptPath) {
|
|
2176
2176
|
let resolvedScriptPath;
|
|
2177
2177
|
try {
|
|
2178
|
-
resolvedScriptPath =
|
|
2178
|
+
resolvedScriptPath = fs30.realpathSync(this._scriptPath);
|
|
2179
2179
|
} catch {
|
|
2180
2180
|
resolvedScriptPath = this._scriptPath;
|
|
2181
2181
|
}
|
|
2182
|
-
executableDir =
|
|
2183
|
-
|
|
2182
|
+
executableDir = path26.resolve(
|
|
2183
|
+
path26.dirname(resolvedScriptPath),
|
|
2184
2184
|
executableDir
|
|
2185
2185
|
);
|
|
2186
2186
|
}
|
|
2187
2187
|
if (executableDir) {
|
|
2188
2188
|
let localFile = findFile(executableDir, executableFile);
|
|
2189
2189
|
if (!localFile && !subcommand._executableFile && this._scriptPath) {
|
|
2190
|
-
const legacyName =
|
|
2190
|
+
const legacyName = path26.basename(
|
|
2191
2191
|
this._scriptPath,
|
|
2192
|
-
|
|
2192
|
+
path26.extname(this._scriptPath)
|
|
2193
2193
|
);
|
|
2194
2194
|
if (legacyName !== this._name) {
|
|
2195
2195
|
localFile = findFile(
|
|
@@ -2200,7 +2200,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2200
2200
|
}
|
|
2201
2201
|
executableFile = localFile || executableFile;
|
|
2202
2202
|
}
|
|
2203
|
-
launchWithNode = sourceExt.includes(
|
|
2203
|
+
launchWithNode = sourceExt.includes(path26.extname(executableFile));
|
|
2204
2204
|
let proc;
|
|
2205
2205
|
if (process16.platform !== "win32") {
|
|
2206
2206
|
if (launchWithNode) {
|
|
@@ -3047,7 +3047,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3047
3047
|
* @return {Command}
|
|
3048
3048
|
*/
|
|
3049
3049
|
nameFromFilename(filename) {
|
|
3050
|
-
this._name =
|
|
3050
|
+
this._name = path26.basename(filename, path26.extname(filename));
|
|
3051
3051
|
return this;
|
|
3052
3052
|
}
|
|
3053
3053
|
/**
|
|
@@ -3061,9 +3061,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3061
3061
|
* @param {string} [path]
|
|
3062
3062
|
* @return {(string|null|Command)}
|
|
3063
3063
|
*/
|
|
3064
|
-
executableDir(
|
|
3065
|
-
if (
|
|
3066
|
-
this._executableDir =
|
|
3064
|
+
executableDir(path27) {
|
|
3065
|
+
if (path27 === void 0) return this._executableDir;
|
|
3066
|
+
this._executableDir = path27;
|
|
3067
3067
|
return this;
|
|
3068
3068
|
}
|
|
3069
3069
|
/**
|
|
@@ -3797,18 +3797,18 @@ var init_source = __esm({
|
|
|
3797
3797
|
}
|
|
3798
3798
|
}
|
|
3799
3799
|
});
|
|
3800
|
-
createStyler = (
|
|
3800
|
+
createStyler = (open3, close, parent) => {
|
|
3801
3801
|
let openAll;
|
|
3802
3802
|
let closeAll;
|
|
3803
3803
|
if (parent === void 0) {
|
|
3804
|
-
openAll =
|
|
3804
|
+
openAll = open3;
|
|
3805
3805
|
closeAll = close;
|
|
3806
3806
|
} else {
|
|
3807
|
-
openAll = parent.openAll +
|
|
3807
|
+
openAll = parent.openAll + open3;
|
|
3808
3808
|
closeAll = close + parent.closeAll;
|
|
3809
3809
|
}
|
|
3810
3810
|
return {
|
|
3811
|
-
open:
|
|
3811
|
+
open: open3,
|
|
3812
3812
|
close,
|
|
3813
3813
|
openAll,
|
|
3814
3814
|
closeAll,
|
|
@@ -6158,16 +6158,16 @@ __export(ora_exports, {
|
|
|
6158
6158
|
function ora(options) {
|
|
6159
6159
|
return new Ora(options);
|
|
6160
6160
|
}
|
|
6161
|
-
async function oraPromise(
|
|
6162
|
-
const actionIsFunction = typeof
|
|
6163
|
-
const actionIsPromise = typeof
|
|
6161
|
+
async function oraPromise(action78, options) {
|
|
6162
|
+
const actionIsFunction = typeof action78 === "function";
|
|
6163
|
+
const actionIsPromise = typeof action78.then === "function";
|
|
6164
6164
|
if (!actionIsFunction && !actionIsPromise) {
|
|
6165
6165
|
throw new TypeError("Parameter `action` must be a Function or a Promise");
|
|
6166
6166
|
}
|
|
6167
6167
|
const { successText, failText } = typeof options === "object" ? options : { successText: void 0, failText: void 0 };
|
|
6168
6168
|
const spinner = ora(options).start();
|
|
6169
6169
|
try {
|
|
6170
|
-
const promise = actionIsFunction ?
|
|
6170
|
+
const promise = actionIsFunction ? action78(spinner) : action78;
|
|
6171
6171
|
const result = await promise;
|
|
6172
6172
|
spinner.succeed(
|
|
6173
6173
|
successText === void 0 ? void 0 : typeof successText === "string" ? successText : successText(result)
|
|
@@ -7107,15 +7107,15 @@ function clientIdentityHeaders() {
|
|
|
7107
7107
|
// ../api-client/src/http.ts
|
|
7108
7108
|
var APEX_API_VERSION_HEADER = "x-apex-api-version";
|
|
7109
7109
|
var SUPPORTED_APEX_API_VERSION = "1";
|
|
7110
|
-
function extractErrorMessage(json,
|
|
7111
|
-
if (!json) return `HTTP ${
|
|
7110
|
+
function extractErrorMessage(json, status4) {
|
|
7111
|
+
if (!json) return `HTTP ${status4}`;
|
|
7112
7112
|
const error = json.error;
|
|
7113
7113
|
if (typeof error === "string" && error.trim()) return error;
|
|
7114
7114
|
if (error && typeof error === "object") {
|
|
7115
7115
|
const message = error.message;
|
|
7116
7116
|
if (typeof message === "string" && message.trim()) return message;
|
|
7117
7117
|
}
|
|
7118
|
-
return typeof json.message === "string" && json.message.trim() ? json.message : `HTTP ${
|
|
7118
|
+
return typeof json.message === "string" && json.message.trim() ? json.message : `HTTP ${status4}`;
|
|
7119
7119
|
}
|
|
7120
7120
|
function extractErrorDetails(json) {
|
|
7121
7121
|
if (!json) return void 0;
|
|
@@ -7137,11 +7137,11 @@ function createHttpClient(creds, options) {
|
|
|
7137
7137
|
warnedAboutVersionSkew = true;
|
|
7138
7138
|
warn3(version > 1 ? "CLI is behind the server \u2014 update.\n" : "Server API is older than this CLI supports.\n");
|
|
7139
7139
|
}
|
|
7140
|
-
async function request2(method,
|
|
7140
|
+
async function request2(method, path26, body5, requestTimeoutMs, form = false) {
|
|
7141
7141
|
const started2 = performance.now();
|
|
7142
|
-
const observe = (
|
|
7142
|
+
const observe = (status4, requestId2, errorCode) => {
|
|
7143
7143
|
try {
|
|
7144
|
-
options.onResponse?.({ method, path:
|
|
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)) });
|
|
7145
7145
|
} catch {
|
|
7146
7146
|
}
|
|
7147
7147
|
};
|
|
@@ -7155,7 +7155,7 @@ function createHttpClient(creds, options) {
|
|
|
7155
7155
|
if (creds.resolvedContext?.orgId) headers["X-Apex-Org"] = creds.resolvedContext.orgId;
|
|
7156
7156
|
if (creds.resolvedContext?.shopId) headers["X-Apex-Shop"] = creds.resolvedContext.shopId;
|
|
7157
7157
|
}
|
|
7158
|
-
const mock = options.transport?.(method,
|
|
7158
|
+
const mock = options.transport?.(method, path26, body5, headers);
|
|
7159
7159
|
if (mock !== void 0) {
|
|
7160
7160
|
const version = options.testTransportVersion?.();
|
|
7161
7161
|
if (version) inspectVersion(new Headers({ [APEX_API_VERSION_HEADER]: version }));
|
|
@@ -7164,7 +7164,7 @@ function createHttpClient(creds, options) {
|
|
|
7164
7164
|
}
|
|
7165
7165
|
let response;
|
|
7166
7166
|
try {
|
|
7167
|
-
response = await fetch(`${creds.baseUrl}${
|
|
7167
|
+
response = await fetch(`${creds.baseUrl}${path26}`, { method, headers, body: form ? body5 : body5 ? JSON.stringify(body5) : void 0, signal: AbortSignal.timeout(timeoutMs) });
|
|
7168
7168
|
} catch (error) {
|
|
7169
7169
|
const outer = error && typeof error === "object" ? error : void 0;
|
|
7170
7170
|
const nested = outer?.cause && typeof outer.cause === "object" ? outer.cause : void 0;
|
|
@@ -7184,7 +7184,7 @@ function createHttpClient(creds, options) {
|
|
|
7184
7184
|
if (!response.ok) return { ok: false, status: response.status, data: json, error: extractErrorMessage(json, response.status), errorDetails: extractErrorDetails(json), requestId, retryAfter: response.headers.get("retry-after"), rateLimit: { limit: response.headers.get("x-ratelimit-limit"), remaining: response.headers.get("x-ratelimit-remaining"), reset: response.headers.get("x-ratelimit-reset") } };
|
|
7185
7185
|
return { ok: true, status: response.status, data: json };
|
|
7186
7186
|
}
|
|
7187
|
-
return { request: (method,
|
|
7187
|
+
return { request: (method, path26, body5, timeoutMs) => request2(method, path26, body5, timeoutMs), requestForm: (method, path26, body5) => request2(method, path26, body5, void 0, true) };
|
|
7188
7188
|
}
|
|
7189
7189
|
|
|
7190
7190
|
// ../api-client/src/credentials.ts
|
|
@@ -7325,7 +7325,7 @@ async function checkForUpdate(current) {
|
|
|
7325
7325
|
// package.json
|
|
7326
7326
|
var package_default = {
|
|
7327
7327
|
name: "@drip-apex/apex-cli",
|
|
7328
|
-
version: "0.
|
|
7328
|
+
version: "0.15.0",
|
|
7329
7329
|
license: "MIT",
|
|
7330
7330
|
description: "Apex CLI for managing experiments, approved roadmap ideas, and draft workspaces.",
|
|
7331
7331
|
private: false,
|
|
@@ -7579,18 +7579,18 @@ function commandPath(command) {
|
|
|
7579
7579
|
function fail(command, message, exitCode = EXIT_CODES.general, details, api) {
|
|
7580
7580
|
throw new CliError(message, exitCode, details, api);
|
|
7581
7581
|
}
|
|
7582
|
-
function classifyStatus(
|
|
7583
|
-
if (
|
|
7584
|
-
if (
|
|
7585
|
-
if (
|
|
7582
|
+
function classifyStatus(status4) {
|
|
7583
|
+
if (status4 === 401 || status4 === 403) return EXIT_CODES.auth;
|
|
7584
|
+
if (status4 === 409) return EXIT_CODES.conflict;
|
|
7585
|
+
if (status4 === 400 || status4 === 413 || status4 === 422) return EXIT_CODES.validation;
|
|
7586
7586
|
return EXIT_CODES.general;
|
|
7587
7587
|
}
|
|
7588
|
-
function classifyApiError(
|
|
7588
|
+
function classifyApiError(status4, details) {
|
|
7589
7589
|
const code = details?.code;
|
|
7590
7590
|
if (code === "shop_context_required" || code === "org_context_required")
|
|
7591
7591
|
return EXIT_CODES.validation;
|
|
7592
7592
|
if (code === "shop_access_denied" || code === "org_access_denied") return EXIT_CODES.auth;
|
|
7593
|
-
return classifyStatus(
|
|
7593
|
+
return classifyStatus(status4);
|
|
7594
7594
|
}
|
|
7595
7595
|
function confirmationRequired(command, message, retry) {
|
|
7596
7596
|
return fail(command, message, EXIT_CODES.confirmationRequired, { retry });
|
|
@@ -7690,12 +7690,12 @@ function errorReason(error) {
|
|
|
7690
7690
|
String(details?.code)
|
|
7691
7691
|
))
|
|
7692
7692
|
return String(details?.code);
|
|
7693
|
-
const
|
|
7694
|
-
if (
|
|
7695
|
-
if (
|
|
7696
|
-
if (
|
|
7697
|
-
if (
|
|
7698
|
-
if (
|
|
7693
|
+
const status4 = error.api?.status;
|
|
7694
|
+
if (status4 === 401) return "auth_required";
|
|
7695
|
+
if (status4 === 403) return "auth_forbidden";
|
|
7696
|
+
if (status4 === 404) return "not_found";
|
|
7697
|
+
if (status4 === 429) return "rate_limited";
|
|
7698
|
+
if (status4 === 409 || error.exitCode === EXIT_CODES.conflict) return "conflict";
|
|
7699
7699
|
if (error.exitCode === EXIT_CODES.auth) return "auth_required";
|
|
7700
7700
|
if (error.exitCode === EXIT_CODES.confirmationRequired) return "confirmation_required";
|
|
7701
7701
|
if (error.exitCode === EXIT_CODES.validation) return "validation_failed";
|
|
@@ -8107,9 +8107,9 @@ var staticRouteSegments = /* @__PURE__ */ new Set([
|
|
|
8107
8107
|
"webhooks",
|
|
8108
8108
|
"workspace"
|
|
8109
8109
|
]);
|
|
8110
|
-
function normalizeRoute(
|
|
8111
|
-
if (!
|
|
8112
|
-
const parts =
|
|
8110
|
+
function normalizeRoute(path26) {
|
|
8111
|
+
if (!path26.startsWith("/api/")) return ":route";
|
|
8112
|
+
const parts = path26.split(/[?#]/, 1)[0].split("/");
|
|
8113
8113
|
const files = parts.indexOf("files");
|
|
8114
8114
|
if (files !== -1 && parts.length > files + 1) parts.splice(files + 1, parts.length, ":id");
|
|
8115
8115
|
return parts.map((part) => !part || staticRouteSegments.has(part) ? part : ":id").join("/");
|
|
@@ -8119,18 +8119,18 @@ function recordApiResponse2(response) {
|
|
|
8119
8119
|
if (response.status >= 400 && !firstError)
|
|
8120
8120
|
firstError = { ...response, path: normalizeRoute(response.path) };
|
|
8121
8121
|
}
|
|
8122
|
-
function classifyError(exitCode, error,
|
|
8122
|
+
function classifyError(exitCode, error, status4) {
|
|
8123
8123
|
const detail5 = error && typeof error === "object" ? error : {};
|
|
8124
8124
|
if (exitCode === 0) return "ok";
|
|
8125
8125
|
if (detail5.code === "commander.unknownCommand") return "unknown_command";
|
|
8126
8126
|
if (detail5.code?.startsWith("commander.")) return "validation";
|
|
8127
8127
|
if (exitCode === 4) return "confirmation_required";
|
|
8128
|
-
if (
|
|
8129
|
-
if (
|
|
8130
|
-
if (
|
|
8131
|
-
if (
|
|
8132
|
-
if (
|
|
8133
|
-
if (
|
|
8128
|
+
if (status4 === 401 || status4 === 403 || exitCode === 2) return "auth";
|
|
8129
|
+
if (status4 === 404) return "not_found";
|
|
8130
|
+
if (status4 === 429) return "rate_limited";
|
|
8131
|
+
if (status4 === 409 || exitCode === 5) return "conflict";
|
|
8132
|
+
if (status4 && status4 >= 500) return "server";
|
|
8133
|
+
if (status4 === 400 || status4 === 422 || exitCode === 3) return "validation";
|
|
8134
8134
|
if (Boolean(
|
|
8135
8135
|
detail5.details?.reason && ["network", "dns", "timeout", "tls", "refused", "reset", "unknown"].includes(
|
|
8136
8136
|
detail5.details.reason
|
|
@@ -8206,6 +8206,63 @@ var import_node_readline2 = __toESM(require("node:readline"));
|
|
|
8206
8206
|
|
|
8207
8207
|
// src/api.ts
|
|
8208
8208
|
var import_node_crypto2 = require("node:crypto");
|
|
8209
|
+
|
|
8210
|
+
// ../api-client/src/landing-pages.ts
|
|
8211
|
+
var LANDING_PAGE_MANIFEST_FILE = "apex-landing-page.json";
|
|
8212
|
+
var LANDING_PAGE_CHECKOUT_MARKER = ".apex/landing-page.json";
|
|
8213
|
+
var LANDING_PAGE_CONTEXT_DIR = "context";
|
|
8214
|
+
var LANDING_PAGE_BATCH_FILES = 32;
|
|
8215
|
+
var LANDING_PAGE_BATCH_BYTES = 3 * 1024 * 1024;
|
|
8216
|
+
function isLandingPageCheckoutPath(relativePath) {
|
|
8217
|
+
const segments = relativePath.split("/");
|
|
8218
|
+
if (segments.some((segment) => segment.startsWith(".") || segment === "node_modules")) return false;
|
|
8219
|
+
if (relativePath === LANDING_PAGE_MANIFEST_FILE) return false;
|
|
8220
|
+
if (segments[0] === LANDING_PAGE_CONTEXT_DIR) return false;
|
|
8221
|
+
return true;
|
|
8222
|
+
}
|
|
8223
|
+
function pagePath(id, action78) {
|
|
8224
|
+
return `/api/v1/landing-pages/${encodeURIComponent(id)}${action78 ? `/${action78}` : ""}`;
|
|
8225
|
+
}
|
|
8226
|
+
function landingPagesApi(request2) {
|
|
8227
|
+
const call = async (method, path26, body5) => {
|
|
8228
|
+
const response = await request2(method, path26, body5);
|
|
8229
|
+
return { ...response, data: response.data?.data };
|
|
8230
|
+
};
|
|
8231
|
+
return {
|
|
8232
|
+
create: (body5) => call("POST", "/api/v1/landing-pages", body5),
|
|
8233
|
+
checkout: (id, paths) => call(
|
|
8234
|
+
"GET",
|
|
8235
|
+
paths?.length ? `${pagePath(id, "filesystem")}?paths=${encodeURIComponent(paths.join(","))}` : pagePath(id, "filesystem")
|
|
8236
|
+
),
|
|
8237
|
+
plan: (id, body5) => call("POST", pagePath(id, "filesystem/plan"), body5),
|
|
8238
|
+
upload: (id, files) => call("POST", pagePath(id, "filesystem/upload"), { files }),
|
|
8239
|
+
apply: (id, body5) => call("POST", pagePath(id, "filesystem/apply"), body5),
|
|
8240
|
+
deploy: (id, body5) => call("POST", pagePath(id, "deploy"), body5),
|
|
8241
|
+
versions: (id) => call("GET", pagePath(id, "versions")),
|
|
8242
|
+
preview: (id, versionNumber) => call("GET", versionNumber ? `${pagePath(id, "preview")}?version=${versionNumber}` : pagePath(id, "preview")),
|
|
8243
|
+
rollback: (id, body5) => call("POST", pagePath(id, "rollback"), body5),
|
|
8244
|
+
experiment: (id, body5) => call("POST", pagePath(id, "experiment"), body5)
|
|
8245
|
+
};
|
|
8246
|
+
}
|
|
8247
|
+
function landingPageBatches(items, size) {
|
|
8248
|
+
const batches = [];
|
|
8249
|
+
let current = [];
|
|
8250
|
+
let bytes = 0;
|
|
8251
|
+
for (const item of items) {
|
|
8252
|
+
const itemBytes = size(item);
|
|
8253
|
+
if (current.length && (current.length >= LANDING_PAGE_BATCH_FILES || bytes + itemBytes > LANDING_PAGE_BATCH_BYTES)) {
|
|
8254
|
+
batches.push(current);
|
|
8255
|
+
current = [];
|
|
8256
|
+
bytes = 0;
|
|
8257
|
+
}
|
|
8258
|
+
current.push(item);
|
|
8259
|
+
bytes += itemBytes;
|
|
8260
|
+
}
|
|
8261
|
+
if (current.length) batches.push(current);
|
|
8262
|
+
return batches;
|
|
8263
|
+
}
|
|
8264
|
+
|
|
8265
|
+
// src/api.ts
|
|
8209
8266
|
function simulateTriggers(creds, body5) {
|
|
8210
8267
|
return request(creds, "POST", "/api/v1/tracking/simulate-trigger", body5);
|
|
8211
8268
|
}
|
|
@@ -8229,11 +8286,11 @@ function getUserKeyIdentity(creds) {
|
|
|
8229
8286
|
function getAccountContext(creds) {
|
|
8230
8287
|
return userRequest(creds, "GET", "/api/v1/me/context");
|
|
8231
8288
|
}
|
|
8232
|
-
function userRequest(creds, method,
|
|
8289
|
+
function userRequest(creds, method, path26, body5) {
|
|
8233
8290
|
return request(
|
|
8234
8291
|
{ ...creds, apiKey: creds.userApiKey ?? "", baseUrl: creds.userBaseUrl ?? creds.baseUrl },
|
|
8235
8292
|
method,
|
|
8236
|
-
|
|
8293
|
+
path26,
|
|
8237
8294
|
body5
|
|
8238
8295
|
);
|
|
8239
8296
|
}
|
|
@@ -8315,13 +8372,13 @@ function sharedClient(creds) {
|
|
|
8315
8372
|
}
|
|
8316
8373
|
return client;
|
|
8317
8374
|
}
|
|
8318
|
-
async function request(creds, method,
|
|
8319
|
-
const response = await sharedClient(creds).request(method,
|
|
8375
|
+
async function request(creds, method, path26, body5, requestTimeoutMs) {
|
|
8376
|
+
const response = await sharedClient(creds).request(method, path26, body5, requestTimeoutMs);
|
|
8320
8377
|
recordApiResponse(response);
|
|
8321
8378
|
return response;
|
|
8322
8379
|
}
|
|
8323
|
-
async function requestForm(creds, method,
|
|
8324
|
-
const response = await sharedClient(creds).requestForm(method,
|
|
8380
|
+
async function requestForm(creds, method, path26, body5) {
|
|
8381
|
+
const response = await sharedClient(creds).requestForm(method, path26, body5);
|
|
8325
8382
|
recordApiResponse(response);
|
|
8326
8383
|
return response;
|
|
8327
8384
|
}
|
|
@@ -8553,6 +8610,36 @@ function getInstallDomains(creds) {
|
|
|
8553
8610
|
function updateInstallDomains(creds, body5) {
|
|
8554
8611
|
return request(creds, "PATCH", "/api/v1/install-domains", body5);
|
|
8555
8612
|
}
|
|
8613
|
+
function getLandingPageDomains(creds, filter = {}) {
|
|
8614
|
+
const query = filter.host ? `?host=${encodeURIComponent(filter.host)}` : "";
|
|
8615
|
+
return request(
|
|
8616
|
+
creds,
|
|
8617
|
+
"GET",
|
|
8618
|
+
`/api/v1/landing-pages/domains${query}`
|
|
8619
|
+
);
|
|
8620
|
+
}
|
|
8621
|
+
function createLandingPageDomain(creds, body5) {
|
|
8622
|
+
return request(
|
|
8623
|
+
creds,
|
|
8624
|
+
"POST",
|
|
8625
|
+
"/api/v1/landing-pages/domains",
|
|
8626
|
+
body5
|
|
8627
|
+
);
|
|
8628
|
+
}
|
|
8629
|
+
function verifyLandingPageDomain(creds, id) {
|
|
8630
|
+
return request(
|
|
8631
|
+
creds,
|
|
8632
|
+
"POST",
|
|
8633
|
+
`/api/v1/landing-pages/domains/${encodeURIComponent(id)}/verify`
|
|
8634
|
+
);
|
|
8635
|
+
}
|
|
8636
|
+
function deleteLandingPageDomain(creds, id) {
|
|
8637
|
+
return request(
|
|
8638
|
+
creds,
|
|
8639
|
+
"DELETE",
|
|
8640
|
+
`/api/v1/landing-pages/domains/${encodeURIComponent(id)}`
|
|
8641
|
+
);
|
|
8642
|
+
}
|
|
8556
8643
|
function getExternalTracking(creds) {
|
|
8557
8644
|
return request(creds, "GET", "/api/v1/external-tracking");
|
|
8558
8645
|
}
|
|
@@ -8560,18 +8647,18 @@ function updateExternalTracking(creds, body5) {
|
|
|
8560
8647
|
return request(creds, "PATCH", "/api/v1/external-tracking", body5);
|
|
8561
8648
|
}
|
|
8562
8649
|
function trackingEventProperties(event) {
|
|
8563
|
-
let
|
|
8650
|
+
let preview2 = null;
|
|
8564
8651
|
if (typeof event.properties_preview === "string") {
|
|
8565
8652
|
try {
|
|
8566
8653
|
const parsed = JSON.parse(event.properties_preview);
|
|
8567
8654
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
8568
|
-
|
|
8655
|
+
preview2 = parsed;
|
|
8569
8656
|
}
|
|
8570
8657
|
} catch {
|
|
8571
8658
|
}
|
|
8572
8659
|
}
|
|
8573
8660
|
const legacy = event.properties && typeof event.properties === "object" && !Array.isArray(event.properties) ? event.properties : null;
|
|
8574
|
-
const properties = { ...legacy ?? {}, ...
|
|
8661
|
+
const properties = { ...legacy ?? {}, ...preview2 ?? {} };
|
|
8575
8662
|
if (event.goal_id != null) properties.goalId = event.goal_id;
|
|
8576
8663
|
return properties;
|
|
8577
8664
|
}
|
|
@@ -8763,11 +8850,11 @@ function requestTargetingResource(creds, method, kind5, id, body5, limit, cursor
|
|
|
8763
8850
|
if (limit) params.set("limit", String(limit));
|
|
8764
8851
|
if (cursor) params.set("cursor", cursor);
|
|
8765
8852
|
const query = params.toString();
|
|
8766
|
-
const
|
|
8767
|
-
return request(creds, method,
|
|
8853
|
+
const path26 = `/api/v1/${kind5}${id ? `/${id.split("/").map(encodeURIComponent).join("/")}` : ""}${query ? `?${query}` : ""}`;
|
|
8854
|
+
return request(creds, method, path26, body5);
|
|
8768
8855
|
}
|
|
8769
|
-
function managedPath(kind5, id,
|
|
8770
|
-
return `/api/v1/${kind5}${id ? `/${encodeURIComponent(id)}` : ""}${
|
|
8856
|
+
function managedPath(kind5, id, action78) {
|
|
8857
|
+
return `/api/v1/${kind5}${id ? `/${encodeURIComponent(id)}` : ""}${action78 ? `/${action78}` : ""}`;
|
|
8771
8858
|
}
|
|
8772
8859
|
function listManagedDeliveries(creds, kind5, limit) {
|
|
8773
8860
|
const q = limit ? `?limit=${limit}` : "";
|
|
@@ -8785,8 +8872,8 @@ function createManagedDelivery(creds, kind5, body5) {
|
|
|
8785
8872
|
function updateManagedDelivery(creds, kind5, id, body5) {
|
|
8786
8873
|
return request(creds, "PATCH", managedPath(kind5, id), body5);
|
|
8787
8874
|
}
|
|
8788
|
-
function transitionManagedDelivery(creds, kind5, id,
|
|
8789
|
-
return request(creds, "POST", managedPath(kind5, id,
|
|
8875
|
+
function transitionManagedDelivery(creds, kind5, id, action78) {
|
|
8876
|
+
return request(creds, "POST", managedPath(kind5, id, action78));
|
|
8790
8877
|
}
|
|
8791
8878
|
function listConfigResources(creds, family, opts) {
|
|
8792
8879
|
const params = new URLSearchParams();
|
|
@@ -8798,6 +8885,11 @@ function listConfigResources(creds, family, opts) {
|
|
|
8798
8885
|
function getConfigResource(creds, family, id) {
|
|
8799
8886
|
return request(creds, "GET", `/api/v1/${family}/${encodeURIComponent(id)}`);
|
|
8800
8887
|
}
|
|
8888
|
+
function landingPages(creds) {
|
|
8889
|
+
return landingPagesApi(
|
|
8890
|
+
(method, path26, body5) => request(creds, method, path26, body5)
|
|
8891
|
+
);
|
|
8892
|
+
}
|
|
8801
8893
|
function deleteAsset(creds, id) {
|
|
8802
8894
|
return request(creds, "DELETE", `/api/v1/assets/${encodeURIComponent(id)}`);
|
|
8803
8895
|
}
|
|
@@ -9103,9 +9195,9 @@ var STATUS_COLORS = {
|
|
|
9103
9195
|
paused: source_default.yellow,
|
|
9104
9196
|
completed: source_default.blue
|
|
9105
9197
|
};
|
|
9106
|
-
function statusBadge(
|
|
9107
|
-
const colorize = STATUS_COLORS[
|
|
9108
|
-
return colorize(
|
|
9198
|
+
function statusBadge(status4) {
|
|
9199
|
+
const colorize = STATUS_COLORS[status4] ?? source_default.white;
|
|
9200
|
+
return colorize(status4.toUpperCase());
|
|
9109
9201
|
}
|
|
9110
9202
|
function printTable(headers, rows, widths) {
|
|
9111
9203
|
const colWidths = widths ?? headers.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length)));
|
|
@@ -9284,8 +9376,8 @@ async function waitForBrowserLogin(baseUrl, userMode = false) {
|
|
|
9284
9376
|
${loginUrl.toString()}
|
|
9285
9377
|
`));
|
|
9286
9378
|
try {
|
|
9287
|
-
const
|
|
9288
|
-
await
|
|
9379
|
+
const open3 = (await Promise.resolve().then(() => (init_open(), open_exports))).default;
|
|
9380
|
+
await open3(loginUrl.toString());
|
|
9289
9381
|
} catch {
|
|
9290
9382
|
}
|
|
9291
9383
|
});
|
|
@@ -9549,10 +9641,10 @@ var evidenceKinds = [
|
|
|
9549
9641
|
|
|
9550
9642
|
// src/lib/experiment-handover.ts
|
|
9551
9643
|
function summarizeHandoverQa(value) {
|
|
9552
|
-
const
|
|
9553
|
-
const qa3 =
|
|
9554
|
-
const run3 =
|
|
9555
|
-
const summary =
|
|
9644
|
+
const record4 = (value2) => value2 !== null && typeof value2 === "object" && !Array.isArray(value2) ? value2 : {};
|
|
9645
|
+
const qa3 = record4(value);
|
|
9646
|
+
const run3 = record4(qa3.latestRun);
|
|
9647
|
+
const summary = record4(run3.summary);
|
|
9556
9648
|
const count2 = (value2) => typeof value2 === "number" && Number.isInteger(value2) && value2 >= 0 ? value2 : null;
|
|
9557
9649
|
let requiredDone = count2(summary.completedRequired);
|
|
9558
9650
|
let requiredTotal = count2(summary.required);
|
|
@@ -9595,12 +9687,12 @@ function applyQaPreviewParams(url, params) {
|
|
|
9595
9687
|
url.search += `${url.search ? "&" : "?"}drip_force=${force}`;
|
|
9596
9688
|
return url;
|
|
9597
9689
|
}
|
|
9598
|
-
function buildQaPreviewUrl(
|
|
9599
|
-
const variation = options.variationName ?
|
|
9600
|
-
const target = options.storeUrl ??
|
|
9690
|
+
function buildQaPreviewUrl(experiment2, options = {}) {
|
|
9691
|
+
const variation = options.variationName ? experiment2.variations?.find((v) => v.name === options.variationName) : experiment2.variations?.find((v) => !v.is_control) ?? experiment2.variations?.[0];
|
|
9692
|
+
const target = options.storeUrl ?? experiment2.store_url;
|
|
9601
9693
|
if (!target || !variation) return null;
|
|
9602
9694
|
try {
|
|
9603
|
-
return applyQaPreviewParams(new URL(target), { experimentId:
|
|
9695
|
+
return applyQaPreviewParams(new URL(target), { experimentId: experiment2.id, variationId: variation.id, experimentPublicId: experiment2.public_id, variationPublicId: variation.public_id, ...options });
|
|
9604
9696
|
} catch {
|
|
9605
9697
|
return null;
|
|
9606
9698
|
}
|
|
@@ -9627,12 +9719,6 @@ function deviceLabel(targeting) {
|
|
|
9627
9719
|
return void 0;
|
|
9628
9720
|
}
|
|
9629
9721
|
|
|
9630
|
-
// src/lib/test-key.ts
|
|
9631
|
-
var TEST_KEY_RE = /^([A-Z0-9]{2,8})-(\d+)$/;
|
|
9632
|
-
function isTestKeyReference(value) {
|
|
9633
|
-
return value === value.toUpperCase() && TEST_KEY_RE.test(value.toUpperCase());
|
|
9634
|
-
}
|
|
9635
|
-
|
|
9636
9722
|
// src/lib/effective-page-targeting.ts
|
|
9637
9723
|
var ALL_PAGES_LABEL = "All pages";
|
|
9638
9724
|
var CHECKOUT_LABEL = "Checkout";
|
|
@@ -9719,6 +9805,103 @@ function classifyIdeaReference(reference) {
|
|
|
9719
9805
|
return { kind: "invalid", value: trimmed };
|
|
9720
9806
|
}
|
|
9721
9807
|
|
|
9808
|
+
// ../api-client/src/experiments.ts
|
|
9809
|
+
var ID_REFERENCE = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|[1-9][0-9]{9})$/i;
|
|
9810
|
+
function isExperimentTestKey(value) {
|
|
9811
|
+
return /^([A-Z0-9]{2,8})-(\d+)$/.test(value);
|
|
9812
|
+
}
|
|
9813
|
+
function record(value) {
|
|
9814
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
9815
|
+
}
|
|
9816
|
+
async function resolveExperimentReference(request2, idOrSlug, variationRef) {
|
|
9817
|
+
const explicitSlug = idOrSlug.startsWith("slug:");
|
|
9818
|
+
const reference = explicitSlug ? idOrSlug.slice(5) : idOrSlug;
|
|
9819
|
+
const idShaped = !explicitSlug && ID_REFERENCE.test(reference);
|
|
9820
|
+
const keyShaped = !explicitSlug && isExperimentTestKey(reference);
|
|
9821
|
+
const slugPath = `/api/v1/experiments/by-slug/${encodeURIComponent(reference)}`;
|
|
9822
|
+
let res = await request2("GET", idShaped || keyShaped ? `/api/v1/experiments/${encodeURIComponent(reference)}` : slugPath);
|
|
9823
|
+
if (!res.ok && res.status === 404 && idShaped) res = await request2("GET", slugPath);
|
|
9824
|
+
if (!res.ok) return { ok: false, error: res.error ?? "Experiment not found", status: res.status, response: res };
|
|
9825
|
+
const experiment2 = record(res.data);
|
|
9826
|
+
if (typeof experiment2.id !== "string") return { ok: false, error: "Experiment has no id" };
|
|
9827
|
+
const variations = Array.isArray(experiment2.variations) ? experiment2.variations.map(record) : [];
|
|
9828
|
+
if (variationRef === void 0) return { ok: true, experimentId: experiment2.id, variations, experiment: experiment2 };
|
|
9829
|
+
const available = variations.map((v) => String(v.slug ?? v.name)).join(", ") || "none";
|
|
9830
|
+
const byIdentity = variations.filter((v) => v.id === variationRef || v.public_id === variationRef || v.slug === variationRef);
|
|
9831
|
+
const byName = variations.filter((v) => v.name === variationRef);
|
|
9832
|
+
const matches = byIdentity.length > 0 ? byIdentity : byName;
|
|
9833
|
+
if (matches.length > 1) return {
|
|
9834
|
+
ok: false,
|
|
9835
|
+
error: `Variation "${variationRef}" is ambiguous; use one of these slugs: ${matches.map((v) => String(v.slug)).join(", ")}`
|
|
9836
|
+
};
|
|
9837
|
+
const match2 = matches[0];
|
|
9838
|
+
if (typeof match2?.id !== "string") return { ok: false, error: `Variation "${variationRef}" not found. Available: ${available}` };
|
|
9839
|
+
return { ok: true, experimentId: experiment2.id, variationId: match2.id, variations, experiment: experiment2 };
|
|
9840
|
+
}
|
|
9841
|
+
function summarizeResults(payload) {
|
|
9842
|
+
const data = record(payload);
|
|
9843
|
+
const experiment2 = record(data.experiment);
|
|
9844
|
+
const decision = record(data.decision);
|
|
9845
|
+
const outcome = record(data.effective_outcome);
|
|
9846
|
+
const commerce = record(data.commerce);
|
|
9847
|
+
const rawArms = Array.isArray(data.arms) ? data.arms.map(record) : [];
|
|
9848
|
+
const degradedFlags = {
|
|
9849
|
+
explicit: data.degraded === true,
|
|
9850
|
+
commerceUnavailable: commerce.unavailable === true,
|
|
9851
|
+
armMetricsMissing: rawArms.some((arm) => arm.primaryConversions == null || arm.primaryRate == null)
|
|
9852
|
+
};
|
|
9853
|
+
const missing = /* @__PURE__ */ new Set();
|
|
9854
|
+
if (degradedFlags.commerceUnavailable) missing.add("commerce orders");
|
|
9855
|
+
const controlRate = rawArms.find((arm) => arm.isControl === true)?.primaryRate;
|
|
9856
|
+
const treatmentCount = rawArms.filter((arm) => arm.isControl !== true).length;
|
|
9857
|
+
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;
|
|
9858
|
+
const runtimeDay = typeof data.runtime_day === "number" && Number.isInteger(data.runtime_day) && data.runtime_day >= 1 ? data.runtime_day : null;
|
|
9859
|
+
const confidenceIndication = data.confidence_visible === false ? typeof data.confidence_indication === "string" && data.confidence_indication ? data.confidence_indication : runtimeDay !== null ? plannedDays !== null && runtimeDay <= plannedDays ? `Indication \xB7 day ${runtimeDay} of about ${plannedDays}` : `Indication \xB7 day ${runtimeDay}` : null : null;
|
|
9860
|
+
const arms = rawArms.map((arm) => {
|
|
9861
|
+
const judged = arm.isControl !== true && (decision.treatmentVariationId != null ? arm.variationId === decision.treatmentVariationId : treatmentCount === 1);
|
|
9862
|
+
let lift = null;
|
|
9863
|
+
let confidence = null;
|
|
9864
|
+
if (arm.isControl !== true) {
|
|
9865
|
+
if (judged) {
|
|
9866
|
+
lift = decision.lift;
|
|
9867
|
+
confidence = decision.judgedConfidence;
|
|
9868
|
+
} else if (typeof arm.primaryRate === "number" && typeof controlRate === "number" && controlRate > 0)
|
|
9869
|
+
lift = Math.round((arm.primaryRate / controlRate - 1) * 1e4) / 100;
|
|
9870
|
+
if (lift == null) missing.add("lift");
|
|
9871
|
+
if (judged && confidence == null && data.confidence_visible !== false) missing.add("confidence");
|
|
9872
|
+
}
|
|
9873
|
+
if (arm.visitors == null) missing.add("visitors");
|
|
9874
|
+
if (arm.primaryConversions == null) missing.add("conversions");
|
|
9875
|
+
if (arm.primaryRate == null) missing.add("conversion rate");
|
|
9876
|
+
return { ...arm, lift, confidence, judged };
|
|
9877
|
+
});
|
|
9878
|
+
const verdictWithheld = decision.state === "untrustworthy";
|
|
9879
|
+
return {
|
|
9880
|
+
experiment: experiment2,
|
|
9881
|
+
runWindow: { startedAt: experiment2.resultsWindowStartedAt ?? experiment2.startedAt, endedAt: experiment2.endedAt },
|
|
9882
|
+
primaryGoal: decision.primaryGoalName,
|
|
9883
|
+
totals: record(data.totals),
|
|
9884
|
+
decision,
|
|
9885
|
+
outcome,
|
|
9886
|
+
arms,
|
|
9887
|
+
judgedArm: arms.find((arm) => arm.judged)?.variationId,
|
|
9888
|
+
verdict: outcome.outcome ?? decision.state,
|
|
9889
|
+
verdictWithheld,
|
|
9890
|
+
statisticsState: (verdictWithheld ? decision.state : outcome.statisticsOutcome ?? decision.state) ?? "unknown",
|
|
9891
|
+
presentedOutcome: (verdictWithheld && outcome.provenance !== "manager_decided" ? decision.state : outcome.outcome ?? decision.state) ?? "unknown",
|
|
9892
|
+
degraded: Object.values(degradedFlags).some(Boolean),
|
|
9893
|
+
degradedFlags,
|
|
9894
|
+
missingMetrics: [...missing],
|
|
9895
|
+
warnings: Array.isArray(data.warnings) ? data.warnings.map((value) => ({
|
|
9896
|
+
message: record(value).message,
|
|
9897
|
+
value
|
|
9898
|
+
})) : [],
|
|
9899
|
+
metrics: Array.isArray(data.metrics) ? data.metrics.map(record) : [],
|
|
9900
|
+
resultsWeekdays: data.results_weekdays,
|
|
9901
|
+
confidenceIndication
|
|
9902
|
+
};
|
|
9903
|
+
}
|
|
9904
|
+
|
|
9722
9905
|
// ../api-client/src/experiment-create.ts
|
|
9723
9906
|
function deriveStoreUrl(patterns) {
|
|
9724
9907
|
for (const pattern of patterns) {
|
|
@@ -9984,22 +10167,17 @@ function formatRevenue(value, currency) {
|
|
|
9984
10167
|
}
|
|
9985
10168
|
}
|
|
9986
10169
|
function printExperimentResults(results2, currency) {
|
|
9987
|
-
const
|
|
9988
|
-
const
|
|
9989
|
-
const
|
|
9990
|
-
const effectiveOutcome = results2.effective_outcome ?? {};
|
|
9991
|
-
const outcomeProvenance = effectiveOutcome.provenance === "manager_decided" ? " (manager decided)" : "";
|
|
9992
|
-
const verdictWithheld = decision.state === "untrustworthy";
|
|
9993
|
-
const statisticsState = verdictWithheld ? decision.state : effectiveOutcome.statisticsOutcome ?? decision.state ?? "unknown";
|
|
9994
|
-
const presentedOutcome = verdictWithheld && effectiveOutcome.provenance !== "manager_decided" ? decision.state : effectiveOutcome.outcome ?? decision.state ?? "unknown";
|
|
10170
|
+
const summary = summarizeResults(results2);
|
|
10171
|
+
const { experiment: experiment2, totals, decision, verdictWithheld, statisticsState, presentedOutcome } = summary;
|
|
10172
|
+
const outcomeProvenance = summary.outcome.provenance === "manager_decided" ? " (manager decided)" : "";
|
|
9995
10173
|
console.log();
|
|
9996
|
-
console.log(source_default.bold(String(
|
|
9997
|
-
console.log(` Status: ${statusBadge(String(
|
|
10174
|
+
console.log(source_default.bold(String(experiment2.name ?? experiment2.id ?? "Experiment results")));
|
|
10175
|
+
console.log(` Status: ${statusBadge(String(experiment2.status ?? "unknown"))}`);
|
|
9998
10176
|
console.log(` Visitors: ${String(totals.visitors ?? 0)}`);
|
|
9999
10177
|
console.log(` Conversions: ${String(totals.primaryConversions ?? 0)}`);
|
|
10000
10178
|
console.log(` Revenue: ${formatRevenue(totals.revenue, currency)}`);
|
|
10001
10179
|
console.log(` Outcome: ${statisticsStateLabel(presentedOutcome)}${outcomeProvenance}`);
|
|
10002
|
-
const resultsWeekdays =
|
|
10180
|
+
const resultsWeekdays = summary.resultsWeekdays;
|
|
10003
10181
|
if (resultsWeekdays && Array.isArray(resultsWeekdays.days)) {
|
|
10004
10182
|
const labels = {
|
|
10005
10183
|
monday: "Mon",
|
|
@@ -10020,9 +10198,9 @@ function printExperimentResults(results2, currency) {
|
|
|
10020
10198
|
}
|
|
10021
10199
|
console.log(` Stats lift: ${formatPct(decision.lift)}`);
|
|
10022
10200
|
console.log(
|
|
10023
|
-
` Confidence (${confidenceBasisLabel(decision.basis)}): ${verdictWithheld ? "withheld \u2014 not decision-grade" : formatPct(decision.judgedConfidence)}`
|
|
10201
|
+
` Confidence (${confidenceBasisLabel(decision.basis)}): ${verdictWithheld ? "withheld \u2014 not decision-grade" : summary.confidenceIndication ?? formatPct(decision.judgedConfidence)}`
|
|
10024
10202
|
);
|
|
10025
|
-
const arms =
|
|
10203
|
+
const arms = summary.arms;
|
|
10026
10204
|
if (arms.length > 0) {
|
|
10027
10205
|
console.log();
|
|
10028
10206
|
printTable(
|
|
@@ -10040,7 +10218,7 @@ function printExperimentResults(results2, currency) {
|
|
|
10040
10218
|
[32, 8, 6, 8, 10]
|
|
10041
10219
|
);
|
|
10042
10220
|
}
|
|
10043
|
-
const metrics =
|
|
10221
|
+
const metrics = summary.metrics;
|
|
10044
10222
|
if (metrics.length > 0) {
|
|
10045
10223
|
console.log();
|
|
10046
10224
|
console.log(source_default.bold("Goal counting"));
|
|
@@ -10050,7 +10228,7 @@ function printExperimentResults(results2, currency) {
|
|
|
10050
10228
|
});
|
|
10051
10229
|
}
|
|
10052
10230
|
console.log(" Definitions: https://docs.drip-apex.com/docs/experiments/metric-definitions");
|
|
10053
|
-
const warnings =
|
|
10231
|
+
const warnings = summary.warnings;
|
|
10054
10232
|
if (warnings.length > 0) {
|
|
10055
10233
|
console.log();
|
|
10056
10234
|
console.log(source_default.yellow("Warnings"));
|
|
@@ -10157,16 +10335,16 @@ function listExperimentFilesystemFiles(folder, session) {
|
|
|
10157
10335
|
const absPath = import_node_path6.default.join(folder, fileName);
|
|
10158
10336
|
if (!import_node_fs8.default.existsSync(absPath)) continue;
|
|
10159
10337
|
const content = import_node_fs8.default.readFileSync(absPath, "utf8");
|
|
10160
|
-
const
|
|
10338
|
+
const sha2562 = sha256Text(content);
|
|
10161
10339
|
if (SERVER_OWNED_EXPERIMENT_FILES.has(fileName)) {
|
|
10162
|
-
if (session.files[fileName]?.sha256 !==
|
|
10340
|
+
if (session.files[fileName]?.sha256 !== sha2562) {
|
|
10163
10341
|
console.error(
|
|
10164
10342
|
`${fileName} is server-owned; local edits are ignored (dashboard flow owns QA state)`
|
|
10165
10343
|
);
|
|
10166
10344
|
}
|
|
10167
10345
|
continue;
|
|
10168
10346
|
}
|
|
10169
|
-
files.push({ path: fileName, content, sha256 });
|
|
10347
|
+
files.push({ path: fileName, content, sha256: sha2562 });
|
|
10170
10348
|
}
|
|
10171
10349
|
for (const fileName of EXPERIMENT_FILESYSTEM_PATH_SCHEMA.experiment) {
|
|
10172
10350
|
const relativePath = `experiment/${fileName}`;
|
|
@@ -10232,35 +10410,35 @@ async function rewriteExperimentFilesystemAssets(input) {
|
|
|
10232
10410
|
})
|
|
10233
10411
|
);
|
|
10234
10412
|
}
|
|
10235
|
-
function printFilesystemPlan(
|
|
10236
|
-
if (
|
|
10413
|
+
function printFilesystemPlan(plan2) {
|
|
10414
|
+
if (plan2.errors.length > 0) {
|
|
10237
10415
|
console.log(source_default.red("\nErrors"));
|
|
10238
|
-
|
|
10416
|
+
plan2.errors.forEach((error) => console.log(` - ${error}`));
|
|
10239
10417
|
}
|
|
10240
|
-
const compatibilityWarnings = (
|
|
10418
|
+
const compatibilityWarnings = (plan2.previewCompatibility ?? []).map(
|
|
10241
10419
|
(finding) => `${finding.path}:${finding.line}:${finding.column} ${finding.rule}: ${finding.message}. Try: ${finding.alternative}`
|
|
10242
10420
|
);
|
|
10243
10421
|
if (compatibilityWarnings.length > 0) {
|
|
10244
10422
|
console.log(source_default.yellow("\nPreview compatibility"));
|
|
10245
10423
|
compatibilityWarnings.forEach((warning) => console.log(` - ${warning}`));
|
|
10246
10424
|
}
|
|
10247
|
-
const otherWarnings =
|
|
10425
|
+
const otherWarnings = plan2.warnings.filter((warning) => !compatibilityWarnings.includes(warning));
|
|
10248
10426
|
if (otherWarnings.length > 0) {
|
|
10249
10427
|
console.log(source_default.yellow("\nWarnings"));
|
|
10250
10428
|
otherWarnings.forEach((warning) => console.log(` - ${warning}`));
|
|
10251
10429
|
}
|
|
10252
|
-
if ((
|
|
10430
|
+
if ((plan2.blockingIssues?.length ?? 0) > 0) {
|
|
10253
10431
|
console.log(source_default.red("\nBlocking issues"));
|
|
10254
|
-
(
|
|
10432
|
+
(plan2.blockingIssues ?? []).forEach((issue) => {
|
|
10255
10433
|
const context = [issue.path, issue.variation ? `variation: ${issue.variation}` : null].filter(Boolean).join(", ");
|
|
10256
10434
|
console.log(` - [${issue.code}] ${issue.message}${context ? ` (${context})` : ""}`);
|
|
10257
10435
|
});
|
|
10258
10436
|
}
|
|
10259
10437
|
console.log(source_default.bold("\nChanges"));
|
|
10260
|
-
if (
|
|
10438
|
+
if (plan2.changes.length === 0) {
|
|
10261
10439
|
console.log(source_default.dim(" No changes."));
|
|
10262
10440
|
} else {
|
|
10263
|
-
|
|
10441
|
+
plan2.changes.forEach((change) => {
|
|
10264
10442
|
if (change.entity === "experiment.targeting" && Array.isArray(change.detail?.changedKeys)) {
|
|
10265
10443
|
console.log(` Changed keys: ${change.detail.changedKeys.join(", ")}`);
|
|
10266
10444
|
}
|
|
@@ -10272,9 +10450,9 @@ function printFilesystemPlan(plan) {
|
|
|
10272
10450
|
});
|
|
10273
10451
|
}
|
|
10274
10452
|
console.log();
|
|
10275
|
-
console.log(`QA invalidated: ${
|
|
10276
|
-
console.log(`Acknowledgment: ${
|
|
10277
|
-
console.log(`Precompile: ${
|
|
10453
|
+
console.log(`QA invalidated: ${plan2.invalidatesQa ? source_default.yellow("yes") : "no"}`);
|
|
10454
|
+
console.log(`Acknowledgment: ${plan2.invalidatesQa ? "--accept-qa-invalidation" : "none"}`);
|
|
10455
|
+
console.log(`Precompile: ${plan2.precompileRequired ? "yes" : "no"}`);
|
|
10278
10456
|
}
|
|
10279
10457
|
function blockingIssueSummary(issues) {
|
|
10280
10458
|
if (!issues?.length) return "Experiment filesystem plan has errors.";
|
|
@@ -10298,11 +10476,11 @@ async function getQaPreviewUrls(creds, experimentId) {
|
|
|
10298
10476
|
if (!res.ok || !res.data || !storeUrl) {
|
|
10299
10477
|
return { previewUrls: [], unavailable: true, dashboardUrl };
|
|
10300
10478
|
}
|
|
10301
|
-
const
|
|
10302
|
-
const previewUrls = (
|
|
10479
|
+
const experiment2 = res.data;
|
|
10480
|
+
const previewUrls = (experiment2.variations ?? []).filter((variation) => !variation.is_control).map((variation) => ({
|
|
10303
10481
|
variationId: variation.id,
|
|
10304
10482
|
variationName: variation.name,
|
|
10305
|
-
url: buildQaPreviewUrl2(storeUrl,
|
|
10483
|
+
url: buildQaPreviewUrl2(storeUrl, experiment2.id, variation.id).toString()
|
|
10306
10484
|
}));
|
|
10307
10485
|
return { previewUrls, unavailable: false, dashboardUrl };
|
|
10308
10486
|
} catch {
|
|
@@ -10316,9 +10494,9 @@ function printQaPreviewUrls(experimentId, result) {
|
|
|
10316
10494
|
);
|
|
10317
10495
|
return;
|
|
10318
10496
|
}
|
|
10319
|
-
for (const
|
|
10497
|
+
for (const preview2 of result.previewUrls) {
|
|
10320
10498
|
console.log(
|
|
10321
|
-
`Preview (interactive, works while draft \u2014 no on-site editing needed): ${
|
|
10499
|
+
`Preview (interactive, works while draft \u2014 no on-site editing needed): ${preview2.url}`
|
|
10322
10500
|
);
|
|
10323
10501
|
}
|
|
10324
10502
|
}
|
|
@@ -10353,10 +10531,10 @@ function printQaRun(run3) {
|
|
|
10353
10531
|
}
|
|
10354
10532
|
if ((run3.evidenceRecords ?? []).length > 0) {
|
|
10355
10533
|
console.log(source_default.bold("\nEvidence records"));
|
|
10356
|
-
for (const
|
|
10357
|
-
const device =
|
|
10534
|
+
for (const record4 of run3.evidenceRecords ?? []) {
|
|
10535
|
+
const device = record4.device ? ` \xB7 ${record4.device}` : "";
|
|
10358
10536
|
console.log(
|
|
10359
|
-
` - ${
|
|
10537
|
+
` - ${record4.kind} \xB7 ${record4.label} \xB7 ${record4.status}${device} \xB7 ${record4.capturedAt}${qaVersionMarker(record4)}`
|
|
10360
10538
|
);
|
|
10361
10539
|
}
|
|
10362
10540
|
}
|
|
@@ -10475,26 +10653,29 @@ async function refreshServerOwnedFilesIfNeeded(creds, folder, experimentId, upda
|
|
|
10475
10653
|
}
|
|
10476
10654
|
}
|
|
10477
10655
|
var EXPERIMENT_REFERENCE_DESCRIPTION = "Experiment ID, slug, or test key (for example BIO-085)";
|
|
10656
|
+
function resolveCliExperimentReference(creds, reference, variationRef) {
|
|
10657
|
+
return resolveExperimentReference(
|
|
10658
|
+
(_method, requestPath) => {
|
|
10659
|
+
const prefix = "/api/v1/experiments/by-slug/";
|
|
10660
|
+
const bySlug = requestPath.startsWith(prefix);
|
|
10661
|
+
const idOrSlug = decodeURIComponent(
|
|
10662
|
+
requestPath.slice(bySlug ? prefix.length : "/api/v1/experiments/".length)
|
|
10663
|
+
);
|
|
10664
|
+
return bySlug ? getExperimentBySlug(creds, idOrSlug) : getExperiment(creds, idOrSlug);
|
|
10665
|
+
},
|
|
10666
|
+
reference,
|
|
10667
|
+
variationRef
|
|
10668
|
+
);
|
|
10669
|
+
}
|
|
10478
10670
|
async function resolveExperiment(idOrSlug) {
|
|
10479
10671
|
const creds = requireCredentials();
|
|
10480
|
-
|
|
10481
|
-
|
|
10482
|
-
if (res2.ok) return res2.data;
|
|
10483
|
-
throw new CliError(
|
|
10484
|
-
res2.status === 404 && isTestKeyReference(idOrSlug) ? `Experiment "${idOrSlug}" not found in selected shop ${creds.resolvedContext?.shopId ?? "(API key shop)"}. Test keys resolve within the selected shop; use --shop <shop-id> to change it.` : res2.error ?? `Experiment "${idOrSlug}" not found`,
|
|
10485
|
-
classifyStatus(res2.status),
|
|
10486
|
-
res2.errorDetails,
|
|
10487
|
-
apiFailureOf(res2)
|
|
10488
|
-
);
|
|
10489
|
-
}
|
|
10490
|
-
const slug = idOrSlug.startsWith("slug:") ? idOrSlug.slice(5) : idOrSlug;
|
|
10491
|
-
const res = await getExperimentBySlug(creds, slug);
|
|
10492
|
-
if (res.ok) return res.data;
|
|
10672
|
+
const res = await resolveCliExperimentReference(creds, idOrSlug);
|
|
10673
|
+
if (res.ok) return res.experiment;
|
|
10493
10674
|
throw new CliError(
|
|
10494
|
-
res.error ?? `Experiment "${idOrSlug}" not found
|
|
10495
|
-
classifyStatus(res.status),
|
|
10496
|
-
res.errorDetails,
|
|
10497
|
-
apiFailureOf(res)
|
|
10675
|
+
res.status === 404 && isExperimentTestKey(idOrSlug) ? `Experiment "${idOrSlug}" not found in selected shop ${creds.resolvedContext?.shopId ?? "(API key shop)"}. Test keys resolve within the selected shop; use --shop <shop-id> to change it.` : res.response ? res.response.error ?? `Experiment "${idOrSlug}" not found` : res.error,
|
|
10676
|
+
classifyStatus(res.status ?? 500),
|
|
10677
|
+
res.response?.errorDetails,
|
|
10678
|
+
res.response ? apiFailureOf(res.response) : void 0
|
|
10498
10679
|
);
|
|
10499
10680
|
}
|
|
10500
10681
|
var list = new Command("list").alias("ls").description(
|
|
@@ -10533,8 +10714,8 @@ var list = new Command("list").alias("ls").description(
|
|
|
10533
10714
|
printTable(
|
|
10534
10715
|
fields3.map((field) => field.toUpperCase()),
|
|
10535
10716
|
experiments.map(
|
|
10536
|
-
(
|
|
10537
|
-
const value =
|
|
10717
|
+
(experiment2) => fields3.map((field) => {
|
|
10718
|
+
const value = experiment2[field];
|
|
10538
10719
|
return value == null ? "-" : typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
10539
10720
|
})
|
|
10540
10721
|
)
|
|
@@ -10576,13 +10757,13 @@ var get = new Command("get").description(
|
|
|
10576
10757
|
output(this, { ...exp, dashboard_url: dashboardUrl }, () => printExperiment(exp, dashboardUrl));
|
|
10577
10758
|
});
|
|
10578
10759
|
var attachIdea = new Command("attach-idea").description("Attach an experiment to an idea").argument("<experiment>", EXPERIMENT_REFERENCE_DESCRIPTION).requiredOption("--idea <key>", "Idea UUID or Apex backlog key").option("--follow-up", "Attach as a follow-up test").action(async function action5(experimentRef, opts) {
|
|
10579
|
-
const
|
|
10580
|
-
if (!
|
|
10760
|
+
const experiment2 = await resolveExperiment(experimentRef);
|
|
10761
|
+
if (!experiment2) fail(this, `Experiment "${experimentRef}" not found.`, EXIT_CODES.validation);
|
|
10581
10762
|
const classified = classifyIdeaReference(opts.idea);
|
|
10582
10763
|
if (classified.kind === "invalid") {
|
|
10583
10764
|
fail(this, "--idea must be a UUID or Apex backlog key", EXIT_CODES.validation);
|
|
10584
10765
|
}
|
|
10585
|
-
const res = await attachExperimentIdea(requireCredentials(),
|
|
10766
|
+
const res = await attachExperimentIdea(requireCredentials(), experiment2.id, {
|
|
10586
10767
|
...classified.kind === "uuid" ? { backlog_item_id: classified.value } : { idea_key: classified.value },
|
|
10587
10768
|
...opts.followUp ? { as_follow_up: true } : {}
|
|
10588
10769
|
});
|
|
@@ -10596,9 +10777,9 @@ var attachIdea = new Command("attach-idea").description("Attach an experiment to
|
|
|
10596
10777
|
output(this, res.data, () => printSuccess("Idea attached."));
|
|
10597
10778
|
});
|
|
10598
10779
|
var detachIdea = new Command("detach-idea").description("Detach an experiment from its idea").argument("<experiment>", EXPERIMENT_REFERENCE_DESCRIPTION).action(async function action6(experimentRef) {
|
|
10599
|
-
const
|
|
10600
|
-
if (!
|
|
10601
|
-
const res = await detachExperimentIdea(requireCredentials(),
|
|
10780
|
+
const experiment2 = await resolveExperiment(experimentRef);
|
|
10781
|
+
if (!experiment2) fail(this, `Experiment "${experimentRef}" not found.`, EXIT_CODES.validation);
|
|
10782
|
+
const res = await detachExperimentIdea(requireCredentials(), experiment2.id);
|
|
10602
10783
|
if (!res.ok)
|
|
10603
10784
|
fail(
|
|
10604
10785
|
this,
|
|
@@ -11409,9 +11590,9 @@ var experimentStatus = new Command("status").description(
|
|
|
11409
11590
|
apiFailureOf(response)
|
|
11410
11591
|
);
|
|
11411
11592
|
}
|
|
11412
|
-
const
|
|
11413
|
-
if (!
|
|
11414
|
-
const snapshot = await getExperimentFilesystem(creds,
|
|
11593
|
+
const experiment2 = response?.data ?? await resolveExperiment(target);
|
|
11594
|
+
if (!experiment2) fail(this, "Experiment not found.");
|
|
11595
|
+
const snapshot = await getExperimentFilesystem(creds, experiment2.id);
|
|
11415
11596
|
if (!snapshot.ok || !snapshot.data) {
|
|
11416
11597
|
throw new CliError(
|
|
11417
11598
|
snapshot.error ?? "Failed to read experiment filesystem",
|
|
@@ -11420,7 +11601,7 @@ var experimentStatus = new Command("status").description(
|
|
|
11420
11601
|
apiFailureOf(snapshot)
|
|
11421
11602
|
);
|
|
11422
11603
|
}
|
|
11423
|
-
let
|
|
11604
|
+
let versions2 = null;
|
|
11424
11605
|
if (session) {
|
|
11425
11606
|
const localFiles = listExperimentFilesystemFiles(candidate, session);
|
|
11426
11607
|
const localHashes = new Map(localFiles.map((file) => [file.path, file.sha256]));
|
|
@@ -11448,24 +11629,24 @@ var experimentStatus = new Command("status").description(
|
|
|
11448
11629
|
assets: session.assets ?? {},
|
|
11449
11630
|
credentials: creds
|
|
11450
11631
|
});
|
|
11451
|
-
const
|
|
11452
|
-
if (!
|
|
11632
|
+
const plan2 = await planExperimentFilesystem(creds, experiment2.id, { files: plannedFiles });
|
|
11633
|
+
if (!plan2.ok || !plan2.data) {
|
|
11453
11634
|
throw new CliError(
|
|
11454
|
-
|
|
11455
|
-
classifyStatus(
|
|
11456
|
-
|
|
11457
|
-
apiFailureOf(
|
|
11635
|
+
plan2.error ?? "Failed to read saved state",
|
|
11636
|
+
classifyStatus(plan2.status),
|
|
11637
|
+
plan2.errorDetails,
|
|
11638
|
+
apiFailureOf(plan2)
|
|
11458
11639
|
);
|
|
11459
11640
|
}
|
|
11460
|
-
|
|
11641
|
+
versions2 = {
|
|
11461
11642
|
files,
|
|
11462
11643
|
saved: {
|
|
11463
|
-
would_change:
|
|
11464
|
-
invalidates_qa:
|
|
11465
|
-
blocking_issue_count:
|
|
11466
|
-
valid:
|
|
11467
|
-
errors:
|
|
11468
|
-
warnings:
|
|
11644
|
+
would_change: plan2.data.changes.length > 0,
|
|
11645
|
+
invalidates_qa: plan2.data.invalidatesQa,
|
|
11646
|
+
blocking_issue_count: plan2.data.blockingIssues?.filter((issue) => issue.severity === "blocking").length ?? 0,
|
|
11647
|
+
valid: plan2.data.ok,
|
|
11648
|
+
errors: plan2.data.errors,
|
|
11649
|
+
warnings: plan2.data.warnings
|
|
11469
11650
|
}
|
|
11470
11651
|
};
|
|
11471
11652
|
}
|
|
@@ -11481,9 +11662,9 @@ var experimentStatus = new Command("status").description(
|
|
|
11481
11662
|
}
|
|
11482
11663
|
const qa3 = {
|
|
11483
11664
|
...summarizeHandoverQa(qaSnapshot),
|
|
11484
|
-
ready_version_id:
|
|
11665
|
+
ready_version_id: experiment2.ready_version_id ?? null
|
|
11485
11666
|
};
|
|
11486
|
-
const shopId =
|
|
11667
|
+
const shopId = experiment2.shop_id ?? creds.resolvedContext?.shopId ?? null;
|
|
11487
11668
|
let org = {
|
|
11488
11669
|
id: creds.resolvedContext?.orgId ?? null,
|
|
11489
11670
|
name: null
|
|
@@ -11509,13 +11690,13 @@ var experimentStatus = new Command("status").description(
|
|
|
11509
11690
|
"Shop credentials do not expose account context; organization and shop names are unknown."
|
|
11510
11691
|
);
|
|
11511
11692
|
const nextSteps = [];
|
|
11512
|
-
const ref = shellQuote(session ? target :
|
|
11693
|
+
const ref = shellQuote(session ? target : experiment2.id);
|
|
11513
11694
|
if (!session)
|
|
11514
11695
|
nextSteps.push({
|
|
11515
11696
|
command: `apex experiments pull ${ref} --out <folder>`,
|
|
11516
11697
|
why: "Pull files to verify the local version."
|
|
11517
11698
|
});
|
|
11518
|
-
if (
|
|
11699
|
+
if (versions2 && (versions2.files.some((file) => file.state !== "unchanged") || versions2.saved.would_change || !versions2.saved.valid || versions2.saved.blocking_issue_count > 0)) {
|
|
11519
11700
|
nextSteps.push(
|
|
11520
11701
|
{
|
|
11521
11702
|
command: `apex experiments plan ${ref} --json`,
|
|
@@ -11544,14 +11725,14 @@ var experimentStatus = new Command("status").description(
|
|
|
11544
11725
|
why: "Required QA checks are complete; approve after verifying the saved version."
|
|
11545
11726
|
});
|
|
11546
11727
|
}
|
|
11547
|
-
const locallyChanged =
|
|
11728
|
+
const locallyChanged = versions2 !== null && (versions2.files.some((file) => file.state !== "unchanged") || versions2.saved.would_change || !versions2.saved.valid || versions2.saved.blocking_issue_count > 0);
|
|
11548
11729
|
const qaComplete = qa3.required_done !== null && qa3.required_total !== null && qa3.required_done === qa3.required_total && qa3.blockers !== null && qa3.blockers.length === 0;
|
|
11549
11730
|
const readyVersionKnown = typeof qa3.ready_version_id === "string" && qa3.ready_version_id.length > 0;
|
|
11550
11731
|
const approvalCurrent = qa3.approval_present && readyVersionKnown && !locallyChanged;
|
|
11551
|
-
const lifecycleReady =
|
|
11732
|
+
const lifecycleReady = experiment2.status === "ready_to_launch";
|
|
11552
11733
|
const readyForHandover = lifecycleReady && approvalCurrent && qaComplete;
|
|
11553
11734
|
if (approvalCurrent && qaComplete && !lifecycleReady)
|
|
11554
|
-
notes.push(`The experiment status is ${
|
|
11735
|
+
notes.push(`The experiment status is ${experiment2.status}; handover needs ready_to_launch.`);
|
|
11555
11736
|
if (qa3.approval_present && !readyVersionKnown && qaComplete && !locallyChanged)
|
|
11556
11737
|
nextSteps.push({
|
|
11557
11738
|
command: `apex experiments qa approve ${ref}`,
|
|
@@ -11562,7 +11743,7 @@ var experimentStatus = new Command("status").description(
|
|
|
11562
11743
|
command: "launch in the Apex dashboard",
|
|
11563
11744
|
why: "QA is approved for the saved version. Starting a test is a human decision."
|
|
11564
11745
|
});
|
|
11565
|
-
if (!
|
|
11746
|
+
if (!versions2)
|
|
11566
11747
|
notes.push(
|
|
11567
11748
|
"Without a pulled folder, readiness reflects the saved version only; local edits elsewhere are not visible."
|
|
11568
11749
|
);
|
|
@@ -11574,13 +11755,13 @@ var experimentStatus = new Command("status").description(
|
|
|
11574
11755
|
base_url: creds.baseUrl
|
|
11575
11756
|
},
|
|
11576
11757
|
experiment: {
|
|
11577
|
-
id:
|
|
11578
|
-
test_key:
|
|
11579
|
-
name:
|
|
11580
|
-
status:
|
|
11581
|
-
dashboard_url: buildDashboardUrl(creds.baseUrl, shopId,
|
|
11758
|
+
id: experiment2.id,
|
|
11759
|
+
test_key: experiment2.test_key ?? null,
|
|
11760
|
+
name: experiment2.name,
|
|
11761
|
+
status: experiment2.status,
|
|
11762
|
+
dashboard_url: buildDashboardUrl(creds.baseUrl, shopId, experiment2.id)
|
|
11582
11763
|
},
|
|
11583
|
-
versions,
|
|
11764
|
+
versions: versions2,
|
|
11584
11765
|
qa: { ...qa3, approval_current: approvalCurrent },
|
|
11585
11766
|
next_steps: nextSteps,
|
|
11586
11767
|
ready_for_handover: readyForHandover,
|
|
@@ -11593,16 +11774,16 @@ Credential: ${data.context.credential_kind}
|
|
|
11593
11774
|
Base URL: ${creds.baseUrl}`
|
|
11594
11775
|
);
|
|
11595
11776
|
console.log(
|
|
11596
|
-
`Experiment: ${
|
|
11777
|
+
`Experiment: ${experiment2.test_key ?? experiment2.id} / ${experiment2.name} (${experiment2.status})
|
|
11597
11778
|
Dashboard: ${data.experiment.dashboard_url ?? "unknown"}`
|
|
11598
11779
|
);
|
|
11599
11780
|
console.log("Versions:");
|
|
11600
|
-
if (
|
|
11601
|
-
for (const file of
|
|
11781
|
+
if (versions2) {
|
|
11782
|
+
for (const file of versions2.files) console.log(` ${file.state}: ${file.path}`);
|
|
11602
11783
|
console.log(
|
|
11603
|
-
` Saved: would change ${
|
|
11784
|
+
` Saved: would change ${versions2.saved.would_change}; invalidates QA ${versions2.saved.invalidates_qa}; blocking issues ${versions2.saved.blocking_issue_count}`
|
|
11604
11785
|
);
|
|
11605
|
-
for (const message of [...
|
|
11786
|
+
for (const message of [...versions2.saved.errors, ...versions2.saved.warnings])
|
|
11606
11787
|
console.log(` ${message}`);
|
|
11607
11788
|
} else console.log(" unknown (no pulled folder)");
|
|
11608
11789
|
console.log(
|
|
@@ -12075,13 +12256,13 @@ function normalizeEvidenceDevice(value) {
|
|
|
12075
12256
|
if (normalized === "ipad_pro") return "tablet";
|
|
12076
12257
|
return normalized;
|
|
12077
12258
|
}
|
|
12078
|
-
function resolveEvidenceVariation(
|
|
12259
|
+
function resolveEvidenceVariation(experiment2, ref) {
|
|
12079
12260
|
if (!ref) return void 0;
|
|
12080
|
-
const exact =
|
|
12261
|
+
const exact = experiment2.variations?.find(
|
|
12081
12262
|
(variation) => variation.id === ref || variation.slug === ref
|
|
12082
12263
|
);
|
|
12083
12264
|
if (exact) return exact.id;
|
|
12084
|
-
const matches = (
|
|
12265
|
+
const matches = (experiment2.variations ?? []).filter(
|
|
12085
12266
|
(variation) => variation.name.toLowerCase() === ref.toLowerCase()
|
|
12086
12267
|
);
|
|
12087
12268
|
if (matches.length === 1) return matches[0].id;
|
|
@@ -12114,7 +12295,7 @@ var qaEvidence = new Command("evidence").description("Attach screenshot jobs or
|
|
|
12114
12295
|
classifyStatus(current.status),
|
|
12115
12296
|
current.errorDetails
|
|
12116
12297
|
);
|
|
12117
|
-
for (const
|
|
12298
|
+
for (const record4 of current.data.run.evidenceRecords) previousIds.add(record4.id);
|
|
12118
12299
|
}
|
|
12119
12300
|
let response;
|
|
12120
12301
|
if (opts.job) {
|
|
@@ -12128,9 +12309,9 @@ var qaEvidence = new Command("evidence").description("Attach screenshot jobs or
|
|
|
12128
12309
|
records: parsed
|
|
12129
12310
|
});
|
|
12130
12311
|
} else {
|
|
12131
|
-
const
|
|
12132
|
-
if (!
|
|
12133
|
-
const variationId = resolveEvidenceVariation(
|
|
12312
|
+
const experiment2 = await resolveExperiment(resolved.experimentId);
|
|
12313
|
+
if (!experiment2) throw new Error("Experiment not found.");
|
|
12314
|
+
const variationId = resolveEvidenceVariation(experiment2, opts.variation);
|
|
12134
12315
|
const form = new FormData();
|
|
12135
12316
|
for (const filePath of opts.file) {
|
|
12136
12317
|
const absolutePath = import_node_path6.default.resolve(process.cwd(), filePath);
|
|
@@ -12159,7 +12340,7 @@ var qaEvidence = new Command("evidence").description("Attach screenshot jobs or
|
|
|
12159
12340
|
const run3 = response.data.run;
|
|
12160
12341
|
const data = opts.summary ? qaSummary(
|
|
12161
12342
|
run3,
|
|
12162
|
-
run3.evidenceRecords.filter((
|
|
12343
|
+
run3.evidenceRecords.filter((record4) => !previousIds.has(record4.id))
|
|
12163
12344
|
) : response.data;
|
|
12164
12345
|
output(this, data, () => {
|
|
12165
12346
|
if (opts.summary) process.stdout.write(`${JSON.stringify(data, null, 2)}
|
|
@@ -12185,8 +12366,8 @@ function shellQuote(value) {
|
|
|
12185
12366
|
if (value.length > 0 && /^[A-Za-z0-9_@%+=:,./-]+$/.test(value)) return value;
|
|
12186
12367
|
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
12187
12368
|
}
|
|
12188
|
-
function isTransientResumeStatus(
|
|
12189
|
-
return
|
|
12369
|
+
function isTransientResumeStatus(status4) {
|
|
12370
|
+
return status4 === 429 || status4 !== void 0 && status4 >= 500;
|
|
12190
12371
|
}
|
|
12191
12372
|
function screenshotTimeoutMessage(job, experimentRef, out, seconds, flags = {}) {
|
|
12192
12373
|
const progress = job.progress ? ` Last progress: ${job.progress.captured}/${job.progress.total} captured.` : "";
|
|
@@ -12214,8 +12395,8 @@ var screenshots = new Command("screenshots").description(
|
|
|
12214
12395
|
const explicitJson = process.argv.includes("--json");
|
|
12215
12396
|
if (!Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0)
|
|
12216
12397
|
fail(this, "--timeout must be a positive number of seconds.", EXIT_CODES.validation);
|
|
12217
|
-
const
|
|
12218
|
-
if (!
|
|
12398
|
+
const experiment2 = await resolveExperiment(experimentRef);
|
|
12399
|
+
if (!experiment2?.variations?.length)
|
|
12219
12400
|
fail(this, "Experiment has no variations to capture.", EXIT_CODES.validation);
|
|
12220
12401
|
const creds = requireCredentials();
|
|
12221
12402
|
let devices;
|
|
@@ -12278,13 +12459,13 @@ var screenshots = new Command("screenshots").description(
|
|
|
12278
12459
|
if (!job.experiment_id)
|
|
12279
12460
|
fail(
|
|
12280
12461
|
this,
|
|
12281
|
-
`Screenshot job ${job.job_id} names no experiment; refusing to resume it under ${experimentRef} (${
|
|
12462
|
+
`Screenshot job ${job.job_id} names no experiment; refusing to resume it under ${experimentRef} (${experiment2.id}).`,
|
|
12282
12463
|
EXIT_CODES.validation
|
|
12283
12464
|
);
|
|
12284
|
-
if (job.experiment_id !==
|
|
12465
|
+
if (job.experiment_id !== experiment2.id)
|
|
12285
12466
|
fail(
|
|
12286
12467
|
this,
|
|
12287
|
-
`Screenshot job ${job.job_id} belongs to experiment ${job.experiment_id}, not ${experimentRef} (${
|
|
12468
|
+
`Screenshot job ${job.job_id} belongs to experiment ${job.experiment_id}, not ${experimentRef} (${experiment2.id}).`,
|
|
12288
12469
|
EXIT_CODES.validation
|
|
12289
12470
|
);
|
|
12290
12471
|
const storedDevices = job.devices ?? [];
|
|
@@ -12307,7 +12488,7 @@ var screenshots = new Command("screenshots").description(
|
|
|
12307
12488
|
`Screenshot job ${job.job_id} stores its results in a legacy shape this CLI cannot download; create a new job instead of resuming it.`,
|
|
12308
12489
|
EXIT_CODES.validation
|
|
12309
12490
|
);
|
|
12310
|
-
const knownVariationIds = new Set(
|
|
12491
|
+
const knownVariationIds = new Set(experiment2.variations.map((variation) => variation.id));
|
|
12311
12492
|
const storedVariationIds = job.variation_ids ?? [];
|
|
12312
12493
|
for (const id of storedVariationIds.filter((id2) => !knownVariationIds.has(id2)))
|
|
12313
12494
|
console.error(
|
|
@@ -12334,15 +12515,15 @@ var screenshots = new Command("screenshots").description(
|
|
|
12334
12515
|
EXIT_CODES.validation
|
|
12335
12516
|
);
|
|
12336
12517
|
const requested = opts.variations ? commaList(opts.variations) : [];
|
|
12337
|
-
const control =
|
|
12518
|
+
const control = experiment2.variations.find((variation) => variation.is_control);
|
|
12338
12519
|
if (!control) fail(this, "Experiment has no control variation.", EXIT_CODES.validation);
|
|
12339
12520
|
const matchedById = /* @__PURE__ */ new Map();
|
|
12340
12521
|
const unmatched = [];
|
|
12341
12522
|
for (const ref of requested) {
|
|
12342
|
-
const exactMatch =
|
|
12523
|
+
const exactMatch = experiment2.variations.find(
|
|
12343
12524
|
(candidate) => ref === candidate.id || ref === candidate.slug
|
|
12344
12525
|
);
|
|
12345
|
-
const nameMatches = exactMatch ? [] :
|
|
12526
|
+
const nameMatches = exactMatch ? [] : experiment2.variations.filter(
|
|
12346
12527
|
(candidate) => ref.toLowerCase() === candidate.name.toLowerCase()
|
|
12347
12528
|
);
|
|
12348
12529
|
if (nameMatches.length > 1) {
|
|
@@ -12361,9 +12542,9 @@ var screenshots = new Command("screenshots").description(
|
|
|
12361
12542
|
}
|
|
12362
12543
|
if (unmatched.length)
|
|
12363
12544
|
fail(this, `Unknown --variations value(s): ${unmatched.join(", ")}`, EXIT_CODES.validation);
|
|
12364
|
-
const selected2 = requested.length ? [...matchedById.values()] :
|
|
12545
|
+
const selected2 = requested.length ? [...matchedById.values()] : experiment2.variations;
|
|
12365
12546
|
variationIds = [.../* @__PURE__ */ new Set([control.id, ...selected2.map((variation) => variation.id)])];
|
|
12366
|
-
const created = await createExperimentScreenshots(creds,
|
|
12547
|
+
const created = await createExperimentScreenshots(creds, experiment2.id, {
|
|
12367
12548
|
devices,
|
|
12368
12549
|
variation_ids: variationIds
|
|
12369
12550
|
});
|
|
@@ -12448,7 +12629,7 @@ var screenshots = new Command("screenshots").description(
|
|
|
12448
12629
|
delayMs = Math.min(Math.round(delayMs * 1.6), 1e4);
|
|
12449
12630
|
}
|
|
12450
12631
|
if (opts.attachQa !== false) {
|
|
12451
|
-
const attached = await attachExperimentQaEvidence(creds,
|
|
12632
|
+
const attached = await attachExperimentQaEvidence(creds, experiment2.id, {
|
|
12452
12633
|
screenshot_job_id: job.job_id
|
|
12453
12634
|
});
|
|
12454
12635
|
if (!attached.ok) {
|
|
@@ -12475,7 +12656,7 @@ var screenshots = new Command("screenshots").description(
|
|
|
12475
12656
|
const rows = [];
|
|
12476
12657
|
const errors = [];
|
|
12477
12658
|
const usedFilenames = /* @__PURE__ */ new Set();
|
|
12478
|
-
const hasControlVariation =
|
|
12659
|
+
const hasControlVariation = experiment2.variations.some((item) => item.is_control);
|
|
12479
12660
|
const downloads = [];
|
|
12480
12661
|
if (Array.isArray(job.results))
|
|
12481
12662
|
fail(
|
|
@@ -12484,7 +12665,7 @@ var screenshots = new Command("screenshots").description(
|
|
|
12484
12665
|
EXIT_CODES.validation
|
|
12485
12666
|
);
|
|
12486
12667
|
for (const variationId of variationIds) {
|
|
12487
|
-
const variation =
|
|
12668
|
+
const variation = experiment2.variations.find((item) => item.id === variationId);
|
|
12488
12669
|
if (!variation) {
|
|
12489
12670
|
console.error(
|
|
12490
12671
|
source_default.yellow(
|
|
@@ -12500,7 +12681,7 @@ var screenshots = new Command("screenshots").description(
|
|
|
12500
12681
|
continue;
|
|
12501
12682
|
}
|
|
12502
12683
|
const pairPrefix = opts.labelPairs === false || !hasControlVariation ? "" : variation.is_control ? "before-" : "after-";
|
|
12503
|
-
const baseFilename = `${pairPrefix}${safeFilenamePart(
|
|
12684
|
+
const baseFilename = `${pairPrefix}${safeFilenamePart(experiment2.slug ?? experiment2.name)}-${safeFilenamePart(variation.slug ?? variation.name)}-${safeFilenamePart(device)}`;
|
|
12504
12685
|
let filename = `${baseFilename}.png`;
|
|
12505
12686
|
if (usedFilenames.has(filename)) {
|
|
12506
12687
|
const suffix = variation.id.replace(/[^a-z0-9]/gi, "").slice(-8).toLowerCase();
|
|
@@ -12872,8 +13053,8 @@ var previewCommand = new Command("preview").description(
|
|
|
12872
13053
|
});
|
|
12873
13054
|
if (opts.open !== false && !shouldUseJson(previewCommand)) {
|
|
12874
13055
|
try {
|
|
12875
|
-
const
|
|
12876
|
-
await
|
|
13056
|
+
const open3 = (await Promise.resolve().then(() => (init_open(), open_exports))).default;
|
|
13057
|
+
await open3(data.preview_url);
|
|
12877
13058
|
} catch {
|
|
12878
13059
|
console.error(source_default.dim("Copy the URL above to preview."));
|
|
12879
13060
|
}
|
|
@@ -12974,8 +13155,8 @@ var previewCommand = new Command("preview").description(
|
|
|
12974
13155
|
);
|
|
12975
13156
|
if (opts?.open !== false && !shouldUseJson(previewCommand)) {
|
|
12976
13157
|
try {
|
|
12977
|
-
const
|
|
12978
|
-
await
|
|
13158
|
+
const open3 = (await Promise.resolve().then(() => (init_open(), open_exports))).default;
|
|
13159
|
+
await open3(previewUrl.toString());
|
|
12979
13160
|
console.log(source_default.dim("Opened in browser."));
|
|
12980
13161
|
} catch {
|
|
12981
13162
|
console.log(source_default.dim("Copy the URL above to preview."));
|
|
@@ -13007,19 +13188,19 @@ function apexConfigPath(folder) {
|
|
|
13007
13188
|
return import_node_path8.default.join(folder, "apex.harness.json");
|
|
13008
13189
|
}
|
|
13009
13190
|
function loadHarnessConfig(folder) {
|
|
13010
|
-
const
|
|
13191
|
+
const experiment2 = readJson(experimentConfigPath(folder)) ?? {};
|
|
13011
13192
|
const apex = readJson(apexConfigPath(folder));
|
|
13012
|
-
if (apex) return { apex, experiment };
|
|
13013
|
-
const targetOrigin =
|
|
13014
|
-
const defaultPath =
|
|
13193
|
+
if (apex) return { apex, experiment: experiment2 };
|
|
13194
|
+
const targetOrigin = experiment2.targetOrigin?.replace(/\/+$/, "") ?? "";
|
|
13195
|
+
const defaultPath = experiment2.defaultPath ?? "/";
|
|
13015
13196
|
const targetUrl = targetOrigin ? new URL(defaultPath, targetOrigin).toString() : "";
|
|
13016
|
-
const id =
|
|
13197
|
+
const id = experiment2.id ?? import_node_path8.default.basename(import_node_path8.default.dirname(folder));
|
|
13017
13198
|
const variation = import_node_path8.default.basename(folder);
|
|
13018
13199
|
return {
|
|
13019
|
-
experiment,
|
|
13200
|
+
experiment: experiment2,
|
|
13020
13201
|
apex: {
|
|
13021
13202
|
experimentSlug: slugify(id, "experiment"),
|
|
13022
|
-
experimentName:
|
|
13203
|
+
experimentName: experiment2.name ?? id,
|
|
13023
13204
|
variationSlug: slugify(variation, "variant-a"),
|
|
13024
13205
|
variationName: `Variant ${variation}`,
|
|
13025
13206
|
targetUrl,
|
|
@@ -13027,8 +13208,8 @@ function loadHarnessConfig(folder) {
|
|
|
13027
13208
|
}
|
|
13028
13209
|
};
|
|
13029
13210
|
}
|
|
13030
|
-
function cleanupResetJs(
|
|
13031
|
-
const cleanupGlobal =
|
|
13211
|
+
function cleanupResetJs(experiment2) {
|
|
13212
|
+
const cleanupGlobal = experiment2.inject?.cleanupGlobal?.trim();
|
|
13032
13213
|
if (!cleanupGlobal) return null;
|
|
13033
13214
|
return `if (typeof window[${JSON.stringify(cleanupGlobal)}] === "function") window[${JSON.stringify(cleanupGlobal)}]();`;
|
|
13034
13215
|
}
|
|
@@ -13046,8 +13227,8 @@ function runAbHarness(args, cwd) {
|
|
|
13046
13227
|
});
|
|
13047
13228
|
});
|
|
13048
13229
|
}
|
|
13049
|
-
function artifactPaths(folder,
|
|
13050
|
-
const variant =
|
|
13230
|
+
function artifactPaths(folder, experiment2) {
|
|
13231
|
+
const variant = experiment2.variant;
|
|
13051
13232
|
if (variant?.endsWith(".bundle.js")) {
|
|
13052
13233
|
const jsPath = import_node_path8.default.join(folder, variant.replace(/\.bundle\.js$/, ".js"));
|
|
13053
13234
|
const cssPath = import_node_path8.default.join(folder, variant.replace(/\.bundle\.js$/, ".css"));
|
|
@@ -13103,7 +13284,7 @@ async function uploadVersion(params) {
|
|
|
13103
13284
|
}
|
|
13104
13285
|
async function createSession(folder) {
|
|
13105
13286
|
const creds = requireCredentials();
|
|
13106
|
-
const { apex, experiment } = loadHarnessConfig(folder);
|
|
13287
|
+
const { apex, experiment: experiment2 } = loadHarnessConfig(folder);
|
|
13107
13288
|
if (!apex.targetUrl) {
|
|
13108
13289
|
throw new Error("Missing targetUrl. Run `apex harness init` and set targetUrl.");
|
|
13109
13290
|
}
|
|
@@ -13113,7 +13294,7 @@ async function createSession(folder) {
|
|
|
13113
13294
|
variationSlug: apex.variationSlug,
|
|
13114
13295
|
variationName: apex.variationName,
|
|
13115
13296
|
targetUrl: apex.targetUrl,
|
|
13116
|
-
resetJs: cleanupResetJs(
|
|
13297
|
+
resetJs: cleanupResetJs(experiment2),
|
|
13117
13298
|
antiFlickerSelectors: apex.antiFlickerSelectors ?? []
|
|
13118
13299
|
});
|
|
13119
13300
|
if (!res.ok)
|
|
@@ -13122,7 +13303,7 @@ async function createSession(folder) {
|
|
|
13122
13303
|
classifyStatus(res.status),
|
|
13123
13304
|
res.errorDetails
|
|
13124
13305
|
);
|
|
13125
|
-
return { session: res.data, apex, experiment };
|
|
13306
|
+
return { session: res.data, apex, experiment: experiment2 };
|
|
13126
13307
|
}
|
|
13127
13308
|
var harnessCommand = new Command("harness").description(
|
|
13128
13309
|
"Develop AB Test Harness projects through Apex live QA sessions"
|
|
@@ -13172,8 +13353,8 @@ harnessCommand.command("dev").argument("<folder>", "AB Test Harness experiment f
|
|
|
13172
13353
|
}
|
|
13173
13354
|
);
|
|
13174
13355
|
if (opts.open !== false) {
|
|
13175
|
-
const
|
|
13176
|
-
await
|
|
13356
|
+
const open3 = (await Promise.resolve().then(() => (init_open(), open_exports))).default;
|
|
13357
|
+
await open3(created.session.previewUrl);
|
|
13177
13358
|
}
|
|
13178
13359
|
let uploading = false;
|
|
13179
13360
|
let queued = false;
|
|
@@ -13619,12 +13800,12 @@ operatorCommand.command("checkout").argument("<experiment-id>", "Apex experiment
|
|
|
13619
13800
|
classifyStatus(expRes.status),
|
|
13620
13801
|
expRes.errorDetails
|
|
13621
13802
|
);
|
|
13622
|
-
const
|
|
13623
|
-
const variation = resolveVariation(
|
|
13803
|
+
const experiment2 = expRes.data;
|
|
13804
|
+
const variation = resolveVariation(experiment2, opts.variation);
|
|
13624
13805
|
const folder = folderPath2(
|
|
13625
|
-
opts.out ?? slugify2(`${
|
|
13806
|
+
opts.out ?? slugify2(`${experiment2.name}-${variation.name}`, "apex-operator")
|
|
13626
13807
|
);
|
|
13627
|
-
const workspace = await getOperatorWorkspace(creds,
|
|
13808
|
+
const workspace = await getOperatorWorkspace(creds, experiment2.id, variation.id);
|
|
13628
13809
|
if (!workspace.ok || !workspace.data)
|
|
13629
13810
|
throw new CliError(
|
|
13630
13811
|
workspace.error ?? "Could not load workspace",
|
|
@@ -13637,7 +13818,7 @@ operatorCommand.command("checkout").argument("<experiment-id>", "Apex experiment
|
|
|
13637
13818
|
folder,
|
|
13638
13819
|
sessionFromWorkspace({
|
|
13639
13820
|
credsBaseUrl: creds.baseUrl,
|
|
13640
|
-
experimentId:
|
|
13821
|
+
experimentId: experiment2.id,
|
|
13641
13822
|
variationId: variation.id,
|
|
13642
13823
|
workspace: workspace.data
|
|
13643
13824
|
})
|
|
@@ -13646,12 +13827,12 @@ operatorCommand.command("checkout").argument("<experiment-id>", "Apex experiment
|
|
|
13646
13827
|
this,
|
|
13647
13828
|
{
|
|
13648
13829
|
folder,
|
|
13649
|
-
experimentId:
|
|
13830
|
+
experimentId: experiment2.id,
|
|
13650
13831
|
variationId: variation.id,
|
|
13651
13832
|
files: workspace.data.files.map((file) => file.path)
|
|
13652
13833
|
},
|
|
13653
13834
|
() => {
|
|
13654
|
-
printSuccess(`Checked out ${
|
|
13835
|
+
printSuccess(`Checked out ${experiment2.name} / ${variation.name}`);
|
|
13655
13836
|
console.log(source_default.cyan(folder));
|
|
13656
13837
|
}
|
|
13657
13838
|
);
|
|
@@ -13726,8 +13907,8 @@ operatorCommand.command("dev").argument("[folder]", "Operator workspace folder",
|
|
|
13726
13907
|
}
|
|
13727
13908
|
);
|
|
13728
13909
|
if (opts.open !== false) {
|
|
13729
|
-
const
|
|
13730
|
-
await
|
|
13910
|
+
const open3 = (await Promise.resolve().then(() => (init_open(), open_exports))).default;
|
|
13911
|
+
await open3(harnessSession.previewUrl);
|
|
13731
13912
|
}
|
|
13732
13913
|
if (opts.once) return;
|
|
13733
13914
|
let uploading = false;
|
|
@@ -13845,7 +14026,7 @@ var approved = new Command("approved").description("List approved roadmap ideas
|
|
|
13845
14026
|
["ID", "SCORE", "TYPE", "EXPERIMENT", "TITLE"],
|
|
13846
14027
|
items.map((item) => [
|
|
13847
14028
|
item.id,
|
|
13848
|
-
score(item.
|
|
14029
|
+
score(item.apex_score),
|
|
13849
14030
|
truncate(item.action_type, 14),
|
|
13850
14031
|
truncate(experimentState(item), 18),
|
|
13851
14032
|
truncate(item.title, 54)
|
|
@@ -14147,8 +14328,8 @@ trackingCommand.command("verify").description("Verify live tracking events for t
|
|
|
14147
14328
|
["TIME", "EVENT", "PROPERTIES"],
|
|
14148
14329
|
res.data.recent_events.map((event) => {
|
|
14149
14330
|
const raw = JSON.stringify(trackingEventProperties(event));
|
|
14150
|
-
const
|
|
14151
|
-
return [String(event.timestamp ?? "-"), String(event.event_type ?? "-"),
|
|
14331
|
+
const preview2 = raw.length > 120 ? `${raw.slice(0, 117)}...` : raw;
|
|
14332
|
+
return [String(event.timestamp ?? "-"), String(event.event_type ?? "-"), preview2];
|
|
14152
14333
|
})
|
|
14153
14334
|
);
|
|
14154
14335
|
}
|
|
@@ -15348,8 +15529,8 @@ var import_node_fs18 = __toESM(require("node:fs"));
|
|
|
15348
15529
|
var import_node_path15 = __toESM(require("node:path"));
|
|
15349
15530
|
|
|
15350
15531
|
// src/trigger-proposal.ts
|
|
15351
|
-
function runtimePath(
|
|
15352
|
-
return
|
|
15532
|
+
function runtimePath(path26) {
|
|
15533
|
+
return path26.replaceAll("[]", ".0");
|
|
15353
15534
|
}
|
|
15354
15535
|
function fieldsFrom(value, prefix = "", descriptorList = false) {
|
|
15355
15536
|
if (!value || typeof value !== "object") return [];
|
|
@@ -15362,18 +15543,18 @@ function fieldsFrom(value, prefix = "", descriptorList = false) {
|
|
|
15362
15543
|
)
|
|
15363
15544
|
);
|
|
15364
15545
|
}
|
|
15365
|
-
const
|
|
15366
|
-
const explicitPath = typeof
|
|
15546
|
+
const record4 = value;
|
|
15547
|
+
const explicitPath = typeof record4.path === "string" ? record4.path : void 0;
|
|
15367
15548
|
if (descriptorList && explicitPath) {
|
|
15368
|
-
const rawValues =
|
|
15549
|
+
const rawValues = record4.values ?? record4.samples ?? record4.examples ?? record4.example;
|
|
15369
15550
|
const values = Array.isArray(rawValues) ? rawValues : rawValues === void 0 ? [] : [rawValues];
|
|
15370
|
-
const magnitude = [
|
|
15551
|
+
const magnitude = [record4.mean, record4.average, record4.max, record4.magnitude].find(
|
|
15371
15552
|
(candidate) => typeof candidate === "number"
|
|
15372
15553
|
);
|
|
15373
|
-
const numericType = Array.isArray(
|
|
15554
|
+
const numericType = Array.isArray(record4.types) && record4.types.length > 0 && record4.types.every((type) => type === "num" || type === "numstr" || type === "numstr_comma");
|
|
15374
15555
|
return [{ path: runtimePath(explicitPath), values, magnitude, numericType }];
|
|
15375
15556
|
}
|
|
15376
|
-
return Object.entries(
|
|
15557
|
+
return Object.entries(record4).flatMap(([key, child]) => {
|
|
15377
15558
|
if (["eventName", "event_name", "name", "count", "frequency", "evidence"].includes(key))
|
|
15378
15559
|
return [];
|
|
15379
15560
|
const next = prefix ? `${prefix}.${key}` : key;
|
|
@@ -15406,8 +15587,8 @@ function isDescriptorArray(value) {
|
|
|
15406
15587
|
]);
|
|
15407
15588
|
return Array.isArray(value) && value.length > 0 && value.every((item) => {
|
|
15408
15589
|
if (!item || typeof item !== "object" || Array.isArray(item)) return false;
|
|
15409
|
-
const
|
|
15410
|
-
return typeof
|
|
15590
|
+
const record4 = item;
|
|
15591
|
+
return typeof record4.path === "string" && record4.path.length > 0 && Object.keys(record4).every((key) => descriptorKeys.has(key));
|
|
15411
15592
|
});
|
|
15412
15593
|
}
|
|
15413
15594
|
function candidatesFrom(report) {
|
|
@@ -15427,9 +15608,9 @@ function candidatesFrom(report) {
|
|
|
15427
15608
|
}
|
|
15428
15609
|
return [root];
|
|
15429
15610
|
}
|
|
15430
|
-
function score2(
|
|
15611
|
+
function score2(path26, patterns) {
|
|
15431
15612
|
return patterns.reduce(
|
|
15432
|
-
(total, pattern, index) => total + (pattern.test(
|
|
15613
|
+
(total, pattern, index) => total + (pattern.test(path26) ? patterns.length - index : 0),
|
|
15433
15614
|
0
|
|
15434
15615
|
);
|
|
15435
15616
|
}
|
|
@@ -15563,10 +15744,10 @@ var whenOps = new Set(runtime_settings_trigger_constants_default.whenOps);
|
|
|
15563
15744
|
function strictObject(value, location, keys) {
|
|
15564
15745
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
15565
15746
|
throw new Error(`${location} must be an object`);
|
|
15566
|
-
const
|
|
15567
|
-
for (const key of Object.keys(
|
|
15747
|
+
const record4 = value;
|
|
15748
|
+
for (const key of Object.keys(record4))
|
|
15568
15749
|
if (!keys.has(key)) throw new Error(`${location}.${key} is not allowed`);
|
|
15569
|
-
return
|
|
15750
|
+
return record4;
|
|
15570
15751
|
}
|
|
15571
15752
|
function optionalType(value, type, location) {
|
|
15572
15753
|
if (value !== void 0 && typeof value !== type)
|
|
@@ -15702,19 +15883,19 @@ function strictTriggers(value) {
|
|
|
15702
15883
|
return value.map((item, index) => {
|
|
15703
15884
|
if (!item || typeof item !== "object" || Array.isArray(item))
|
|
15704
15885
|
throw new Error(`trigger[${index}] must be an object`);
|
|
15705
|
-
const
|
|
15706
|
-
for (const key of Object.keys(
|
|
15886
|
+
const record4 = item;
|
|
15887
|
+
for (const key of Object.keys(record4))
|
|
15707
15888
|
if (!allowed.has(key)) throw new Error(`trigger[${index}].${key} is not allowed`);
|
|
15708
|
-
if (typeof
|
|
15889
|
+
if (typeof record4.eventName !== "string" || !record4.eventName.trim())
|
|
15709
15890
|
throw new Error(`trigger[${index}].eventName must be a non-empty string`);
|
|
15710
15891
|
for (const key of ["enabled", "reEvaluate", "trackPageview"])
|
|
15711
|
-
optionalType(
|
|
15892
|
+
optionalType(record4[key], "boolean", `trigger[${index}].${key}`);
|
|
15712
15893
|
for (const key of ["dataLayerName", "trackEventName"])
|
|
15713
|
-
optionalType(
|
|
15714
|
-
strictWhen(
|
|
15715
|
-
if (
|
|
15894
|
+
optionalType(record4[key], "string", `trigger[${index}].${key}`);
|
|
15895
|
+
strictWhen(record4.when, `trigger[${index}].when`);
|
|
15896
|
+
if (record4.trackRevenue !== void 0) {
|
|
15716
15897
|
const revenue = strictObject(
|
|
15717
|
-
|
|
15898
|
+
record4.trackRevenue,
|
|
15718
15899
|
`trigger[${index}].trackRevenue`,
|
|
15719
15900
|
/* @__PURE__ */ new Set([
|
|
15720
15901
|
"enabled",
|
|
@@ -15737,21 +15918,21 @@ function strictTriggers(value) {
|
|
|
15737
15918
|
);
|
|
15738
15919
|
}
|
|
15739
15920
|
}
|
|
15740
|
-
if (
|
|
15741
|
-
if (!Array.isArray(
|
|
15921
|
+
if (record4.mappings !== void 0) {
|
|
15922
|
+
if (!Array.isArray(record4.mappings))
|
|
15742
15923
|
throw new Error(`trigger[${index}].mappings must be an array`);
|
|
15743
|
-
|
|
15924
|
+
record4.mappings.forEach(
|
|
15744
15925
|
(mapping, mappingIndex) => strictMapping(mapping, `trigger[${index}].mappings[${mappingIndex}]`, false)
|
|
15745
15926
|
);
|
|
15746
15927
|
}
|
|
15747
|
-
if (
|
|
15748
|
-
if (!Array.isArray(
|
|
15928
|
+
if (record4.properties !== void 0) {
|
|
15929
|
+
if (!Array.isArray(record4.properties))
|
|
15749
15930
|
throw new Error(`trigger[${index}].properties must be an array`);
|
|
15750
|
-
|
|
15931
|
+
record4.properties.forEach(
|
|
15751
15932
|
(mapping, mappingIndex) => strictMapping(mapping, `trigger[${index}].properties[${mappingIndex}]`, true)
|
|
15752
15933
|
);
|
|
15753
15934
|
}
|
|
15754
|
-
return
|
|
15935
|
+
return record4;
|
|
15755
15936
|
});
|
|
15756
15937
|
}
|
|
15757
15938
|
function writeWarnings(result) {
|
|
@@ -16004,6 +16185,15 @@ init_source();
|
|
|
16004
16185
|
function normalizeDomain(value) {
|
|
16005
16186
|
return value.trim().replace(/^https?:\/\//i, "").replace(/[/?#].*$/, "").replace(/\.$/, "").toLowerCase();
|
|
16006
16187
|
}
|
|
16188
|
+
function landingHost(value) {
|
|
16189
|
+
const trimmed = value.trim();
|
|
16190
|
+
try {
|
|
16191
|
+
const withScheme = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
16192
|
+
return new URL(withScheme).hostname.toLowerCase().replace(/\.$/, "") || normalizeDomain(trimmed);
|
|
16193
|
+
} catch {
|
|
16194
|
+
return normalizeDomain(trimmed);
|
|
16195
|
+
}
|
|
16196
|
+
}
|
|
16007
16197
|
function warn2(command, precompile) {
|
|
16008
16198
|
if (!shouldUseJson(command) && precompile && precompile.status !== "ok")
|
|
16009
16199
|
console.error(source_default.yellow(`Warning (${precompile.status}): ${precompile.summary}`));
|
|
@@ -16058,7 +16248,7 @@ var add = new Command("add").description("Add install domains").argument("<domai
|
|
|
16058
16248
|
);
|
|
16059
16249
|
output(this, data, () => printDomains(data));
|
|
16060
16250
|
});
|
|
16061
|
-
var
|
|
16251
|
+
var removeInstall = new Command("remove").description("Remove install domains").argument("<domain...>").action(async function action53(domain) {
|
|
16062
16252
|
const removeSet = new Set(domain.map(normalizeDomain));
|
|
16063
16253
|
const data = await patchWithRetry(
|
|
16064
16254
|
this,
|
|
@@ -16071,7 +16261,194 @@ var set3 = new Command("set").description("Replace install domains").argument("<
|
|
|
16071
16261
|
const data = await patchWithRetry(this, () => replacement);
|
|
16072
16262
|
output(this, data, () => printDomains(data));
|
|
16073
16263
|
});
|
|
16074
|
-
var
|
|
16264
|
+
var configuredInterval = Number(process.env.APEX_CLI_DOMAIN_VERIFY_INTERVAL_MS);
|
|
16265
|
+
var VERIFY_INTERVAL_MS = Number.isFinite(configuredInterval) && configuredInterval > 0 ? configuredInterval : 1e4;
|
|
16266
|
+
var VERIFY_WINDOW_MS = 12e4;
|
|
16267
|
+
function domainFail(command, result, fallback2) {
|
|
16268
|
+
return fail(
|
|
16269
|
+
command,
|
|
16270
|
+
result.error ?? fallback2,
|
|
16271
|
+
classifyStatus(result.status),
|
|
16272
|
+
result.errorDetails
|
|
16273
|
+
);
|
|
16274
|
+
}
|
|
16275
|
+
function printInstructions(domain) {
|
|
16276
|
+
if (!domain.dns_instructions.length) return;
|
|
16277
|
+
console.log("Add these DNS records at the DNS provider:");
|
|
16278
|
+
printTable(
|
|
16279
|
+
["TYPE", "NAME", "VALUE"],
|
|
16280
|
+
domain.dns_instructions.map((record4) => [record4.type, record4.name, record4.value]),
|
|
16281
|
+
[6, 44, 48]
|
|
16282
|
+
);
|
|
16283
|
+
}
|
|
16284
|
+
function printDomainStatus(domain) {
|
|
16285
|
+
console.log(
|
|
16286
|
+
`${domain.host}: ${domain.status}${domain.status_message ? ` (${domain.status_message})` : ""}`
|
|
16287
|
+
);
|
|
16288
|
+
}
|
|
16289
|
+
async function findLandingDomain(command, host) {
|
|
16290
|
+
const wanted = landingHost(host);
|
|
16291
|
+
const listed = await getLandingPageDomains(requireCredentials(), { host: wanted });
|
|
16292
|
+
if (!listed.ok || !listed.data)
|
|
16293
|
+
domainFail(command, listed, "Failed to load landing-page domains");
|
|
16294
|
+
const row = listed.data.data.find((item) => item.host === wanted);
|
|
16295
|
+
if (!row) fail(command, `No landing-page domain ${wanted} on this shop`, EXIT_CODES.general);
|
|
16296
|
+
return row;
|
|
16297
|
+
}
|
|
16298
|
+
async function verifyOnce(command, id) {
|
|
16299
|
+
const checked = await verifyLandingPageDomain(requireCredentials(), id);
|
|
16300
|
+
if (!checked.ok || !checked.data) domainFail(command, checked, "Failed to verify the domain");
|
|
16301
|
+
return checked.data.data;
|
|
16302
|
+
}
|
|
16303
|
+
var claim = new Command("claim").description("Claim a client hostname for landing pages (CNAME to the Apex edge)").argument("<host>", "hostname the client controls, for example offers.example.com").option("--wait", "check DNS and the certificate every 10 seconds for up to two minutes").action(async function action55(host, opts) {
|
|
16304
|
+
const result = await createLandingPageDomain(requireCredentials(), {
|
|
16305
|
+
host: landingHost(host),
|
|
16306
|
+
mode: "claim"
|
|
16307
|
+
});
|
|
16308
|
+
if (!result.ok || !result.data) domainFail(this, result, "Failed to claim the hostname");
|
|
16309
|
+
let domain = result.data.data;
|
|
16310
|
+
if (!shouldUseJson(this)) {
|
|
16311
|
+
console.log(`CNAME ${domain.host} -> ${domain.dns_target}`);
|
|
16312
|
+
printInstructions(domain);
|
|
16313
|
+
}
|
|
16314
|
+
if (opts.wait) {
|
|
16315
|
+
const deadline = Date.now() + VERIFY_WINDOW_MS;
|
|
16316
|
+
while (domain.status !== "active" && domain.status !== "failed" && Date.now() < deadline) {
|
|
16317
|
+
await new Promise((resolve2) => setTimeout(resolve2, VERIFY_INTERVAL_MS));
|
|
16318
|
+
domain = await verifyOnce(this, domain.id);
|
|
16319
|
+
}
|
|
16320
|
+
}
|
|
16321
|
+
output(this, { data: domain }, () => printDomainStatus(domain));
|
|
16322
|
+
});
|
|
16323
|
+
function parseConfirmPrice(command, raw) {
|
|
16324
|
+
if (!raw) return void 0;
|
|
16325
|
+
const parts = raw.join(" ").trim().split(/\s+/);
|
|
16326
|
+
const [amount, currency] = parts;
|
|
16327
|
+
if (parts.length !== 2 || !amount || !currency || !/^\d+(\.\d{1,2})?$/.test(amount) || !/^[A-Za-z]{3}$/.test(currency)) {
|
|
16328
|
+
fail(
|
|
16329
|
+
command,
|
|
16330
|
+
'--confirm-price takes the quoted amount and currency, for example --confirm-price "10.44 USD"',
|
|
16331
|
+
EXIT_CODES.validation
|
|
16332
|
+
);
|
|
16333
|
+
}
|
|
16334
|
+
return { amount, currency: currency.toUpperCase() };
|
|
16335
|
+
}
|
|
16336
|
+
function describeQuote(quote) {
|
|
16337
|
+
const term = quote.years === 1 ? "1 year" : `${quote.years} years`;
|
|
16338
|
+
return `${quote.domain}: ${quote.total} ${quote.currency} for ${term}, renews at ${quote.renewal_cost} ${quote.currency} a year (auto-renew on, billed to the Drip Cloudflare account)`;
|
|
16339
|
+
}
|
|
16340
|
+
var register = new Command("register").description("Register a new domain on the Drip Cloudflare account (billable, non-refundable)").argument("<name>", "domain to register, for example example-shop.com").option("--years <years>", "registration term in years (1 to 10)", "1").option(
|
|
16341
|
+
"--confirm-price <price...>",
|
|
16342
|
+
'confirm the quoted total, for example "10.44 USD" (required without a terminal)'
|
|
16343
|
+
).action(async function action56(name, opts) {
|
|
16344
|
+
const years = Number(opts.years);
|
|
16345
|
+
if (!Number.isInteger(years) || years < 1 || years > 10)
|
|
16346
|
+
fail(this, "--years must be a whole number from 1 to 10", EXIT_CODES.validation);
|
|
16347
|
+
const host = landingHost(name);
|
|
16348
|
+
let confirm3 = parseConfirmPrice(this, opts.confirmPrice);
|
|
16349
|
+
let result = await createLandingPageDomain(requireCredentials(), {
|
|
16350
|
+
host,
|
|
16351
|
+
mode: "register",
|
|
16352
|
+
years,
|
|
16353
|
+
confirm_price: confirm3
|
|
16354
|
+
});
|
|
16355
|
+
const quote = result.data?.quote;
|
|
16356
|
+
if (!result.ok && result.status === 409 && result.errorDetails?.code === "price_confirmation_required" && quote) {
|
|
16357
|
+
if (confirm3) {
|
|
16358
|
+
fail(this, `The price changed: ${describeQuote(quote)}`, EXIT_CODES.conflict, { quote });
|
|
16359
|
+
}
|
|
16360
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
16361
|
+
confirmationRequired(
|
|
16362
|
+
this,
|
|
16363
|
+
`Registering spends money. ${describeQuote(quote)}`,
|
|
16364
|
+
`apex domains register ${host} --years ${years} --confirm-price "${quote.total} ${quote.currency}"`
|
|
16365
|
+
);
|
|
16366
|
+
}
|
|
16367
|
+
const readline3 = await import("node:readline");
|
|
16368
|
+
const rl = readline3.createInterface({ input: process.stdin, output: process.stderr });
|
|
16369
|
+
console.error(describeQuote(quote));
|
|
16370
|
+
const answer = await new Promise(
|
|
16371
|
+
(resolve2) => rl.question(
|
|
16372
|
+
source_default.yellow(
|
|
16373
|
+
`Register ${quote.domain} for ${quote.total} ${quote.currency}? This is non-refundable. [y/N] `
|
|
16374
|
+
),
|
|
16375
|
+
resolve2
|
|
16376
|
+
)
|
|
16377
|
+
);
|
|
16378
|
+
rl.close();
|
|
16379
|
+
if (answer.trim().toLowerCase() !== "y") {
|
|
16380
|
+
output(
|
|
16381
|
+
this,
|
|
16382
|
+
{ aborted: true, quote },
|
|
16383
|
+
() => console.log("Aborted. Nothing was registered.")
|
|
16384
|
+
);
|
|
16385
|
+
return;
|
|
16386
|
+
}
|
|
16387
|
+
confirm3 = { amount: quote.total, currency: quote.currency };
|
|
16388
|
+
result = await createLandingPageDomain(requireCredentials(), {
|
|
16389
|
+
host,
|
|
16390
|
+
mode: "register",
|
|
16391
|
+
years,
|
|
16392
|
+
confirm_price: confirm3
|
|
16393
|
+
});
|
|
16394
|
+
}
|
|
16395
|
+
if (!result.ok || !result.data) domainFail(this, result, "Failed to register the domain");
|
|
16396
|
+
const domain = result.data.data;
|
|
16397
|
+
output(this, { data: domain }, () => {
|
|
16398
|
+
const registration = domain.registration;
|
|
16399
|
+
console.log(
|
|
16400
|
+
`Registered ${domain.host}${registration ? ` for ${registration.price} ${registration.currency}` : ""}; status ${domain.status}.`
|
|
16401
|
+
);
|
|
16402
|
+
if (domain.status_message) console.log(domain.status_message);
|
|
16403
|
+
console.log(`Check progress with: apex domains status ${domain.host}`);
|
|
16404
|
+
});
|
|
16405
|
+
});
|
|
16406
|
+
var status = new Command("status").description("List landing-page domains, or check one hostname now").argument("[host]", "check this hostname against Cloudflare and DNS").action(async function action57(host) {
|
|
16407
|
+
if (host) {
|
|
16408
|
+
const row = await findLandingDomain(this, host);
|
|
16409
|
+
if (row.status === "removed")
|
|
16410
|
+
fail(
|
|
16411
|
+
this,
|
|
16412
|
+
`${row.host} was removed, but Cloudflare has not confirmed the hostname delete; run apex domains unclaim ${row.host} again`,
|
|
16413
|
+
EXIT_CODES.general
|
|
16414
|
+
);
|
|
16415
|
+
const domain = await verifyOnce(this, row.id);
|
|
16416
|
+
output(this, { data: domain }, () => {
|
|
16417
|
+
printDomainStatus(domain);
|
|
16418
|
+
if (domain.status !== "active") printInstructions(domain);
|
|
16419
|
+
});
|
|
16420
|
+
return;
|
|
16421
|
+
}
|
|
16422
|
+
const result = await getLandingPageDomains(requireCredentials());
|
|
16423
|
+
if (!result.ok || !result.data) domainFail(this, result, "Failed to load landing-page domains");
|
|
16424
|
+
const rows = result.data.data;
|
|
16425
|
+
output(this, result.data, () => {
|
|
16426
|
+
if (!rows.length) {
|
|
16427
|
+
console.log("No landing-page domains. Claim one with: apex domains claim <host>");
|
|
16428
|
+
return;
|
|
16429
|
+
}
|
|
16430
|
+
printTable(
|
|
16431
|
+
["HOST", "MODE", "STATUS", "DNS TARGET"],
|
|
16432
|
+
rows.map((row) => [row.host, row.mode, row.status, row.dns_target]),
|
|
16433
|
+
[40, 9, 10, 24]
|
|
16434
|
+
);
|
|
16435
|
+
if (result.data.has_more)
|
|
16436
|
+
console.log(`Showing the newest ${rows.length}; more landing-page domains exist.`);
|
|
16437
|
+
});
|
|
16438
|
+
});
|
|
16439
|
+
var unclaim = new Command("unclaim").description(
|
|
16440
|
+
"Remove a landing-page domain from Apex (a registered domain stays on the Drip account)"
|
|
16441
|
+
).argument("<host>").action(async function action58(host) {
|
|
16442
|
+
const row = await findLandingDomain(this, host);
|
|
16443
|
+
const result = await deleteLandingPageDomain(requireCredentials(), row.id);
|
|
16444
|
+
if (!result.ok || !result.data) domainFail(this, result, "Failed to remove the domain");
|
|
16445
|
+
const domain = result.data.data;
|
|
16446
|
+
output(this, { data: domain }, () => {
|
|
16447
|
+
console.log(`Removed ${domain.host} from Apex.`);
|
|
16448
|
+
if (domain.status_message) console.log(domain.status_message);
|
|
16449
|
+
});
|
|
16450
|
+
});
|
|
16451
|
+
var domainsCommand = new Command("domains").description("Manage install domains and landing-page domains").addCommand(list5).addCommand(add).addCommand(removeInstall).addCommand(set3).addCommand(claim).addCommand(register).addCommand(status).addCommand(unclaim);
|
|
16075
16452
|
|
|
16076
16453
|
// src/commands/handover.ts
|
|
16077
16454
|
init_source();
|
|
@@ -16158,14 +16535,14 @@ async function patchWithRetry2(command, build) {
|
|
|
16158
16535
|
);
|
|
16159
16536
|
return result.data;
|
|
16160
16537
|
}
|
|
16161
|
-
var get4 = new Command("get").description("Show cross-domain handover settings").action(async function
|
|
16538
|
+
var get4 = new Command("get").description("Show cross-domain handover settings").action(async function action59() {
|
|
16162
16539
|
const data = await load5(this);
|
|
16163
16540
|
output(this, data, () => printSettings(data.externalTracking));
|
|
16164
16541
|
});
|
|
16165
16542
|
var set4 = new Command("set").description("Enable cross-domain identity handover").requiredOption("--domain <domain...>", "Bound handover domain(s)").option("--param <param>", "Handover query parameter").option(
|
|
16166
16543
|
"--signed",
|
|
16167
16544
|
"Require signed handover tokens (the default; unsigning is a platform-support action)"
|
|
16168
|
-
).action(async function
|
|
16545
|
+
).action(async function action60(opts) {
|
|
16169
16546
|
const domains = opts.domain.map(normalizeDomain2);
|
|
16170
16547
|
if (domains.some((domain) => !domain))
|
|
16171
16548
|
fail(
|
|
@@ -16185,14 +16562,14 @@ var set4 = new Command("set").description("Enable cross-domain identity handover
|
|
|
16185
16562
|
}));
|
|
16186
16563
|
output(this, data, () => printSettings(data.externalTracking));
|
|
16187
16564
|
});
|
|
16188
|
-
var off2 = new Command("off").description("Disable cross-domain handover").action(async function
|
|
16565
|
+
var off2 = new Command("off").description("Disable cross-domain handover").action(async function action61() {
|
|
16189
16566
|
const data = await patchWithRetry2(this, (current) => ({
|
|
16190
16567
|
externalTracking: { domains: [], signed: true },
|
|
16191
16568
|
ifRevision: current.revision
|
|
16192
16569
|
}));
|
|
16193
16570
|
output(this, data, () => printSettings(data.externalTracking));
|
|
16194
16571
|
});
|
|
16195
|
-
var qa2 = new Command("qa").description("Check static link/form handover parameter carriage").requiredOption("--from <url>", "Page URL to inspect").requiredOption("--to-domain <domain>", "Target domain that should carry the handover parameter").action(async function
|
|
16572
|
+
var qa2 = new Command("qa").description("Check static link/form handover parameter carriage").requiredOption("--from <url>", "Page URL to inspect").requiredOption("--to-domain <domain>", "Target domain that should carry the handover parameter").action(async function action62(opts) {
|
|
16196
16573
|
const config = await load5(this);
|
|
16197
16574
|
const target = normalizeDomain2(opts.toDomain);
|
|
16198
16575
|
if (!target) fail(this, "--to-domain must be a valid hostname", EXIT_CODES.validation);
|
|
@@ -16280,17 +16657,17 @@ function compare(a, b) {
|
|
|
16280
16657
|
const left = a.fraction.padEnd(precision, "0"), right = b.fraction.padEnd(precision, "0");
|
|
16281
16658
|
return left === right ? 0 : left < right ? -1 : 1;
|
|
16282
16659
|
}
|
|
16283
|
-
var
|
|
16660
|
+
var record2 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
16284
16661
|
var text = (v) => typeof v === "string" && v.length > 0;
|
|
16285
16662
|
var reason = (v) => typeof v === "string" && (/^[a-z][a-z0-9_]{0,79}$/.test(v) || /^(publishedConfig|consentPolicy|goalMapping|sdkArtifact|sdkPolicy|identityPolicy|goalDefinition|resultWindow|inputSnapshot)_(expected_unknown|observed_unknown|mismatch|binding_mismatch)$/.test(
|
|
16286
16663
|
v
|
|
16287
16664
|
));
|
|
16288
16665
|
var member = (v, values) => typeof v === "string" && values.includes(v);
|
|
16289
|
-
var
|
|
16666
|
+
var status2 = (v) => member(v, ["pass", "fail", "inconclusive"]);
|
|
16290
16667
|
var count = (v) => typeof v === "number" && Number.isSafeInteger(v) && v >= 0;
|
|
16291
16668
|
function validReport(v) {
|
|
16292
|
-
if (!
|
|
16293
|
-
(s) =>
|
|
16669
|
+
if (!record2(v) || v.schemaVersion !== 1 || v.finalStatisticalContribution !== "not_evaluated" || !record2(v.evaluation) || !status2(v.evaluation.status) || !member(v.evaluation.freshness, ["current", "stale", "unknown"]) || !Array.isArray(v.evaluation.stages) || !v.evaluation.stages.every(
|
|
16670
|
+
(s) => record2(s) && (s.freshness === void 0 || member(s.freshness, ["current", "stale", "unknown"])) && member(s.stage, [
|
|
16294
16671
|
"consent",
|
|
16295
16672
|
"assignment",
|
|
16296
16673
|
"event",
|
|
@@ -16298,21 +16675,21 @@ function validReport(v) {
|
|
|
16298
16675
|
"authoritative_fact",
|
|
16299
16676
|
"attribution",
|
|
16300
16677
|
"result"
|
|
16301
|
-
]) &&
|
|
16678
|
+
]) && status2(s.status) && Array.isArray(s.reasonCodes) && s.reasonCodes.every(reason)
|
|
16302
16679
|
))
|
|
16303
16680
|
return false;
|
|
16304
16681
|
if (v.receiptOutcomes !== void 0 && (!Array.isArray(v.receiptOutcomes) || v.receiptOutcomes.length > 6856 || !v.receiptOutcomes.every(
|
|
16305
|
-
(r) =>
|
|
16682
|
+
(r) => record2(r) && text(r.receiptId) && text(r.stage) && text(r.producerContract) && record2(r.scope) && [r.scope.experimentId, r.scope.variationId, r.scope.goalId].every(
|
|
16306
16683
|
(x) => x === null || text(x)
|
|
16307
16684
|
) && member(r.freshness, ["current", "stale", "unknown"]) && Array.isArray(r.reasonCodes) && r.reasonCodes.every(reason)
|
|
16308
16685
|
)))
|
|
16309
16686
|
return false;
|
|
16310
16687
|
const coverage = v.assignmentCoverage;
|
|
16311
|
-
if (!
|
|
16688
|
+
if (!record2(coverage) || typeof coverage.complete !== "boolean" || typeof coverage.overflowCountIsLowerBound !== "boolean" || !(coverage.overflowCount === null || count(coverage.overflowCount)))
|
|
16312
16689
|
return false;
|
|
16313
16690
|
return Array.isArray(v.assignments) && v.assignments.length <= 50 && v.assignments.every(
|
|
16314
|
-
(a) =>
|
|
16315
|
-
(g) =>
|
|
16691
|
+
(a) => record2(a) && text(a.experimentId) && text(a.variationId) && (a.storedAttribution === null || typeof a.storedAttribution === "boolean") && reason(a.reasonCode) && Array.isArray(a.goals) && a.goals.length <= 50 && a.goals.every(
|
|
16692
|
+
(g) => record2(g) && text(g.goalId) && member(g.status, [
|
|
16316
16693
|
"eligible_for_attributed_goal_read",
|
|
16317
16694
|
"excluded_from_attributed_goal_read",
|
|
16318
16695
|
"unknown"
|
|
@@ -16683,7 +17060,7 @@ var PURCHASE_CADENCE_WINDOW_MINUTES = PURCHASE_WINDOW_MINUTES * 4;
|
|
|
16683
17060
|
var MIN_CAPTURE_WINDOW_MINUTES = 60;
|
|
16684
17061
|
var DUPLICATE_RATE_THRESHOLD = 0.02;
|
|
16685
17062
|
var MAGNITUDE_TOLERANCE_RATE = 0.1;
|
|
16686
|
-
function
|
|
17063
|
+
function record3(value) {
|
|
16687
17064
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
16688
17065
|
}
|
|
16689
17066
|
function purchasePlane(config) {
|
|
@@ -16691,8 +17068,8 @@ function purchasePlane(config) {
|
|
|
16691
17068
|
const names = [];
|
|
16692
17069
|
const triggers = config.runtime?.dataLayerTriggers ?? config.dataLayerTriggers;
|
|
16693
17070
|
if (Array.isArray(triggers) && triggers.some((trigger) => {
|
|
16694
|
-
const row =
|
|
16695
|
-
const trackRevenue =
|
|
17071
|
+
const row = record3(trigger);
|
|
17072
|
+
const trackRevenue = record3(row?.trackRevenue);
|
|
16696
17073
|
return row?.enabled !== false && trackRevenue !== null && trackRevenue.enabled !== false;
|
|
16697
17074
|
}))
|
|
16698
17075
|
names.push("dataLayer trigger");
|
|
@@ -16734,7 +17111,7 @@ function lineItemSum(value) {
|
|
|
16734
17111
|
if (!Array.isArray(value)) return null;
|
|
16735
17112
|
let sum = 0;
|
|
16736
17113
|
for (const item of value) {
|
|
16737
|
-
const row =
|
|
17114
|
+
const row = record3(item);
|
|
16738
17115
|
const price = numeric(row?.price);
|
|
16739
17116
|
if (price === null) return null;
|
|
16740
17117
|
sum += price * (numeric(row?.quantity) ?? 1);
|
|
@@ -17381,7 +17758,7 @@ var doctorCommand = new Command("doctor").description("Check shop configuration
|
|
|
17381
17758
|
).option("--check <id>", "Run one check (for example purchase_capture)").option(
|
|
17382
17759
|
"--worker <url>",
|
|
17383
17760
|
`Apex Worker base URL; defaults to DRIP_WORKER_URL or ${PRODUCTION_WORKER_BASE_URL}`
|
|
17384
|
-
).action(async function
|
|
17761
|
+
).action(async function action63(options) {
|
|
17385
17762
|
const data = await runDoctor(options);
|
|
17386
17763
|
if (options.check && data.checks.length === 0)
|
|
17387
17764
|
fail(this, `Unknown doctor check: ${options.check}`, EXIT_CODES.validation);
|
|
@@ -17403,7 +17780,7 @@ function printDoctorReport(checks) {
|
|
|
17403
17780
|
var consentRateCommand = new Command("consent-rate").description("Estimate analytics consent acceptance from visitor series").option(
|
|
17404
17781
|
"--since <date>",
|
|
17405
17782
|
"Consent gating start date (YYYY-MM-DD); splits the baseline and gated windows"
|
|
17406
|
-
).action(async function
|
|
17783
|
+
).action(async function action64(opts) {
|
|
17407
17784
|
if (opts.since !== void 0 && parseDay(opts.since) === null)
|
|
17408
17785
|
fail(this, "--since must be a calendar date in YYYY-MM-DD form", EXIT_CODES.validation);
|
|
17409
17786
|
const result = await getVisitorSeries(requireCredentials(), { days: VISITOR_SERIES_DAYS });
|
|
@@ -17463,7 +17840,7 @@ function analyticsSubcommand(name, description, defaultDays) {
|
|
|
17463
17840
|
command.option("--planning-days <n>", "Planning window in days", "180");
|
|
17464
17841
|
command.option("--limit <n>", "Maximum page rows", "50");
|
|
17465
17842
|
}
|
|
17466
|
-
return command.action(async function
|
|
17843
|
+
return command.action(async function action78(opts) {
|
|
17467
17844
|
const result = await getAnalytics(requireCredentials(), name, {
|
|
17468
17845
|
days: positiveInteger(this, opts.days, "--days"),
|
|
17469
17846
|
planningDays: positiveInteger(this, opts.planningDays, "--planning-days"),
|
|
@@ -17483,7 +17860,7 @@ function urlsCommand() {
|
|
|
17483
17860
|
return new Command("urls").description("Show storefront URL inventory from pageview analytics").option("-d, --days <n>", "Analytics window in days", "14").option("--host <host>", "Filter inventory to one host").option("--contains <substring>", "Server-side path substring filter").option(
|
|
17484
17861
|
"--match <regex>",
|
|
17485
17862
|
"Client-side regex filter; exhausts inventory pages before filtering"
|
|
17486
|
-
).option("--limit <n>", "Maximum rows to print", "50").action(async function
|
|
17863
|
+
).option("--limit <n>", "Maximum rows to print", "50").action(async function action78(opts) {
|
|
17487
17864
|
const creds = requireCredentials();
|
|
17488
17865
|
const days = positiveInteger(this, opts.days, "--days") ?? 14;
|
|
17489
17866
|
const displayLimit = Math.min(200, positiveInteger(this, opts.limit, "--limit") ?? 50);
|
|
@@ -17694,10 +18071,10 @@ function decodeBody(raw, header, limits, hostname, inputTruncated = false) {
|
|
|
17694
18071
|
const chunks = [];
|
|
17695
18072
|
let bytes = 0;
|
|
17696
18073
|
let settled = false;
|
|
17697
|
-
const settle = (
|
|
18074
|
+
const settle = (action78) => {
|
|
17698
18075
|
if (settled) return;
|
|
17699
18076
|
settled = true;
|
|
17700
|
-
|
|
18077
|
+
action78();
|
|
17701
18078
|
};
|
|
17702
18079
|
stream.on("data", (chunk) => {
|
|
17703
18080
|
if (settled) return;
|
|
@@ -17739,7 +18116,7 @@ function overLimitMessage(hostname, maxBytes) {
|
|
|
17739
18116
|
return `Response from ${hostname} exceeded the ${maxBytes}-byte cap`;
|
|
17740
18117
|
}
|
|
17741
18118
|
function responseFromIncoming(res, body5, decoded) {
|
|
17742
|
-
const
|
|
18119
|
+
const status4 = res.statusCode ?? 0;
|
|
17743
18120
|
const headers = new Headers();
|
|
17744
18121
|
for (const [key, value] of Object.entries(res.headers)) {
|
|
17745
18122
|
if (Array.isArray(value)) for (const entry of value) headers.append(key, entry);
|
|
@@ -17749,9 +18126,9 @@ function responseFromIncoming(res, body5, decoded) {
|
|
|
17749
18126
|
headers.delete("content-encoding");
|
|
17750
18127
|
headers.delete("content-length");
|
|
17751
18128
|
}
|
|
17752
|
-
const nullBody =
|
|
18129
|
+
const nullBody = status4 === 101 || status4 === 204 || status4 === 205 || status4 === 304;
|
|
17753
18130
|
return new Response(nullBody ? null : body5, {
|
|
17754
|
-
status:
|
|
18131
|
+
status: status4,
|
|
17755
18132
|
statusText: res.statusMessage ?? "",
|
|
17756
18133
|
headers
|
|
17757
18134
|
});
|
|
@@ -17768,11 +18145,11 @@ function pinnedFetch(url, addresses, limits) {
|
|
|
17768
18145
|
else callback(null, addresses[0], (0, import_node_net.isIP)(addresses[0]));
|
|
17769
18146
|
};
|
|
17770
18147
|
let settled = false;
|
|
17771
|
-
const settle = (
|
|
18148
|
+
const settle = (action78) => {
|
|
17772
18149
|
if (settled) return;
|
|
17773
18150
|
settled = true;
|
|
17774
18151
|
clearTimeout(deadline);
|
|
17775
|
-
|
|
18152
|
+
action78();
|
|
17776
18153
|
};
|
|
17777
18154
|
const req = send3(
|
|
17778
18155
|
url,
|
|
@@ -17783,8 +18160,8 @@ function pinnedFetch(url, addresses, limits) {
|
|
|
17783
18160
|
lookup: pinnedLookup
|
|
17784
18161
|
},
|
|
17785
18162
|
(res) => {
|
|
17786
|
-
const
|
|
17787
|
-
if (Number.isFinite(
|
|
18163
|
+
const declared2 = Number.parseInt(res.headers["content-length"] ?? "", 10);
|
|
18164
|
+
if (Number.isFinite(declared2) && declared2 > limits.maxBytes && limits.onExceed === "error") {
|
|
17788
18165
|
req.destroy();
|
|
17789
18166
|
settle(() => reject(new BodyLimitError(overLimitMessage(url.hostname, limits.maxBytes))));
|
|
17790
18167
|
return;
|
|
@@ -17899,7 +18276,7 @@ function detectChallenge(html) {
|
|
|
17899
18276
|
);
|
|
17900
18277
|
}
|
|
17901
18278
|
var LEGACY_SNIPPET_MARKER = /sdk\.drip-apex\.com|events\.drip-apex\.com|\/drip\.js\b|\bDrip\.init\s*\(|\bwindow\.drip\b/i;
|
|
17902
|
-
function pageReport(url, shopId,
|
|
18279
|
+
function pageReport(url, shopId, status4, html, workerSnippetPresent, bodyTruncated) {
|
|
17903
18280
|
const challenge = detectChallenge(html);
|
|
17904
18281
|
const shopIdMatches = html.includes(shopId);
|
|
17905
18282
|
const snippetPresent = workerSnippetPresent || LEGACY_SNIPPET_MARKER.test(html) && shopIdMatches;
|
|
@@ -17907,29 +18284,29 @@ function pageReport(url, shopId, status3, html, workerSnippetPresent, bodyTrunca
|
|
|
17907
18284
|
return {
|
|
17908
18285
|
url,
|
|
17909
18286
|
status: "WARN",
|
|
17910
|
-
httpStatus:
|
|
18287
|
+
httpStatus: status4,
|
|
17911
18288
|
snippetPresent,
|
|
17912
18289
|
shopIdMatches,
|
|
17913
18290
|
botChallenge: true,
|
|
17914
18291
|
bodyTruncated,
|
|
17915
18292
|
message: "Bot challenge detected; verify in a real browser."
|
|
17916
18293
|
};
|
|
17917
|
-
if (
|
|
18294
|
+
if (status4 < 200 || status4 >= 300)
|
|
17918
18295
|
return {
|
|
17919
18296
|
url,
|
|
17920
18297
|
status: "FAIL",
|
|
17921
|
-
httpStatus:
|
|
18298
|
+
httpStatus: status4,
|
|
17922
18299
|
snippetPresent,
|
|
17923
18300
|
shopIdMatches,
|
|
17924
18301
|
botChallenge: false,
|
|
17925
18302
|
bodyTruncated,
|
|
17926
|
-
message: `Storefront page returned HTTP ${
|
|
18303
|
+
message: `Storefront page returned HTTP ${status4}; the install cannot be verified from this response.`
|
|
17927
18304
|
};
|
|
17928
18305
|
if (!snippetPresent)
|
|
17929
18306
|
return {
|
|
17930
18307
|
url,
|
|
17931
18308
|
status: "FAIL",
|
|
17932
|
-
httpStatus:
|
|
18309
|
+
httpStatus: status4,
|
|
17933
18310
|
snippetPresent,
|
|
17934
18311
|
shopIdMatches,
|
|
17935
18312
|
botChallenge: false,
|
|
@@ -17940,7 +18317,7 @@ function pageReport(url, shopId, status3, html, workerSnippetPresent, bodyTrunca
|
|
|
17940
18317
|
return {
|
|
17941
18318
|
url,
|
|
17942
18319
|
status: "FAIL",
|
|
17943
|
-
httpStatus:
|
|
18320
|
+
httpStatus: status4,
|
|
17944
18321
|
snippetPresent,
|
|
17945
18322
|
shopIdMatches,
|
|
17946
18323
|
botChallenge: false,
|
|
@@ -17950,7 +18327,7 @@ function pageReport(url, shopId, status3, html, workerSnippetPresent, bodyTrunca
|
|
|
17950
18327
|
return {
|
|
17951
18328
|
url,
|
|
17952
18329
|
status: "PASS",
|
|
17953
|
-
httpStatus:
|
|
18330
|
+
httpStatus: status4,
|
|
17954
18331
|
snippetPresent,
|
|
17955
18332
|
shopIdMatches,
|
|
17956
18333
|
botChallenge: false,
|
|
@@ -18110,7 +18487,7 @@ async function checkServedConfig(configUrl, workerBaseUrl, workerBaseSource) {
|
|
|
18110
18487
|
var installCommand = new Command("install").description("Check Apex storefront installation").command("check").description("Verify snippet, served config, and recent tracking for the current shop").option("--url <url>", "Check one explicit bound storefront URL").option("--shop-id <id>", "Apex shop ID to verify (falls back to DRIP_SHOP_ID)").option(
|
|
18111
18488
|
"--worker-url <url>",
|
|
18112
18489
|
"Apex Worker base URL that serves /config (defaults to the URL in the served snippet, then DRIP_WORKER_URL, then the production worker)"
|
|
18113
|
-
).option("--environment <key>", "Environment key to request from the worker", "production").action(async function
|
|
18490
|
+
).option("--environment <key>", "Environment key to request from the worker", "production").action(async function action65(opts) {
|
|
18114
18491
|
const creds = requireCredentials();
|
|
18115
18492
|
const shopId = opts.shopId?.trim() || process.env.DRIP_SHOP_ID?.trim() || null;
|
|
18116
18493
|
if (!shopId)
|
|
@@ -18274,10 +18651,10 @@ function getEdgeDeliveryStatus(deliveryType, expectedWorstCaseSeconds = 360) {
|
|
|
18274
18651
|
return deliveryType === "api" ? { edge_delivery: "not_applicable", message: `API-delivered flag \u2014 edge delivery is not applicable. A previously cached visual payload may remain at some PoPs for up to ~${expectedWorstCaseSeconds}s; a single-vantage status cannot prove global propagation.` } : { edge_delivery: "applicable", message: null };
|
|
18275
18652
|
}
|
|
18276
18653
|
function parsePublicationMetadata(value) {
|
|
18277
|
-
const
|
|
18654
|
+
const record4 = value && typeof value === "object" ? value : {};
|
|
18278
18655
|
return {
|
|
18279
|
-
publication_revision: Number.isSafeInteger(
|
|
18280
|
-
generated_at: typeof
|
|
18656
|
+
publication_revision: Number.isSafeInteger(record4.publication_revision) && record4.publication_revision >= 0 ? record4.publication_revision : null,
|
|
18657
|
+
generated_at: typeof record4.generated_at === "string" && !Number.isNaN(Date.parse(record4.generated_at)) ? record4.generated_at : null
|
|
18281
18658
|
};
|
|
18282
18659
|
}
|
|
18283
18660
|
function compareEdgePublication(currentValue, edgeValue, expectedWorstCaseSeconds, flagUpdatedAtValue, confirmedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
@@ -18294,13 +18671,13 @@ function compareEdgePublication(currentValue, edgeValue, expectedWorstCaseSecond
|
|
|
18294
18671
|
if (edge.publication_revision < current.publication_revision) return { ...base, verdict: "propagating", relationship: "edge_behind" };
|
|
18295
18672
|
return { ...base, verdict: "live", relationship: edge.publication_revision > current.publication_revision ? "edge_ahead" : "equal" };
|
|
18296
18673
|
}
|
|
18297
|
-
function formatEdgePropagationStatus(
|
|
18674
|
+
function formatEdgePropagationStatus(status4) {
|
|
18298
18675
|
const caveat = "This checks one CDN vantage point; other PoPs may lag by up to max-age.";
|
|
18299
|
-
if (
|
|
18300
|
-
if (
|
|
18301
|
-
if (
|
|
18302
|
-
const anomaly =
|
|
18303
|
-
return `Live at edge \u2014 revision ${
|
|
18676
|
+
if (status4.verdict === "pending_publication") return `Pending publication \u2014 flag updated ${status4.flag_updated_at}, after the latest publication generated ${status4.current.generated_at ?? "never"}; the latest change is not live. After publication, allow ~${status4.expected_worst_case_seconds}s for propagation. ${caveat}`;
|
|
18677
|
+
if (status4.verdict === "unknown") return `Unknown \u2014 edge payload predates publication metadata. ${caveat}`;
|
|
18678
|
+
if (status4.verdict === "propagating") return `Propagating \u2014 edge revision ${status4.edge.publication_revision} (generated ${status4.edge.generated_at}) vs current revision ${status4.current.publication_revision} (generated ${status4.current.generated_at}); expected within ~${status4.expected_worst_case_seconds}s after generation. ${caveat}`;
|
|
18679
|
+
const anomaly = status4.relationship === "edge_ahead" ? ` (edge is ahead of current revision ${status4.current.publication_revision}; anomalous)` : "";
|
|
18680
|
+
return `Live at edge \u2014 revision ${status4.edge.publication_revision} (generated ${status4.edge.generated_at}) confirmed at edge at ${status4.confirmed_at}${anomaly}. ${caveat}`;
|
|
18304
18681
|
}
|
|
18305
18682
|
|
|
18306
18683
|
// src/commands/flags.ts
|
|
@@ -18359,7 +18736,7 @@ var get5 = new Command("get").description("Get a flag").argument("<id>").action(
|
|
|
18359
18736
|
fail(this, r.error ?? "Failed to get", classifyStatus(r.status), r.errorDetails);
|
|
18360
18737
|
output(this, r.data, () => detail(r.data));
|
|
18361
18738
|
});
|
|
18362
|
-
var
|
|
18739
|
+
var status3 = new Command("status").description("Check whether a flag publication is live at the edge").argument("<id>").action(async function(id) {
|
|
18363
18740
|
const r = await getFlagStatus(requireCredentials(), id);
|
|
18364
18741
|
if (!r.ok || !r.data)
|
|
18365
18742
|
fail(this, r.error ?? "Failed to get flag status", classifyStatus(r.status), r.errorDetails);
|
|
@@ -18422,15 +18799,15 @@ function write(name) {
|
|
|
18422
18799
|
});
|
|
18423
18800
|
return c;
|
|
18424
18801
|
}
|
|
18425
|
-
function lifecycle(
|
|
18426
|
-
return new Command(
|
|
18427
|
-
const r = await transitionManagedDelivery(requireCredentials(), kind, id,
|
|
18802
|
+
function lifecycle(action78) {
|
|
18803
|
+
return new Command(action78).description(`${action78[0].toUpperCase()}${action78.slice(1)} a flag`).argument("<id>").action(async function(id) {
|
|
18804
|
+
const r = await transitionManagedDelivery(requireCredentials(), kind, id, action78);
|
|
18428
18805
|
if (!r.ok || !r.data)
|
|
18429
|
-
fail(this, r.error ?? `Failed to ${
|
|
18806
|
+
fail(this, r.error ?? `Failed to ${action78}`, classifyStatus(r.status), r.errorDetails);
|
|
18430
18807
|
output(this, r.data, () => detail(r.data));
|
|
18431
18808
|
});
|
|
18432
18809
|
}
|
|
18433
|
-
var flagsCommand = new Command("flags").description("Manage flags").addCommand(list6).addCommand(get5).addCommand(
|
|
18810
|
+
var flagsCommand = new Command("flags").description("Manage flags").addCommand(list6).addCommand(get5).addCommand(status3).addCommand(write("create")).addCommand(write("update")).addCommand(lifecycle("activate")).addCommand(lifecycle("pause")).addCommand(lifecycle("archive")).addHelpText(
|
|
18434
18811
|
"after",
|
|
18435
18812
|
`
|
|
18436
18813
|
Examples:
|
|
@@ -18563,11 +18940,11 @@ var get6 = new Command("get").description("Get a backlog item identified as apex
|
|
|
18563
18940
|
const labels = await fieldLabels(this, r.data);
|
|
18564
18941
|
output(this, r.data, () => detail2(r.data, labels));
|
|
18565
18942
|
});
|
|
18566
|
-
function write2(
|
|
18567
|
-
const c = new Command(
|
|
18568
|
-
`${
|
|
18943
|
+
function write2(action78) {
|
|
18944
|
+
const c = new Command(action78).description(
|
|
18945
|
+
`${action78 === "create" ? "Create" : "Update"} a ${singular}`
|
|
18569
18946
|
);
|
|
18570
|
-
if (
|
|
18947
|
+
if (action78 === "update") c.argument("<id>", `${singular} ID`);
|
|
18571
18948
|
c.argument("[json]", "Inline JSON, @file, or file path").option("--file <path>", "JSON body file").option(
|
|
18572
18949
|
"--field <key=value>",
|
|
18573
18950
|
"Set one of the shop's own fields; repeatable. Use key=null to clear it.",
|
|
@@ -18576,17 +18953,17 @@ function write2(action74) {
|
|
|
18576
18953
|
);
|
|
18577
18954
|
c.action(async function(...args) {
|
|
18578
18955
|
const o = this.opts();
|
|
18579
|
-
const id =
|
|
18580
|
-
const raw = o.file ? `@${o.file}` : String(args[
|
|
18956
|
+
const id = action78 === "update" ? String(args[0]) : void 0;
|
|
18957
|
+
const raw = o.file ? `@${o.file}` : String(args[action78 === "update" ? 1 : 0] ?? "") || void 0;
|
|
18581
18958
|
const body5 = parse(this, raw);
|
|
18582
18959
|
const customFields = await customFieldsFromPairs(this, o.field);
|
|
18583
18960
|
if (customFields) {
|
|
18584
18961
|
body5.custom_fields = { ...customFieldsRecord(body5.custom_fields), ...customFields };
|
|
18585
18962
|
}
|
|
18586
18963
|
if (!Object.keys(body5).length) fail(this, "Provide a JSON body.", EXIT_CODES.validation);
|
|
18587
|
-
const r =
|
|
18964
|
+
const r = action78 === "create" ? await createInsight(requireCredentials(), kind2, body5) : await updateInsight(requireCredentials(), kind2, id, body5);
|
|
18588
18965
|
if (!r.ok || !r.data)
|
|
18589
|
-
fail(this, r.error ?? `Failed to ${
|
|
18966
|
+
fail(this, r.error ?? `Failed to ${action78}`, classifyStatus(r.status), r.errorDetails);
|
|
18590
18967
|
const labels = await fieldLabels(this, r.data);
|
|
18591
18968
|
output(this, r.data, () => detail2(r.data, labels));
|
|
18592
18969
|
});
|
|
@@ -18609,7 +18986,7 @@ var restore = new Command("restore").description("Restore an archived backlog it
|
|
|
18609
18986
|
fail(this, r.error ?? "Failed to restore", classifyStatus(r.status), r.errorDetails);
|
|
18610
18987
|
output(this, r.data, () => console.log(`Restored ${id}`));
|
|
18611
18988
|
});
|
|
18612
|
-
var
|
|
18989
|
+
var remove = new Command("delete").description("Delete a backlog item").argument("<id>", "backlog item ID").option("-f, --force", "Skip confirmation").action(async function(id, opts) {
|
|
18613
18990
|
if (!opts.force) {
|
|
18614
18991
|
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
18615
18992
|
confirmationRequired(
|
|
@@ -18725,7 +19102,7 @@ var fieldsArchive = new Command("archive").description("Archive an idea field; v
|
|
|
18725
19102
|
output(this, r.data, () => fieldDetail(r.data));
|
|
18726
19103
|
});
|
|
18727
19104
|
var fields2 = new Command("fields").description("Manage the shop's own idea fields").addCommand(fieldsList).addCommand(fieldsCreate).addCommand(fieldsUpdate).addCommand(fieldsArchive);
|
|
18728
|
-
var backlogCommand = new Command("backlog").description("Manage backlog").addCommand(list7).addCommand(get6).addCommand(write2("create")).addCommand(write2("update")).addCommand(archive).addCommand(restore).addCommand(
|
|
19105
|
+
var backlogCommand = new Command("backlog").description("Manage backlog").addCommand(list7).addCommand(get6).addCommand(write2("create")).addCommand(write2("update")).addCommand(archive).addCommand(restore).addCommand(remove).addCommand(fields2);
|
|
18729
19106
|
|
|
18730
19107
|
// src/commands/signals.ts
|
|
18731
19108
|
var import_node_fs21 = __toESM(require("node:fs"));
|
|
@@ -18787,35 +19164,35 @@ var get7 = new Command("get").description("Get a signal").argument("<id>", "sign
|
|
|
18787
19164
|
fail(this, r.error ?? "Failed to get", classifyStatus(r.status), r.errorDetails);
|
|
18788
19165
|
output(this, r.data, () => detail3(r.data));
|
|
18789
19166
|
});
|
|
18790
|
-
function write3(
|
|
18791
|
-
const c = new Command(
|
|
18792
|
-
`${
|
|
19167
|
+
function write3(action78) {
|
|
19168
|
+
const c = new Command(action78).description(
|
|
19169
|
+
`${action78 === "create" ? "Create" : "Update"} a ${singular2}`
|
|
18793
19170
|
);
|
|
18794
|
-
if (
|
|
19171
|
+
if (action78 === "update") c.argument("<id>", `${singular2} ID`);
|
|
18795
19172
|
c.argument("[json]", "Inline JSON, @file, or file path").option(
|
|
18796
19173
|
"--file <path>",
|
|
18797
19174
|
"JSON body file"
|
|
18798
19175
|
);
|
|
18799
19176
|
c.action(async function(...args) {
|
|
18800
19177
|
const o = this.opts();
|
|
18801
|
-
const id =
|
|
18802
|
-
const raw = o.file ? `@${o.file}` : String(args[
|
|
19178
|
+
const id = action78 === "update" ? String(args[0]) : void 0;
|
|
19179
|
+
const raw = o.file ? `@${o.file}` : String(args[action78 === "update" ? 1 : 0] ?? "") || void 0;
|
|
18803
19180
|
const body5 = parse2(this, raw);
|
|
18804
19181
|
if (!Object.keys(body5).length) fail(this, "Provide a JSON body.", EXIT_CODES.validation);
|
|
18805
|
-
const r =
|
|
19182
|
+
const r = action78 === "create" ? await createInsight(requireCredentials(), kind3, body5) : await updateInsight(requireCredentials(), kind3, id, body5);
|
|
18806
19183
|
if (!r.ok || !r.data)
|
|
18807
|
-
fail(this, r.error ?? `Failed to ${
|
|
19184
|
+
fail(this, r.error ?? `Failed to ${action78}`, classifyStatus(r.status), r.errorDetails);
|
|
18808
19185
|
output(this, r.data, () => detail3(r.data));
|
|
18809
19186
|
});
|
|
18810
19187
|
return c;
|
|
18811
19188
|
}
|
|
18812
|
-
var
|
|
19189
|
+
var remove2 = new Command("delete").alias("archive").description("Delete or archive a signal").argument("<id>", "signal ID").action(async function(id) {
|
|
18813
19190
|
const r = await deleteInsight(requireCredentials(), kind3, id);
|
|
18814
19191
|
if (!r.ok || !r.data)
|
|
18815
19192
|
fail(this, r.error ?? "Failed to delete", classifyStatus(r.status), r.errorDetails);
|
|
18816
19193
|
output(this, r.data, () => console.log(`Deleted ${id}`));
|
|
18817
19194
|
});
|
|
18818
|
-
var signalsCommand = new Command("signals").description("Manage signals").addCommand(list8).addCommand(get7).addCommand(write3("create")).addCommand(write3("update")).addCommand(
|
|
19195
|
+
var signalsCommand = new Command("signals").description("Manage signals").addCommand(list8).addCommand(get7).addCommand(write3("create")).addCommand(write3("update")).addCommand(remove2);
|
|
18819
19196
|
|
|
18820
19197
|
// src/commands/personalizations.ts
|
|
18821
19198
|
init_source();
|
|
@@ -18896,11 +19273,11 @@ function write4(name) {
|
|
|
18896
19273
|
});
|
|
18897
19274
|
return c;
|
|
18898
19275
|
}
|
|
18899
|
-
function lifecycle2(
|
|
18900
|
-
return new Command(
|
|
18901
|
-
const r = await transitionManagedDelivery(requireCredentials(), kind4, id,
|
|
19276
|
+
function lifecycle2(action78) {
|
|
19277
|
+
return new Command(action78).description(`${action78[0].toUpperCase()}${action78.slice(1)} a personalization`).argument("<id>").action(async function(id) {
|
|
19278
|
+
const r = await transitionManagedDelivery(requireCredentials(), kind4, id, action78);
|
|
18902
19279
|
if (!r.ok || !r.data)
|
|
18903
|
-
fail(this, r.error ?? `Failed to ${
|
|
19280
|
+
fail(this, r.error ?? `Failed to ${action78}`, classifyStatus(r.status), r.errorDetails);
|
|
18904
19281
|
output(this, r.data, () => detail4(r.data));
|
|
18905
19282
|
});
|
|
18906
19283
|
}
|
|
@@ -18982,21 +19359,21 @@ function targetingResourceCommand(kind5) {
|
|
|
18982
19359
|
);
|
|
18983
19360
|
})
|
|
18984
19361
|
);
|
|
18985
|
-
for (const
|
|
18986
|
-
const command = new Command(
|
|
18987
|
-
`${
|
|
19362
|
+
for (const action78 of ["create", "update"]) {
|
|
19363
|
+
const command = new Command(action78).description(
|
|
19364
|
+
`${action78 === "create" ? "Create" : "Update"} a ${singular3}`
|
|
18988
19365
|
);
|
|
18989
|
-
if (
|
|
19366
|
+
if (action78 === "update") command.argument("<id>");
|
|
18990
19367
|
command.argument("[json]", "Inline JSON, @file, or file path").option("--file <path>", "JSON body file").action(async function(...args) {
|
|
18991
19368
|
const opts = this.opts();
|
|
18992
|
-
const id =
|
|
18993
|
-
const value = opts.file ? `@${opts.file}` : String(args[
|
|
19369
|
+
const id = action78 === "update" ? String(args[0]) : void 0;
|
|
19370
|
+
const value = opts.file ? `@${opts.file}` : String(args[action78 === "update" ? 1 : 0] ?? "") || void 0;
|
|
18994
19371
|
const requestBody = body3(this, value);
|
|
18995
|
-
if (
|
|
19372
|
+
if (action78 === "update" && typeof requestBody === "object" && requestBody !== null && !Array.isArray(requestBody) && Object.keys(requestBody).length === 0)
|
|
18996
19373
|
fail(this, `Updating a ${singular3} requires at least one field.`, EXIT_CODES.validation);
|
|
18997
19374
|
const response = await requestTargetingResource(
|
|
18998
19375
|
requireCredentials(),
|
|
18999
|
-
|
|
19376
|
+
action78 === "create" ? "POST" : "PATCH",
|
|
19000
19377
|
kind5,
|
|
19001
19378
|
id,
|
|
19002
19379
|
requestBody
|
|
@@ -19004,11 +19381,11 @@ function targetingResourceCommand(kind5) {
|
|
|
19004
19381
|
if (!response.ok || !response.data)
|
|
19005
19382
|
fail(
|
|
19006
19383
|
this,
|
|
19007
|
-
response.error ?? `Failed to ${
|
|
19384
|
+
response.error ?? `Failed to ${action78}`,
|
|
19008
19385
|
classifyStatus(response.status),
|
|
19009
19386
|
response.errorDetails
|
|
19010
19387
|
);
|
|
19011
|
-
output(this, response.data, () => console.log(source_default.green(`${
|
|
19388
|
+
output(this, response.data, () => console.log(source_default.green(`${action78}d ${singular3}`)));
|
|
19012
19389
|
});
|
|
19013
19390
|
root.addCommand(command);
|
|
19014
19391
|
}
|
|
@@ -19077,7 +19454,7 @@ var exclusionGroupsCommand = targetingResourceCommand("exclusion-groups");
|
|
|
19077
19454
|
// src/commands/brand.ts
|
|
19078
19455
|
init_source();
|
|
19079
19456
|
var NO_PROFILE_MESSAGE = "No brand profile yet \u2014 run onboarding or a site check";
|
|
19080
|
-
var brandCommand = new Command("brand").description("Print the shop brand context for external agents").action(async function
|
|
19457
|
+
var brandCommand = new Command("brand").description("Print the shop brand context for external agents").action(async function action66() {
|
|
19081
19458
|
const creds = requireCredentials();
|
|
19082
19459
|
const res = await getBrandContext(creds);
|
|
19083
19460
|
if (res.status === 404) {
|
|
@@ -19112,7 +19489,7 @@ var brandCommand = new Command("brand").description("Print the shop brand contex
|
|
|
19112
19489
|
|
|
19113
19490
|
// src/commands/targeting.ts
|
|
19114
19491
|
init_source();
|
|
19115
|
-
var test2 = new Command("test").description("Evaluate experiment URL targeting for a page").argument("<url>", "URL to evaluate").option("--status <status>", "Comma-separated statuses to include, or all", "all").action(async function
|
|
19492
|
+
var test2 = new Command("test").description("Evaluate experiment URL targeting for a page").argument("<url>", "URL to evaluate").option("--status <status>", "Comma-separated statuses to include, or all", "all").action(async function action67(url, opts) {
|
|
19116
19493
|
const creds = requireCredentials();
|
|
19117
19494
|
const res = await testTargeting(creds, url, { status: opts.status });
|
|
19118
19495
|
if (!res.ok || !res.data) {
|
|
@@ -19197,7 +19574,7 @@ var create4 = new Command("create").description("Create an Apex SOS ticket").opt
|
|
|
19197
19574
|
"--type <type>",
|
|
19198
19575
|
"full_handover|design|development|qa|config|bug|help (internal types require a DRIP account)",
|
|
19199
19576
|
"qa"
|
|
19200
|
-
).option("--loom <url>", "Loom URL with reproduction or QA context").option("--experiment <id>", "Experiment ID").option("--backlog-item <id>", "Backlog item ID (internal SOS only)").option("--sub-class <text>", "SOS sub-classification").option("--description <text-or-@file>", "Description text or @path").argument("[description]", "Description text").action(async function
|
|
19577
|
+
).option("--loom <url>", "Loom URL with reproduction or QA context").option("--experiment <id>", "Experiment ID").option("--backlog-item <id>", "Backlog item ID (internal SOS only)").option("--sub-class <text>", "SOS sub-classification").option("--description <text-or-@file>", "Description text or @path").argument("[description]", "Description text").action(async function action68(descriptionArg, opts) {
|
|
19201
19578
|
if (!SOS_TYPES.has(opts.type)) {
|
|
19202
19579
|
fail(
|
|
19203
19580
|
this,
|
|
@@ -19228,7 +19605,7 @@ var create4 = new Command("create").description("Create an Apex SOS ticket").opt
|
|
|
19228
19605
|
}
|
|
19229
19606
|
output(this, res.data, () => printCreatedTicket(res.data));
|
|
19230
19607
|
});
|
|
19231
|
-
var mine = new Command("mine").description("List SOS tickets created by this API key").action(async function
|
|
19608
|
+
var mine = new Command("mine").description("List SOS tickets created by this API key").action(async function action69() {
|
|
19232
19609
|
const creds = requireCredentials();
|
|
19233
19610
|
const res = await listMySos(creds);
|
|
19234
19611
|
if (!res.ok || !res.data) {
|
|
@@ -19544,7 +19921,7 @@ function commandSchema(command) {
|
|
|
19544
19921
|
};
|
|
19545
19922
|
}
|
|
19546
19923
|
function createSchemaCommand(program3) {
|
|
19547
|
-
return new Command("schema").description("Print the current Apex CLI command and flag schema").option("--json", "Print JSON schema").action(function
|
|
19924
|
+
return new Command("schema").description("Print the current Apex CLI command and flag schema").option("--json", "Print JSON schema").action(function action78() {
|
|
19548
19925
|
const schema = commandSchema(program3);
|
|
19549
19926
|
output(this, schema, () => {
|
|
19550
19927
|
process.stdout.write(`${JSON.stringify(schema, null, 2)}
|
|
@@ -19697,7 +20074,7 @@ function parseContainer(source, id, dataLayerName = "dataLayer") {
|
|
|
19697
20074
|
vtpPaths,
|
|
19698
20075
|
ecommerceFrom: [...ecommerce][0] ?? null,
|
|
19699
20076
|
serverContainerUrl: [...serverUrls][0] ?? null,
|
|
19700
|
-
piiPaths: vtpPaths.filter((
|
|
20077
|
+
piiPaths: vtpPaths.filter((path26) => /email|phone|address|name/i.test(path26))
|
|
19701
20078
|
}
|
|
19702
20079
|
};
|
|
19703
20080
|
}
|
|
@@ -19766,11 +20143,11 @@ function isContainerField(field) {
|
|
|
19766
20143
|
const types = baseFieldTypes(field);
|
|
19767
20144
|
return types.has("obj") || types.has("arr");
|
|
19768
20145
|
}
|
|
19769
|
-
function hasSyntheticPlaceholder(
|
|
19770
|
-
return
|
|
20146
|
+
function hasSyntheticPlaceholder(path26) {
|
|
20147
|
+
return path26.split(".").map((segment) => segment.replace(/\[\]$/, "")).some((segment) => /^<[^>]+>$/.test(segment));
|
|
19771
20148
|
}
|
|
19772
|
-
function ambiguousDottedPrefix(event,
|
|
19773
|
-
const segments =
|
|
20149
|
+
function ambiguousDottedPrefix(event, path26) {
|
|
20150
|
+
const segments = path26.split(".");
|
|
19774
20151
|
for (let index = 1; index < segments.length; index += 1) {
|
|
19775
20152
|
const prefix = segments.slice(0, index).join(".");
|
|
19776
20153
|
const parent = event.fields.find((field) => field.path === prefix);
|
|
@@ -19779,42 +20156,42 @@ function ambiguousDottedPrefix(event, path25) {
|
|
|
19779
20156
|
return void 0;
|
|
19780
20157
|
}
|
|
19781
20158
|
function dimensionEligibility(event, field) {
|
|
19782
|
-
const
|
|
19783
|
-
if (hasSyntheticPlaceholder(
|
|
20159
|
+
const path26 = field.path;
|
|
20160
|
+
if (hasSyntheticPlaceholder(path26)) {
|
|
19784
20161
|
return {
|
|
19785
20162
|
eligible: false,
|
|
19786
|
-
message: `Path ${JSON.stringify(
|
|
20163
|
+
message: `Path ${JSON.stringify(path26)} is a synthetic placeholder and cannot create a dimension. Choose an observed scalar path.`
|
|
19787
20164
|
};
|
|
19788
20165
|
}
|
|
19789
20166
|
if (isContainerField(field)) {
|
|
19790
20167
|
return {
|
|
19791
20168
|
eligible: false,
|
|
19792
|
-
message: `Path ${JSON.stringify(
|
|
20169
|
+
message: `Path ${JSON.stringify(path26)} is container-typed. Choose an observed scalar path.`
|
|
19793
20170
|
};
|
|
19794
20171
|
}
|
|
19795
|
-
if (
|
|
20172
|
+
if (path26.includes("[]")) {
|
|
19796
20173
|
return {
|
|
19797
20174
|
eligible: false,
|
|
19798
|
-
message: `Path ${JSON.stringify(
|
|
20175
|
+
message: `Path ${JSON.stringify(path26)} includes an array-element segment and cannot create a reliable dimension. Choose a scalar path outside an array.`
|
|
19799
20176
|
};
|
|
19800
20177
|
}
|
|
19801
20178
|
if (!isStableField(field)) {
|
|
19802
20179
|
return {
|
|
19803
20180
|
eligible: false,
|
|
19804
|
-
message: `Path ${JSON.stringify(
|
|
20181
|
+
message: `Path ${JSON.stringify(path26)} does not have a stable observed type. Choose a path without a type collision.`
|
|
19805
20182
|
};
|
|
19806
20183
|
}
|
|
19807
20184
|
if (!event.event) {
|
|
19808
20185
|
return {
|
|
19809
20186
|
eligible: false,
|
|
19810
|
-
message: `Path ${JSON.stringify(
|
|
20187
|
+
message: `Path ${JSON.stringify(path26)} has no observed event name. Choose a path from a named event.`
|
|
19811
20188
|
};
|
|
19812
20189
|
}
|
|
19813
|
-
const ambiguousPrefix = ambiguousDottedPrefix(event,
|
|
20190
|
+
const ambiguousPrefix = ambiguousDottedPrefix(event, path26);
|
|
19814
20191
|
if (ambiguousPrefix) {
|
|
19815
20192
|
return {
|
|
19816
20193
|
eligible: false,
|
|
19817
|
-
message: `Path ${JSON.stringify(
|
|
20194
|
+
message: `Path ${JSON.stringify(path26)} could be a literal key containing dots because the discovery report has no observed container at ${JSON.stringify(ambiguousPrefix)}. Apex cannot safely create a capture for it.`
|
|
19818
20195
|
};
|
|
19819
20196
|
}
|
|
19820
20197
|
return { eligible: true };
|
|
@@ -19823,24 +20200,24 @@ function isNumericField(field) {
|
|
|
19823
20200
|
const numericTypes = /* @__PURE__ */ new Set(["num", "numstr", "numstr_comma"]);
|
|
19824
20201
|
return field.types.length > 0 && field.types.every((type) => numericTypes.has(type.split(":")[0]));
|
|
19825
20202
|
}
|
|
19826
|
-
function defaultDimensionLabel(
|
|
19827
|
-
const words =
|
|
19828
|
-
return words ? `${words[0].toUpperCase()}${words.slice(1)}` :
|
|
20203
|
+
function defaultDimensionLabel(path26) {
|
|
20204
|
+
const words = path26.split(/[._-]+/).filter(Boolean).join(" ");
|
|
20205
|
+
return words ? `${words[0].toUpperCase()}${words.slice(1)}` : path26;
|
|
19829
20206
|
}
|
|
19830
20207
|
function quoteShellArgument(value) {
|
|
19831
20208
|
return "'" + value.replaceAll("'", "'\\''") + "'";
|
|
19832
20209
|
}
|
|
19833
|
-
function createDimensionInvocation(
|
|
19834
|
-
return `apex discover --create-dimension ${quoteShellArgument(
|
|
20210
|
+
function createDimensionInvocation(path26, eventName) {
|
|
20211
|
+
return `apex discover --create-dimension ${quoteShellArgument(path26)} --event ${quoteShellArgument(eventName)}`;
|
|
19835
20212
|
}
|
|
19836
|
-
function resolveDimensionObservation(command, report,
|
|
20213
|
+
function resolveDimensionObservation(command, report, path26, eventName) {
|
|
19837
20214
|
const matches = report.data_layer.flatMap(
|
|
19838
|
-
(event) => event.fields.filter((field) => field.path ===
|
|
20215
|
+
(event) => event.fields.filter((field) => field.path === path26).map((field) => ({ event, field }))
|
|
19839
20216
|
);
|
|
19840
20217
|
if (!matches.length) {
|
|
19841
20218
|
fail(
|
|
19842
20219
|
command,
|
|
19843
|
-
`Path ${JSON.stringify(
|
|
20220
|
+
`Path ${JSON.stringify(path26)} was not found in the current discovery report. Run apex discover and copy an observed path exactly.`,
|
|
19844
20221
|
EXIT_CODES.validation
|
|
19845
20222
|
);
|
|
19846
20223
|
}
|
|
@@ -19850,7 +20227,7 @@ function resolveDimensionObservation(command, report, path25, eventName) {
|
|
|
19850
20227
|
if (!eventMatches.length) {
|
|
19851
20228
|
fail(
|
|
19852
20229
|
command,
|
|
19853
|
-
`Path ${JSON.stringify(
|
|
20230
|
+
`Path ${JSON.stringify(path26)} was not observed on event ${JSON.stringify(eventName)}. Run apex discover and copy the event name exactly.`,
|
|
19854
20231
|
EXIT_CODES.validation
|
|
19855
20232
|
);
|
|
19856
20233
|
}
|
|
@@ -19858,7 +20235,7 @@ function resolveDimensionObservation(command, report, path25, eventName) {
|
|
|
19858
20235
|
if (layers.length > 1) {
|
|
19859
20236
|
fail(
|
|
19860
20237
|
command,
|
|
19861
|
-
`Path ${JSON.stringify(
|
|
20238
|
+
`Path ${JSON.stringify(path26)} on event ${JSON.stringify(eventName)} was observed on multiple data layers: ${layers.join(", ")}. Dimensions bind to one layer; this ambiguity cannot be resolved automatically \u2014 capture the value under one layer, or define the dimension in the dashboard with the data layer name set explicitly.`,
|
|
19862
20239
|
EXIT_CODES.validation
|
|
19863
20240
|
);
|
|
19864
20241
|
}
|
|
@@ -19870,7 +20247,7 @@ function resolveDimensionObservation(command, report, path25, eventName) {
|
|
|
19870
20247
|
if (namedEvents.length > 1) {
|
|
19871
20248
|
fail(
|
|
19872
20249
|
command,
|
|
19873
|
-
`Path ${JSON.stringify(
|
|
20250
|
+
`Path ${JSON.stringify(path26)} was observed on multiple events: ${namedEvents.join(", ")}. Retry with --event <name>.`,
|
|
19874
20251
|
EXIT_CODES.validation
|
|
19875
20252
|
);
|
|
19876
20253
|
}
|
|
@@ -19879,7 +20256,7 @@ function resolveDimensionObservation(command, report, path25, eventName) {
|
|
|
19879
20256
|
if (layers.length > 1) {
|
|
19880
20257
|
fail(
|
|
19881
20258
|
command,
|
|
19882
|
-
`Path ${JSON.stringify(
|
|
20259
|
+
`Path ${JSON.stringify(path26)} on event ${JSON.stringify(namedEvents[0] ?? "")} was observed on multiple data layers: ${layers.join(", ")}. Dimensions bind to one layer; this ambiguity cannot be resolved automatically \u2014 capture the value under one layer, or define the dimension in the dashboard with the data layer name set explicitly.`,
|
|
19883
20260
|
EXIT_CODES.validation
|
|
19884
20261
|
);
|
|
19885
20262
|
}
|
|
@@ -19914,9 +20291,9 @@ function renderDiscovery(report) {
|
|
|
19914
20291
|
`
|
|
19915
20292
|
${event.event || "(unnamed event)"} [${event.dlName}] \u2014 ${event.count} observation(s)${event.matched ? " \u2014 matched" : ""}; t_ms p50=${event.t_ms_p50 ?? "n/a"}, p95=${event.t_ms_p95 ?? "n/a"}, pre-init=${event.pre_init_count}`
|
|
19916
20293
|
);
|
|
19917
|
-
for (const
|
|
20294
|
+
for (const path26 of event.paths)
|
|
19918
20295
|
lines.push(
|
|
19919
|
-
` URL ${
|
|
20296
|
+
` URL ${path26.path}: ${path26.pageviews} pageviews, ${path26.count} pushes, ${path26.pspReturn_count} PSP returns`
|
|
19920
20297
|
);
|
|
19921
20298
|
for (const field of event.fields) {
|
|
19922
20299
|
lines.push(
|
|
@@ -19959,7 +20336,7 @@ ${event.event || "(unnamed event)"} [${event.dlName}] \u2014 ${event.count} obse
|
|
|
19959
20336
|
return `${lines.join("\n")}
|
|
19960
20337
|
`;
|
|
19961
20338
|
}
|
|
19962
|
-
var discoverCommand = new Command("discover").description("Read back the tracking schema observed for the current shop").option("--minutes <n>", "Observation window in minutes").option("--gtm <url-or-id>", "Mine a public page or GTM container locally").option("--gtm-url <url>", "Fetch an explicit GTM container URL, including first-party proxies").option("--create-dimension <path>", "Create a custom dimension from an observed dataLayer path").option("--event <name>", "Select the observed event when a path appears on multiple events").option("--key <signal-key>", "Override the dimension signal key").option("--label <text>", "Override the dimension label").action(async function
|
|
20339
|
+
var discoverCommand = new Command("discover").description("Read back the tracking schema observed for the current shop").option("--minutes <n>", "Observation window in minutes").option("--gtm <url-or-id>", "Mine a public page or GTM container locally").option("--gtm-url <url>", "Fetch an explicit GTM container URL, including first-party proxies").option("--create-dimension <path>", "Create a custom dimension from an observed dataLayer path").option("--event <name>", "Select the observed event when a path appears on multiple events").option("--key <signal-key>", "Override the dimension signal key").option("--label <text>", "Override the dimension label").action(async function action70(opts) {
|
|
19963
20340
|
if (opts.gtm && opts.gtmUrl)
|
|
19964
20341
|
fail(this, "Use only one of --gtm or --gtm-url", EXIT_CODES.validation);
|
|
19965
20342
|
if (opts.createDimension && (opts.gtm || opts.gtmUrl)) {
|
|
@@ -19979,7 +20356,7 @@ var discoverCommand = new Command("discover").description("Read back the trackin
|
|
|
19979
20356
|
let report;
|
|
19980
20357
|
let apiSucceeded = false;
|
|
19981
20358
|
let discoveryCredentials;
|
|
19982
|
-
let
|
|
20359
|
+
let apiFailure2;
|
|
19983
20360
|
try {
|
|
19984
20361
|
discoveryCredentials = requireCredentials();
|
|
19985
20362
|
const result = await discoverTrackingSchema(discoveryCredentials, minutes);
|
|
@@ -19987,24 +20364,24 @@ var discoverCommand = new Command("discover").description("Read back the trackin
|
|
|
19987
20364
|
report = result.data;
|
|
19988
20365
|
apiSucceeded = true;
|
|
19989
20366
|
} else
|
|
19990
|
-
|
|
20367
|
+
apiFailure2 = {
|
|
19991
20368
|
message: result.error ?? "Discovery API unavailable",
|
|
19992
20369
|
status: result.status,
|
|
19993
20370
|
details: result.errorDetails
|
|
19994
20371
|
};
|
|
19995
20372
|
} catch (error) {
|
|
19996
|
-
|
|
20373
|
+
apiFailure2 = {
|
|
19997
20374
|
message: error instanceof Error ? error.message : String(error),
|
|
19998
20375
|
...error instanceof CliError ? { details: error.details, cliError: error } : {}
|
|
19999
20376
|
};
|
|
20000
20377
|
}
|
|
20001
20378
|
if (!report && (!gtmInput || opts.createDimension)) {
|
|
20002
|
-
if (
|
|
20379
|
+
if (apiFailure2?.cliError) throw apiFailure2.cliError;
|
|
20003
20380
|
fail(
|
|
20004
20381
|
this,
|
|
20005
|
-
|
|
20006
|
-
classifyStatus(
|
|
20007
|
-
|
|
20382
|
+
apiFailure2?.message ?? "Failed to discover tracking schema",
|
|
20383
|
+
classifyStatus(apiFailure2?.status ?? 0),
|
|
20384
|
+
apiFailure2?.details
|
|
20008
20385
|
);
|
|
20009
20386
|
}
|
|
20010
20387
|
report ??= {
|
|
@@ -20019,7 +20396,7 @@ var discoverCommand = new Command("discover").description("Read back the trackin
|
|
|
20019
20396
|
trigger_diagnoses: [],
|
|
20020
20397
|
storage: [],
|
|
20021
20398
|
notes: [
|
|
20022
|
-
`GTM static analysis completed without discovery API data: ${
|
|
20399
|
+
`GTM static analysis completed without discovery API data: ${apiFailure2?.message ?? "platform credentials were not provided"}`
|
|
20023
20400
|
]
|
|
20024
20401
|
};
|
|
20025
20402
|
if (gtmInput) {
|
|
@@ -20057,9 +20434,9 @@ var discoverCommand = new Command("discover").description("Read back the trackin
|
|
|
20057
20434
|
if (!apiSucceeded || !discoveryCredentials) {
|
|
20058
20435
|
fail(
|
|
20059
20436
|
this,
|
|
20060
|
-
|
|
20061
|
-
classifyStatus(
|
|
20062
|
-
|
|
20437
|
+
apiFailure2?.message ?? "Discovery API data is required to create a dimension",
|
|
20438
|
+
classifyStatus(apiFailure2?.status ?? 0),
|
|
20439
|
+
apiFailure2?.details
|
|
20063
20440
|
);
|
|
20064
20441
|
}
|
|
20065
20442
|
const observation = resolveDimensionObservation(
|
|
@@ -20162,7 +20539,7 @@ function installSkill(paths, targetRoot) {
|
|
|
20162
20539
|
import_node_fs26.default.copyFileSync(paths.agents, import_node_path23.default.join(target, "AGENTS.md"));
|
|
20163
20540
|
return target;
|
|
20164
20541
|
}
|
|
20165
|
-
var skillsCommand = new Command("skills").description("Print or install the Apex CLI agent skill files").option("--install", "Copy the Apex CLI skill into ./.claude/skills/apex-cli").option("--target <dir>", "Install target skills directory", ".claude/skills").option("--json", "Print JSON").action(function
|
|
20542
|
+
var skillsCommand = new Command("skills").description("Print or install the Apex CLI agent skill files").option("--install", "Copy the Apex CLI skill into ./.claude/skills/apex-cli").option("--target <dir>", "Install target skills directory", ".claude/skills").option("--json", "Print JSON").action(function action71(opts) {
|
|
20166
20543
|
let paths;
|
|
20167
20544
|
try {
|
|
20168
20545
|
paths = skillPaths();
|
|
@@ -20589,8 +20966,8 @@ async function fetchText(url) {
|
|
|
20589
20966
|
});
|
|
20590
20967
|
return { status: res.status, text: await readResponseText(res), finalUrl: res.url };
|
|
20591
20968
|
}
|
|
20592
|
-
function abs(base,
|
|
20593
|
-
return new URL(
|
|
20969
|
+
function abs(base, path26) {
|
|
20970
|
+
return new URL(path26, base).toString();
|
|
20594
20971
|
}
|
|
20595
20972
|
function looksLikeShopifyCart(text2) {
|
|
20596
20973
|
try {
|
|
@@ -20610,9 +20987,9 @@ async function collectSignals(url) {
|
|
|
20610
20987
|
const chunks = [`__apex_primary_status_${primary.status}__`, primary.text];
|
|
20611
20988
|
const protectedHit = primary.status === 403 || primary.status === 429 || matchDetectionMarkers(primary.text, "protected").length > 0;
|
|
20612
20989
|
if (!protectedHit) {
|
|
20613
|
-
for (const
|
|
20990
|
+
for (const path26 of ["/cart.js"]) {
|
|
20614
20991
|
try {
|
|
20615
|
-
const secondary = await fetchText(abs(primary.finalUrl || url,
|
|
20992
|
+
const secondary = await fetchText(abs(primary.finalUrl || url, path26));
|
|
20616
20993
|
if (secondary.status >= 200 && secondary.status < 300 && looksLikeShopifyCart(secondary.text)) {
|
|
20617
20994
|
chunks.push("__apex_secondary_shopify_cart_js__", secondary.text.slice(0, 2e3));
|
|
20618
20995
|
}
|
|
@@ -20791,7 +21168,7 @@ function selectedEvents(opts) {
|
|
|
20791
21168
|
}
|
|
20792
21169
|
var list14 = new Command("list").alias("ls").description(
|
|
20793
21170
|
"List webhooks. JSON includes availableEvents; secrets are never returned after create."
|
|
20794
|
-
).action(async function
|
|
21171
|
+
).action(async function action72() {
|
|
20795
21172
|
const data = await loadWebhookIndex(this);
|
|
20796
21173
|
output(this, data, () => printWebhooks(data.data));
|
|
20797
21174
|
});
|
|
@@ -20800,7 +21177,7 @@ var create7 = new Command("create").description(`Create a webhook. Prints the si
|
|
|
20800
21177
|
"Webhook event to subscribe to; repeat for multiple events",
|
|
20801
21178
|
(value, previous = []) => [...previous, value],
|
|
20802
21179
|
[]
|
|
20803
|
-
).action(async function
|
|
21180
|
+
).action(async function action73(opts) {
|
|
20804
21181
|
const events = selectedEvents(opts);
|
|
20805
21182
|
if (events.length === 0) fail(this, "At least one --event is required", EXIT_CODES.validation);
|
|
20806
21183
|
const index = await loadWebhookIndex(this);
|
|
@@ -20829,7 +21206,7 @@ Signature verification: ${SIGNATURE_DOC}
|
|
|
20829
21206
|
console.log(`Signature verification: ${SIGNATURE_DOC}`);
|
|
20830
21207
|
});
|
|
20831
21208
|
});
|
|
20832
|
-
var
|
|
21209
|
+
var remove3 = new Command("delete").alias("rm").description("Delete a webhook").argument("<id>", "Webhook id").action(async function action74(id) {
|
|
20833
21210
|
requireShopSecretKey(this);
|
|
20834
21211
|
const result = handleResult(
|
|
20835
21212
|
this,
|
|
@@ -20838,7 +21215,7 @@ var remove4 = new Command("delete").alias("rm").description("Delete a webhook").
|
|
|
20838
21215
|
);
|
|
20839
21216
|
output(this, result, () => console.log(source_default.green(`Deleted webhook ${id}`)));
|
|
20840
21217
|
});
|
|
20841
|
-
var deliveries = new Command("deliveries").description("List webhook deliveries including failure and disabled state").argument("<id>", "Webhook id").option("--limit <n>", "Maximum deliveries to show", "20").action(async function
|
|
21218
|
+
var deliveries = new Command("deliveries").description("List webhook deliveries including failure and disabled state").argument("<id>", "Webhook id").option("--limit <n>", "Maximum deliveries to show", "20").action(async function action75(id, opts) {
|
|
20842
21219
|
const limit = Number.parseInt(opts.limit, 10);
|
|
20843
21220
|
if (!Number.isInteger(limit) || limit < 1)
|
|
20844
21221
|
fail(this, "--limit must be a positive integer", EXIT_CODES.validation);
|
|
@@ -20855,7 +21232,7 @@ var update5 = new Command("update").description("Update a webhook URL, events, o
|
|
|
20855
21232
|
"Replacement event; repeat for multiple events",
|
|
20856
21233
|
(value, previous = []) => [...previous, value],
|
|
20857
21234
|
[]
|
|
20858
|
-
).option("--active", "Enable the webhook").option("--inactive", "Disable the webhook").action(async function
|
|
21235
|
+
).option("--active", "Enable the webhook").option("--inactive", "Disable the webhook").action(async function action76(id, opts) {
|
|
20859
21236
|
if (opts.active && opts.inactive)
|
|
20860
21237
|
fail(this, "Use only one of --active or --inactive", EXIT_CODES.validation);
|
|
20861
21238
|
const events = selectedEvents(opts);
|
|
@@ -20888,7 +21265,7 @@ var update5 = new Command("update").description("Update a webhook URL, events, o
|
|
|
20888
21265
|
);
|
|
20889
21266
|
output(this, result, () => printWebhooks([result]));
|
|
20890
21267
|
});
|
|
20891
|
-
var test3 = new Command("test").description("Send a signed test event to a webhook").argument("<id>", "Webhook id").action(async function
|
|
21268
|
+
var test3 = new Command("test").description("Send a signed test event to a webhook").argument("<id>", "Webhook id").action(async function action77(id) {
|
|
20892
21269
|
requireShopSecretKey(this);
|
|
20893
21270
|
const result = handleResult(
|
|
20894
21271
|
this,
|
|
@@ -20903,7 +21280,7 @@ var test3 = new Command("test").description("Send a signed test event to a webho
|
|
|
20903
21280
|
)
|
|
20904
21281
|
);
|
|
20905
21282
|
});
|
|
20906
|
-
var webhooksCommand = new Command("webhooks").description("Manage shop webhooks").addCommand(list14).addCommand(create7).addCommand(update5).addCommand(
|
|
21283
|
+
var webhooksCommand = new Command("webhooks").description("Manage shop webhooks").addCommand(list14).addCommand(create7).addCommand(update5).addCommand(remove3).addCommand(deliveries).addCommand(test3);
|
|
20907
21284
|
|
|
20908
21285
|
// src/commands/simulate.ts
|
|
20909
21286
|
init_source();
|
|
@@ -21032,8 +21409,584 @@ var simulateCommand = new Command("simulate").description("Simulate data-layer t
|
|
|
21032
21409
|
});
|
|
21033
21410
|
|
|
21034
21411
|
// src/commands/config-resources.ts
|
|
21412
|
+
var import_node_fs29 = __toESM(require("node:fs"));
|
|
21413
|
+
var import_node_path26 = __toESM(require("node:path"));
|
|
21414
|
+
|
|
21415
|
+
// src/commands/landing-pages.ts
|
|
21416
|
+
var import_node_crypto7 = __toESM(require("node:crypto"));
|
|
21035
21417
|
var import_node_fs28 = __toESM(require("node:fs"));
|
|
21036
21418
|
var import_node_path25 = __toESM(require("node:path"));
|
|
21419
|
+
function apiFailure(command, response, fallback2) {
|
|
21420
|
+
if (!response.ok || response.data === void 0) {
|
|
21421
|
+
fail(
|
|
21422
|
+
command,
|
|
21423
|
+
response.error ?? fallback2,
|
|
21424
|
+
classifyStatus(response.status),
|
|
21425
|
+
response.errorDetails
|
|
21426
|
+
);
|
|
21427
|
+
}
|
|
21428
|
+
}
|
|
21429
|
+
function sha256(body5) {
|
|
21430
|
+
return import_node_crypto7.default.createHash("sha256").update(body5).digest("hex");
|
|
21431
|
+
}
|
|
21432
|
+
function markerPath(folder) {
|
|
21433
|
+
return import_node_path25.default.join(folder, LANDING_PAGE_CHECKOUT_MARKER);
|
|
21434
|
+
}
|
|
21435
|
+
function readMarker(folder) {
|
|
21436
|
+
const file = markerPath(folder);
|
|
21437
|
+
if (!import_node_fs28.default.existsSync(file)) return null;
|
|
21438
|
+
try {
|
|
21439
|
+
return JSON.parse(import_node_fs28.default.readFileSync(file, "utf8"));
|
|
21440
|
+
} catch (error) {
|
|
21441
|
+
throw new CliError(
|
|
21442
|
+
`${LANDING_PAGE_CHECKOUT_MARKER} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
21443
|
+
EXIT_CODES.validation,
|
|
21444
|
+
{
|
|
21445
|
+
code: "validation_failed",
|
|
21446
|
+
next_steps: [
|
|
21447
|
+
{
|
|
21448
|
+
command: "apex landing-pages pull <id> --out <new folder>",
|
|
21449
|
+
why: "Pull a fresh checkout and copy your changes over."
|
|
21450
|
+
}
|
|
21451
|
+
]
|
|
21452
|
+
}
|
|
21453
|
+
);
|
|
21454
|
+
}
|
|
21455
|
+
}
|
|
21456
|
+
function writeMarker(folder, marker) {
|
|
21457
|
+
import_node_fs28.default.mkdirSync(import_node_path25.default.dirname(markerPath(folder)), { recursive: true });
|
|
21458
|
+
import_node_fs28.default.writeFileSync(import_node_path25.default.join(folder, ".apex", ".gitignore"), "landing-page.json\n", "utf8");
|
|
21459
|
+
import_node_fs28.default.writeFileSync(markerPath(folder), `${JSON.stringify(marker, null, 2)}
|
|
21460
|
+
`, "utf8");
|
|
21461
|
+
}
|
|
21462
|
+
function requireCheckout(command, folderArg) {
|
|
21463
|
+
const folder = import_node_path25.default.resolve(process.cwd(), folderArg);
|
|
21464
|
+
const marker = import_node_fs28.default.existsSync(folder) && import_node_fs28.default.statSync(folder).isDirectory() ? readMarker(folder) : null;
|
|
21465
|
+
if (!marker) {
|
|
21466
|
+
fail(command, `${folderArg} is not a landing-page checkout.`, EXIT_CODES.validation, {
|
|
21467
|
+
code: "validation_failed",
|
|
21468
|
+
next_steps: [
|
|
21469
|
+
{ command: "apex landing-pages pull <id> --out <folder>", why: "Create a checkout first." }
|
|
21470
|
+
]
|
|
21471
|
+
});
|
|
21472
|
+
}
|
|
21473
|
+
return { folder, marker };
|
|
21474
|
+
}
|
|
21475
|
+
function resolvePageId(target) {
|
|
21476
|
+
const folder = import_node_path25.default.resolve(process.cwd(), target);
|
|
21477
|
+
if (import_node_fs28.default.existsSync(folder) && import_node_fs28.default.statSync(folder).isDirectory()) {
|
|
21478
|
+
const marker = readMarker(folder);
|
|
21479
|
+
if (marker) return marker.landingPageId;
|
|
21480
|
+
throw new CliError(
|
|
21481
|
+
`${target} is a folder without a landing-page checkout.`,
|
|
21482
|
+
EXIT_CODES.validation,
|
|
21483
|
+
{
|
|
21484
|
+
code: "validation_failed",
|
|
21485
|
+
next_steps: [
|
|
21486
|
+
{
|
|
21487
|
+
command: "apex landing-pages pull <id> --out <folder>",
|
|
21488
|
+
why: "Create a checkout first."
|
|
21489
|
+
}
|
|
21490
|
+
]
|
|
21491
|
+
}
|
|
21492
|
+
);
|
|
21493
|
+
}
|
|
21494
|
+
return target;
|
|
21495
|
+
}
|
|
21496
|
+
function readCheckoutFiles(folder) {
|
|
21497
|
+
const files = [];
|
|
21498
|
+
const visit2 = (directory) => {
|
|
21499
|
+
for (const entry of import_node_fs28.default.readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
21500
|
+
const absolute = import_node_path25.default.join(directory, entry.name);
|
|
21501
|
+
const relative = import_node_path25.default.relative(folder, absolute).split(import_node_path25.default.sep).join("/");
|
|
21502
|
+
if (!isLandingPageCheckoutPath(relative)) continue;
|
|
21503
|
+
if (entry.isDirectory()) visit2(absolute);
|
|
21504
|
+
else if (entry.isFile()) {
|
|
21505
|
+
const body5 = import_node_fs28.default.readFileSync(absolute);
|
|
21506
|
+
files.push({ path: relative, sha256: sha256(body5), bytes: body5.byteLength, body: body5 });
|
|
21507
|
+
}
|
|
21508
|
+
}
|
|
21509
|
+
};
|
|
21510
|
+
visit2(folder);
|
|
21511
|
+
return files;
|
|
21512
|
+
}
|
|
21513
|
+
function readManifest2(command, folder) {
|
|
21514
|
+
const file = import_node_path25.default.join(folder, LANDING_PAGE_MANIFEST_FILE);
|
|
21515
|
+
if (!import_node_fs28.default.existsSync(file)) {
|
|
21516
|
+
fail(
|
|
21517
|
+
command,
|
|
21518
|
+
`${LANDING_PAGE_MANIFEST_FILE} is missing from the checkout.`,
|
|
21519
|
+
EXIT_CODES.validation,
|
|
21520
|
+
{
|
|
21521
|
+
code: "validation_failed"
|
|
21522
|
+
}
|
|
21523
|
+
);
|
|
21524
|
+
}
|
|
21525
|
+
try {
|
|
21526
|
+
return JSON.parse(import_node_fs28.default.readFileSync(file, "utf8"));
|
|
21527
|
+
} catch (error) {
|
|
21528
|
+
fail(
|
|
21529
|
+
command,
|
|
21530
|
+
`${LANDING_PAGE_MANIFEST_FILE} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
21531
|
+
EXIT_CODES.validation,
|
|
21532
|
+
{
|
|
21533
|
+
code: "validation_failed"
|
|
21534
|
+
}
|
|
21535
|
+
);
|
|
21536
|
+
}
|
|
21537
|
+
}
|
|
21538
|
+
function declared(files) {
|
|
21539
|
+
return files.map(({ path: filePath, sha256: digest, bytes }) => ({
|
|
21540
|
+
path: filePath,
|
|
21541
|
+
sha256: digest,
|
|
21542
|
+
bytes
|
|
21543
|
+
}));
|
|
21544
|
+
}
|
|
21545
|
+
function printPlan(plan2) {
|
|
21546
|
+
const { files, manifest } = plan2;
|
|
21547
|
+
if (!plan2.would_change) {
|
|
21548
|
+
console.log("No changes.");
|
|
21549
|
+
return;
|
|
21550
|
+
}
|
|
21551
|
+
for (const file of files.added) console.log(` + ${file}`);
|
|
21552
|
+
for (const file of files.changed) console.log(` ~ ${file}`);
|
|
21553
|
+
for (const file of files.removed) console.log(` - ${file}`);
|
|
21554
|
+
console.log(` ${files.unchanged} unchanged \xB7 ${plan2.file_count} files \xB7 ${plan2.bytes} bytes`);
|
|
21555
|
+
if (manifest.fields.length) console.log(`Manifest: ${manifest.fields.join(", ")} changed`);
|
|
21556
|
+
const goals = manifest.goals;
|
|
21557
|
+
if (goals.added.length + goals.changed.length + goals.removed.length) {
|
|
21558
|
+
console.log(
|
|
21559
|
+
`Goals: ${[
|
|
21560
|
+
...goals.added.map((key) => `+${key}`),
|
|
21561
|
+
...goals.changed.map((key) => `~${key}`),
|
|
21562
|
+
...goals.removed.map((key) => `-${key} (kept in Apex)`)
|
|
21563
|
+
].join(" ")}`
|
|
21564
|
+
);
|
|
21565
|
+
}
|
|
21566
|
+
console.log(
|
|
21567
|
+
`Deploy target: ${plan2.deploy_target ?? "none yet: set host in apex-landing-page.json to a domain this shop holds"}`
|
|
21568
|
+
);
|
|
21569
|
+
}
|
|
21570
|
+
async function confirm2(message) {
|
|
21571
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) return null;
|
|
21572
|
+
const readline3 = await import("node:readline");
|
|
21573
|
+
const rl = readline3.createInterface({ input: process.stdin, output: process.stderr });
|
|
21574
|
+
const answer = await new Promise((resolve2) => rl.question(`${message} [y/N] `, resolve2));
|
|
21575
|
+
rl.close();
|
|
21576
|
+
return answer.trim().toLowerCase() === "y";
|
|
21577
|
+
}
|
|
21578
|
+
function manifestDigest(folder) {
|
|
21579
|
+
const file = import_node_path25.default.join(folder, LANDING_PAGE_MANIFEST_FILE);
|
|
21580
|
+
return import_node_fs28.default.existsSync(file) ? sha256(import_node_fs28.default.readFileSync(file)) : null;
|
|
21581
|
+
}
|
|
21582
|
+
function localChanges(folder, marker) {
|
|
21583
|
+
const local = new Map(readCheckoutFiles(folder).map((file) => [file.path, file.sha256]));
|
|
21584
|
+
const changed = /* @__PURE__ */ new Set();
|
|
21585
|
+
for (const [file, digest] of local) if (marker.files[file] !== digest) changed.add(file);
|
|
21586
|
+
for (const file of Object.keys(marker.files)) {
|
|
21587
|
+
if (isLandingPageCheckoutPath(file) && !local.has(file)) changed.add(file);
|
|
21588
|
+
}
|
|
21589
|
+
if (marker.manifestSha256 && manifestDigest(folder) !== marker.manifestSha256) {
|
|
21590
|
+
changed.add(LANDING_PAGE_MANIFEST_FILE);
|
|
21591
|
+
}
|
|
21592
|
+
return [...changed].sort();
|
|
21593
|
+
}
|
|
21594
|
+
function removeStaleFiles(folder, previous, next, added = []) {
|
|
21595
|
+
if (!previous) return;
|
|
21596
|
+
for (const file of [...Object.keys(previous.files), ...added]) {
|
|
21597
|
+
if (next.has(file) || !isLandingPageCheckoutPath(file)) continue;
|
|
21598
|
+
const absolute = import_node_path25.default.resolve(folder, file);
|
|
21599
|
+
if (absolute.startsWith(folder + import_node_path25.default.sep) && import_node_fs28.default.existsSync(absolute)) import_node_fs28.default.rmSync(absolute);
|
|
21600
|
+
}
|
|
21601
|
+
}
|
|
21602
|
+
async function pullCheckout(command, creds, id, folder, force = false) {
|
|
21603
|
+
const existing = readMarker(folder);
|
|
21604
|
+
if (existing && existing.landingPageId !== id) {
|
|
21605
|
+
fail(
|
|
21606
|
+
command,
|
|
21607
|
+
`${folder} is a checkout of another landing page (${existing.landingPageId}).`,
|
|
21608
|
+
EXIT_CODES.validation
|
|
21609
|
+
);
|
|
21610
|
+
}
|
|
21611
|
+
const changed = existing && !force ? localChanges(folder, existing) : [];
|
|
21612
|
+
if (changed.length) {
|
|
21613
|
+
fail(
|
|
21614
|
+
command,
|
|
21615
|
+
`${folder} has changes that are not applied: ${changed.slice(0, 5).join(", ")}${changed.length > 5 ? ` and ${changed.length - 5} more` : ""}.`,
|
|
21616
|
+
EXIT_CODES.conflict,
|
|
21617
|
+
{
|
|
21618
|
+
code: "landing_page_local_changes",
|
|
21619
|
+
next_steps: [
|
|
21620
|
+
{
|
|
21621
|
+
command: `apex landing-pages pull ${id} --out <new folder>`,
|
|
21622
|
+
why: "Pull the current version next to your checkout, then copy your changes over."
|
|
21623
|
+
},
|
|
21624
|
+
{
|
|
21625
|
+
command: `apex landing-pages apply ${folder} --yes --force`,
|
|
21626
|
+
why: "Replace the current version with your checkout."
|
|
21627
|
+
},
|
|
21628
|
+
{
|
|
21629
|
+
command: `apex landing-pages pull ${id} --out ${folder} --force`,
|
|
21630
|
+
why: "Discard your changes."
|
|
21631
|
+
}
|
|
21632
|
+
]
|
|
21633
|
+
}
|
|
21634
|
+
);
|
|
21635
|
+
}
|
|
21636
|
+
const api = landingPages(creds);
|
|
21637
|
+
const listing = await api.checkout(id);
|
|
21638
|
+
apiFailure(command, listing, "Failed to pull landing page");
|
|
21639
|
+
import_node_fs28.default.mkdirSync(folder, { recursive: true });
|
|
21640
|
+
const written = {};
|
|
21641
|
+
const downloaded = [];
|
|
21642
|
+
for (const batch of landingPageBatches(listing.data.files, (file) => file.bytes)) {
|
|
21643
|
+
const contents = await api.checkout(
|
|
21644
|
+
id,
|
|
21645
|
+
batch.map((file) => file.path)
|
|
21646
|
+
);
|
|
21647
|
+
apiFailure(command, contents, "Failed to pull landing page files");
|
|
21648
|
+
const returned2 = new Set(contents.data.files.map((file) => file.path));
|
|
21649
|
+
const missing = batch.filter((file) => !returned2.has(file.path)).map((file) => file.path);
|
|
21650
|
+
if (missing.length)
|
|
21651
|
+
fail(command, `The server did not return ${missing.slice(0, 5).join(", ")}. Pull again.`);
|
|
21652
|
+
for (const file of contents.data.files) {
|
|
21653
|
+
const target = import_node_path25.default.resolve(folder, file.path);
|
|
21654
|
+
if (!target.startsWith(folder + import_node_path25.default.sep))
|
|
21655
|
+
fail(command, `Refusing to write outside the checkout: ${file.path}`);
|
|
21656
|
+
const body5 = file.encoding === "base64" ? Buffer.from(file.content ?? "", "base64") : Buffer.from(file.content ?? "", "utf8");
|
|
21657
|
+
if (sha256(body5) !== file.sha256)
|
|
21658
|
+
fail(command, `The server returned ${file.path} incomplete. Pull again.`);
|
|
21659
|
+
downloaded.push({ path: file.path, target, body: body5, sha256: file.sha256 });
|
|
21660
|
+
}
|
|
21661
|
+
}
|
|
21662
|
+
if (!existing && !force) {
|
|
21663
|
+
const clobbered = downloaded.filter(
|
|
21664
|
+
({ target, sha256: digest }) => import_node_fs28.default.existsSync(target) && sha256(import_node_fs28.default.readFileSync(target)) !== digest
|
|
21665
|
+
).map((file) => file.path);
|
|
21666
|
+
if (clobbered.length) {
|
|
21667
|
+
fail(
|
|
21668
|
+
command,
|
|
21669
|
+
`${folder} already has files the pull would replace: ${clobbered.slice(0, 5).join(", ")}${clobbered.length > 5 ? ` and ${clobbered.length - 5} more` : ""}.`,
|
|
21670
|
+
EXIT_CODES.conflict,
|
|
21671
|
+
{
|
|
21672
|
+
code: "landing_page_folder_not_empty",
|
|
21673
|
+
next_steps: [
|
|
21674
|
+
{
|
|
21675
|
+
command: `apex landing-pages pull ${id} --out <new folder>`,
|
|
21676
|
+
why: "Pull into an empty folder."
|
|
21677
|
+
},
|
|
21678
|
+
{
|
|
21679
|
+
command: `apex landing-pages pull ${id} --out ${folder} --force`,
|
|
21680
|
+
why: "Replace those files."
|
|
21681
|
+
}
|
|
21682
|
+
]
|
|
21683
|
+
}
|
|
21684
|
+
);
|
|
21685
|
+
}
|
|
21686
|
+
}
|
|
21687
|
+
for (const file of downloaded) {
|
|
21688
|
+
import_node_fs28.default.mkdirSync(import_node_path25.default.dirname(file.target), { recursive: true });
|
|
21689
|
+
import_node_fs28.default.writeFileSync(file.target, file.body);
|
|
21690
|
+
written[file.path] = file.sha256;
|
|
21691
|
+
}
|
|
21692
|
+
removeStaleFiles(
|
|
21693
|
+
folder,
|
|
21694
|
+
existing,
|
|
21695
|
+
new Set(Object.keys(written)),
|
|
21696
|
+
force ? readCheckoutFiles(folder).map((file) => file.path) : []
|
|
21697
|
+
);
|
|
21698
|
+
import_node_fs28.default.writeFileSync(
|
|
21699
|
+
import_node_path25.default.join(folder, LANDING_PAGE_MANIFEST_FILE),
|
|
21700
|
+
`${JSON.stringify(listing.data.manifest ?? {}, null, 2)}
|
|
21701
|
+
`,
|
|
21702
|
+
"utf8"
|
|
21703
|
+
);
|
|
21704
|
+
const contextDir = import_node_path25.default.join(folder, LANDING_PAGE_CONTEXT_DIR);
|
|
21705
|
+
import_node_fs28.default.mkdirSync(contextDir, { recursive: true });
|
|
21706
|
+
const contextName = /^[a-z0-9_-]+\.json$/i;
|
|
21707
|
+
const contextEntries = Object.entries(listing.data.context ?? {}).filter(
|
|
21708
|
+
([name]) => contextName.test(name)
|
|
21709
|
+
);
|
|
21710
|
+
const returned = new Set(contextEntries.map(([name]) => name));
|
|
21711
|
+
for (const name of import_node_fs28.default.readdirSync(contextDir)) {
|
|
21712
|
+
if (contextName.test(name) && !returned.has(name)) import_node_fs28.default.rmSync(import_node_path25.default.join(contextDir, name));
|
|
21713
|
+
}
|
|
21714
|
+
for (const [name, value] of contextEntries) {
|
|
21715
|
+
import_node_fs28.default.writeFileSync(import_node_path25.default.join(contextDir, name), `${JSON.stringify(value, null, 2)}
|
|
21716
|
+
`, "utf8");
|
|
21717
|
+
}
|
|
21718
|
+
writeMarker(folder, {
|
|
21719
|
+
schemaVersion: 1,
|
|
21720
|
+
baseUrl: creds.baseUrl,
|
|
21721
|
+
landingPageId: id,
|
|
21722
|
+
versionId: listing.data.version?.id ?? null,
|
|
21723
|
+
versionNumber: listing.data.version?.version_number ?? null,
|
|
21724
|
+
pulledAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
21725
|
+
files: written,
|
|
21726
|
+
manifestSha256: manifestDigest(folder) ?? void 0
|
|
21727
|
+
});
|
|
21728
|
+
return {
|
|
21729
|
+
folder,
|
|
21730
|
+
landing_page: listing.data.landing_page,
|
|
21731
|
+
version: listing.data.version,
|
|
21732
|
+
files: Object.keys(written).length,
|
|
21733
|
+
notes: listing.data.notes ?? []
|
|
21734
|
+
};
|
|
21735
|
+
}
|
|
21736
|
+
var create8 = new Command("create").description("Create a landing page to build outside Apex").requiredOption("--name <name>", "Page name").option("--slug <slug>", "URL slug (default: from the name)").option("--idea <key>", "Idea key or id the page is built for").option("--out <folder>", "Also pull the new page into this folder").action(async function(opts) {
|
|
21737
|
+
const creds = requireCredentials();
|
|
21738
|
+
const created = await landingPages(creds).create({
|
|
21739
|
+
name: opts.name,
|
|
21740
|
+
...opts.slug ? { slug: opts.slug } : {},
|
|
21741
|
+
...opts.idea ? { idea_key: opts.idea } : {}
|
|
21742
|
+
});
|
|
21743
|
+
apiFailure(this, created, "Failed to create landing page");
|
|
21744
|
+
const pulled = opts.out ? await pullCheckout(this, creds, created.data.id, import_node_path25.default.resolve(process.cwd(), opts.out)) : null;
|
|
21745
|
+
output(
|
|
21746
|
+
this,
|
|
21747
|
+
{ landing_page: created.data, ...pulled ? { folder: pulled.folder } : {} },
|
|
21748
|
+
() => {
|
|
21749
|
+
console.log(created.data.id);
|
|
21750
|
+
if (pulled) console.log(`Checkout: ${pulled.folder}`);
|
|
21751
|
+
}
|
|
21752
|
+
);
|
|
21753
|
+
});
|
|
21754
|
+
var pull = new Command("pull").description("Pull a landing page into a checkout folder").argument("<id>", "Landing page id").option("--out <folder>", "Target folder (default: a folder named after the id)").option("--force", "Overwrite changes in the checkout that are not applied").action(async function(id, opts) {
|
|
21755
|
+
const creds = requireCredentials();
|
|
21756
|
+
const folder = import_node_path25.default.resolve(process.cwd(), opts.out ?? id);
|
|
21757
|
+
const result = await pullCheckout(this, creds, id, folder, opts.force === true);
|
|
21758
|
+
output(this, result, () => {
|
|
21759
|
+
console.log(
|
|
21760
|
+
`Pulled ${result.landing_page.name} (version ${result.version?.version_number ?? "none"}, ${result.files} files)`
|
|
21761
|
+
);
|
|
21762
|
+
console.log(result.folder);
|
|
21763
|
+
for (const note of result.notes) console.log(note);
|
|
21764
|
+
});
|
|
21765
|
+
});
|
|
21766
|
+
var plan = new Command("plan").description("Show what applying a checkout would change").argument("[folder]", "Checkout folder", ".").action(async function(folderArg) {
|
|
21767
|
+
const creds = requireCredentials();
|
|
21768
|
+
const { folder, marker } = requireCheckout(this, folderArg);
|
|
21769
|
+
const files = readCheckoutFiles(folder);
|
|
21770
|
+
const response = await landingPages(creds).plan(marker.landingPageId, {
|
|
21771
|
+
manifest: readManifest2(this, folder),
|
|
21772
|
+
files: declared(files)
|
|
21773
|
+
});
|
|
21774
|
+
apiFailure(this, response, "Failed to plan landing page");
|
|
21775
|
+
output(
|
|
21776
|
+
this,
|
|
21777
|
+
{ folder, landingPageId: marker.landingPageId, plan: response.data },
|
|
21778
|
+
() => printPlan(response.data)
|
|
21779
|
+
);
|
|
21780
|
+
});
|
|
21781
|
+
var apply = new Command("apply").description("Apply a checkout as a new version").argument("[folder]", "Checkout folder", ".").option("--yes", "Apply without asking for confirmation").option("--force", "Apply even when the page changed since this checkout was pulled").action(async function(folderArg, opts) {
|
|
21782
|
+
const creds = requireCredentials();
|
|
21783
|
+
const { folder, marker } = requireCheckout(this, folderArg);
|
|
21784
|
+
const api = landingPages(creds);
|
|
21785
|
+
const files = readCheckoutFiles(folder);
|
|
21786
|
+
const manifest = readManifest2(this, folder);
|
|
21787
|
+
const planned = await api.plan(marker.landingPageId, { manifest, files: declared(files) });
|
|
21788
|
+
apiFailure(this, planned, "Failed to plan landing page");
|
|
21789
|
+
if (!planned.data.would_change) {
|
|
21790
|
+
output(
|
|
21791
|
+
this,
|
|
21792
|
+
{ folder, applied: false, plan: planned.data, message: "No changes." },
|
|
21793
|
+
() => console.log("No changes.")
|
|
21794
|
+
);
|
|
21795
|
+
return;
|
|
21796
|
+
}
|
|
21797
|
+
if (!opts.yes) {
|
|
21798
|
+
if (!shouldUseJson(this)) printPlan(planned.data);
|
|
21799
|
+
const ok = await confirm2("Apply these changes as a new version?");
|
|
21800
|
+
if (ok === null) {
|
|
21801
|
+
confirmationRequired(
|
|
21802
|
+
this,
|
|
21803
|
+
"Applying a landing page needs confirmation.",
|
|
21804
|
+
`apex landing-pages apply ${folderArg} --yes`
|
|
21805
|
+
);
|
|
21806
|
+
}
|
|
21807
|
+
if (!ok) {
|
|
21808
|
+
output(this, { aborted: true }, () => console.log("Aborted."));
|
|
21809
|
+
return;
|
|
21810
|
+
}
|
|
21811
|
+
}
|
|
21812
|
+
const required = new Set(planned.data.files.upload_required);
|
|
21813
|
+
const toUpload = files.filter((file) => required.has(file.path));
|
|
21814
|
+
for (const batch of landingPageBatches(toUpload, (file) => file.bytes)) {
|
|
21815
|
+
const uploaded = await api.upload(
|
|
21816
|
+
marker.landingPageId,
|
|
21817
|
+
batch.map((file) => ({
|
|
21818
|
+
path: file.path,
|
|
21819
|
+
content: file.body.toString("base64"),
|
|
21820
|
+
encoding: "base64"
|
|
21821
|
+
}))
|
|
21822
|
+
);
|
|
21823
|
+
apiFailure(this, uploaded, "Failed to upload landing page files");
|
|
21824
|
+
}
|
|
21825
|
+
const applied = await api.apply(marker.landingPageId, {
|
|
21826
|
+
manifest,
|
|
21827
|
+
files: declared(files),
|
|
21828
|
+
...opts.force ? {} : { base_version_id: marker.versionId }
|
|
21829
|
+
});
|
|
21830
|
+
if (!applied.ok && applied.status === 409 && applied.errorDetails?.code === "landing_page_version_conflict") {
|
|
21831
|
+
fail(
|
|
21832
|
+
this,
|
|
21833
|
+
applied.error ?? "The page changed since this checkout was pulled.",
|
|
21834
|
+
EXIT_CODES.conflict,
|
|
21835
|
+
{
|
|
21836
|
+
...applied.errorDetails,
|
|
21837
|
+
next_steps: [
|
|
21838
|
+
{
|
|
21839
|
+
command: `apex landing-pages pull ${marker.landingPageId} --out ${folderArg}`,
|
|
21840
|
+
why: "Bring the checkout up to date."
|
|
21841
|
+
},
|
|
21842
|
+
{
|
|
21843
|
+
command: `apex landing-pages apply ${folderArg} --yes --force`,
|
|
21844
|
+
why: "Replace the current version anyway."
|
|
21845
|
+
}
|
|
21846
|
+
]
|
|
21847
|
+
}
|
|
21848
|
+
);
|
|
21849
|
+
}
|
|
21850
|
+
apiFailure(this, applied, "Failed to apply landing page");
|
|
21851
|
+
writeMarker(folder, {
|
|
21852
|
+
...marker,
|
|
21853
|
+
versionId: applied.data.version_id,
|
|
21854
|
+
versionNumber: applied.data.version_number,
|
|
21855
|
+
pulledAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
21856
|
+
files: Object.fromEntries(files.map((file) => [file.path, file.sha256])),
|
|
21857
|
+
manifestSha256: manifestDigest(folder) ?? void 0
|
|
21858
|
+
});
|
|
21859
|
+
output(this, { folder, applied: true, ...applied.data }, () => {
|
|
21860
|
+
console.log(`Version ${applied.data.version_number}`);
|
|
21861
|
+
if (applied.data.preview_url) console.log(`Preview: ${applied.data.preview_url}`);
|
|
21862
|
+
console.log(
|
|
21863
|
+
`Deploy target: ${applied.data.deploy_target ?? "none yet: set host in apex-landing-page.json to a domain this shop holds"}`
|
|
21864
|
+
);
|
|
21865
|
+
});
|
|
21866
|
+
});
|
|
21867
|
+
var preview = new Command("preview").description("Print a signed preview link (valid 24 hours)").argument("[folder-or-id]", "Checkout folder or landing page id", ".").option("--version <number>", "Version number (default: current)").action(async function(target, opts) {
|
|
21868
|
+
const versionNumber = opts.version === void 0 ? void 0 : Number(opts.version);
|
|
21869
|
+
if (versionNumber !== void 0 && (!Number.isInteger(versionNumber) || versionNumber < 1)) {
|
|
21870
|
+
fail(this, "--version must be a version number.", EXIT_CODES.validation);
|
|
21871
|
+
}
|
|
21872
|
+
const response = await landingPages(requireCredentials()).preview(
|
|
21873
|
+
resolvePageId(target),
|
|
21874
|
+
versionNumber
|
|
21875
|
+
);
|
|
21876
|
+
apiFailure(this, response, "Failed to get preview link");
|
|
21877
|
+
output(this, response.data, () => console.log(response.data.preview_url));
|
|
21878
|
+
});
|
|
21879
|
+
async function versionIdFor(command, creds, id, raw) {
|
|
21880
|
+
if (raw === void 0) return void 0;
|
|
21881
|
+
const number = Number(raw);
|
|
21882
|
+
if (!Number.isInteger(number) || number < 1)
|
|
21883
|
+
fail(command, "--version must be a version number.", EXIT_CODES.validation);
|
|
21884
|
+
const versions2 = await landingPages(creds).versions(id);
|
|
21885
|
+
apiFailure(command, versions2, "Failed to list versions");
|
|
21886
|
+
const match2 = versions2.data.find((version) => version.version_number === number);
|
|
21887
|
+
if (!match2) fail(command, `Version ${number} not found.`, EXIT_CODES.validation);
|
|
21888
|
+
return match2.id;
|
|
21889
|
+
}
|
|
21890
|
+
var SPLIT_KEEPS_SERVING_HELP = "\nWhile an experiment splits this page, visitors keep getting its two arms and its goals stay as they are (new manifest goals are added). The deployed version serves when the experiment ends; its goal changes apply at the next deploy after that.\n";
|
|
21891
|
+
var deploy = new Command("deploy").description("Deploy a version to a domain this shop holds").argument("[folder-or-id]", "Checkout folder or landing page id", ".").option(
|
|
21892
|
+
"--host <host>",
|
|
21893
|
+
"Domain claimed with apex domains claim (default: the manifest host; one of the two is required)"
|
|
21894
|
+
).option("--path <path>", "Path on the host").option("--version <number>", "Version number (default: current)").action(async function(target, opts) {
|
|
21895
|
+
const creds = requireCredentials();
|
|
21896
|
+
const id = resolvePageId(target);
|
|
21897
|
+
const versionId = await versionIdFor(this, creds, id, opts.version);
|
|
21898
|
+
const response = await landingPages(creds).deploy(id, {
|
|
21899
|
+
...versionId ? { version_id: versionId } : {},
|
|
21900
|
+
...opts.host ? { host: opts.host } : {},
|
|
21901
|
+
...opts.path ? { path: opts.path } : {}
|
|
21902
|
+
});
|
|
21903
|
+
apiFailure(this, response, "Failed to deploy landing page");
|
|
21904
|
+
output(this, response.data, () => {
|
|
21905
|
+
console.log(response.data.url);
|
|
21906
|
+
console.log(`Version ${response.data.version_number} is live.`);
|
|
21907
|
+
});
|
|
21908
|
+
}).addHelpText("after", SPLIT_KEEPS_SERVING_HELP);
|
|
21909
|
+
var versions = new Command("versions").description("List versions").argument("[folder-or-id]", "Checkout folder or landing page id", ".").action(async function(target) {
|
|
21910
|
+
const response = await landingPages(requireCredentials()).versions(resolvePageId(target));
|
|
21911
|
+
apiFailure(this, response, "Failed to list versions");
|
|
21912
|
+
output(
|
|
21913
|
+
this,
|
|
21914
|
+
response.data,
|
|
21915
|
+
() => printTable(
|
|
21916
|
+
["VERSION", "SOURCE", "STATE", "FILES", "BYTES", "CREATED"],
|
|
21917
|
+
response.data.map((version) => [
|
|
21918
|
+
String(version.version_number),
|
|
21919
|
+
version.source,
|
|
21920
|
+
[version.published ? "live" : "", version.current ? "current" : ""].filter(Boolean).join(", ") || "-",
|
|
21921
|
+
version.file_count === null ? "-" : String(version.file_count),
|
|
21922
|
+
version.bytes === null ? "-" : String(version.bytes),
|
|
21923
|
+
version.created_at
|
|
21924
|
+
]),
|
|
21925
|
+
[8, 10, 14, 6, 10, 26]
|
|
21926
|
+
)
|
|
21927
|
+
);
|
|
21928
|
+
});
|
|
21929
|
+
var rollback = new Command("rollback").description("Deploy an earlier version to the page's current address").argument("[folder-or-id]", "Checkout folder or landing page id", ".").requiredOption("--version <number>", "Version number to roll back to").action(async function(target, opts) {
|
|
21930
|
+
const number = Number(opts.version);
|
|
21931
|
+
if (!Number.isInteger(number) || number < 1)
|
|
21932
|
+
fail(this, "--version must be a version number.", EXIT_CODES.validation);
|
|
21933
|
+
const response = await landingPages(requireCredentials()).rollback(resolvePageId(target), {
|
|
21934
|
+
version_number: number
|
|
21935
|
+
});
|
|
21936
|
+
apiFailure(this, response, "Failed to roll back landing page");
|
|
21937
|
+
output(this, response.data, () => {
|
|
21938
|
+
console.log(response.data.url);
|
|
21939
|
+
console.log(`Version ${response.data.version_number} is live.`);
|
|
21940
|
+
});
|
|
21941
|
+
}).addHelpText("after", SPLIT_KEEPS_SERVING_HELP);
|
|
21942
|
+
var experiment = new Command("experiment").description("Create a draft experiment that splits the live page between two versions").argument("[folder-or-id]", "Checkout folder or landing page id", ".").option(
|
|
21943
|
+
"--control <number>",
|
|
21944
|
+
"Control version number (default: the manifest's experiment.control)"
|
|
21945
|
+
).option(
|
|
21946
|
+
"--variant <number>",
|
|
21947
|
+
"Variant version number (default: the manifest's experiment.variant)"
|
|
21948
|
+
).option("--name <name>", "Experiment name").option("--primary-goal <key>", "Manifest goal key to make primary (default: the first goal)").action(async function(target, opts) {
|
|
21949
|
+
const folder = import_node_path25.default.resolve(process.cwd(), target);
|
|
21950
|
+
const fromManifest = import_node_fs28.default.existsSync(import_node_path25.default.join(folder, LANDING_PAGE_MANIFEST_FILE)) ? readManifest2(this, folder)?.experiment ?? {} : {};
|
|
21951
|
+
const control = Number(opts.control ?? fromManifest.control);
|
|
21952
|
+
const variant = Number(opts.variant ?? fromManifest.variant);
|
|
21953
|
+
if (!Number.isInteger(control) || !Number.isInteger(variant) || control < 1 || variant < 1) {
|
|
21954
|
+
fail(
|
|
21955
|
+
this,
|
|
21956
|
+
"Name the control and variant version numbers (--control, --variant, or experiment in apex-landing-page.json).",
|
|
21957
|
+
EXIT_CODES.validation
|
|
21958
|
+
);
|
|
21959
|
+
}
|
|
21960
|
+
const response = await landingPages(requireCredentials()).experiment(resolvePageId(target), {
|
|
21961
|
+
control,
|
|
21962
|
+
variant,
|
|
21963
|
+
...opts.name ? { name: opts.name } : {},
|
|
21964
|
+
...opts.primaryGoal ? { primary_goal: opts.primaryGoal } : {}
|
|
21965
|
+
});
|
|
21966
|
+
apiFailure(this, response, "Failed to create experiment");
|
|
21967
|
+
output(this, response.data, () => {
|
|
21968
|
+
console.log(`Draft experiment ${String(response.data.experiment.id)} created.`);
|
|
21969
|
+
console.log("Start it in the Apex dashboard when it is ready.");
|
|
21970
|
+
});
|
|
21971
|
+
});
|
|
21972
|
+
var open2 = new Command("open").description("Print the page's dashboard URL").argument("[folder-or-id]", "Checkout folder or landing page id", ".").action(function(target) {
|
|
21973
|
+
const url = `${requireCredentials().baseUrl.replace(/\/+$/, "")}/landing-pages/${encodeURIComponent(resolvePageId(target))}`;
|
|
21974
|
+
output(this, { url }, () => console.log(url));
|
|
21975
|
+
});
|
|
21976
|
+
var landingPageWorkspaceCommands = [
|
|
21977
|
+
create8,
|
|
21978
|
+
pull,
|
|
21979
|
+
plan,
|
|
21980
|
+
apply,
|
|
21981
|
+
preview,
|
|
21982
|
+
deploy,
|
|
21983
|
+
versions,
|
|
21984
|
+
rollback,
|
|
21985
|
+
experiment,
|
|
21986
|
+
open2
|
|
21987
|
+
];
|
|
21988
|
+
|
|
21989
|
+
// src/commands/config-resources.ts
|
|
21037
21990
|
function show(resource) {
|
|
21038
21991
|
process.stdout.write(`${JSON.stringify(resource, null, 2)}
|
|
21039
21992
|
`);
|
|
@@ -21099,12 +22052,12 @@ function readFamily(family, label) {
|
|
|
21099
22052
|
}
|
|
21100
22053
|
var assetReads = readFamily("assets", "assets");
|
|
21101
22054
|
var upload = new Command("upload").description("Upload an asset").argument("<file>").action(async function(file) {
|
|
21102
|
-
const absolute =
|
|
21103
|
-
if (!
|
|
21104
|
-
const fileStat =
|
|
22055
|
+
const absolute = import_node_path26.default.resolve(process.cwd(), file);
|
|
22056
|
+
if (!import_node_fs29.default.existsSync(absolute)) fail(this, `File not found: ${file}`, 3);
|
|
22057
|
+
const fileStat = import_node_fs29.default.statSync(absolute);
|
|
21105
22058
|
if (!fileStat.isFile()) fail(this, `File not found: ${file}`, 3);
|
|
21106
22059
|
if (fileStat.size > MAX_ASSET_SIZE_BYTES) fail(this, `${file} is larger than 10 MB`, 3);
|
|
21107
|
-
const mimeType = ASSET_MIME_TYPES[
|
|
22060
|
+
const mimeType = ASSET_MIME_TYPES[import_node_path26.default.extname(absolute).toLowerCase()];
|
|
21108
22061
|
if (!mimeType)
|
|
21109
22062
|
fail(
|
|
21110
22063
|
this,
|
|
@@ -21113,8 +22066,8 @@ var upload = new Command("upload").description("Upload an asset").argument("<fil
|
|
|
21113
22066
|
);
|
|
21114
22067
|
const r = await uploadAssets(requireCredentials(), [
|
|
21115
22068
|
{
|
|
21116
|
-
name:
|
|
21117
|
-
bytes:
|
|
22069
|
+
name: import_node_path26.default.basename(absolute),
|
|
22070
|
+
bytes: import_node_fs29.default.readFileSync(absolute),
|
|
21118
22071
|
type: mimeType
|
|
21119
22072
|
}
|
|
21120
22073
|
]);
|
|
@@ -21122,7 +22075,7 @@ var upload = new Command("upload").description("Upload an asset").argument("<fil
|
|
|
21122
22075
|
fail(this, r.error ?? "Failed to upload asset", classifyStatus(r.status), r.errorDetails);
|
|
21123
22076
|
output(this, r.data, () => r.data.assets.forEach(show));
|
|
21124
22077
|
});
|
|
21125
|
-
var
|
|
22078
|
+
var remove4 = new Command("delete").alias("rm").description("Delete an asset").argument("<id>").option("-f, --force", "Skip confirmation").action(async function(id, opts) {
|
|
21126
22079
|
if (!opts.force) {
|
|
21127
22080
|
const confirmed = await confirmAssetDelete(id);
|
|
21128
22081
|
if (!confirmed) {
|
|
@@ -21138,9 +22091,12 @@ var remove5 = new Command("delete").alias("rm").description("Delete an asset").a
|
|
|
21138
22091
|
fail(this, r.error ?? "Failed to delete asset", classifyStatus(r.status), r.errorDetails);
|
|
21139
22092
|
output(this, r.data, () => console.log(`Deleted ${id}`));
|
|
21140
22093
|
});
|
|
21141
|
-
var assetsCommand = new Command("assets").description("Manage assets").addCommand(assetReads.list).addCommand(assetReads.get).addCommand(upload).addCommand(
|
|
22094
|
+
var assetsCommand = new Command("assets").description("Manage assets").addCommand(assetReads.list).addCommand(assetReads.get).addCommand(upload).addCommand(remove4);
|
|
21142
22095
|
var landingReads = readFamily("landing-pages", "landing pages");
|
|
21143
|
-
var landingPagesCommand = new Command("landing-pages").description(
|
|
22096
|
+
var landingPagesCommand = new Command("landing-pages").description(
|
|
22097
|
+
"Build landing pages with your own tools, then deploy, track and test them with Apex"
|
|
22098
|
+
).addCommand(landingReads.list).addCommand(landingReads.get);
|
|
22099
|
+
for (const command of landingPageWorkspaceCommands) landingPagesCommand.addCommand(command);
|
|
21144
22100
|
var snippet = new Command("snippet").description("Generate an external purchase pixel snippet").option("--provider <name>").option("--handover-param <name>").option("--global-name <name>").action(async function(opts) {
|
|
21145
22101
|
const body5 = Object.fromEntries(
|
|
21146
22102
|
Object.entries(opts).filter(([, value]) => value !== void 0)
|