@drip-apex/apex-cli 0.14.2 → 0.15.1
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 +69 -3
- package/dist/cli.js +1433 -366
- 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.1",
|
|
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,111 @@ 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
|
+
uploadGrant: (id, file) => call("POST", pagePath(id, "filesystem/upload-grant"), file),
|
|
8240
|
+
apply: (id, body5) => call("POST", pagePath(id, "filesystem/apply"), body5),
|
|
8241
|
+
deploy: (id, body5) => call("POST", pagePath(id, "deploy"), body5),
|
|
8242
|
+
versions: (id) => call("GET", pagePath(id, "versions")),
|
|
8243
|
+
preview: (id, versionNumber) => call("GET", versionNumber ? `${pagePath(id, "preview")}?version=${versionNumber}` : pagePath(id, "preview")),
|
|
8244
|
+
rollback: (id, body5) => call("POST", pagePath(id, "rollback"), body5),
|
|
8245
|
+
experiment: (id, body5) => call("POST", pagePath(id, "experiment"), body5)
|
|
8246
|
+
};
|
|
8247
|
+
}
|
|
8248
|
+
function landingPageBatches(items, size) {
|
|
8249
|
+
const batches = [];
|
|
8250
|
+
let current = [];
|
|
8251
|
+
let bytes = 0;
|
|
8252
|
+
for (const item of items) {
|
|
8253
|
+
const itemBytes = size(item);
|
|
8254
|
+
if (current.length && (current.length >= LANDING_PAGE_BATCH_FILES || bytes + itemBytes > LANDING_PAGE_BATCH_BYTES)) {
|
|
8255
|
+
batches.push(current);
|
|
8256
|
+
current = [];
|
|
8257
|
+
bytes = 0;
|
|
8258
|
+
}
|
|
8259
|
+
current.push(item);
|
|
8260
|
+
bytes += itemBytes;
|
|
8261
|
+
}
|
|
8262
|
+
if (current.length) batches.push(current);
|
|
8263
|
+
return batches;
|
|
8264
|
+
}
|
|
8265
|
+
async function uploadLandingVideo(grant, body5, options = {}) {
|
|
8266
|
+
if (!Number.isSafeInteger(grant.part_bytes) || grant.part_bytes !== 8 * 1024 * 1024) throw new Error("Invalid video part size");
|
|
8267
|
+
const fetcher = options.fetch ?? fetch;
|
|
8268
|
+
const send3 = async (action78, method = "POST", part, bytes) => {
|
|
8269
|
+
const url = new URL(grant.upload_url);
|
|
8270
|
+
url.searchParams.set("action", action78);
|
|
8271
|
+
if (part !== void 0) url.searchParams.set("part", String(part));
|
|
8272
|
+
let error;
|
|
8273
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
8274
|
+
try {
|
|
8275
|
+
const response = await fetcher(url, { method, ...bytes ? { body: bytes } : {}, signal: AbortSignal.timeout(6e4) });
|
|
8276
|
+
if (!response.ok) {
|
|
8277
|
+
const message = await response.text();
|
|
8278
|
+
if (response.status < 500 && response.status !== 429) throw new Error(`Video upload refused (${response.status}): ${message}`);
|
|
8279
|
+
error = new Error(`Video upload failed (${response.status}): ${message}`);
|
|
8280
|
+
} else return await response.json();
|
|
8281
|
+
} catch (caught) {
|
|
8282
|
+
if (caught instanceof Error && caught.message.startsWith("Video upload refused")) throw caught;
|
|
8283
|
+
error = caught;
|
|
8284
|
+
}
|
|
8285
|
+
}
|
|
8286
|
+
throw error;
|
|
8287
|
+
};
|
|
8288
|
+
const started2 = await send3("start");
|
|
8289
|
+
if (!started2.complete) {
|
|
8290
|
+
for (let offset = 0, part = 1; offset < body5.byteLength; offset += grant.part_bytes, part++) {
|
|
8291
|
+
const bytes = body5.subarray(offset, Math.min(body5.byteLength, offset + grant.part_bytes));
|
|
8292
|
+
await send3("part", "PUT", part, bytes);
|
|
8293
|
+
options.progress?.(offset + bytes.byteLength, body5.byteLength);
|
|
8294
|
+
}
|
|
8295
|
+
const result = await send3("complete");
|
|
8296
|
+
if (!result.complete) throw new Error("Worker did not confirm the video upload");
|
|
8297
|
+
}
|
|
8298
|
+
}
|
|
8299
|
+
function validateLandingPageLocalFiles(files) {
|
|
8300
|
+
const allowed2 = new Set("html css js mjs json svg png jpg jpeg webp avif gif ico woff woff2 txt xml webmanifest mp4 webm".split(" "));
|
|
8301
|
+
if (files.length > 300) throw new Error("A bundle is limited to 300 files");
|
|
8302
|
+
let total = 0;
|
|
8303
|
+
for (const file of files) {
|
|
8304
|
+
const ext = file.path.split(".").at(-1)?.toLowerCase() ?? "";
|
|
8305
|
+
if (!allowed2.has(ext)) throw new Error(`File type is not allowed: ${file.path}`);
|
|
8306
|
+
const cap = ext === "mp4" || ext === "webm" ? 50 * 1024 * 1024 : LANDING_PAGE_BATCH_BYTES;
|
|
8307
|
+
if (!Number.isSafeInteger(file.bytes) || file.bytes < 0 || file.bytes > cap) throw new Error(`File exceeds ${cap} bytes: ${file.path}`);
|
|
8308
|
+
total += file.bytes;
|
|
8309
|
+
}
|
|
8310
|
+
if (total > 100 * 1024 * 1024) throw new Error("A bundle is limited to 100 MB");
|
|
8311
|
+
}
|
|
8312
|
+
|
|
8313
|
+
// src/api.ts
|
|
8209
8314
|
function simulateTriggers(creds, body5) {
|
|
8210
8315
|
return request(creds, "POST", "/api/v1/tracking/simulate-trigger", body5);
|
|
8211
8316
|
}
|
|
@@ -8229,11 +8334,11 @@ function getUserKeyIdentity(creds) {
|
|
|
8229
8334
|
function getAccountContext(creds) {
|
|
8230
8335
|
return userRequest(creds, "GET", "/api/v1/me/context");
|
|
8231
8336
|
}
|
|
8232
|
-
function userRequest(creds, method,
|
|
8337
|
+
function userRequest(creds, method, path26, body5) {
|
|
8233
8338
|
return request(
|
|
8234
8339
|
{ ...creds, apiKey: creds.userApiKey ?? "", baseUrl: creds.userBaseUrl ?? creds.baseUrl },
|
|
8235
8340
|
method,
|
|
8236
|
-
|
|
8341
|
+
path26,
|
|
8237
8342
|
body5
|
|
8238
8343
|
);
|
|
8239
8344
|
}
|
|
@@ -8315,13 +8420,13 @@ function sharedClient(creds) {
|
|
|
8315
8420
|
}
|
|
8316
8421
|
return client;
|
|
8317
8422
|
}
|
|
8318
|
-
async function request(creds, method,
|
|
8319
|
-
const response = await sharedClient(creds).request(method,
|
|
8423
|
+
async function request(creds, method, path26, body5, requestTimeoutMs) {
|
|
8424
|
+
const response = await sharedClient(creds).request(method, path26, body5, requestTimeoutMs);
|
|
8320
8425
|
recordApiResponse(response);
|
|
8321
8426
|
return response;
|
|
8322
8427
|
}
|
|
8323
|
-
async function requestForm(creds, method,
|
|
8324
|
-
const response = await sharedClient(creds).requestForm(method,
|
|
8428
|
+
async function requestForm(creds, method, path26, body5) {
|
|
8429
|
+
const response = await sharedClient(creds).requestForm(method, path26, body5);
|
|
8325
8430
|
recordApiResponse(response);
|
|
8326
8431
|
return response;
|
|
8327
8432
|
}
|
|
@@ -8553,6 +8658,36 @@ function getInstallDomains(creds) {
|
|
|
8553
8658
|
function updateInstallDomains(creds, body5) {
|
|
8554
8659
|
return request(creds, "PATCH", "/api/v1/install-domains", body5);
|
|
8555
8660
|
}
|
|
8661
|
+
function getLandingPageDomains(creds, filter = {}) {
|
|
8662
|
+
const query = filter.host ? `?host=${encodeURIComponent(filter.host)}` : "";
|
|
8663
|
+
return request(
|
|
8664
|
+
creds,
|
|
8665
|
+
"GET",
|
|
8666
|
+
`/api/v1/landing-pages/domains${query}`
|
|
8667
|
+
);
|
|
8668
|
+
}
|
|
8669
|
+
function createLandingPageDomain(creds, body5) {
|
|
8670
|
+
return request(
|
|
8671
|
+
creds,
|
|
8672
|
+
"POST",
|
|
8673
|
+
"/api/v1/landing-pages/domains",
|
|
8674
|
+
body5
|
|
8675
|
+
);
|
|
8676
|
+
}
|
|
8677
|
+
function verifyLandingPageDomain(creds, id) {
|
|
8678
|
+
return request(
|
|
8679
|
+
creds,
|
|
8680
|
+
"POST",
|
|
8681
|
+
`/api/v1/landing-pages/domains/${encodeURIComponent(id)}/verify`
|
|
8682
|
+
);
|
|
8683
|
+
}
|
|
8684
|
+
function deleteLandingPageDomain(creds, id) {
|
|
8685
|
+
return request(
|
|
8686
|
+
creds,
|
|
8687
|
+
"DELETE",
|
|
8688
|
+
`/api/v1/landing-pages/domains/${encodeURIComponent(id)}`
|
|
8689
|
+
);
|
|
8690
|
+
}
|
|
8556
8691
|
function getExternalTracking(creds) {
|
|
8557
8692
|
return request(creds, "GET", "/api/v1/external-tracking");
|
|
8558
8693
|
}
|
|
@@ -8560,18 +8695,18 @@ function updateExternalTracking(creds, body5) {
|
|
|
8560
8695
|
return request(creds, "PATCH", "/api/v1/external-tracking", body5);
|
|
8561
8696
|
}
|
|
8562
8697
|
function trackingEventProperties(event) {
|
|
8563
|
-
let
|
|
8698
|
+
let preview2 = null;
|
|
8564
8699
|
if (typeof event.properties_preview === "string") {
|
|
8565
8700
|
try {
|
|
8566
8701
|
const parsed = JSON.parse(event.properties_preview);
|
|
8567
8702
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
8568
|
-
|
|
8703
|
+
preview2 = parsed;
|
|
8569
8704
|
}
|
|
8570
8705
|
} catch {
|
|
8571
8706
|
}
|
|
8572
8707
|
}
|
|
8573
8708
|
const legacy = event.properties && typeof event.properties === "object" && !Array.isArray(event.properties) ? event.properties : null;
|
|
8574
|
-
const properties = { ...legacy ?? {}, ...
|
|
8709
|
+
const properties = { ...legacy ?? {}, ...preview2 ?? {} };
|
|
8575
8710
|
if (event.goal_id != null) properties.goalId = event.goal_id;
|
|
8576
8711
|
return properties;
|
|
8577
8712
|
}
|
|
@@ -8726,6 +8861,14 @@ function markInboxRead(creds, channel) {
|
|
|
8726
8861
|
function userCredentials(creds) {
|
|
8727
8862
|
return { ...creds, apiKey: creds.userApiKey ?? "", baseUrl: creds.userBaseUrl ?? creds.baseUrl };
|
|
8728
8863
|
}
|
|
8864
|
+
function createAdminCustomer(creds, body5) {
|
|
8865
|
+
return request(
|
|
8866
|
+
userCredentials(creds),
|
|
8867
|
+
"POST",
|
|
8868
|
+
"/api/v1/admin/customers",
|
|
8869
|
+
body5
|
|
8870
|
+
);
|
|
8871
|
+
}
|
|
8729
8872
|
function getAdminSettings(creds) {
|
|
8730
8873
|
return request(userCredentials(creds), "GET", "/api/v1/admin/settings");
|
|
8731
8874
|
}
|
|
@@ -8763,11 +8906,11 @@ function requestTargetingResource(creds, method, kind5, id, body5, limit, cursor
|
|
|
8763
8906
|
if (limit) params.set("limit", String(limit));
|
|
8764
8907
|
if (cursor) params.set("cursor", cursor);
|
|
8765
8908
|
const query = params.toString();
|
|
8766
|
-
const
|
|
8767
|
-
return request(creds, method,
|
|
8909
|
+
const path26 = `/api/v1/${kind5}${id ? `/${id.split("/").map(encodeURIComponent).join("/")}` : ""}${query ? `?${query}` : ""}`;
|
|
8910
|
+
return request(creds, method, path26, body5);
|
|
8768
8911
|
}
|
|
8769
|
-
function managedPath(kind5, id,
|
|
8770
|
-
return `/api/v1/${kind5}${id ? `/${encodeURIComponent(id)}` : ""}${
|
|
8912
|
+
function managedPath(kind5, id, action78) {
|
|
8913
|
+
return `/api/v1/${kind5}${id ? `/${encodeURIComponent(id)}` : ""}${action78 ? `/${action78}` : ""}`;
|
|
8771
8914
|
}
|
|
8772
8915
|
function listManagedDeliveries(creds, kind5, limit) {
|
|
8773
8916
|
const q = limit ? `?limit=${limit}` : "";
|
|
@@ -8785,8 +8928,8 @@ function createManagedDelivery(creds, kind5, body5) {
|
|
|
8785
8928
|
function updateManagedDelivery(creds, kind5, id, body5) {
|
|
8786
8929
|
return request(creds, "PATCH", managedPath(kind5, id), body5);
|
|
8787
8930
|
}
|
|
8788
|
-
function transitionManagedDelivery(creds, kind5, id,
|
|
8789
|
-
return request(creds, "POST", managedPath(kind5, id,
|
|
8931
|
+
function transitionManagedDelivery(creds, kind5, id, action78) {
|
|
8932
|
+
return request(creds, "POST", managedPath(kind5, id, action78));
|
|
8790
8933
|
}
|
|
8791
8934
|
function listConfigResources(creds, family, opts) {
|
|
8792
8935
|
const params = new URLSearchParams();
|
|
@@ -8798,6 +8941,11 @@ function listConfigResources(creds, family, opts) {
|
|
|
8798
8941
|
function getConfigResource(creds, family, id) {
|
|
8799
8942
|
return request(creds, "GET", `/api/v1/${family}/${encodeURIComponent(id)}`);
|
|
8800
8943
|
}
|
|
8944
|
+
function landingPages(creds) {
|
|
8945
|
+
return landingPagesApi(
|
|
8946
|
+
(method, path26, body5) => request(creds, method, path26, body5)
|
|
8947
|
+
);
|
|
8948
|
+
}
|
|
8801
8949
|
function deleteAsset(creds, id) {
|
|
8802
8950
|
return request(creds, "DELETE", `/api/v1/assets/${encodeURIComponent(id)}`);
|
|
8803
8951
|
}
|
|
@@ -9103,9 +9251,9 @@ var STATUS_COLORS = {
|
|
|
9103
9251
|
paused: source_default.yellow,
|
|
9104
9252
|
completed: source_default.blue
|
|
9105
9253
|
};
|
|
9106
|
-
function statusBadge(
|
|
9107
|
-
const colorize = STATUS_COLORS[
|
|
9108
|
-
return colorize(
|
|
9254
|
+
function statusBadge(status4) {
|
|
9255
|
+
const colorize = STATUS_COLORS[status4] ?? source_default.white;
|
|
9256
|
+
return colorize(status4.toUpperCase());
|
|
9109
9257
|
}
|
|
9110
9258
|
function printTable(headers, rows, widths) {
|
|
9111
9259
|
const colWidths = widths ?? headers.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length)));
|
|
@@ -9284,8 +9432,8 @@ async function waitForBrowserLogin(baseUrl, userMode = false) {
|
|
|
9284
9432
|
${loginUrl.toString()}
|
|
9285
9433
|
`));
|
|
9286
9434
|
try {
|
|
9287
|
-
const
|
|
9288
|
-
await
|
|
9435
|
+
const open3 = (await Promise.resolve().then(() => (init_open(), open_exports))).default;
|
|
9436
|
+
await open3(loginUrl.toString());
|
|
9289
9437
|
} catch {
|
|
9290
9438
|
}
|
|
9291
9439
|
});
|
|
@@ -9595,12 +9743,12 @@ function applyQaPreviewParams(url, params) {
|
|
|
9595
9743
|
url.search += `${url.search ? "&" : "?"}drip_force=${force}`;
|
|
9596
9744
|
return url;
|
|
9597
9745
|
}
|
|
9598
|
-
function buildQaPreviewUrl(
|
|
9599
|
-
const variation = options.variationName ?
|
|
9600
|
-
const target = options.storeUrl ??
|
|
9746
|
+
function buildQaPreviewUrl(experiment2, options = {}) {
|
|
9747
|
+
const variation = options.variationName ? experiment2.variations?.find((v) => v.name === options.variationName) : experiment2.variations?.find((v) => !v.is_control) ?? experiment2.variations?.[0];
|
|
9748
|
+
const target = options.storeUrl ?? experiment2.store_url;
|
|
9601
9749
|
if (!target || !variation) return null;
|
|
9602
9750
|
try {
|
|
9603
|
-
return applyQaPreviewParams(new URL(target), { experimentId:
|
|
9751
|
+
return applyQaPreviewParams(new URL(target), { experimentId: experiment2.id, variationId: variation.id, experimentPublicId: experiment2.public_id, variationPublicId: variation.public_id, ...options });
|
|
9604
9752
|
} catch {
|
|
9605
9753
|
return null;
|
|
9606
9754
|
}
|
|
@@ -9730,10 +9878,10 @@ async function resolveExperimentReference(request2, idOrSlug, variationRef) {
|
|
|
9730
9878
|
let res = await request2("GET", idShaped || keyShaped ? `/api/v1/experiments/${encodeURIComponent(reference)}` : slugPath);
|
|
9731
9879
|
if (!res.ok && res.status === 404 && idShaped) res = await request2("GET", slugPath);
|
|
9732
9880
|
if (!res.ok) return { ok: false, error: res.error ?? "Experiment not found", status: res.status, response: res };
|
|
9733
|
-
const
|
|
9734
|
-
if (typeof
|
|
9735
|
-
const variations = Array.isArray(
|
|
9736
|
-
if (variationRef === void 0) return { ok: true, experimentId:
|
|
9881
|
+
const experiment2 = record(res.data);
|
|
9882
|
+
if (typeof experiment2.id !== "string") return { ok: false, error: "Experiment has no id" };
|
|
9883
|
+
const variations = Array.isArray(experiment2.variations) ? experiment2.variations.map(record) : [];
|
|
9884
|
+
if (variationRef === void 0) return { ok: true, experimentId: experiment2.id, variations, experiment: experiment2 };
|
|
9737
9885
|
const available = variations.map((v) => String(v.slug ?? v.name)).join(", ") || "none";
|
|
9738
9886
|
const byIdentity = variations.filter((v) => v.id === variationRef || v.public_id === variationRef || v.slug === variationRef);
|
|
9739
9887
|
const byName = variations.filter((v) => v.name === variationRef);
|
|
@@ -9744,11 +9892,11 @@ async function resolveExperimentReference(request2, idOrSlug, variationRef) {
|
|
|
9744
9892
|
};
|
|
9745
9893
|
const match2 = matches[0];
|
|
9746
9894
|
if (typeof match2?.id !== "string") return { ok: false, error: `Variation "${variationRef}" not found. Available: ${available}` };
|
|
9747
|
-
return { ok: true, experimentId:
|
|
9895
|
+
return { ok: true, experimentId: experiment2.id, variationId: match2.id, variations, experiment: experiment2 };
|
|
9748
9896
|
}
|
|
9749
9897
|
function summarizeResults(payload) {
|
|
9750
9898
|
const data = record(payload);
|
|
9751
|
-
const
|
|
9899
|
+
const experiment2 = record(data.experiment);
|
|
9752
9900
|
const decision = record(data.decision);
|
|
9753
9901
|
const outcome = record(data.effective_outcome);
|
|
9754
9902
|
const commerce = record(data.commerce);
|
|
@@ -9762,6 +9910,9 @@ function summarizeResults(payload) {
|
|
|
9762
9910
|
if (degradedFlags.commerceUnavailable) missing.add("commerce orders");
|
|
9763
9911
|
const controlRate = rawArms.find((arm) => arm.isControl === true)?.primaryRate;
|
|
9764
9912
|
const treatmentCount = rawArms.filter((arm) => arm.isControl !== true).length;
|
|
9913
|
+
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;
|
|
9914
|
+
const runtimeDay = typeof data.runtime_day === "number" && Number.isInteger(data.runtime_day) && data.runtime_day >= 1 ? data.runtime_day : null;
|
|
9915
|
+
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;
|
|
9765
9916
|
const arms = rawArms.map((arm) => {
|
|
9766
9917
|
const judged = arm.isControl !== true && (decision.treatmentVariationId != null ? arm.variationId === decision.treatmentVariationId : treatmentCount === 1);
|
|
9767
9918
|
let lift = null;
|
|
@@ -9773,7 +9924,7 @@ function summarizeResults(payload) {
|
|
|
9773
9924
|
} else if (typeof arm.primaryRate === "number" && typeof controlRate === "number" && controlRate > 0)
|
|
9774
9925
|
lift = Math.round((arm.primaryRate / controlRate - 1) * 1e4) / 100;
|
|
9775
9926
|
if (lift == null) missing.add("lift");
|
|
9776
|
-
if (judged && confidence == null) missing.add("confidence");
|
|
9927
|
+
if (judged && confidence == null && data.confidence_visible !== false) missing.add("confidence");
|
|
9777
9928
|
}
|
|
9778
9929
|
if (arm.visitors == null) missing.add("visitors");
|
|
9779
9930
|
if (arm.primaryConversions == null) missing.add("conversions");
|
|
@@ -9782,8 +9933,8 @@ function summarizeResults(payload) {
|
|
|
9782
9933
|
});
|
|
9783
9934
|
const verdictWithheld = decision.state === "untrustworthy";
|
|
9784
9935
|
return {
|
|
9785
|
-
experiment,
|
|
9786
|
-
runWindow: { startedAt:
|
|
9936
|
+
experiment: experiment2,
|
|
9937
|
+
runWindow: { startedAt: experiment2.resultsWindowStartedAt ?? experiment2.startedAt, endedAt: experiment2.endedAt },
|
|
9787
9938
|
primaryGoal: decision.primaryGoalName,
|
|
9788
9939
|
totals: record(data.totals),
|
|
9789
9940
|
decision,
|
|
@@ -9802,7 +9953,8 @@ function summarizeResults(payload) {
|
|
|
9802
9953
|
value
|
|
9803
9954
|
})) : [],
|
|
9804
9955
|
metrics: Array.isArray(data.metrics) ? data.metrics.map(record) : [],
|
|
9805
|
-
resultsWeekdays: data.results_weekdays
|
|
9956
|
+
resultsWeekdays: data.results_weekdays,
|
|
9957
|
+
confidenceIndication
|
|
9806
9958
|
};
|
|
9807
9959
|
}
|
|
9808
9960
|
|
|
@@ -10072,11 +10224,11 @@ function formatRevenue(value, currency) {
|
|
|
10072
10224
|
}
|
|
10073
10225
|
function printExperimentResults(results2, currency) {
|
|
10074
10226
|
const summary = summarizeResults(results2);
|
|
10075
|
-
const { experiment, totals, decision, verdictWithheld, statisticsState, presentedOutcome } = summary;
|
|
10227
|
+
const { experiment: experiment2, totals, decision, verdictWithheld, statisticsState, presentedOutcome } = summary;
|
|
10076
10228
|
const outcomeProvenance = summary.outcome.provenance === "manager_decided" ? " (manager decided)" : "";
|
|
10077
10229
|
console.log();
|
|
10078
|
-
console.log(source_default.bold(String(
|
|
10079
|
-
console.log(` Status: ${statusBadge(String(
|
|
10230
|
+
console.log(source_default.bold(String(experiment2.name ?? experiment2.id ?? "Experiment results")));
|
|
10231
|
+
console.log(` Status: ${statusBadge(String(experiment2.status ?? "unknown"))}`);
|
|
10080
10232
|
console.log(` Visitors: ${String(totals.visitors ?? 0)}`);
|
|
10081
10233
|
console.log(` Conversions: ${String(totals.primaryConversions ?? 0)}`);
|
|
10082
10234
|
console.log(` Revenue: ${formatRevenue(totals.revenue, currency)}`);
|
|
@@ -10102,7 +10254,7 @@ function printExperimentResults(results2, currency) {
|
|
|
10102
10254
|
}
|
|
10103
10255
|
console.log(` Stats lift: ${formatPct(decision.lift)}`);
|
|
10104
10256
|
console.log(
|
|
10105
|
-
` Confidence (${confidenceBasisLabel(decision.basis)}): ${verdictWithheld ? "withheld \u2014 not decision-grade" : formatPct(decision.judgedConfidence)}`
|
|
10257
|
+
` Confidence (${confidenceBasisLabel(decision.basis)}): ${verdictWithheld ? "withheld \u2014 not decision-grade" : summary.confidenceIndication ?? formatPct(decision.judgedConfidence)}`
|
|
10106
10258
|
);
|
|
10107
10259
|
const arms = summary.arms;
|
|
10108
10260
|
if (arms.length > 0) {
|
|
@@ -10239,16 +10391,16 @@ function listExperimentFilesystemFiles(folder, session) {
|
|
|
10239
10391
|
const absPath = import_node_path6.default.join(folder, fileName);
|
|
10240
10392
|
if (!import_node_fs8.default.existsSync(absPath)) continue;
|
|
10241
10393
|
const content = import_node_fs8.default.readFileSync(absPath, "utf8");
|
|
10242
|
-
const
|
|
10394
|
+
const sha2562 = sha256Text(content);
|
|
10243
10395
|
if (SERVER_OWNED_EXPERIMENT_FILES.has(fileName)) {
|
|
10244
|
-
if (session.files[fileName]?.sha256 !==
|
|
10396
|
+
if (session.files[fileName]?.sha256 !== sha2562) {
|
|
10245
10397
|
console.error(
|
|
10246
10398
|
`${fileName} is server-owned; local edits are ignored (dashboard flow owns QA state)`
|
|
10247
10399
|
);
|
|
10248
10400
|
}
|
|
10249
10401
|
continue;
|
|
10250
10402
|
}
|
|
10251
|
-
files.push({ path: fileName, content, sha256 });
|
|
10403
|
+
files.push({ path: fileName, content, sha256: sha2562 });
|
|
10252
10404
|
}
|
|
10253
10405
|
for (const fileName of EXPERIMENT_FILESYSTEM_PATH_SCHEMA.experiment) {
|
|
10254
10406
|
const relativePath = `experiment/${fileName}`;
|
|
@@ -10314,35 +10466,35 @@ async function rewriteExperimentFilesystemAssets(input) {
|
|
|
10314
10466
|
})
|
|
10315
10467
|
);
|
|
10316
10468
|
}
|
|
10317
|
-
function printFilesystemPlan(
|
|
10318
|
-
if (
|
|
10469
|
+
function printFilesystemPlan(plan2) {
|
|
10470
|
+
if (plan2.errors.length > 0) {
|
|
10319
10471
|
console.log(source_default.red("\nErrors"));
|
|
10320
|
-
|
|
10472
|
+
plan2.errors.forEach((error) => console.log(` - ${error}`));
|
|
10321
10473
|
}
|
|
10322
|
-
const compatibilityWarnings = (
|
|
10474
|
+
const compatibilityWarnings = (plan2.previewCompatibility ?? []).map(
|
|
10323
10475
|
(finding) => `${finding.path}:${finding.line}:${finding.column} ${finding.rule}: ${finding.message}. Try: ${finding.alternative}`
|
|
10324
10476
|
);
|
|
10325
10477
|
if (compatibilityWarnings.length > 0) {
|
|
10326
10478
|
console.log(source_default.yellow("\nPreview compatibility"));
|
|
10327
10479
|
compatibilityWarnings.forEach((warning) => console.log(` - ${warning}`));
|
|
10328
10480
|
}
|
|
10329
|
-
const otherWarnings =
|
|
10481
|
+
const otherWarnings = plan2.warnings.filter((warning) => !compatibilityWarnings.includes(warning));
|
|
10330
10482
|
if (otherWarnings.length > 0) {
|
|
10331
10483
|
console.log(source_default.yellow("\nWarnings"));
|
|
10332
10484
|
otherWarnings.forEach((warning) => console.log(` - ${warning}`));
|
|
10333
10485
|
}
|
|
10334
|
-
if ((
|
|
10486
|
+
if ((plan2.blockingIssues?.length ?? 0) > 0) {
|
|
10335
10487
|
console.log(source_default.red("\nBlocking issues"));
|
|
10336
|
-
(
|
|
10488
|
+
(plan2.blockingIssues ?? []).forEach((issue) => {
|
|
10337
10489
|
const context = [issue.path, issue.variation ? `variation: ${issue.variation}` : null].filter(Boolean).join(", ");
|
|
10338
10490
|
console.log(` - [${issue.code}] ${issue.message}${context ? ` (${context})` : ""}`);
|
|
10339
10491
|
});
|
|
10340
10492
|
}
|
|
10341
10493
|
console.log(source_default.bold("\nChanges"));
|
|
10342
|
-
if (
|
|
10494
|
+
if (plan2.changes.length === 0) {
|
|
10343
10495
|
console.log(source_default.dim(" No changes."));
|
|
10344
10496
|
} else {
|
|
10345
|
-
|
|
10497
|
+
plan2.changes.forEach((change) => {
|
|
10346
10498
|
if (change.entity === "experiment.targeting" && Array.isArray(change.detail?.changedKeys)) {
|
|
10347
10499
|
console.log(` Changed keys: ${change.detail.changedKeys.join(", ")}`);
|
|
10348
10500
|
}
|
|
@@ -10354,9 +10506,9 @@ function printFilesystemPlan(plan) {
|
|
|
10354
10506
|
});
|
|
10355
10507
|
}
|
|
10356
10508
|
console.log();
|
|
10357
|
-
console.log(`QA invalidated: ${
|
|
10358
|
-
console.log(`Acknowledgment: ${
|
|
10359
|
-
console.log(`Precompile: ${
|
|
10509
|
+
console.log(`QA invalidated: ${plan2.invalidatesQa ? source_default.yellow("yes") : "no"}`);
|
|
10510
|
+
console.log(`Acknowledgment: ${plan2.invalidatesQa ? "--accept-qa-invalidation" : "none"}`);
|
|
10511
|
+
console.log(`Precompile: ${plan2.precompileRequired ? "yes" : "no"}`);
|
|
10360
10512
|
}
|
|
10361
10513
|
function blockingIssueSummary(issues) {
|
|
10362
10514
|
if (!issues?.length) return "Experiment filesystem plan has errors.";
|
|
@@ -10380,11 +10532,11 @@ async function getQaPreviewUrls(creds, experimentId) {
|
|
|
10380
10532
|
if (!res.ok || !res.data || !storeUrl) {
|
|
10381
10533
|
return { previewUrls: [], unavailable: true, dashboardUrl };
|
|
10382
10534
|
}
|
|
10383
|
-
const
|
|
10384
|
-
const previewUrls = (
|
|
10535
|
+
const experiment2 = res.data;
|
|
10536
|
+
const previewUrls = (experiment2.variations ?? []).filter((variation) => !variation.is_control).map((variation) => ({
|
|
10385
10537
|
variationId: variation.id,
|
|
10386
10538
|
variationName: variation.name,
|
|
10387
|
-
url: buildQaPreviewUrl2(storeUrl,
|
|
10539
|
+
url: buildQaPreviewUrl2(storeUrl, experiment2.id, variation.id).toString()
|
|
10388
10540
|
}));
|
|
10389
10541
|
return { previewUrls, unavailable: false, dashboardUrl };
|
|
10390
10542
|
} catch {
|
|
@@ -10398,9 +10550,9 @@ function printQaPreviewUrls(experimentId, result) {
|
|
|
10398
10550
|
);
|
|
10399
10551
|
return;
|
|
10400
10552
|
}
|
|
10401
|
-
for (const
|
|
10553
|
+
for (const preview2 of result.previewUrls) {
|
|
10402
10554
|
console.log(
|
|
10403
|
-
`Preview (interactive, works while draft \u2014 no on-site editing needed): ${
|
|
10555
|
+
`Preview (interactive, works while draft \u2014 no on-site editing needed): ${preview2.url}`
|
|
10404
10556
|
);
|
|
10405
10557
|
}
|
|
10406
10558
|
}
|
|
@@ -10618,8 +10770,8 @@ var list = new Command("list").alias("ls").description(
|
|
|
10618
10770
|
printTable(
|
|
10619
10771
|
fields3.map((field) => field.toUpperCase()),
|
|
10620
10772
|
experiments.map(
|
|
10621
|
-
(
|
|
10622
|
-
const value =
|
|
10773
|
+
(experiment2) => fields3.map((field) => {
|
|
10774
|
+
const value = experiment2[field];
|
|
10623
10775
|
return value == null ? "-" : typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
10624
10776
|
})
|
|
10625
10777
|
)
|
|
@@ -10661,13 +10813,13 @@ var get = new Command("get").description(
|
|
|
10661
10813
|
output(this, { ...exp, dashboard_url: dashboardUrl }, () => printExperiment(exp, dashboardUrl));
|
|
10662
10814
|
});
|
|
10663
10815
|
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) {
|
|
10664
|
-
const
|
|
10665
|
-
if (!
|
|
10816
|
+
const experiment2 = await resolveExperiment(experimentRef);
|
|
10817
|
+
if (!experiment2) fail(this, `Experiment "${experimentRef}" not found.`, EXIT_CODES.validation);
|
|
10666
10818
|
const classified = classifyIdeaReference(opts.idea);
|
|
10667
10819
|
if (classified.kind === "invalid") {
|
|
10668
10820
|
fail(this, "--idea must be a UUID or Apex backlog key", EXIT_CODES.validation);
|
|
10669
10821
|
}
|
|
10670
|
-
const res = await attachExperimentIdea(requireCredentials(),
|
|
10822
|
+
const res = await attachExperimentIdea(requireCredentials(), experiment2.id, {
|
|
10671
10823
|
...classified.kind === "uuid" ? { backlog_item_id: classified.value } : { idea_key: classified.value },
|
|
10672
10824
|
...opts.followUp ? { as_follow_up: true } : {}
|
|
10673
10825
|
});
|
|
@@ -10681,9 +10833,9 @@ var attachIdea = new Command("attach-idea").description("Attach an experiment to
|
|
|
10681
10833
|
output(this, res.data, () => printSuccess("Idea attached."));
|
|
10682
10834
|
});
|
|
10683
10835
|
var detachIdea = new Command("detach-idea").description("Detach an experiment from its idea").argument("<experiment>", EXPERIMENT_REFERENCE_DESCRIPTION).action(async function action6(experimentRef) {
|
|
10684
|
-
const
|
|
10685
|
-
if (!
|
|
10686
|
-
const res = await detachExperimentIdea(requireCredentials(),
|
|
10836
|
+
const experiment2 = await resolveExperiment(experimentRef);
|
|
10837
|
+
if (!experiment2) fail(this, `Experiment "${experimentRef}" not found.`, EXIT_CODES.validation);
|
|
10838
|
+
const res = await detachExperimentIdea(requireCredentials(), experiment2.id);
|
|
10687
10839
|
if (!res.ok)
|
|
10688
10840
|
fail(
|
|
10689
10841
|
this,
|
|
@@ -11494,9 +11646,9 @@ var experimentStatus = new Command("status").description(
|
|
|
11494
11646
|
apiFailureOf(response)
|
|
11495
11647
|
);
|
|
11496
11648
|
}
|
|
11497
|
-
const
|
|
11498
|
-
if (!
|
|
11499
|
-
const snapshot = await getExperimentFilesystem(creds,
|
|
11649
|
+
const experiment2 = response?.data ?? await resolveExperiment(target);
|
|
11650
|
+
if (!experiment2) fail(this, "Experiment not found.");
|
|
11651
|
+
const snapshot = await getExperimentFilesystem(creds, experiment2.id);
|
|
11500
11652
|
if (!snapshot.ok || !snapshot.data) {
|
|
11501
11653
|
throw new CliError(
|
|
11502
11654
|
snapshot.error ?? "Failed to read experiment filesystem",
|
|
@@ -11505,7 +11657,7 @@ var experimentStatus = new Command("status").description(
|
|
|
11505
11657
|
apiFailureOf(snapshot)
|
|
11506
11658
|
);
|
|
11507
11659
|
}
|
|
11508
|
-
let
|
|
11660
|
+
let versions2 = null;
|
|
11509
11661
|
if (session) {
|
|
11510
11662
|
const localFiles = listExperimentFilesystemFiles(candidate, session);
|
|
11511
11663
|
const localHashes = new Map(localFiles.map((file) => [file.path, file.sha256]));
|
|
@@ -11533,24 +11685,24 @@ var experimentStatus = new Command("status").description(
|
|
|
11533
11685
|
assets: session.assets ?? {},
|
|
11534
11686
|
credentials: creds
|
|
11535
11687
|
});
|
|
11536
|
-
const
|
|
11537
|
-
if (!
|
|
11688
|
+
const plan2 = await planExperimentFilesystem(creds, experiment2.id, { files: plannedFiles });
|
|
11689
|
+
if (!plan2.ok || !plan2.data) {
|
|
11538
11690
|
throw new CliError(
|
|
11539
|
-
|
|
11540
|
-
classifyStatus(
|
|
11541
|
-
|
|
11542
|
-
apiFailureOf(
|
|
11691
|
+
plan2.error ?? "Failed to read saved state",
|
|
11692
|
+
classifyStatus(plan2.status),
|
|
11693
|
+
plan2.errorDetails,
|
|
11694
|
+
apiFailureOf(plan2)
|
|
11543
11695
|
);
|
|
11544
11696
|
}
|
|
11545
|
-
|
|
11697
|
+
versions2 = {
|
|
11546
11698
|
files,
|
|
11547
11699
|
saved: {
|
|
11548
|
-
would_change:
|
|
11549
|
-
invalidates_qa:
|
|
11550
|
-
blocking_issue_count:
|
|
11551
|
-
valid:
|
|
11552
|
-
errors:
|
|
11553
|
-
warnings:
|
|
11700
|
+
would_change: plan2.data.changes.length > 0,
|
|
11701
|
+
invalidates_qa: plan2.data.invalidatesQa,
|
|
11702
|
+
blocking_issue_count: plan2.data.blockingIssues?.filter((issue) => issue.severity === "blocking").length ?? 0,
|
|
11703
|
+
valid: plan2.data.ok,
|
|
11704
|
+
errors: plan2.data.errors,
|
|
11705
|
+
warnings: plan2.data.warnings
|
|
11554
11706
|
}
|
|
11555
11707
|
};
|
|
11556
11708
|
}
|
|
@@ -11566,9 +11718,9 @@ var experimentStatus = new Command("status").description(
|
|
|
11566
11718
|
}
|
|
11567
11719
|
const qa3 = {
|
|
11568
11720
|
...summarizeHandoverQa(qaSnapshot),
|
|
11569
|
-
ready_version_id:
|
|
11721
|
+
ready_version_id: experiment2.ready_version_id ?? null
|
|
11570
11722
|
};
|
|
11571
|
-
const shopId =
|
|
11723
|
+
const shopId = experiment2.shop_id ?? creds.resolvedContext?.shopId ?? null;
|
|
11572
11724
|
let org = {
|
|
11573
11725
|
id: creds.resolvedContext?.orgId ?? null,
|
|
11574
11726
|
name: null
|
|
@@ -11594,13 +11746,13 @@ var experimentStatus = new Command("status").description(
|
|
|
11594
11746
|
"Shop credentials do not expose account context; organization and shop names are unknown."
|
|
11595
11747
|
);
|
|
11596
11748
|
const nextSteps = [];
|
|
11597
|
-
const ref = shellQuote(session ? target :
|
|
11749
|
+
const ref = shellQuote(session ? target : experiment2.id);
|
|
11598
11750
|
if (!session)
|
|
11599
11751
|
nextSteps.push({
|
|
11600
11752
|
command: `apex experiments pull ${ref} --out <folder>`,
|
|
11601
11753
|
why: "Pull files to verify the local version."
|
|
11602
11754
|
});
|
|
11603
|
-
if (
|
|
11755
|
+
if (versions2 && (versions2.files.some((file) => file.state !== "unchanged") || versions2.saved.would_change || !versions2.saved.valid || versions2.saved.blocking_issue_count > 0)) {
|
|
11604
11756
|
nextSteps.push(
|
|
11605
11757
|
{
|
|
11606
11758
|
command: `apex experiments plan ${ref} --json`,
|
|
@@ -11629,14 +11781,14 @@ var experimentStatus = new Command("status").description(
|
|
|
11629
11781
|
why: "Required QA checks are complete; approve after verifying the saved version."
|
|
11630
11782
|
});
|
|
11631
11783
|
}
|
|
11632
|
-
const locallyChanged =
|
|
11784
|
+
const locallyChanged = versions2 !== null && (versions2.files.some((file) => file.state !== "unchanged") || versions2.saved.would_change || !versions2.saved.valid || versions2.saved.blocking_issue_count > 0);
|
|
11633
11785
|
const qaComplete = qa3.required_done !== null && qa3.required_total !== null && qa3.required_done === qa3.required_total && qa3.blockers !== null && qa3.blockers.length === 0;
|
|
11634
11786
|
const readyVersionKnown = typeof qa3.ready_version_id === "string" && qa3.ready_version_id.length > 0;
|
|
11635
11787
|
const approvalCurrent = qa3.approval_present && readyVersionKnown && !locallyChanged;
|
|
11636
|
-
const lifecycleReady =
|
|
11788
|
+
const lifecycleReady = experiment2.status === "ready_to_launch";
|
|
11637
11789
|
const readyForHandover = lifecycleReady && approvalCurrent && qaComplete;
|
|
11638
11790
|
if (approvalCurrent && qaComplete && !lifecycleReady)
|
|
11639
|
-
notes.push(`The experiment status is ${
|
|
11791
|
+
notes.push(`The experiment status is ${experiment2.status}; handover needs ready_to_launch.`);
|
|
11640
11792
|
if (qa3.approval_present && !readyVersionKnown && qaComplete && !locallyChanged)
|
|
11641
11793
|
nextSteps.push({
|
|
11642
11794
|
command: `apex experiments qa approve ${ref}`,
|
|
@@ -11647,7 +11799,7 @@ var experimentStatus = new Command("status").description(
|
|
|
11647
11799
|
command: "launch in the Apex dashboard",
|
|
11648
11800
|
why: "QA is approved for the saved version. Starting a test is a human decision."
|
|
11649
11801
|
});
|
|
11650
|
-
if (!
|
|
11802
|
+
if (!versions2)
|
|
11651
11803
|
notes.push(
|
|
11652
11804
|
"Without a pulled folder, readiness reflects the saved version only; local edits elsewhere are not visible."
|
|
11653
11805
|
);
|
|
@@ -11659,13 +11811,13 @@ var experimentStatus = new Command("status").description(
|
|
|
11659
11811
|
base_url: creds.baseUrl
|
|
11660
11812
|
},
|
|
11661
11813
|
experiment: {
|
|
11662
|
-
id:
|
|
11663
|
-
test_key:
|
|
11664
|
-
name:
|
|
11665
|
-
status:
|
|
11666
|
-
dashboard_url: buildDashboardUrl(creds.baseUrl, shopId,
|
|
11814
|
+
id: experiment2.id,
|
|
11815
|
+
test_key: experiment2.test_key ?? null,
|
|
11816
|
+
name: experiment2.name,
|
|
11817
|
+
status: experiment2.status,
|
|
11818
|
+
dashboard_url: buildDashboardUrl(creds.baseUrl, shopId, experiment2.id)
|
|
11667
11819
|
},
|
|
11668
|
-
versions,
|
|
11820
|
+
versions: versions2,
|
|
11669
11821
|
qa: { ...qa3, approval_current: approvalCurrent },
|
|
11670
11822
|
next_steps: nextSteps,
|
|
11671
11823
|
ready_for_handover: readyForHandover,
|
|
@@ -11678,16 +11830,16 @@ Credential: ${data.context.credential_kind}
|
|
|
11678
11830
|
Base URL: ${creds.baseUrl}`
|
|
11679
11831
|
);
|
|
11680
11832
|
console.log(
|
|
11681
|
-
`Experiment: ${
|
|
11833
|
+
`Experiment: ${experiment2.test_key ?? experiment2.id} / ${experiment2.name} (${experiment2.status})
|
|
11682
11834
|
Dashboard: ${data.experiment.dashboard_url ?? "unknown"}`
|
|
11683
11835
|
);
|
|
11684
11836
|
console.log("Versions:");
|
|
11685
|
-
if (
|
|
11686
|
-
for (const file of
|
|
11837
|
+
if (versions2) {
|
|
11838
|
+
for (const file of versions2.files) console.log(` ${file.state}: ${file.path}`);
|
|
11687
11839
|
console.log(
|
|
11688
|
-
` Saved: would change ${
|
|
11840
|
+
` Saved: would change ${versions2.saved.would_change}; invalidates QA ${versions2.saved.invalidates_qa}; blocking issues ${versions2.saved.blocking_issue_count}`
|
|
11689
11841
|
);
|
|
11690
|
-
for (const message of [...
|
|
11842
|
+
for (const message of [...versions2.saved.errors, ...versions2.saved.warnings])
|
|
11691
11843
|
console.log(` ${message}`);
|
|
11692
11844
|
} else console.log(" unknown (no pulled folder)");
|
|
11693
11845
|
console.log(
|
|
@@ -12160,13 +12312,13 @@ function normalizeEvidenceDevice(value) {
|
|
|
12160
12312
|
if (normalized === "ipad_pro") return "tablet";
|
|
12161
12313
|
return normalized;
|
|
12162
12314
|
}
|
|
12163
|
-
function resolveEvidenceVariation(
|
|
12315
|
+
function resolveEvidenceVariation(experiment2, ref) {
|
|
12164
12316
|
if (!ref) return void 0;
|
|
12165
|
-
const exact =
|
|
12317
|
+
const exact = experiment2.variations?.find(
|
|
12166
12318
|
(variation) => variation.id === ref || variation.slug === ref
|
|
12167
12319
|
);
|
|
12168
12320
|
if (exact) return exact.id;
|
|
12169
|
-
const matches = (
|
|
12321
|
+
const matches = (experiment2.variations ?? []).filter(
|
|
12170
12322
|
(variation) => variation.name.toLowerCase() === ref.toLowerCase()
|
|
12171
12323
|
);
|
|
12172
12324
|
if (matches.length === 1) return matches[0].id;
|
|
@@ -12213,9 +12365,9 @@ var qaEvidence = new Command("evidence").description("Attach screenshot jobs or
|
|
|
12213
12365
|
records: parsed
|
|
12214
12366
|
});
|
|
12215
12367
|
} else {
|
|
12216
|
-
const
|
|
12217
|
-
if (!
|
|
12218
|
-
const variationId = resolveEvidenceVariation(
|
|
12368
|
+
const experiment2 = await resolveExperiment(resolved.experimentId);
|
|
12369
|
+
if (!experiment2) throw new Error("Experiment not found.");
|
|
12370
|
+
const variationId = resolveEvidenceVariation(experiment2, opts.variation);
|
|
12219
12371
|
const form = new FormData();
|
|
12220
12372
|
for (const filePath of opts.file) {
|
|
12221
12373
|
const absolutePath = import_node_path6.default.resolve(process.cwd(), filePath);
|
|
@@ -12270,8 +12422,8 @@ function shellQuote(value) {
|
|
|
12270
12422
|
if (value.length > 0 && /^[A-Za-z0-9_@%+=:,./-]+$/.test(value)) return value;
|
|
12271
12423
|
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
12272
12424
|
}
|
|
12273
|
-
function isTransientResumeStatus(
|
|
12274
|
-
return
|
|
12425
|
+
function isTransientResumeStatus(status4) {
|
|
12426
|
+
return status4 === 429 || status4 !== void 0 && status4 >= 500;
|
|
12275
12427
|
}
|
|
12276
12428
|
function screenshotTimeoutMessage(job, experimentRef, out, seconds, flags = {}) {
|
|
12277
12429
|
const progress = job.progress ? ` Last progress: ${job.progress.captured}/${job.progress.total} captured.` : "";
|
|
@@ -12299,8 +12451,8 @@ var screenshots = new Command("screenshots").description(
|
|
|
12299
12451
|
const explicitJson = process.argv.includes("--json");
|
|
12300
12452
|
if (!Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0)
|
|
12301
12453
|
fail(this, "--timeout must be a positive number of seconds.", EXIT_CODES.validation);
|
|
12302
|
-
const
|
|
12303
|
-
if (!
|
|
12454
|
+
const experiment2 = await resolveExperiment(experimentRef);
|
|
12455
|
+
if (!experiment2?.variations?.length)
|
|
12304
12456
|
fail(this, "Experiment has no variations to capture.", EXIT_CODES.validation);
|
|
12305
12457
|
const creds = requireCredentials();
|
|
12306
12458
|
let devices;
|
|
@@ -12363,13 +12515,13 @@ var screenshots = new Command("screenshots").description(
|
|
|
12363
12515
|
if (!job.experiment_id)
|
|
12364
12516
|
fail(
|
|
12365
12517
|
this,
|
|
12366
|
-
`Screenshot job ${job.job_id} names no experiment; refusing to resume it under ${experimentRef} (${
|
|
12518
|
+
`Screenshot job ${job.job_id} names no experiment; refusing to resume it under ${experimentRef} (${experiment2.id}).`,
|
|
12367
12519
|
EXIT_CODES.validation
|
|
12368
12520
|
);
|
|
12369
|
-
if (job.experiment_id !==
|
|
12521
|
+
if (job.experiment_id !== experiment2.id)
|
|
12370
12522
|
fail(
|
|
12371
12523
|
this,
|
|
12372
|
-
`Screenshot job ${job.job_id} belongs to experiment ${job.experiment_id}, not ${experimentRef} (${
|
|
12524
|
+
`Screenshot job ${job.job_id} belongs to experiment ${job.experiment_id}, not ${experimentRef} (${experiment2.id}).`,
|
|
12373
12525
|
EXIT_CODES.validation
|
|
12374
12526
|
);
|
|
12375
12527
|
const storedDevices = job.devices ?? [];
|
|
@@ -12392,7 +12544,7 @@ var screenshots = new Command("screenshots").description(
|
|
|
12392
12544
|
`Screenshot job ${job.job_id} stores its results in a legacy shape this CLI cannot download; create a new job instead of resuming it.`,
|
|
12393
12545
|
EXIT_CODES.validation
|
|
12394
12546
|
);
|
|
12395
|
-
const knownVariationIds = new Set(
|
|
12547
|
+
const knownVariationIds = new Set(experiment2.variations.map((variation) => variation.id));
|
|
12396
12548
|
const storedVariationIds = job.variation_ids ?? [];
|
|
12397
12549
|
for (const id of storedVariationIds.filter((id2) => !knownVariationIds.has(id2)))
|
|
12398
12550
|
console.error(
|
|
@@ -12419,15 +12571,15 @@ var screenshots = new Command("screenshots").description(
|
|
|
12419
12571
|
EXIT_CODES.validation
|
|
12420
12572
|
);
|
|
12421
12573
|
const requested = opts.variations ? commaList(opts.variations) : [];
|
|
12422
|
-
const control =
|
|
12574
|
+
const control = experiment2.variations.find((variation) => variation.is_control);
|
|
12423
12575
|
if (!control) fail(this, "Experiment has no control variation.", EXIT_CODES.validation);
|
|
12424
12576
|
const matchedById = /* @__PURE__ */ new Map();
|
|
12425
12577
|
const unmatched = [];
|
|
12426
12578
|
for (const ref of requested) {
|
|
12427
|
-
const exactMatch =
|
|
12579
|
+
const exactMatch = experiment2.variations.find(
|
|
12428
12580
|
(candidate) => ref === candidate.id || ref === candidate.slug
|
|
12429
12581
|
);
|
|
12430
|
-
const nameMatches = exactMatch ? [] :
|
|
12582
|
+
const nameMatches = exactMatch ? [] : experiment2.variations.filter(
|
|
12431
12583
|
(candidate) => ref.toLowerCase() === candidate.name.toLowerCase()
|
|
12432
12584
|
);
|
|
12433
12585
|
if (nameMatches.length > 1) {
|
|
@@ -12446,9 +12598,9 @@ var screenshots = new Command("screenshots").description(
|
|
|
12446
12598
|
}
|
|
12447
12599
|
if (unmatched.length)
|
|
12448
12600
|
fail(this, `Unknown --variations value(s): ${unmatched.join(", ")}`, EXIT_CODES.validation);
|
|
12449
|
-
const selected2 = requested.length ? [...matchedById.values()] :
|
|
12601
|
+
const selected2 = requested.length ? [...matchedById.values()] : experiment2.variations;
|
|
12450
12602
|
variationIds = [.../* @__PURE__ */ new Set([control.id, ...selected2.map((variation) => variation.id)])];
|
|
12451
|
-
const created = await createExperimentScreenshots(creds,
|
|
12603
|
+
const created = await createExperimentScreenshots(creds, experiment2.id, {
|
|
12452
12604
|
devices,
|
|
12453
12605
|
variation_ids: variationIds
|
|
12454
12606
|
});
|
|
@@ -12533,7 +12685,7 @@ var screenshots = new Command("screenshots").description(
|
|
|
12533
12685
|
delayMs = Math.min(Math.round(delayMs * 1.6), 1e4);
|
|
12534
12686
|
}
|
|
12535
12687
|
if (opts.attachQa !== false) {
|
|
12536
|
-
const attached = await attachExperimentQaEvidence(creds,
|
|
12688
|
+
const attached = await attachExperimentQaEvidence(creds, experiment2.id, {
|
|
12537
12689
|
screenshot_job_id: job.job_id
|
|
12538
12690
|
});
|
|
12539
12691
|
if (!attached.ok) {
|
|
@@ -12560,7 +12712,7 @@ var screenshots = new Command("screenshots").description(
|
|
|
12560
12712
|
const rows = [];
|
|
12561
12713
|
const errors = [];
|
|
12562
12714
|
const usedFilenames = /* @__PURE__ */ new Set();
|
|
12563
|
-
const hasControlVariation =
|
|
12715
|
+
const hasControlVariation = experiment2.variations.some((item) => item.is_control);
|
|
12564
12716
|
const downloads = [];
|
|
12565
12717
|
if (Array.isArray(job.results))
|
|
12566
12718
|
fail(
|
|
@@ -12569,7 +12721,7 @@ var screenshots = new Command("screenshots").description(
|
|
|
12569
12721
|
EXIT_CODES.validation
|
|
12570
12722
|
);
|
|
12571
12723
|
for (const variationId of variationIds) {
|
|
12572
|
-
const variation =
|
|
12724
|
+
const variation = experiment2.variations.find((item) => item.id === variationId);
|
|
12573
12725
|
if (!variation) {
|
|
12574
12726
|
console.error(
|
|
12575
12727
|
source_default.yellow(
|
|
@@ -12585,7 +12737,7 @@ var screenshots = new Command("screenshots").description(
|
|
|
12585
12737
|
continue;
|
|
12586
12738
|
}
|
|
12587
12739
|
const pairPrefix = opts.labelPairs === false || !hasControlVariation ? "" : variation.is_control ? "before-" : "after-";
|
|
12588
|
-
const baseFilename = `${pairPrefix}${safeFilenamePart(
|
|
12740
|
+
const baseFilename = `${pairPrefix}${safeFilenamePart(experiment2.slug ?? experiment2.name)}-${safeFilenamePart(variation.slug ?? variation.name)}-${safeFilenamePart(device)}`;
|
|
12589
12741
|
let filename = `${baseFilename}.png`;
|
|
12590
12742
|
if (usedFilenames.has(filename)) {
|
|
12591
12743
|
const suffix = variation.id.replace(/[^a-z0-9]/gi, "").slice(-8).toLowerCase();
|
|
@@ -12957,8 +13109,8 @@ var previewCommand = new Command("preview").description(
|
|
|
12957
13109
|
});
|
|
12958
13110
|
if (opts.open !== false && !shouldUseJson(previewCommand)) {
|
|
12959
13111
|
try {
|
|
12960
|
-
const
|
|
12961
|
-
await
|
|
13112
|
+
const open3 = (await Promise.resolve().then(() => (init_open(), open_exports))).default;
|
|
13113
|
+
await open3(data.preview_url);
|
|
12962
13114
|
} catch {
|
|
12963
13115
|
console.error(source_default.dim("Copy the URL above to preview."));
|
|
12964
13116
|
}
|
|
@@ -13059,8 +13211,8 @@ var previewCommand = new Command("preview").description(
|
|
|
13059
13211
|
);
|
|
13060
13212
|
if (opts?.open !== false && !shouldUseJson(previewCommand)) {
|
|
13061
13213
|
try {
|
|
13062
|
-
const
|
|
13063
|
-
await
|
|
13214
|
+
const open3 = (await Promise.resolve().then(() => (init_open(), open_exports))).default;
|
|
13215
|
+
await open3(previewUrl.toString());
|
|
13064
13216
|
console.log(source_default.dim("Opened in browser."));
|
|
13065
13217
|
} catch {
|
|
13066
13218
|
console.log(source_default.dim("Copy the URL above to preview."));
|
|
@@ -13092,19 +13244,19 @@ function apexConfigPath(folder) {
|
|
|
13092
13244
|
return import_node_path8.default.join(folder, "apex.harness.json");
|
|
13093
13245
|
}
|
|
13094
13246
|
function loadHarnessConfig(folder) {
|
|
13095
|
-
const
|
|
13247
|
+
const experiment2 = readJson(experimentConfigPath(folder)) ?? {};
|
|
13096
13248
|
const apex = readJson(apexConfigPath(folder));
|
|
13097
|
-
if (apex) return { apex, experiment };
|
|
13098
|
-
const targetOrigin =
|
|
13099
|
-
const defaultPath =
|
|
13249
|
+
if (apex) return { apex, experiment: experiment2 };
|
|
13250
|
+
const targetOrigin = experiment2.targetOrigin?.replace(/\/+$/, "") ?? "";
|
|
13251
|
+
const defaultPath = experiment2.defaultPath ?? "/";
|
|
13100
13252
|
const targetUrl = targetOrigin ? new URL(defaultPath, targetOrigin).toString() : "";
|
|
13101
|
-
const id =
|
|
13253
|
+
const id = experiment2.id ?? import_node_path8.default.basename(import_node_path8.default.dirname(folder));
|
|
13102
13254
|
const variation = import_node_path8.default.basename(folder);
|
|
13103
13255
|
return {
|
|
13104
|
-
experiment,
|
|
13256
|
+
experiment: experiment2,
|
|
13105
13257
|
apex: {
|
|
13106
13258
|
experimentSlug: slugify(id, "experiment"),
|
|
13107
|
-
experimentName:
|
|
13259
|
+
experimentName: experiment2.name ?? id,
|
|
13108
13260
|
variationSlug: slugify(variation, "variant-a"),
|
|
13109
13261
|
variationName: `Variant ${variation}`,
|
|
13110
13262
|
targetUrl,
|
|
@@ -13112,8 +13264,8 @@ function loadHarnessConfig(folder) {
|
|
|
13112
13264
|
}
|
|
13113
13265
|
};
|
|
13114
13266
|
}
|
|
13115
|
-
function cleanupResetJs(
|
|
13116
|
-
const cleanupGlobal =
|
|
13267
|
+
function cleanupResetJs(experiment2) {
|
|
13268
|
+
const cleanupGlobal = experiment2.inject?.cleanupGlobal?.trim();
|
|
13117
13269
|
if (!cleanupGlobal) return null;
|
|
13118
13270
|
return `if (typeof window[${JSON.stringify(cleanupGlobal)}] === "function") window[${JSON.stringify(cleanupGlobal)}]();`;
|
|
13119
13271
|
}
|
|
@@ -13131,8 +13283,8 @@ function runAbHarness(args, cwd) {
|
|
|
13131
13283
|
});
|
|
13132
13284
|
});
|
|
13133
13285
|
}
|
|
13134
|
-
function artifactPaths(folder,
|
|
13135
|
-
const variant =
|
|
13286
|
+
function artifactPaths(folder, experiment2) {
|
|
13287
|
+
const variant = experiment2.variant;
|
|
13136
13288
|
if (variant?.endsWith(".bundle.js")) {
|
|
13137
13289
|
const jsPath = import_node_path8.default.join(folder, variant.replace(/\.bundle\.js$/, ".js"));
|
|
13138
13290
|
const cssPath = import_node_path8.default.join(folder, variant.replace(/\.bundle\.js$/, ".css"));
|
|
@@ -13188,7 +13340,7 @@ async function uploadVersion(params) {
|
|
|
13188
13340
|
}
|
|
13189
13341
|
async function createSession(folder) {
|
|
13190
13342
|
const creds = requireCredentials();
|
|
13191
|
-
const { apex, experiment } = loadHarnessConfig(folder);
|
|
13343
|
+
const { apex, experiment: experiment2 } = loadHarnessConfig(folder);
|
|
13192
13344
|
if (!apex.targetUrl) {
|
|
13193
13345
|
throw new Error("Missing targetUrl. Run `apex harness init` and set targetUrl.");
|
|
13194
13346
|
}
|
|
@@ -13198,7 +13350,7 @@ async function createSession(folder) {
|
|
|
13198
13350
|
variationSlug: apex.variationSlug,
|
|
13199
13351
|
variationName: apex.variationName,
|
|
13200
13352
|
targetUrl: apex.targetUrl,
|
|
13201
|
-
resetJs: cleanupResetJs(
|
|
13353
|
+
resetJs: cleanupResetJs(experiment2),
|
|
13202
13354
|
antiFlickerSelectors: apex.antiFlickerSelectors ?? []
|
|
13203
13355
|
});
|
|
13204
13356
|
if (!res.ok)
|
|
@@ -13207,7 +13359,7 @@ async function createSession(folder) {
|
|
|
13207
13359
|
classifyStatus(res.status),
|
|
13208
13360
|
res.errorDetails
|
|
13209
13361
|
);
|
|
13210
|
-
return { session: res.data, apex, experiment };
|
|
13362
|
+
return { session: res.data, apex, experiment: experiment2 };
|
|
13211
13363
|
}
|
|
13212
13364
|
var harnessCommand = new Command("harness").description(
|
|
13213
13365
|
"Develop AB Test Harness projects through Apex live QA sessions"
|
|
@@ -13257,8 +13409,8 @@ harnessCommand.command("dev").argument("<folder>", "AB Test Harness experiment f
|
|
|
13257
13409
|
}
|
|
13258
13410
|
);
|
|
13259
13411
|
if (opts.open !== false) {
|
|
13260
|
-
const
|
|
13261
|
-
await
|
|
13412
|
+
const open3 = (await Promise.resolve().then(() => (init_open(), open_exports))).default;
|
|
13413
|
+
await open3(created.session.previewUrl);
|
|
13262
13414
|
}
|
|
13263
13415
|
let uploading = false;
|
|
13264
13416
|
let queued = false;
|
|
@@ -13704,12 +13856,12 @@ operatorCommand.command("checkout").argument("<experiment-id>", "Apex experiment
|
|
|
13704
13856
|
classifyStatus(expRes.status),
|
|
13705
13857
|
expRes.errorDetails
|
|
13706
13858
|
);
|
|
13707
|
-
const
|
|
13708
|
-
const variation = resolveVariation(
|
|
13859
|
+
const experiment2 = expRes.data;
|
|
13860
|
+
const variation = resolveVariation(experiment2, opts.variation);
|
|
13709
13861
|
const folder = folderPath2(
|
|
13710
|
-
opts.out ?? slugify2(`${
|
|
13862
|
+
opts.out ?? slugify2(`${experiment2.name}-${variation.name}`, "apex-operator")
|
|
13711
13863
|
);
|
|
13712
|
-
const workspace = await getOperatorWorkspace(creds,
|
|
13864
|
+
const workspace = await getOperatorWorkspace(creds, experiment2.id, variation.id);
|
|
13713
13865
|
if (!workspace.ok || !workspace.data)
|
|
13714
13866
|
throw new CliError(
|
|
13715
13867
|
workspace.error ?? "Could not load workspace",
|
|
@@ -13722,7 +13874,7 @@ operatorCommand.command("checkout").argument("<experiment-id>", "Apex experiment
|
|
|
13722
13874
|
folder,
|
|
13723
13875
|
sessionFromWorkspace({
|
|
13724
13876
|
credsBaseUrl: creds.baseUrl,
|
|
13725
|
-
experimentId:
|
|
13877
|
+
experimentId: experiment2.id,
|
|
13726
13878
|
variationId: variation.id,
|
|
13727
13879
|
workspace: workspace.data
|
|
13728
13880
|
})
|
|
@@ -13731,12 +13883,12 @@ operatorCommand.command("checkout").argument("<experiment-id>", "Apex experiment
|
|
|
13731
13883
|
this,
|
|
13732
13884
|
{
|
|
13733
13885
|
folder,
|
|
13734
|
-
experimentId:
|
|
13886
|
+
experimentId: experiment2.id,
|
|
13735
13887
|
variationId: variation.id,
|
|
13736
13888
|
files: workspace.data.files.map((file) => file.path)
|
|
13737
13889
|
},
|
|
13738
13890
|
() => {
|
|
13739
|
-
printSuccess(`Checked out ${
|
|
13891
|
+
printSuccess(`Checked out ${experiment2.name} / ${variation.name}`);
|
|
13740
13892
|
console.log(source_default.cyan(folder));
|
|
13741
13893
|
}
|
|
13742
13894
|
);
|
|
@@ -13811,8 +13963,8 @@ operatorCommand.command("dev").argument("[folder]", "Operator workspace folder",
|
|
|
13811
13963
|
}
|
|
13812
13964
|
);
|
|
13813
13965
|
if (opts.open !== false) {
|
|
13814
|
-
const
|
|
13815
|
-
await
|
|
13966
|
+
const open3 = (await Promise.resolve().then(() => (init_open(), open_exports))).default;
|
|
13967
|
+
await open3(harnessSession.previewUrl);
|
|
13816
13968
|
}
|
|
13817
13969
|
if (opts.once) return;
|
|
13818
13970
|
let uploading = false;
|
|
@@ -13930,7 +14082,7 @@ var approved = new Command("approved").description("List approved roadmap ideas
|
|
|
13930
14082
|
["ID", "SCORE", "TYPE", "EXPERIMENT", "TITLE"],
|
|
13931
14083
|
items.map((item) => [
|
|
13932
14084
|
item.id,
|
|
13933
|
-
score(item.
|
|
14085
|
+
score(item.apex_score),
|
|
13934
14086
|
truncate(item.action_type, 14),
|
|
13935
14087
|
truncate(experimentState(item), 18),
|
|
13936
14088
|
truncate(item.title, 54)
|
|
@@ -14232,8 +14384,8 @@ trackingCommand.command("verify").description("Verify live tracking events for t
|
|
|
14232
14384
|
["TIME", "EVENT", "PROPERTIES"],
|
|
14233
14385
|
res.data.recent_events.map((event) => {
|
|
14234
14386
|
const raw = JSON.stringify(trackingEventProperties(event));
|
|
14235
|
-
const
|
|
14236
|
-
return [String(event.timestamp ?? "-"), String(event.event_type ?? "-"),
|
|
14387
|
+
const preview2 = raw.length > 120 ? `${raw.slice(0, 117)}...` : raw;
|
|
14388
|
+
return [String(event.timestamp ?? "-"), String(event.event_type ?? "-"), preview2];
|
|
14237
14389
|
})
|
|
14238
14390
|
);
|
|
14239
14391
|
}
|
|
@@ -15433,8 +15585,8 @@ var import_node_fs18 = __toESM(require("node:fs"));
|
|
|
15433
15585
|
var import_node_path15 = __toESM(require("node:path"));
|
|
15434
15586
|
|
|
15435
15587
|
// src/trigger-proposal.ts
|
|
15436
|
-
function runtimePath(
|
|
15437
|
-
return
|
|
15588
|
+
function runtimePath(path26) {
|
|
15589
|
+
return path26.replaceAll("[]", ".0");
|
|
15438
15590
|
}
|
|
15439
15591
|
function fieldsFrom(value, prefix = "", descriptorList = false) {
|
|
15440
15592
|
if (!value || typeof value !== "object") return [];
|
|
@@ -15512,9 +15664,9 @@ function candidatesFrom(report) {
|
|
|
15512
15664
|
}
|
|
15513
15665
|
return [root];
|
|
15514
15666
|
}
|
|
15515
|
-
function score2(
|
|
15667
|
+
function score2(path26, patterns) {
|
|
15516
15668
|
return patterns.reduce(
|
|
15517
|
-
(total, pattern, index) => total + (pattern.test(
|
|
15669
|
+
(total, pattern, index) => total + (pattern.test(path26) ? patterns.length - index : 0),
|
|
15518
15670
|
0
|
|
15519
15671
|
);
|
|
15520
15672
|
}
|
|
@@ -16089,6 +16241,15 @@ init_source();
|
|
|
16089
16241
|
function normalizeDomain(value) {
|
|
16090
16242
|
return value.trim().replace(/^https?:\/\//i, "").replace(/[/?#].*$/, "").replace(/\.$/, "").toLowerCase();
|
|
16091
16243
|
}
|
|
16244
|
+
function landingHost(value) {
|
|
16245
|
+
const trimmed = value.trim();
|
|
16246
|
+
try {
|
|
16247
|
+
const withScheme = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
16248
|
+
return new URL(withScheme).hostname.toLowerCase().replace(/\.$/, "") || normalizeDomain(trimmed);
|
|
16249
|
+
} catch {
|
|
16250
|
+
return normalizeDomain(trimmed);
|
|
16251
|
+
}
|
|
16252
|
+
}
|
|
16092
16253
|
function warn2(command, precompile) {
|
|
16093
16254
|
if (!shouldUseJson(command) && precompile && precompile.status !== "ok")
|
|
16094
16255
|
console.error(source_default.yellow(`Warning (${precompile.status}): ${precompile.summary}`));
|
|
@@ -16143,7 +16304,7 @@ var add = new Command("add").description("Add install domains").argument("<domai
|
|
|
16143
16304
|
);
|
|
16144
16305
|
output(this, data, () => printDomains(data));
|
|
16145
16306
|
});
|
|
16146
|
-
var
|
|
16307
|
+
var removeInstall = new Command("remove").description("Remove install domains").argument("<domain...>").action(async function action53(domain) {
|
|
16147
16308
|
const removeSet = new Set(domain.map(normalizeDomain));
|
|
16148
16309
|
const data = await patchWithRetry(
|
|
16149
16310
|
this,
|
|
@@ -16156,7 +16317,194 @@ var set3 = new Command("set").description("Replace install domains").argument("<
|
|
|
16156
16317
|
const data = await patchWithRetry(this, () => replacement);
|
|
16157
16318
|
output(this, data, () => printDomains(data));
|
|
16158
16319
|
});
|
|
16159
|
-
var
|
|
16320
|
+
var configuredInterval = Number(process.env.APEX_CLI_DOMAIN_VERIFY_INTERVAL_MS);
|
|
16321
|
+
var VERIFY_INTERVAL_MS = Number.isFinite(configuredInterval) && configuredInterval > 0 ? configuredInterval : 1e4;
|
|
16322
|
+
var VERIFY_WINDOW_MS = 12e4;
|
|
16323
|
+
function domainFail(command, result, fallback2) {
|
|
16324
|
+
return fail(
|
|
16325
|
+
command,
|
|
16326
|
+
result.error ?? fallback2,
|
|
16327
|
+
classifyStatus(result.status),
|
|
16328
|
+
result.errorDetails
|
|
16329
|
+
);
|
|
16330
|
+
}
|
|
16331
|
+
function printInstructions(domain) {
|
|
16332
|
+
if (!domain.dns_instructions.length) return;
|
|
16333
|
+
console.log("Add these DNS records at the DNS provider:");
|
|
16334
|
+
printTable(
|
|
16335
|
+
["TYPE", "NAME", "VALUE"],
|
|
16336
|
+
domain.dns_instructions.map((record4) => [record4.type, record4.name, record4.value]),
|
|
16337
|
+
[6, 44, 48]
|
|
16338
|
+
);
|
|
16339
|
+
}
|
|
16340
|
+
function printDomainStatus(domain) {
|
|
16341
|
+
console.log(
|
|
16342
|
+
`${domain.host}: ${domain.status}${domain.status_message ? ` (${domain.status_message})` : ""}`
|
|
16343
|
+
);
|
|
16344
|
+
}
|
|
16345
|
+
async function findLandingDomain(command, host) {
|
|
16346
|
+
const wanted = landingHost(host);
|
|
16347
|
+
const listed = await getLandingPageDomains(requireCredentials(), { host: wanted });
|
|
16348
|
+
if (!listed.ok || !listed.data)
|
|
16349
|
+
domainFail(command, listed, "Failed to load landing-page domains");
|
|
16350
|
+
const row = listed.data.data.find((item) => item.host === wanted);
|
|
16351
|
+
if (!row) fail(command, `No landing-page domain ${wanted} on this shop`, EXIT_CODES.general);
|
|
16352
|
+
return row;
|
|
16353
|
+
}
|
|
16354
|
+
async function verifyOnce(command, id) {
|
|
16355
|
+
const checked = await verifyLandingPageDomain(requireCredentials(), id);
|
|
16356
|
+
if (!checked.ok || !checked.data) domainFail(command, checked, "Failed to verify the domain");
|
|
16357
|
+
return checked.data.data;
|
|
16358
|
+
}
|
|
16359
|
+
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) {
|
|
16360
|
+
const result = await createLandingPageDomain(requireCredentials(), {
|
|
16361
|
+
host: landingHost(host),
|
|
16362
|
+
mode: "claim"
|
|
16363
|
+
});
|
|
16364
|
+
if (!result.ok || !result.data) domainFail(this, result, "Failed to claim the hostname");
|
|
16365
|
+
let domain = result.data.data;
|
|
16366
|
+
if (!shouldUseJson(this)) {
|
|
16367
|
+
console.log(`CNAME ${domain.host} -> ${domain.dns_target}`);
|
|
16368
|
+
printInstructions(domain);
|
|
16369
|
+
}
|
|
16370
|
+
if (opts.wait) {
|
|
16371
|
+
const deadline = Date.now() + VERIFY_WINDOW_MS;
|
|
16372
|
+
while (domain.status !== "active" && domain.status !== "failed" && Date.now() < deadline) {
|
|
16373
|
+
await new Promise((resolve2) => setTimeout(resolve2, VERIFY_INTERVAL_MS));
|
|
16374
|
+
domain = await verifyOnce(this, domain.id);
|
|
16375
|
+
}
|
|
16376
|
+
}
|
|
16377
|
+
output(this, { data: domain }, () => printDomainStatus(domain));
|
|
16378
|
+
});
|
|
16379
|
+
function parseConfirmPrice(command, raw) {
|
|
16380
|
+
if (!raw) return void 0;
|
|
16381
|
+
const parts = raw.join(" ").trim().split(/\s+/);
|
|
16382
|
+
const [amount, currency] = parts;
|
|
16383
|
+
if (parts.length !== 2 || !amount || !currency || !/^\d+(\.\d{1,2})?$/.test(amount) || !/^[A-Za-z]{3}$/.test(currency)) {
|
|
16384
|
+
fail(
|
|
16385
|
+
command,
|
|
16386
|
+
'--confirm-price takes the quoted amount and currency, for example --confirm-price "10.44 USD"',
|
|
16387
|
+
EXIT_CODES.validation
|
|
16388
|
+
);
|
|
16389
|
+
}
|
|
16390
|
+
return { amount, currency: currency.toUpperCase() };
|
|
16391
|
+
}
|
|
16392
|
+
function describeQuote(quote) {
|
|
16393
|
+
const term = quote.years === 1 ? "1 year" : `${quote.years} years`;
|
|
16394
|
+
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)`;
|
|
16395
|
+
}
|
|
16396
|
+
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(
|
|
16397
|
+
"--confirm-price <price...>",
|
|
16398
|
+
'confirm the quoted total, for example "10.44 USD" (required without a terminal)'
|
|
16399
|
+
).action(async function action56(name, opts) {
|
|
16400
|
+
const years = Number(opts.years);
|
|
16401
|
+
if (!Number.isInteger(years) || years < 1 || years > 10)
|
|
16402
|
+
fail(this, "--years must be a whole number from 1 to 10", EXIT_CODES.validation);
|
|
16403
|
+
const host = landingHost(name);
|
|
16404
|
+
let confirm3 = parseConfirmPrice(this, opts.confirmPrice);
|
|
16405
|
+
let result = await createLandingPageDomain(requireCredentials(), {
|
|
16406
|
+
host,
|
|
16407
|
+
mode: "register",
|
|
16408
|
+
years,
|
|
16409
|
+
confirm_price: confirm3
|
|
16410
|
+
});
|
|
16411
|
+
const quote = result.data?.quote;
|
|
16412
|
+
if (!result.ok && result.status === 409 && result.errorDetails?.code === "price_confirmation_required" && quote) {
|
|
16413
|
+
if (confirm3) {
|
|
16414
|
+
fail(this, `The price changed: ${describeQuote(quote)}`, EXIT_CODES.conflict, { quote });
|
|
16415
|
+
}
|
|
16416
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
16417
|
+
confirmationRequired(
|
|
16418
|
+
this,
|
|
16419
|
+
`Registering spends money. ${describeQuote(quote)}`,
|
|
16420
|
+
`apex domains register ${host} --years ${years} --confirm-price "${quote.total} ${quote.currency}"`
|
|
16421
|
+
);
|
|
16422
|
+
}
|
|
16423
|
+
const readline3 = await import("node:readline");
|
|
16424
|
+
const rl = readline3.createInterface({ input: process.stdin, output: process.stderr });
|
|
16425
|
+
console.error(describeQuote(quote));
|
|
16426
|
+
const answer = await new Promise(
|
|
16427
|
+
(resolve2) => rl.question(
|
|
16428
|
+
source_default.yellow(
|
|
16429
|
+
`Register ${quote.domain} for ${quote.total} ${quote.currency}? This is non-refundable. [y/N] `
|
|
16430
|
+
),
|
|
16431
|
+
resolve2
|
|
16432
|
+
)
|
|
16433
|
+
);
|
|
16434
|
+
rl.close();
|
|
16435
|
+
if (answer.trim().toLowerCase() !== "y") {
|
|
16436
|
+
output(
|
|
16437
|
+
this,
|
|
16438
|
+
{ aborted: true, quote },
|
|
16439
|
+
() => console.log("Aborted. Nothing was registered.")
|
|
16440
|
+
);
|
|
16441
|
+
return;
|
|
16442
|
+
}
|
|
16443
|
+
confirm3 = { amount: quote.total, currency: quote.currency };
|
|
16444
|
+
result = await createLandingPageDomain(requireCredentials(), {
|
|
16445
|
+
host,
|
|
16446
|
+
mode: "register",
|
|
16447
|
+
years,
|
|
16448
|
+
confirm_price: confirm3
|
|
16449
|
+
});
|
|
16450
|
+
}
|
|
16451
|
+
if (!result.ok || !result.data) domainFail(this, result, "Failed to register the domain");
|
|
16452
|
+
const domain = result.data.data;
|
|
16453
|
+
output(this, { data: domain }, () => {
|
|
16454
|
+
const registration = domain.registration;
|
|
16455
|
+
console.log(
|
|
16456
|
+
`Registered ${domain.host}${registration ? ` for ${registration.price} ${registration.currency}` : ""}; status ${domain.status}.`
|
|
16457
|
+
);
|
|
16458
|
+
if (domain.status_message) console.log(domain.status_message);
|
|
16459
|
+
console.log(`Check progress with: apex domains status ${domain.host}`);
|
|
16460
|
+
});
|
|
16461
|
+
});
|
|
16462
|
+
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) {
|
|
16463
|
+
if (host) {
|
|
16464
|
+
const row = await findLandingDomain(this, host);
|
|
16465
|
+
if (row.status === "removed")
|
|
16466
|
+
fail(
|
|
16467
|
+
this,
|
|
16468
|
+
`${row.host} was removed, but Cloudflare has not confirmed the hostname delete; run apex domains unclaim ${row.host} again`,
|
|
16469
|
+
EXIT_CODES.general
|
|
16470
|
+
);
|
|
16471
|
+
const domain = await verifyOnce(this, row.id);
|
|
16472
|
+
output(this, { data: domain }, () => {
|
|
16473
|
+
printDomainStatus(domain);
|
|
16474
|
+
if (domain.status !== "active") printInstructions(domain);
|
|
16475
|
+
});
|
|
16476
|
+
return;
|
|
16477
|
+
}
|
|
16478
|
+
const result = await getLandingPageDomains(requireCredentials());
|
|
16479
|
+
if (!result.ok || !result.data) domainFail(this, result, "Failed to load landing-page domains");
|
|
16480
|
+
const rows = result.data.data;
|
|
16481
|
+
output(this, result.data, () => {
|
|
16482
|
+
if (!rows.length) {
|
|
16483
|
+
console.log("No landing-page domains. Claim one with: apex domains claim <host>");
|
|
16484
|
+
return;
|
|
16485
|
+
}
|
|
16486
|
+
printTable(
|
|
16487
|
+
["HOST", "MODE", "STATUS", "DNS TARGET"],
|
|
16488
|
+
rows.map((row) => [row.host, row.mode, row.status, row.dns_target]),
|
|
16489
|
+
[40, 9, 10, 24]
|
|
16490
|
+
);
|
|
16491
|
+
if (result.data.has_more)
|
|
16492
|
+
console.log(`Showing the newest ${rows.length}; more landing-page domains exist.`);
|
|
16493
|
+
});
|
|
16494
|
+
});
|
|
16495
|
+
var unclaim = new Command("unclaim").description(
|
|
16496
|
+
"Remove a landing-page domain from Apex (a registered domain stays on the Drip account)"
|
|
16497
|
+
).argument("<host>").action(async function action58(host) {
|
|
16498
|
+
const row = await findLandingDomain(this, host);
|
|
16499
|
+
const result = await deleteLandingPageDomain(requireCredentials(), row.id);
|
|
16500
|
+
if (!result.ok || !result.data) domainFail(this, result, "Failed to remove the domain");
|
|
16501
|
+
const domain = result.data.data;
|
|
16502
|
+
output(this, { data: domain }, () => {
|
|
16503
|
+
console.log(`Removed ${domain.host} from Apex.`);
|
|
16504
|
+
if (domain.status_message) console.log(domain.status_message);
|
|
16505
|
+
});
|
|
16506
|
+
});
|
|
16507
|
+
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);
|
|
16160
16508
|
|
|
16161
16509
|
// src/commands/handover.ts
|
|
16162
16510
|
init_source();
|
|
@@ -16243,14 +16591,14 @@ async function patchWithRetry2(command, build) {
|
|
|
16243
16591
|
);
|
|
16244
16592
|
return result.data;
|
|
16245
16593
|
}
|
|
16246
|
-
var get4 = new Command("get").description("Show cross-domain handover settings").action(async function
|
|
16594
|
+
var get4 = new Command("get").description("Show cross-domain handover settings").action(async function action59() {
|
|
16247
16595
|
const data = await load5(this);
|
|
16248
16596
|
output(this, data, () => printSettings(data.externalTracking));
|
|
16249
16597
|
});
|
|
16250
16598
|
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(
|
|
16251
16599
|
"--signed",
|
|
16252
16600
|
"Require signed handover tokens (the default; unsigning is a platform-support action)"
|
|
16253
|
-
).action(async function
|
|
16601
|
+
).action(async function action60(opts) {
|
|
16254
16602
|
const domains = opts.domain.map(normalizeDomain2);
|
|
16255
16603
|
if (domains.some((domain) => !domain))
|
|
16256
16604
|
fail(
|
|
@@ -16270,14 +16618,14 @@ var set4 = new Command("set").description("Enable cross-domain identity handover
|
|
|
16270
16618
|
}));
|
|
16271
16619
|
output(this, data, () => printSettings(data.externalTracking));
|
|
16272
16620
|
});
|
|
16273
|
-
var off2 = new Command("off").description("Disable cross-domain handover").action(async function
|
|
16621
|
+
var off2 = new Command("off").description("Disable cross-domain handover").action(async function action61() {
|
|
16274
16622
|
const data = await patchWithRetry2(this, (current) => ({
|
|
16275
16623
|
externalTracking: { domains: [], signed: true },
|
|
16276
16624
|
ifRevision: current.revision
|
|
16277
16625
|
}));
|
|
16278
16626
|
output(this, data, () => printSettings(data.externalTracking));
|
|
16279
16627
|
});
|
|
16280
|
-
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
|
|
16628
|
+
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) {
|
|
16281
16629
|
const config = await load5(this);
|
|
16282
16630
|
const target = normalizeDomain2(opts.toDomain);
|
|
16283
16631
|
if (!target) fail(this, "--to-domain must be a valid hostname", EXIT_CODES.validation);
|
|
@@ -16371,10 +16719,10 @@ var reason = (v) => typeof v === "string" && (/^[a-z][a-z0-9_]{0,79}$/.test(v) |
|
|
|
16371
16719
|
v
|
|
16372
16720
|
));
|
|
16373
16721
|
var member = (v, values) => typeof v === "string" && values.includes(v);
|
|
16374
|
-
var
|
|
16722
|
+
var status2 = (v) => member(v, ["pass", "fail", "inconclusive"]);
|
|
16375
16723
|
var count = (v) => typeof v === "number" && Number.isSafeInteger(v) && v >= 0;
|
|
16376
16724
|
function validReport(v) {
|
|
16377
|
-
if (!record2(v) || v.schemaVersion !== 1 || v.finalStatisticalContribution !== "not_evaluated" || !record2(v.evaluation) || !
|
|
16725
|
+
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(
|
|
16378
16726
|
(s) => record2(s) && (s.freshness === void 0 || member(s.freshness, ["current", "stale", "unknown"])) && member(s.stage, [
|
|
16379
16727
|
"consent",
|
|
16380
16728
|
"assignment",
|
|
@@ -16383,7 +16731,7 @@ function validReport(v) {
|
|
|
16383
16731
|
"authoritative_fact",
|
|
16384
16732
|
"attribution",
|
|
16385
16733
|
"result"
|
|
16386
|
-
]) &&
|
|
16734
|
+
]) && status2(s.status) && Array.isArray(s.reasonCodes) && s.reasonCodes.every(reason)
|
|
16387
16735
|
))
|
|
16388
16736
|
return false;
|
|
16389
16737
|
if (v.receiptOutcomes !== void 0 && (!Array.isArray(v.receiptOutcomes) || v.receiptOutcomes.length > 6856 || !v.receiptOutcomes.every(
|
|
@@ -17466,7 +17814,7 @@ var doctorCommand = new Command("doctor").description("Check shop configuration
|
|
|
17466
17814
|
).option("--check <id>", "Run one check (for example purchase_capture)").option(
|
|
17467
17815
|
"--worker <url>",
|
|
17468
17816
|
`Apex Worker base URL; defaults to DRIP_WORKER_URL or ${PRODUCTION_WORKER_BASE_URL}`
|
|
17469
|
-
).action(async function
|
|
17817
|
+
).action(async function action63(options) {
|
|
17470
17818
|
const data = await runDoctor(options);
|
|
17471
17819
|
if (options.check && data.checks.length === 0)
|
|
17472
17820
|
fail(this, `Unknown doctor check: ${options.check}`, EXIT_CODES.validation);
|
|
@@ -17488,7 +17836,7 @@ function printDoctorReport(checks) {
|
|
|
17488
17836
|
var consentRateCommand = new Command("consent-rate").description("Estimate analytics consent acceptance from visitor series").option(
|
|
17489
17837
|
"--since <date>",
|
|
17490
17838
|
"Consent gating start date (YYYY-MM-DD); splits the baseline and gated windows"
|
|
17491
|
-
).action(async function
|
|
17839
|
+
).action(async function action64(opts) {
|
|
17492
17840
|
if (opts.since !== void 0 && parseDay(opts.since) === null)
|
|
17493
17841
|
fail(this, "--since must be a calendar date in YYYY-MM-DD form", EXIT_CODES.validation);
|
|
17494
17842
|
const result = await getVisitorSeries(requireCredentials(), { days: VISITOR_SERIES_DAYS });
|
|
@@ -17548,7 +17896,7 @@ function analyticsSubcommand(name, description, defaultDays) {
|
|
|
17548
17896
|
command.option("--planning-days <n>", "Planning window in days", "180");
|
|
17549
17897
|
command.option("--limit <n>", "Maximum page rows", "50");
|
|
17550
17898
|
}
|
|
17551
|
-
return command.action(async function
|
|
17899
|
+
return command.action(async function action78(opts) {
|
|
17552
17900
|
const result = await getAnalytics(requireCredentials(), name, {
|
|
17553
17901
|
days: positiveInteger(this, opts.days, "--days"),
|
|
17554
17902
|
planningDays: positiveInteger(this, opts.planningDays, "--planning-days"),
|
|
@@ -17568,7 +17916,7 @@ function urlsCommand() {
|
|
|
17568
17916
|
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(
|
|
17569
17917
|
"--match <regex>",
|
|
17570
17918
|
"Client-side regex filter; exhausts inventory pages before filtering"
|
|
17571
|
-
).option("--limit <n>", "Maximum rows to print", "50").action(async function
|
|
17919
|
+
).option("--limit <n>", "Maximum rows to print", "50").action(async function action78(opts) {
|
|
17572
17920
|
const creds = requireCredentials();
|
|
17573
17921
|
const days = positiveInteger(this, opts.days, "--days") ?? 14;
|
|
17574
17922
|
const displayLimit = Math.min(200, positiveInteger(this, opts.limit, "--limit") ?? 50);
|
|
@@ -17779,10 +18127,10 @@ function decodeBody(raw, header, limits, hostname, inputTruncated = false) {
|
|
|
17779
18127
|
const chunks = [];
|
|
17780
18128
|
let bytes = 0;
|
|
17781
18129
|
let settled = false;
|
|
17782
|
-
const settle = (
|
|
18130
|
+
const settle = (action78) => {
|
|
17783
18131
|
if (settled) return;
|
|
17784
18132
|
settled = true;
|
|
17785
|
-
|
|
18133
|
+
action78();
|
|
17786
18134
|
};
|
|
17787
18135
|
stream.on("data", (chunk) => {
|
|
17788
18136
|
if (settled) return;
|
|
@@ -17824,7 +18172,7 @@ function overLimitMessage(hostname, maxBytes) {
|
|
|
17824
18172
|
return `Response from ${hostname} exceeded the ${maxBytes}-byte cap`;
|
|
17825
18173
|
}
|
|
17826
18174
|
function responseFromIncoming(res, body5, decoded) {
|
|
17827
|
-
const
|
|
18175
|
+
const status4 = res.statusCode ?? 0;
|
|
17828
18176
|
const headers = new Headers();
|
|
17829
18177
|
for (const [key, value] of Object.entries(res.headers)) {
|
|
17830
18178
|
if (Array.isArray(value)) for (const entry of value) headers.append(key, entry);
|
|
@@ -17834,9 +18182,9 @@ function responseFromIncoming(res, body5, decoded) {
|
|
|
17834
18182
|
headers.delete("content-encoding");
|
|
17835
18183
|
headers.delete("content-length");
|
|
17836
18184
|
}
|
|
17837
|
-
const nullBody =
|
|
18185
|
+
const nullBody = status4 === 101 || status4 === 204 || status4 === 205 || status4 === 304;
|
|
17838
18186
|
return new Response(nullBody ? null : body5, {
|
|
17839
|
-
status:
|
|
18187
|
+
status: status4,
|
|
17840
18188
|
statusText: res.statusMessage ?? "",
|
|
17841
18189
|
headers
|
|
17842
18190
|
});
|
|
@@ -17853,11 +18201,11 @@ function pinnedFetch(url, addresses, limits) {
|
|
|
17853
18201
|
else callback(null, addresses[0], (0, import_node_net.isIP)(addresses[0]));
|
|
17854
18202
|
};
|
|
17855
18203
|
let settled = false;
|
|
17856
|
-
const settle = (
|
|
18204
|
+
const settle = (action78) => {
|
|
17857
18205
|
if (settled) return;
|
|
17858
18206
|
settled = true;
|
|
17859
18207
|
clearTimeout(deadline);
|
|
17860
|
-
|
|
18208
|
+
action78();
|
|
17861
18209
|
};
|
|
17862
18210
|
const req = send3(
|
|
17863
18211
|
url,
|
|
@@ -17868,8 +18216,8 @@ function pinnedFetch(url, addresses, limits) {
|
|
|
17868
18216
|
lookup: pinnedLookup
|
|
17869
18217
|
},
|
|
17870
18218
|
(res) => {
|
|
17871
|
-
const
|
|
17872
|
-
if (Number.isFinite(
|
|
18219
|
+
const declared2 = Number.parseInt(res.headers["content-length"] ?? "", 10);
|
|
18220
|
+
if (Number.isFinite(declared2) && declared2 > limits.maxBytes && limits.onExceed === "error") {
|
|
17873
18221
|
req.destroy();
|
|
17874
18222
|
settle(() => reject(new BodyLimitError(overLimitMessage(url.hostname, limits.maxBytes))));
|
|
17875
18223
|
return;
|
|
@@ -17984,7 +18332,7 @@ function detectChallenge(html) {
|
|
|
17984
18332
|
);
|
|
17985
18333
|
}
|
|
17986
18334
|
var LEGACY_SNIPPET_MARKER = /sdk\.drip-apex\.com|events\.drip-apex\.com|\/drip\.js\b|\bDrip\.init\s*\(|\bwindow\.drip\b/i;
|
|
17987
|
-
function pageReport(url, shopId,
|
|
18335
|
+
function pageReport(url, shopId, status4, html, workerSnippetPresent, bodyTruncated) {
|
|
17988
18336
|
const challenge = detectChallenge(html);
|
|
17989
18337
|
const shopIdMatches = html.includes(shopId);
|
|
17990
18338
|
const snippetPresent = workerSnippetPresent || LEGACY_SNIPPET_MARKER.test(html) && shopIdMatches;
|
|
@@ -17992,29 +18340,29 @@ function pageReport(url, shopId, status3, html, workerSnippetPresent, bodyTrunca
|
|
|
17992
18340
|
return {
|
|
17993
18341
|
url,
|
|
17994
18342
|
status: "WARN",
|
|
17995
|
-
httpStatus:
|
|
18343
|
+
httpStatus: status4,
|
|
17996
18344
|
snippetPresent,
|
|
17997
18345
|
shopIdMatches,
|
|
17998
18346
|
botChallenge: true,
|
|
17999
18347
|
bodyTruncated,
|
|
18000
18348
|
message: "Bot challenge detected; verify in a real browser."
|
|
18001
18349
|
};
|
|
18002
|
-
if (
|
|
18350
|
+
if (status4 < 200 || status4 >= 300)
|
|
18003
18351
|
return {
|
|
18004
18352
|
url,
|
|
18005
18353
|
status: "FAIL",
|
|
18006
|
-
httpStatus:
|
|
18354
|
+
httpStatus: status4,
|
|
18007
18355
|
snippetPresent,
|
|
18008
18356
|
shopIdMatches,
|
|
18009
18357
|
botChallenge: false,
|
|
18010
18358
|
bodyTruncated,
|
|
18011
|
-
message: `Storefront page returned HTTP ${
|
|
18359
|
+
message: `Storefront page returned HTTP ${status4}; the install cannot be verified from this response.`
|
|
18012
18360
|
};
|
|
18013
18361
|
if (!snippetPresent)
|
|
18014
18362
|
return {
|
|
18015
18363
|
url,
|
|
18016
18364
|
status: "FAIL",
|
|
18017
|
-
httpStatus:
|
|
18365
|
+
httpStatus: status4,
|
|
18018
18366
|
snippetPresent,
|
|
18019
18367
|
shopIdMatches,
|
|
18020
18368
|
botChallenge: false,
|
|
@@ -18025,7 +18373,7 @@ function pageReport(url, shopId, status3, html, workerSnippetPresent, bodyTrunca
|
|
|
18025
18373
|
return {
|
|
18026
18374
|
url,
|
|
18027
18375
|
status: "FAIL",
|
|
18028
|
-
httpStatus:
|
|
18376
|
+
httpStatus: status4,
|
|
18029
18377
|
snippetPresent,
|
|
18030
18378
|
shopIdMatches,
|
|
18031
18379
|
botChallenge: false,
|
|
@@ -18035,7 +18383,7 @@ function pageReport(url, shopId, status3, html, workerSnippetPresent, bodyTrunca
|
|
|
18035
18383
|
return {
|
|
18036
18384
|
url,
|
|
18037
18385
|
status: "PASS",
|
|
18038
|
-
httpStatus:
|
|
18386
|
+
httpStatus: status4,
|
|
18039
18387
|
snippetPresent,
|
|
18040
18388
|
shopIdMatches,
|
|
18041
18389
|
botChallenge: false,
|
|
@@ -18195,7 +18543,7 @@ async function checkServedConfig(configUrl, workerBaseUrl, workerBaseSource) {
|
|
|
18195
18543
|
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(
|
|
18196
18544
|
"--worker-url <url>",
|
|
18197
18545
|
"Apex Worker base URL that serves /config (defaults to the URL in the served snippet, then DRIP_WORKER_URL, then the production worker)"
|
|
18198
|
-
).option("--environment <key>", "Environment key to request from the worker", "production").action(async function
|
|
18546
|
+
).option("--environment <key>", "Environment key to request from the worker", "production").action(async function action65(opts) {
|
|
18199
18547
|
const creds = requireCredentials();
|
|
18200
18548
|
const shopId = opts.shopId?.trim() || process.env.DRIP_SHOP_ID?.trim() || null;
|
|
18201
18549
|
if (!shopId)
|
|
@@ -18379,13 +18727,13 @@ function compareEdgePublication(currentValue, edgeValue, expectedWorstCaseSecond
|
|
|
18379
18727
|
if (edge.publication_revision < current.publication_revision) return { ...base, verdict: "propagating", relationship: "edge_behind" };
|
|
18380
18728
|
return { ...base, verdict: "live", relationship: edge.publication_revision > current.publication_revision ? "edge_ahead" : "equal" };
|
|
18381
18729
|
}
|
|
18382
|
-
function formatEdgePropagationStatus(
|
|
18730
|
+
function formatEdgePropagationStatus(status4) {
|
|
18383
18731
|
const caveat = "This checks one CDN vantage point; other PoPs may lag by up to max-age.";
|
|
18384
|
-
if (
|
|
18385
|
-
if (
|
|
18386
|
-
if (
|
|
18387
|
-
const anomaly =
|
|
18388
|
-
return `Live at edge \u2014 revision ${
|
|
18732
|
+
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}`;
|
|
18733
|
+
if (status4.verdict === "unknown") return `Unknown \u2014 edge payload predates publication metadata. ${caveat}`;
|
|
18734
|
+
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}`;
|
|
18735
|
+
const anomaly = status4.relationship === "edge_ahead" ? ` (edge is ahead of current revision ${status4.current.publication_revision}; anomalous)` : "";
|
|
18736
|
+
return `Live at edge \u2014 revision ${status4.edge.publication_revision} (generated ${status4.edge.generated_at}) confirmed at edge at ${status4.confirmed_at}${anomaly}. ${caveat}`;
|
|
18389
18737
|
}
|
|
18390
18738
|
|
|
18391
18739
|
// src/commands/flags.ts
|
|
@@ -18444,7 +18792,7 @@ var get5 = new Command("get").description("Get a flag").argument("<id>").action(
|
|
|
18444
18792
|
fail(this, r.error ?? "Failed to get", classifyStatus(r.status), r.errorDetails);
|
|
18445
18793
|
output(this, r.data, () => detail(r.data));
|
|
18446
18794
|
});
|
|
18447
|
-
var
|
|
18795
|
+
var status3 = new Command("status").description("Check whether a flag publication is live at the edge").argument("<id>").action(async function(id) {
|
|
18448
18796
|
const r = await getFlagStatus(requireCredentials(), id);
|
|
18449
18797
|
if (!r.ok || !r.data)
|
|
18450
18798
|
fail(this, r.error ?? "Failed to get flag status", classifyStatus(r.status), r.errorDetails);
|
|
@@ -18507,15 +18855,15 @@ function write(name) {
|
|
|
18507
18855
|
});
|
|
18508
18856
|
return c;
|
|
18509
18857
|
}
|
|
18510
|
-
function lifecycle(
|
|
18511
|
-
return new Command(
|
|
18512
|
-
const r = await transitionManagedDelivery(requireCredentials(), kind, id,
|
|
18858
|
+
function lifecycle(action78) {
|
|
18859
|
+
return new Command(action78).description(`${action78[0].toUpperCase()}${action78.slice(1)} a flag`).argument("<id>").action(async function(id) {
|
|
18860
|
+
const r = await transitionManagedDelivery(requireCredentials(), kind, id, action78);
|
|
18513
18861
|
if (!r.ok || !r.data)
|
|
18514
|
-
fail(this, r.error ?? `Failed to ${
|
|
18862
|
+
fail(this, r.error ?? `Failed to ${action78}`, classifyStatus(r.status), r.errorDetails);
|
|
18515
18863
|
output(this, r.data, () => detail(r.data));
|
|
18516
18864
|
});
|
|
18517
18865
|
}
|
|
18518
|
-
var flagsCommand = new Command("flags").description("Manage flags").addCommand(list6).addCommand(get5).addCommand(
|
|
18866
|
+
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(
|
|
18519
18867
|
"after",
|
|
18520
18868
|
`
|
|
18521
18869
|
Examples:
|
|
@@ -18648,11 +18996,11 @@ var get6 = new Command("get").description("Get a backlog item identified as apex
|
|
|
18648
18996
|
const labels = await fieldLabels(this, r.data);
|
|
18649
18997
|
output(this, r.data, () => detail2(r.data, labels));
|
|
18650
18998
|
});
|
|
18651
|
-
function write2(
|
|
18652
|
-
const c = new Command(
|
|
18653
|
-
`${
|
|
18999
|
+
function write2(action78) {
|
|
19000
|
+
const c = new Command(action78).description(
|
|
19001
|
+
`${action78 === "create" ? "Create" : "Update"} a ${singular}`
|
|
18654
19002
|
);
|
|
18655
|
-
if (
|
|
19003
|
+
if (action78 === "update") c.argument("<id>", `${singular} ID`);
|
|
18656
19004
|
c.argument("[json]", "Inline JSON, @file, or file path").option("--file <path>", "JSON body file").option(
|
|
18657
19005
|
"--field <key=value>",
|
|
18658
19006
|
"Set one of the shop's own fields; repeatable. Use key=null to clear it.",
|
|
@@ -18661,17 +19009,17 @@ function write2(action74) {
|
|
|
18661
19009
|
);
|
|
18662
19010
|
c.action(async function(...args) {
|
|
18663
19011
|
const o = this.opts();
|
|
18664
|
-
const id =
|
|
18665
|
-
const raw = o.file ? `@${o.file}` : String(args[
|
|
19012
|
+
const id = action78 === "update" ? String(args[0]) : void 0;
|
|
19013
|
+
const raw = o.file ? `@${o.file}` : String(args[action78 === "update" ? 1 : 0] ?? "") || void 0;
|
|
18666
19014
|
const body5 = parse(this, raw);
|
|
18667
19015
|
const customFields = await customFieldsFromPairs(this, o.field);
|
|
18668
19016
|
if (customFields) {
|
|
18669
19017
|
body5.custom_fields = { ...customFieldsRecord(body5.custom_fields), ...customFields };
|
|
18670
19018
|
}
|
|
18671
19019
|
if (!Object.keys(body5).length) fail(this, "Provide a JSON body.", EXIT_CODES.validation);
|
|
18672
|
-
const r =
|
|
19020
|
+
const r = action78 === "create" ? await createInsight(requireCredentials(), kind2, body5) : await updateInsight(requireCredentials(), kind2, id, body5);
|
|
18673
19021
|
if (!r.ok || !r.data)
|
|
18674
|
-
fail(this, r.error ?? `Failed to ${
|
|
19022
|
+
fail(this, r.error ?? `Failed to ${action78}`, classifyStatus(r.status), r.errorDetails);
|
|
18675
19023
|
const labels = await fieldLabels(this, r.data);
|
|
18676
19024
|
output(this, r.data, () => detail2(r.data, labels));
|
|
18677
19025
|
});
|
|
@@ -18694,7 +19042,7 @@ var restore = new Command("restore").description("Restore an archived backlog it
|
|
|
18694
19042
|
fail(this, r.error ?? "Failed to restore", classifyStatus(r.status), r.errorDetails);
|
|
18695
19043
|
output(this, r.data, () => console.log(`Restored ${id}`));
|
|
18696
19044
|
});
|
|
18697
|
-
var
|
|
19045
|
+
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) {
|
|
18698
19046
|
if (!opts.force) {
|
|
18699
19047
|
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
18700
19048
|
confirmationRequired(
|
|
@@ -18810,7 +19158,7 @@ var fieldsArchive = new Command("archive").description("Archive an idea field; v
|
|
|
18810
19158
|
output(this, r.data, () => fieldDetail(r.data));
|
|
18811
19159
|
});
|
|
18812
19160
|
var fields2 = new Command("fields").description("Manage the shop's own idea fields").addCommand(fieldsList).addCommand(fieldsCreate).addCommand(fieldsUpdate).addCommand(fieldsArchive);
|
|
18813
|
-
var backlogCommand = new Command("backlog").description("Manage backlog").addCommand(list7).addCommand(get6).addCommand(write2("create")).addCommand(write2("update")).addCommand(archive).addCommand(restore).addCommand(
|
|
19161
|
+
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);
|
|
18814
19162
|
|
|
18815
19163
|
// src/commands/signals.ts
|
|
18816
19164
|
var import_node_fs21 = __toESM(require("node:fs"));
|
|
@@ -18872,35 +19220,35 @@ var get7 = new Command("get").description("Get a signal").argument("<id>", "sign
|
|
|
18872
19220
|
fail(this, r.error ?? "Failed to get", classifyStatus(r.status), r.errorDetails);
|
|
18873
19221
|
output(this, r.data, () => detail3(r.data));
|
|
18874
19222
|
});
|
|
18875
|
-
function write3(
|
|
18876
|
-
const c = new Command(
|
|
18877
|
-
`${
|
|
19223
|
+
function write3(action78) {
|
|
19224
|
+
const c = new Command(action78).description(
|
|
19225
|
+
`${action78 === "create" ? "Create" : "Update"} a ${singular2}`
|
|
18878
19226
|
);
|
|
18879
|
-
if (
|
|
19227
|
+
if (action78 === "update") c.argument("<id>", `${singular2} ID`);
|
|
18880
19228
|
c.argument("[json]", "Inline JSON, @file, or file path").option(
|
|
18881
19229
|
"--file <path>",
|
|
18882
19230
|
"JSON body file"
|
|
18883
19231
|
);
|
|
18884
19232
|
c.action(async function(...args) {
|
|
18885
19233
|
const o = this.opts();
|
|
18886
|
-
const id =
|
|
18887
|
-
const raw = o.file ? `@${o.file}` : String(args[
|
|
19234
|
+
const id = action78 === "update" ? String(args[0]) : void 0;
|
|
19235
|
+
const raw = o.file ? `@${o.file}` : String(args[action78 === "update" ? 1 : 0] ?? "") || void 0;
|
|
18888
19236
|
const body5 = parse2(this, raw);
|
|
18889
19237
|
if (!Object.keys(body5).length) fail(this, "Provide a JSON body.", EXIT_CODES.validation);
|
|
18890
|
-
const r =
|
|
19238
|
+
const r = action78 === "create" ? await createInsight(requireCredentials(), kind3, body5) : await updateInsight(requireCredentials(), kind3, id, body5);
|
|
18891
19239
|
if (!r.ok || !r.data)
|
|
18892
|
-
fail(this, r.error ?? `Failed to ${
|
|
19240
|
+
fail(this, r.error ?? `Failed to ${action78}`, classifyStatus(r.status), r.errorDetails);
|
|
18893
19241
|
output(this, r.data, () => detail3(r.data));
|
|
18894
19242
|
});
|
|
18895
19243
|
return c;
|
|
18896
19244
|
}
|
|
18897
|
-
var
|
|
19245
|
+
var remove2 = new Command("delete").alias("archive").description("Delete or archive a signal").argument("<id>", "signal ID").action(async function(id) {
|
|
18898
19246
|
const r = await deleteInsight(requireCredentials(), kind3, id);
|
|
18899
19247
|
if (!r.ok || !r.data)
|
|
18900
19248
|
fail(this, r.error ?? "Failed to delete", classifyStatus(r.status), r.errorDetails);
|
|
18901
19249
|
output(this, r.data, () => console.log(`Deleted ${id}`));
|
|
18902
19250
|
});
|
|
18903
|
-
var signalsCommand = new Command("signals").description("Manage signals").addCommand(list8).addCommand(get7).addCommand(write3("create")).addCommand(write3("update")).addCommand(
|
|
19251
|
+
var signalsCommand = new Command("signals").description("Manage signals").addCommand(list8).addCommand(get7).addCommand(write3("create")).addCommand(write3("update")).addCommand(remove2);
|
|
18904
19252
|
|
|
18905
19253
|
// src/commands/personalizations.ts
|
|
18906
19254
|
init_source();
|
|
@@ -18981,11 +19329,11 @@ function write4(name) {
|
|
|
18981
19329
|
});
|
|
18982
19330
|
return c;
|
|
18983
19331
|
}
|
|
18984
|
-
function lifecycle2(
|
|
18985
|
-
return new Command(
|
|
18986
|
-
const r = await transitionManagedDelivery(requireCredentials(), kind4, id,
|
|
19332
|
+
function lifecycle2(action78) {
|
|
19333
|
+
return new Command(action78).description(`${action78[0].toUpperCase()}${action78.slice(1)} a personalization`).argument("<id>").action(async function(id) {
|
|
19334
|
+
const r = await transitionManagedDelivery(requireCredentials(), kind4, id, action78);
|
|
18987
19335
|
if (!r.ok || !r.data)
|
|
18988
|
-
fail(this, r.error ?? `Failed to ${
|
|
19336
|
+
fail(this, r.error ?? `Failed to ${action78}`, classifyStatus(r.status), r.errorDetails);
|
|
18989
19337
|
output(this, r.data, () => detail4(r.data));
|
|
18990
19338
|
});
|
|
18991
19339
|
}
|
|
@@ -19067,21 +19415,21 @@ function targetingResourceCommand(kind5) {
|
|
|
19067
19415
|
);
|
|
19068
19416
|
})
|
|
19069
19417
|
);
|
|
19070
|
-
for (const
|
|
19071
|
-
const command = new Command(
|
|
19072
|
-
`${
|
|
19418
|
+
for (const action78 of ["create", "update"]) {
|
|
19419
|
+
const command = new Command(action78).description(
|
|
19420
|
+
`${action78 === "create" ? "Create" : "Update"} a ${singular3}`
|
|
19073
19421
|
);
|
|
19074
|
-
if (
|
|
19422
|
+
if (action78 === "update") command.argument("<id>");
|
|
19075
19423
|
command.argument("[json]", "Inline JSON, @file, or file path").option("--file <path>", "JSON body file").action(async function(...args) {
|
|
19076
19424
|
const opts = this.opts();
|
|
19077
|
-
const id =
|
|
19078
|
-
const value = opts.file ? `@${opts.file}` : String(args[
|
|
19425
|
+
const id = action78 === "update" ? String(args[0]) : void 0;
|
|
19426
|
+
const value = opts.file ? `@${opts.file}` : String(args[action78 === "update" ? 1 : 0] ?? "") || void 0;
|
|
19079
19427
|
const requestBody = body3(this, value);
|
|
19080
|
-
if (
|
|
19428
|
+
if (action78 === "update" && typeof requestBody === "object" && requestBody !== null && !Array.isArray(requestBody) && Object.keys(requestBody).length === 0)
|
|
19081
19429
|
fail(this, `Updating a ${singular3} requires at least one field.`, EXIT_CODES.validation);
|
|
19082
19430
|
const response = await requestTargetingResource(
|
|
19083
19431
|
requireCredentials(),
|
|
19084
|
-
|
|
19432
|
+
action78 === "create" ? "POST" : "PATCH",
|
|
19085
19433
|
kind5,
|
|
19086
19434
|
id,
|
|
19087
19435
|
requestBody
|
|
@@ -19089,11 +19437,11 @@ function targetingResourceCommand(kind5) {
|
|
|
19089
19437
|
if (!response.ok || !response.data)
|
|
19090
19438
|
fail(
|
|
19091
19439
|
this,
|
|
19092
|
-
response.error ?? `Failed to ${
|
|
19440
|
+
response.error ?? `Failed to ${action78}`,
|
|
19093
19441
|
classifyStatus(response.status),
|
|
19094
19442
|
response.errorDetails
|
|
19095
19443
|
);
|
|
19096
|
-
output(this, response.data, () => console.log(source_default.green(`${
|
|
19444
|
+
output(this, response.data, () => console.log(source_default.green(`${action78}d ${singular3}`)));
|
|
19097
19445
|
});
|
|
19098
19446
|
root.addCommand(command);
|
|
19099
19447
|
}
|
|
@@ -19162,7 +19510,7 @@ var exclusionGroupsCommand = targetingResourceCommand("exclusion-groups");
|
|
|
19162
19510
|
// src/commands/brand.ts
|
|
19163
19511
|
init_source();
|
|
19164
19512
|
var NO_PROFILE_MESSAGE = "No brand profile yet \u2014 run onboarding or a site check";
|
|
19165
|
-
var brandCommand = new Command("brand").description("Print the shop brand context for external agents").action(async function
|
|
19513
|
+
var brandCommand = new Command("brand").description("Print the shop brand context for external agents").action(async function action66() {
|
|
19166
19514
|
const creds = requireCredentials();
|
|
19167
19515
|
const res = await getBrandContext(creds);
|
|
19168
19516
|
if (res.status === 404) {
|
|
@@ -19197,7 +19545,7 @@ var brandCommand = new Command("brand").description("Print the shop brand contex
|
|
|
19197
19545
|
|
|
19198
19546
|
// src/commands/targeting.ts
|
|
19199
19547
|
init_source();
|
|
19200
|
-
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
|
|
19548
|
+
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) {
|
|
19201
19549
|
const creds = requireCredentials();
|
|
19202
19550
|
const res = await testTargeting(creds, url, { status: opts.status });
|
|
19203
19551
|
if (!res.ok || !res.data) {
|
|
@@ -19282,7 +19630,7 @@ var create4 = new Command("create").description("Create an Apex SOS ticket").opt
|
|
|
19282
19630
|
"--type <type>",
|
|
19283
19631
|
"full_handover|design|development|qa|config|bug|help (internal types require a DRIP account)",
|
|
19284
19632
|
"qa"
|
|
19285
|
-
).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
|
|
19633
|
+
).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) {
|
|
19286
19634
|
if (!SOS_TYPES.has(opts.type)) {
|
|
19287
19635
|
fail(
|
|
19288
19636
|
this,
|
|
@@ -19313,7 +19661,7 @@ var create4 = new Command("create").description("Create an Apex SOS ticket").opt
|
|
|
19313
19661
|
}
|
|
19314
19662
|
output(this, res.data, () => printCreatedTicket(res.data));
|
|
19315
19663
|
});
|
|
19316
|
-
var mine = new Command("mine").description("List SOS tickets created by this API key").action(async function
|
|
19664
|
+
var mine = new Command("mine").description("List SOS tickets created by this API key").action(async function action69() {
|
|
19317
19665
|
const creds = requireCredentials();
|
|
19318
19666
|
const res = await listMySos(creds);
|
|
19319
19667
|
if (!res.ok || !res.data) {
|
|
@@ -19629,7 +19977,7 @@ function commandSchema(command) {
|
|
|
19629
19977
|
};
|
|
19630
19978
|
}
|
|
19631
19979
|
function createSchemaCommand(program3) {
|
|
19632
|
-
return new Command("schema").description("Print the current Apex CLI command and flag schema").option("--json", "Print JSON schema").action(function
|
|
19980
|
+
return new Command("schema").description("Print the current Apex CLI command and flag schema").option("--json", "Print JSON schema").action(function action78() {
|
|
19633
19981
|
const schema = commandSchema(program3);
|
|
19634
19982
|
output(this, schema, () => {
|
|
19635
19983
|
process.stdout.write(`${JSON.stringify(schema, null, 2)}
|
|
@@ -19782,7 +20130,7 @@ function parseContainer(source, id, dataLayerName = "dataLayer") {
|
|
|
19782
20130
|
vtpPaths,
|
|
19783
20131
|
ecommerceFrom: [...ecommerce][0] ?? null,
|
|
19784
20132
|
serverContainerUrl: [...serverUrls][0] ?? null,
|
|
19785
|
-
piiPaths: vtpPaths.filter((
|
|
20133
|
+
piiPaths: vtpPaths.filter((path26) => /email|phone|address|name/i.test(path26))
|
|
19786
20134
|
}
|
|
19787
20135
|
};
|
|
19788
20136
|
}
|
|
@@ -19851,11 +20199,11 @@ function isContainerField(field) {
|
|
|
19851
20199
|
const types = baseFieldTypes(field);
|
|
19852
20200
|
return types.has("obj") || types.has("arr");
|
|
19853
20201
|
}
|
|
19854
|
-
function hasSyntheticPlaceholder(
|
|
19855
|
-
return
|
|
20202
|
+
function hasSyntheticPlaceholder(path26) {
|
|
20203
|
+
return path26.split(".").map((segment) => segment.replace(/\[\]$/, "")).some((segment) => /^<[^>]+>$/.test(segment));
|
|
19856
20204
|
}
|
|
19857
|
-
function ambiguousDottedPrefix(event,
|
|
19858
|
-
const segments =
|
|
20205
|
+
function ambiguousDottedPrefix(event, path26) {
|
|
20206
|
+
const segments = path26.split(".");
|
|
19859
20207
|
for (let index = 1; index < segments.length; index += 1) {
|
|
19860
20208
|
const prefix = segments.slice(0, index).join(".");
|
|
19861
20209
|
const parent = event.fields.find((field) => field.path === prefix);
|
|
@@ -19864,42 +20212,42 @@ function ambiguousDottedPrefix(event, path25) {
|
|
|
19864
20212
|
return void 0;
|
|
19865
20213
|
}
|
|
19866
20214
|
function dimensionEligibility(event, field) {
|
|
19867
|
-
const
|
|
19868
|
-
if (hasSyntheticPlaceholder(
|
|
20215
|
+
const path26 = field.path;
|
|
20216
|
+
if (hasSyntheticPlaceholder(path26)) {
|
|
19869
20217
|
return {
|
|
19870
20218
|
eligible: false,
|
|
19871
|
-
message: `Path ${JSON.stringify(
|
|
20219
|
+
message: `Path ${JSON.stringify(path26)} is a synthetic placeholder and cannot create a dimension. Choose an observed scalar path.`
|
|
19872
20220
|
};
|
|
19873
20221
|
}
|
|
19874
20222
|
if (isContainerField(field)) {
|
|
19875
20223
|
return {
|
|
19876
20224
|
eligible: false,
|
|
19877
|
-
message: `Path ${JSON.stringify(
|
|
20225
|
+
message: `Path ${JSON.stringify(path26)} is container-typed. Choose an observed scalar path.`
|
|
19878
20226
|
};
|
|
19879
20227
|
}
|
|
19880
|
-
if (
|
|
20228
|
+
if (path26.includes("[]")) {
|
|
19881
20229
|
return {
|
|
19882
20230
|
eligible: false,
|
|
19883
|
-
message: `Path ${JSON.stringify(
|
|
20231
|
+
message: `Path ${JSON.stringify(path26)} includes an array-element segment and cannot create a reliable dimension. Choose a scalar path outside an array.`
|
|
19884
20232
|
};
|
|
19885
20233
|
}
|
|
19886
20234
|
if (!isStableField(field)) {
|
|
19887
20235
|
return {
|
|
19888
20236
|
eligible: false,
|
|
19889
|
-
message: `Path ${JSON.stringify(
|
|
20237
|
+
message: `Path ${JSON.stringify(path26)} does not have a stable observed type. Choose a path without a type collision.`
|
|
19890
20238
|
};
|
|
19891
20239
|
}
|
|
19892
20240
|
if (!event.event) {
|
|
19893
20241
|
return {
|
|
19894
20242
|
eligible: false,
|
|
19895
|
-
message: `Path ${JSON.stringify(
|
|
20243
|
+
message: `Path ${JSON.stringify(path26)} has no observed event name. Choose a path from a named event.`
|
|
19896
20244
|
};
|
|
19897
20245
|
}
|
|
19898
|
-
const ambiguousPrefix = ambiguousDottedPrefix(event,
|
|
20246
|
+
const ambiguousPrefix = ambiguousDottedPrefix(event, path26);
|
|
19899
20247
|
if (ambiguousPrefix) {
|
|
19900
20248
|
return {
|
|
19901
20249
|
eligible: false,
|
|
19902
|
-
message: `Path ${JSON.stringify(
|
|
20250
|
+
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.`
|
|
19903
20251
|
};
|
|
19904
20252
|
}
|
|
19905
20253
|
return { eligible: true };
|
|
@@ -19908,24 +20256,24 @@ function isNumericField(field) {
|
|
|
19908
20256
|
const numericTypes = /* @__PURE__ */ new Set(["num", "numstr", "numstr_comma"]);
|
|
19909
20257
|
return field.types.length > 0 && field.types.every((type) => numericTypes.has(type.split(":")[0]));
|
|
19910
20258
|
}
|
|
19911
|
-
function defaultDimensionLabel(
|
|
19912
|
-
const words =
|
|
19913
|
-
return words ? `${words[0].toUpperCase()}${words.slice(1)}` :
|
|
20259
|
+
function defaultDimensionLabel(path26) {
|
|
20260
|
+
const words = path26.split(/[._-]+/).filter(Boolean).join(" ");
|
|
20261
|
+
return words ? `${words[0].toUpperCase()}${words.slice(1)}` : path26;
|
|
19914
20262
|
}
|
|
19915
20263
|
function quoteShellArgument(value) {
|
|
19916
20264
|
return "'" + value.replaceAll("'", "'\\''") + "'";
|
|
19917
20265
|
}
|
|
19918
|
-
function createDimensionInvocation(
|
|
19919
|
-
return `apex discover --create-dimension ${quoteShellArgument(
|
|
20266
|
+
function createDimensionInvocation(path26, eventName) {
|
|
20267
|
+
return `apex discover --create-dimension ${quoteShellArgument(path26)} --event ${quoteShellArgument(eventName)}`;
|
|
19920
20268
|
}
|
|
19921
|
-
function resolveDimensionObservation(command, report,
|
|
20269
|
+
function resolveDimensionObservation(command, report, path26, eventName) {
|
|
19922
20270
|
const matches = report.data_layer.flatMap(
|
|
19923
|
-
(event) => event.fields.filter((field) => field.path ===
|
|
20271
|
+
(event) => event.fields.filter((field) => field.path === path26).map((field) => ({ event, field }))
|
|
19924
20272
|
);
|
|
19925
20273
|
if (!matches.length) {
|
|
19926
20274
|
fail(
|
|
19927
20275
|
command,
|
|
19928
|
-
`Path ${JSON.stringify(
|
|
20276
|
+
`Path ${JSON.stringify(path26)} was not found in the current discovery report. Run apex discover and copy an observed path exactly.`,
|
|
19929
20277
|
EXIT_CODES.validation
|
|
19930
20278
|
);
|
|
19931
20279
|
}
|
|
@@ -19935,7 +20283,7 @@ function resolveDimensionObservation(command, report, path25, eventName) {
|
|
|
19935
20283
|
if (!eventMatches.length) {
|
|
19936
20284
|
fail(
|
|
19937
20285
|
command,
|
|
19938
|
-
`Path ${JSON.stringify(
|
|
20286
|
+
`Path ${JSON.stringify(path26)} was not observed on event ${JSON.stringify(eventName)}. Run apex discover and copy the event name exactly.`,
|
|
19939
20287
|
EXIT_CODES.validation
|
|
19940
20288
|
);
|
|
19941
20289
|
}
|
|
@@ -19943,7 +20291,7 @@ function resolveDimensionObservation(command, report, path25, eventName) {
|
|
|
19943
20291
|
if (layers.length > 1) {
|
|
19944
20292
|
fail(
|
|
19945
20293
|
command,
|
|
19946
|
-
`Path ${JSON.stringify(
|
|
20294
|
+
`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.`,
|
|
19947
20295
|
EXIT_CODES.validation
|
|
19948
20296
|
);
|
|
19949
20297
|
}
|
|
@@ -19955,7 +20303,7 @@ function resolveDimensionObservation(command, report, path25, eventName) {
|
|
|
19955
20303
|
if (namedEvents.length > 1) {
|
|
19956
20304
|
fail(
|
|
19957
20305
|
command,
|
|
19958
|
-
`Path ${JSON.stringify(
|
|
20306
|
+
`Path ${JSON.stringify(path26)} was observed on multiple events: ${namedEvents.join(", ")}. Retry with --event <name>.`,
|
|
19959
20307
|
EXIT_CODES.validation
|
|
19960
20308
|
);
|
|
19961
20309
|
}
|
|
@@ -19964,7 +20312,7 @@ function resolveDimensionObservation(command, report, path25, eventName) {
|
|
|
19964
20312
|
if (layers.length > 1) {
|
|
19965
20313
|
fail(
|
|
19966
20314
|
command,
|
|
19967
|
-
`Path ${JSON.stringify(
|
|
20315
|
+
`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.`,
|
|
19968
20316
|
EXIT_CODES.validation
|
|
19969
20317
|
);
|
|
19970
20318
|
}
|
|
@@ -19999,9 +20347,9 @@ function renderDiscovery(report) {
|
|
|
19999
20347
|
`
|
|
20000
20348
|
${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}`
|
|
20001
20349
|
);
|
|
20002
|
-
for (const
|
|
20350
|
+
for (const path26 of event.paths)
|
|
20003
20351
|
lines.push(
|
|
20004
|
-
` URL ${
|
|
20352
|
+
` URL ${path26.path}: ${path26.pageviews} pageviews, ${path26.count} pushes, ${path26.pspReturn_count} PSP returns`
|
|
20005
20353
|
);
|
|
20006
20354
|
for (const field of event.fields) {
|
|
20007
20355
|
lines.push(
|
|
@@ -20044,7 +20392,7 @@ ${event.event || "(unnamed event)"} [${event.dlName}] \u2014 ${event.count} obse
|
|
|
20044
20392
|
return `${lines.join("\n")}
|
|
20045
20393
|
`;
|
|
20046
20394
|
}
|
|
20047
|
-
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
|
|
20395
|
+
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) {
|
|
20048
20396
|
if (opts.gtm && opts.gtmUrl)
|
|
20049
20397
|
fail(this, "Use only one of --gtm or --gtm-url", EXIT_CODES.validation);
|
|
20050
20398
|
if (opts.createDimension && (opts.gtm || opts.gtmUrl)) {
|
|
@@ -20064,7 +20412,7 @@ var discoverCommand = new Command("discover").description("Read back the trackin
|
|
|
20064
20412
|
let report;
|
|
20065
20413
|
let apiSucceeded = false;
|
|
20066
20414
|
let discoveryCredentials;
|
|
20067
|
-
let
|
|
20415
|
+
let apiFailure2;
|
|
20068
20416
|
try {
|
|
20069
20417
|
discoveryCredentials = requireCredentials();
|
|
20070
20418
|
const result = await discoverTrackingSchema(discoveryCredentials, minutes);
|
|
@@ -20072,24 +20420,24 @@ var discoverCommand = new Command("discover").description("Read back the trackin
|
|
|
20072
20420
|
report = result.data;
|
|
20073
20421
|
apiSucceeded = true;
|
|
20074
20422
|
} else
|
|
20075
|
-
|
|
20423
|
+
apiFailure2 = {
|
|
20076
20424
|
message: result.error ?? "Discovery API unavailable",
|
|
20077
20425
|
status: result.status,
|
|
20078
20426
|
details: result.errorDetails
|
|
20079
20427
|
};
|
|
20080
20428
|
} catch (error) {
|
|
20081
|
-
|
|
20429
|
+
apiFailure2 = {
|
|
20082
20430
|
message: error instanceof Error ? error.message : String(error),
|
|
20083
20431
|
...error instanceof CliError ? { details: error.details, cliError: error } : {}
|
|
20084
20432
|
};
|
|
20085
20433
|
}
|
|
20086
20434
|
if (!report && (!gtmInput || opts.createDimension)) {
|
|
20087
|
-
if (
|
|
20435
|
+
if (apiFailure2?.cliError) throw apiFailure2.cliError;
|
|
20088
20436
|
fail(
|
|
20089
20437
|
this,
|
|
20090
|
-
|
|
20091
|
-
classifyStatus(
|
|
20092
|
-
|
|
20438
|
+
apiFailure2?.message ?? "Failed to discover tracking schema",
|
|
20439
|
+
classifyStatus(apiFailure2?.status ?? 0),
|
|
20440
|
+
apiFailure2?.details
|
|
20093
20441
|
);
|
|
20094
20442
|
}
|
|
20095
20443
|
report ??= {
|
|
@@ -20104,7 +20452,7 @@ var discoverCommand = new Command("discover").description("Read back the trackin
|
|
|
20104
20452
|
trigger_diagnoses: [],
|
|
20105
20453
|
storage: [],
|
|
20106
20454
|
notes: [
|
|
20107
|
-
`GTM static analysis completed without discovery API data: ${
|
|
20455
|
+
`GTM static analysis completed without discovery API data: ${apiFailure2?.message ?? "platform credentials were not provided"}`
|
|
20108
20456
|
]
|
|
20109
20457
|
};
|
|
20110
20458
|
if (gtmInput) {
|
|
@@ -20142,9 +20490,9 @@ var discoverCommand = new Command("discover").description("Read back the trackin
|
|
|
20142
20490
|
if (!apiSucceeded || !discoveryCredentials) {
|
|
20143
20491
|
fail(
|
|
20144
20492
|
this,
|
|
20145
|
-
|
|
20146
|
-
classifyStatus(
|
|
20147
|
-
|
|
20493
|
+
apiFailure2?.message ?? "Discovery API data is required to create a dimension",
|
|
20494
|
+
classifyStatus(apiFailure2?.status ?? 0),
|
|
20495
|
+
apiFailure2?.details
|
|
20148
20496
|
);
|
|
20149
20497
|
}
|
|
20150
20498
|
const observation = resolveDimensionObservation(
|
|
@@ -20247,7 +20595,7 @@ function installSkill(paths, targetRoot) {
|
|
|
20247
20595
|
import_node_fs26.default.copyFileSync(paths.agents, import_node_path23.default.join(target, "AGENTS.md"));
|
|
20248
20596
|
return target;
|
|
20249
20597
|
}
|
|
20250
|
-
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
|
|
20598
|
+
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) {
|
|
20251
20599
|
let paths;
|
|
20252
20600
|
try {
|
|
20253
20601
|
paths = skillPaths();
|
|
@@ -20385,7 +20733,106 @@ var restore2 = new Command("restore").description("Restore an archived shop").ar
|
|
|
20385
20733
|
);
|
|
20386
20734
|
});
|
|
20387
20735
|
var shops = new Command("shops").description("Archive and restore shops on the platform admin plane").addCommand(archive2).addCommand(restore2);
|
|
20388
|
-
var
|
|
20736
|
+
var create6 = new Command("create").description("Create an internal, demo or dogfood account").requiredOption("--name <text>").requiredOption("--category <category>", "internal, demo or dogfood").option("--type <type>", "company or agency", "company").option("--plan <plan>", "self_managed, apex_managed or drip_managed", "self_managed").option("--domain <host>").option("--platform <platform>", "shopify, shopware, woo or other").option("--claim-shop <uuid>").option("--owner <email>").option("--consultant <uuid>", "Platform access ID").option("--consultant-title <text>").requiredOption("--reason <text>", "Audit reason, 8 to 500 characters").option("--confirm", "Confirm account creation").option("--json", "Output JSON").action(async function(opts) {
|
|
20737
|
+
if (opts.category === "customer") fail(this, "Create customer accounts in Admin, Links", 3);
|
|
20738
|
+
if (!["internal", "demo", "dogfood"].includes(opts.category))
|
|
20739
|
+
fail(this, "--category must be internal, demo or dogfood", 3);
|
|
20740
|
+
if (!["company", "agency"].includes(opts.type))
|
|
20741
|
+
fail(this, "--type must be company or agency", 3);
|
|
20742
|
+
if (!["self_managed", "apex_managed", "drip_managed"].includes(opts.plan))
|
|
20743
|
+
fail(this, "Invalid --plan", 3);
|
|
20744
|
+
if (!opts.name.trim() || opts.name.trim().length > 60)
|
|
20745
|
+
fail(this, "--name must be 1 to 60 characters", 3);
|
|
20746
|
+
if (opts.reason.trim().length < 8 || opts.reason.trim().length > 500)
|
|
20747
|
+
fail(this, "--reason must be 8 to 500 characters", 3);
|
|
20748
|
+
const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
20749
|
+
if (opts.claimShop && !uuid.test(opts.claimShop)) fail(this, "--claim-shop must be a UUID", 3);
|
|
20750
|
+
if (opts.claimShop && (opts.domain || opts.platform))
|
|
20751
|
+
fail(this, "Use --claim-shop or --domain with --platform", 3);
|
|
20752
|
+
if (!!opts.domain !== !!opts.platform)
|
|
20753
|
+
fail(this, "--domain and --platform must be provided together", 3);
|
|
20754
|
+
if (opts.domain && !/^[a-z0-9-]+(\.[a-z0-9-]+)+$/i.test(opts.domain.trim()))
|
|
20755
|
+
fail(this, "--domain must be a hostname without a protocol or path", 3);
|
|
20756
|
+
const platforms = {
|
|
20757
|
+
shopify: "shopify",
|
|
20758
|
+
shopware: "shopware",
|
|
20759
|
+
woo: "woocommerce",
|
|
20760
|
+
other: "custom"
|
|
20761
|
+
};
|
|
20762
|
+
if (opts.platform && !Object.hasOwn(platforms, opts.platform))
|
|
20763
|
+
fail(this, "--platform must be shopify, shopware, woo or other", 3);
|
|
20764
|
+
if (opts.owner && !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(opts.owner.trim()))
|
|
20765
|
+
fail(this, "--owner must be an email address", 3);
|
|
20766
|
+
if (opts.owner && !opts.domain && !opts.claimShop)
|
|
20767
|
+
fail(this, "--owner requires a store for the invitation", 3);
|
|
20768
|
+
if (opts.plan !== "self_managed" && (!opts.consultant || !opts.consultantTitle?.trim()))
|
|
20769
|
+
fail(this, "Managed plans require --consultant and --consultant-title", 3);
|
|
20770
|
+
if (opts.consultant && !uuid.test(opts.consultant))
|
|
20771
|
+
fail(this, "--consultant must be a platform access UUID", 3);
|
|
20772
|
+
if (!!opts.consultant !== !!opts.consultantTitle?.trim())
|
|
20773
|
+
fail(this, "Provide --consultant and --consultant-title together", 3);
|
|
20774
|
+
if (opts.consultantTitle && opts.consultantTitle.trim().length > 240)
|
|
20775
|
+
fail(this, "--consultant-title must be at most 240 characters", 3);
|
|
20776
|
+
if (!opts.confirm) {
|
|
20777
|
+
const flags = [
|
|
20778
|
+
["name", opts.name],
|
|
20779
|
+
["category", opts.category],
|
|
20780
|
+
["type", opts.type],
|
|
20781
|
+
["plan", opts.plan],
|
|
20782
|
+
["reason", opts.reason],
|
|
20783
|
+
["domain", opts.domain],
|
|
20784
|
+
["platform", opts.platform],
|
|
20785
|
+
["claim-shop", opts.claimShop],
|
|
20786
|
+
["owner", opts.owner],
|
|
20787
|
+
["consultant", opts.consultant],
|
|
20788
|
+
["consultant-title", opts.consultantTitle]
|
|
20789
|
+
];
|
|
20790
|
+
const retry = flags.filter((entry) => entry[1] !== void 0).map(([flag, value]) => `--${flag} '${value.replaceAll("'", "'\\''")}'`).join(" ");
|
|
20791
|
+
confirmationRequired(
|
|
20792
|
+
this,
|
|
20793
|
+
"--confirm is required to create an account",
|
|
20794
|
+
`apex admin customers create ${retry} --confirm`
|
|
20795
|
+
);
|
|
20796
|
+
}
|
|
20797
|
+
const body5 = {
|
|
20798
|
+
name: opts.name.trim(),
|
|
20799
|
+
category: opts.category,
|
|
20800
|
+
type: opts.type,
|
|
20801
|
+
plan: opts.plan,
|
|
20802
|
+
reason: opts.reason.trim(),
|
|
20803
|
+
teammates: [],
|
|
20804
|
+
consultant: opts.consultant ? { platformAccessId: opts.consultant, title: opts.consultantTitle.trim() } : null,
|
|
20805
|
+
...opts.claimShop ? { store: { claimShopId: opts.claimShop } } : opts.domain ? {
|
|
20806
|
+
store: {
|
|
20807
|
+
domain: opts.domain.trim().toLowerCase(),
|
|
20808
|
+
platform: platforms[opts.platform]
|
|
20809
|
+
}
|
|
20810
|
+
} : {},
|
|
20811
|
+
...opts.owner ? { ownerEmail: opts.owner.trim() } : {}
|
|
20812
|
+
};
|
|
20813
|
+
const res = await createAdminCustomer(requireUserCredentials(), body5);
|
|
20814
|
+
if (!res.ok || !res.data)
|
|
20815
|
+
fail(
|
|
20816
|
+
this,
|
|
20817
|
+
res.error ?? "Failed to create account",
|
|
20818
|
+
classifyStatus(res.status),
|
|
20819
|
+
res.errorDetails
|
|
20820
|
+
);
|
|
20821
|
+
output(this, res.data, () => {
|
|
20822
|
+
const result = res.data.data;
|
|
20823
|
+
printTable(
|
|
20824
|
+
["ORGANIZATION", "STORE", "INVITATIONS"],
|
|
20825
|
+
[[result.orgId, result.shopId ?? "-", String(result.invitations.length)]]
|
|
20826
|
+
);
|
|
20827
|
+
if (result.invitations.length)
|
|
20828
|
+
printTable(
|
|
20829
|
+
["EMAIL", "ROLE", "STATUS", "NOTE"],
|
|
20830
|
+
result.invitations.map((i) => [i.email, i.role, i.status, i.note])
|
|
20831
|
+
);
|
|
20832
|
+
});
|
|
20833
|
+
});
|
|
20834
|
+
var customers = new Command("customers").description("Create engineering accounts; customer accounts use Admin, Links").addCommand(create6);
|
|
20835
|
+
var adminCommand = new Command("admin").description("Operate allowlisted platform controls").addCommand(settingsGet).addCommand(quarantine).addCommand(shops).addCommand(customers);
|
|
20389
20836
|
|
|
20390
20837
|
// src/commands/membership.ts
|
|
20391
20838
|
init_source();
|
|
@@ -20581,7 +21028,7 @@ var list13 = new Command("list").action(async function() {
|
|
|
20581
21028
|
)
|
|
20582
21029
|
);
|
|
20583
21030
|
});
|
|
20584
|
-
var
|
|
21031
|
+
var create7 = new Command("create").requiredOption("--name <name>").requiredOption("--type <type>").option("--permissions <csv>").action(async function(opts) {
|
|
20585
21032
|
if (!["live_secret", "test_secret", "publishable"].includes(opts.type))
|
|
20586
21033
|
throw new CliError(
|
|
20587
21034
|
"--type must be live_secret, test_secret, or publishable",
|
|
@@ -20621,7 +21068,7 @@ var revoke = new Command("revoke").argument("<keyId>").action(async function(key
|
|
|
20621
21068
|
);
|
|
20622
21069
|
output(this, res.data, () => console.log(`Revoked ${keyId}`));
|
|
20623
21070
|
});
|
|
20624
|
-
var keysCommand = new Command("keys").description("Manage shop API keys in the current context").addCommand(list13).addCommand(
|
|
21071
|
+
var keysCommand = new Command("keys").description("Manage shop API keys in the current context").addCommand(list13).addCommand(create7).addCommand(revoke);
|
|
20625
21072
|
|
|
20626
21073
|
// src/commands/prospect.ts
|
|
20627
21074
|
init_source();
|
|
@@ -20674,8 +21121,8 @@ async function fetchText(url) {
|
|
|
20674
21121
|
});
|
|
20675
21122
|
return { status: res.status, text: await readResponseText(res), finalUrl: res.url };
|
|
20676
21123
|
}
|
|
20677
|
-
function abs(base,
|
|
20678
|
-
return new URL(
|
|
21124
|
+
function abs(base, path26) {
|
|
21125
|
+
return new URL(path26, base).toString();
|
|
20679
21126
|
}
|
|
20680
21127
|
function looksLikeShopifyCart(text2) {
|
|
20681
21128
|
try {
|
|
@@ -20695,9 +21142,9 @@ async function collectSignals(url) {
|
|
|
20695
21142
|
const chunks = [`__apex_primary_status_${primary.status}__`, primary.text];
|
|
20696
21143
|
const protectedHit = primary.status === 403 || primary.status === 429 || matchDetectionMarkers(primary.text, "protected").length > 0;
|
|
20697
21144
|
if (!protectedHit) {
|
|
20698
|
-
for (const
|
|
21145
|
+
for (const path26 of ["/cart.js"]) {
|
|
20699
21146
|
try {
|
|
20700
|
-
const secondary = await fetchText(abs(primary.finalUrl || url,
|
|
21147
|
+
const secondary = await fetchText(abs(primary.finalUrl || url, path26));
|
|
20701
21148
|
if (secondary.status >= 200 && secondary.status < 300 && looksLikeShopifyCart(secondary.text)) {
|
|
20702
21149
|
chunks.push("__apex_secondary_shopify_cart_js__", secondary.text.slice(0, 2e3));
|
|
20703
21150
|
}
|
|
@@ -20876,16 +21323,16 @@ function selectedEvents(opts) {
|
|
|
20876
21323
|
}
|
|
20877
21324
|
var list14 = new Command("list").alias("ls").description(
|
|
20878
21325
|
"List webhooks. JSON includes availableEvents; secrets are never returned after create."
|
|
20879
|
-
).action(async function
|
|
21326
|
+
).action(async function action72() {
|
|
20880
21327
|
const data = await loadWebhookIndex(this);
|
|
20881
21328
|
output(this, data, () => printWebhooks(data.data));
|
|
20882
21329
|
});
|
|
20883
|
-
var
|
|
21330
|
+
var create8 = new Command("create").description(`Create a webhook. Prints the signing secret once. Signature docs: ${SIGNATURE_DOC}`).requiredOption("--url <url>", "HTTPS callback URL").option(
|
|
20884
21331
|
"--event <event>",
|
|
20885
21332
|
"Webhook event to subscribe to; repeat for multiple events",
|
|
20886
21333
|
(value, previous = []) => [...previous, value],
|
|
20887
21334
|
[]
|
|
20888
|
-
).action(async function
|
|
21335
|
+
).action(async function action73(opts) {
|
|
20889
21336
|
const events = selectedEvents(opts);
|
|
20890
21337
|
if (events.length === 0) fail(this, "At least one --event is required", EXIT_CODES.validation);
|
|
20891
21338
|
const index = await loadWebhookIndex(this);
|
|
@@ -20914,7 +21361,7 @@ Signature verification: ${SIGNATURE_DOC}
|
|
|
20914
21361
|
console.log(`Signature verification: ${SIGNATURE_DOC}`);
|
|
20915
21362
|
});
|
|
20916
21363
|
});
|
|
20917
|
-
var
|
|
21364
|
+
var remove3 = new Command("delete").alias("rm").description("Delete a webhook").argument("<id>", "Webhook id").action(async function action74(id) {
|
|
20918
21365
|
requireShopSecretKey(this);
|
|
20919
21366
|
const result = handleResult(
|
|
20920
21367
|
this,
|
|
@@ -20923,7 +21370,7 @@ var remove4 = new Command("delete").alias("rm").description("Delete a webhook").
|
|
|
20923
21370
|
);
|
|
20924
21371
|
output(this, result, () => console.log(source_default.green(`Deleted webhook ${id}`)));
|
|
20925
21372
|
});
|
|
20926
|
-
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
|
|
21373
|
+
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) {
|
|
20927
21374
|
const limit = Number.parseInt(opts.limit, 10);
|
|
20928
21375
|
if (!Number.isInteger(limit) || limit < 1)
|
|
20929
21376
|
fail(this, "--limit must be a positive integer", EXIT_CODES.validation);
|
|
@@ -20940,7 +21387,7 @@ var update5 = new Command("update").description("Update a webhook URL, events, o
|
|
|
20940
21387
|
"Replacement event; repeat for multiple events",
|
|
20941
21388
|
(value, previous = []) => [...previous, value],
|
|
20942
21389
|
[]
|
|
20943
|
-
).option("--active", "Enable the webhook").option("--inactive", "Disable the webhook").action(async function
|
|
21390
|
+
).option("--active", "Enable the webhook").option("--inactive", "Disable the webhook").action(async function action76(id, opts) {
|
|
20944
21391
|
if (opts.active && opts.inactive)
|
|
20945
21392
|
fail(this, "Use only one of --active or --inactive", EXIT_CODES.validation);
|
|
20946
21393
|
const events = selectedEvents(opts);
|
|
@@ -20973,7 +21420,7 @@ var update5 = new Command("update").description("Update a webhook URL, events, o
|
|
|
20973
21420
|
);
|
|
20974
21421
|
output(this, result, () => printWebhooks([result]));
|
|
20975
21422
|
});
|
|
20976
|
-
var test3 = new Command("test").description("Send a signed test event to a webhook").argument("<id>", "Webhook id").action(async function
|
|
21423
|
+
var test3 = new Command("test").description("Send a signed test event to a webhook").argument("<id>", "Webhook id").action(async function action77(id) {
|
|
20977
21424
|
requireShopSecretKey(this);
|
|
20978
21425
|
const result = handleResult(
|
|
20979
21426
|
this,
|
|
@@ -20988,7 +21435,7 @@ var test3 = new Command("test").description("Send a signed test event to a webho
|
|
|
20988
21435
|
)
|
|
20989
21436
|
);
|
|
20990
21437
|
});
|
|
20991
|
-
var webhooksCommand = new Command("webhooks").description("Manage shop webhooks").addCommand(list14).addCommand(
|
|
21438
|
+
var webhooksCommand = new Command("webhooks").description("Manage shop webhooks").addCommand(list14).addCommand(create8).addCommand(update5).addCommand(remove3).addCommand(deliveries).addCommand(test3);
|
|
20992
21439
|
|
|
20993
21440
|
// src/commands/simulate.ts
|
|
20994
21441
|
init_source();
|
|
@@ -21117,8 +21564,625 @@ var simulateCommand = new Command("simulate").description("Simulate data-layer t
|
|
|
21117
21564
|
});
|
|
21118
21565
|
|
|
21119
21566
|
// src/commands/config-resources.ts
|
|
21567
|
+
var import_node_fs29 = __toESM(require("node:fs"));
|
|
21568
|
+
var import_node_path26 = __toESM(require("node:path"));
|
|
21569
|
+
|
|
21570
|
+
// src/commands/landing-pages.ts
|
|
21571
|
+
var import_node_crypto7 = __toESM(require("node:crypto"));
|
|
21120
21572
|
var import_node_fs28 = __toESM(require("node:fs"));
|
|
21121
21573
|
var import_node_path25 = __toESM(require("node:path"));
|
|
21574
|
+
function apiFailure(command, response, fallback2) {
|
|
21575
|
+
if (!response.ok || response.data === void 0) {
|
|
21576
|
+
fail(
|
|
21577
|
+
command,
|
|
21578
|
+
response.error ?? fallback2,
|
|
21579
|
+
classifyStatus(response.status),
|
|
21580
|
+
response.errorDetails
|
|
21581
|
+
);
|
|
21582
|
+
}
|
|
21583
|
+
}
|
|
21584
|
+
function sha256(body5) {
|
|
21585
|
+
return import_node_crypto7.default.createHash("sha256").update(body5).digest("hex");
|
|
21586
|
+
}
|
|
21587
|
+
function markerPath(folder) {
|
|
21588
|
+
return import_node_path25.default.join(folder, LANDING_PAGE_CHECKOUT_MARKER);
|
|
21589
|
+
}
|
|
21590
|
+
function readMarker(folder) {
|
|
21591
|
+
const file = markerPath(folder);
|
|
21592
|
+
if (!import_node_fs28.default.existsSync(file)) return null;
|
|
21593
|
+
try {
|
|
21594
|
+
return JSON.parse(import_node_fs28.default.readFileSync(file, "utf8"));
|
|
21595
|
+
} catch (error) {
|
|
21596
|
+
throw new CliError(
|
|
21597
|
+
`${LANDING_PAGE_CHECKOUT_MARKER} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
21598
|
+
EXIT_CODES.validation,
|
|
21599
|
+
{
|
|
21600
|
+
code: "validation_failed",
|
|
21601
|
+
next_steps: [
|
|
21602
|
+
{
|
|
21603
|
+
command: "apex landing-pages pull <id> --out <new folder>",
|
|
21604
|
+
why: "Pull a fresh checkout and copy your changes over."
|
|
21605
|
+
}
|
|
21606
|
+
]
|
|
21607
|
+
}
|
|
21608
|
+
);
|
|
21609
|
+
}
|
|
21610
|
+
}
|
|
21611
|
+
function writeMarker(folder, marker) {
|
|
21612
|
+
import_node_fs28.default.mkdirSync(import_node_path25.default.dirname(markerPath(folder)), { recursive: true });
|
|
21613
|
+
import_node_fs28.default.writeFileSync(import_node_path25.default.join(folder, ".apex", ".gitignore"), "landing-page.json\n", "utf8");
|
|
21614
|
+
import_node_fs28.default.writeFileSync(markerPath(folder), `${JSON.stringify(marker, null, 2)}
|
|
21615
|
+
`, "utf8");
|
|
21616
|
+
}
|
|
21617
|
+
function requireCheckout(command, folderArg) {
|
|
21618
|
+
const folder = import_node_path25.default.resolve(process.cwd(), folderArg);
|
|
21619
|
+
const marker = import_node_fs28.default.existsSync(folder) && import_node_fs28.default.statSync(folder).isDirectory() ? readMarker(folder) : null;
|
|
21620
|
+
if (!marker) {
|
|
21621
|
+
fail(command, `${folderArg} is not a landing-page checkout.`, EXIT_CODES.validation, {
|
|
21622
|
+
code: "validation_failed",
|
|
21623
|
+
next_steps: [
|
|
21624
|
+
{ command: "apex landing-pages pull <id> --out <folder>", why: "Create a checkout first." }
|
|
21625
|
+
]
|
|
21626
|
+
});
|
|
21627
|
+
}
|
|
21628
|
+
return { folder, marker };
|
|
21629
|
+
}
|
|
21630
|
+
function resolvePageId(target) {
|
|
21631
|
+
const folder = import_node_path25.default.resolve(process.cwd(), target);
|
|
21632
|
+
if (import_node_fs28.default.existsSync(folder) && import_node_fs28.default.statSync(folder).isDirectory()) {
|
|
21633
|
+
const marker = readMarker(folder);
|
|
21634
|
+
if (marker) return marker.landingPageId;
|
|
21635
|
+
throw new CliError(
|
|
21636
|
+
`${target} is a folder without a landing-page checkout.`,
|
|
21637
|
+
EXIT_CODES.validation,
|
|
21638
|
+
{
|
|
21639
|
+
code: "validation_failed",
|
|
21640
|
+
next_steps: [
|
|
21641
|
+
{
|
|
21642
|
+
command: "apex landing-pages pull <id> --out <folder>",
|
|
21643
|
+
why: "Create a checkout first."
|
|
21644
|
+
}
|
|
21645
|
+
]
|
|
21646
|
+
}
|
|
21647
|
+
);
|
|
21648
|
+
}
|
|
21649
|
+
return target;
|
|
21650
|
+
}
|
|
21651
|
+
function readCheckoutFiles(folder) {
|
|
21652
|
+
const files = [];
|
|
21653
|
+
const visit2 = (directory) => {
|
|
21654
|
+
for (const entry of import_node_fs28.default.readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
21655
|
+
const absolute = import_node_path25.default.join(directory, entry.name);
|
|
21656
|
+
const relative = import_node_path25.default.relative(folder, absolute).split(import_node_path25.default.sep).join("/");
|
|
21657
|
+
if (!isLandingPageCheckoutPath(relative)) continue;
|
|
21658
|
+
if (entry.isDirectory()) visit2(absolute);
|
|
21659
|
+
else if (entry.isFile()) {
|
|
21660
|
+
try {
|
|
21661
|
+
validateLandingPageLocalFiles([
|
|
21662
|
+
...files,
|
|
21663
|
+
{ path: relative, bytes: import_node_fs28.default.statSync(absolute).size, sha256: "" }
|
|
21664
|
+
]);
|
|
21665
|
+
} catch (error) {
|
|
21666
|
+
throw new CliError(
|
|
21667
|
+
error instanceof Error ? error.message : "Invalid bundle",
|
|
21668
|
+
EXIT_CODES.validation
|
|
21669
|
+
);
|
|
21670
|
+
}
|
|
21671
|
+
const body5 = import_node_fs28.default.readFileSync(absolute);
|
|
21672
|
+
files.push({ path: relative, sha256: sha256(body5), bytes: body5.byteLength, body: body5 });
|
|
21673
|
+
}
|
|
21674
|
+
}
|
|
21675
|
+
};
|
|
21676
|
+
visit2(folder);
|
|
21677
|
+
try {
|
|
21678
|
+
validateLandingPageLocalFiles(files);
|
|
21679
|
+
} catch (error) {
|
|
21680
|
+
throw new CliError(
|
|
21681
|
+
error instanceof Error ? error.message : "Invalid bundle",
|
|
21682
|
+
EXIT_CODES.validation
|
|
21683
|
+
);
|
|
21684
|
+
}
|
|
21685
|
+
return files;
|
|
21686
|
+
}
|
|
21687
|
+
function readManifest2(command, folder) {
|
|
21688
|
+
const file = import_node_path25.default.join(folder, LANDING_PAGE_MANIFEST_FILE);
|
|
21689
|
+
if (!import_node_fs28.default.existsSync(file)) {
|
|
21690
|
+
fail(
|
|
21691
|
+
command,
|
|
21692
|
+
`${LANDING_PAGE_MANIFEST_FILE} is missing from the checkout.`,
|
|
21693
|
+
EXIT_CODES.validation,
|
|
21694
|
+
{
|
|
21695
|
+
code: "validation_failed"
|
|
21696
|
+
}
|
|
21697
|
+
);
|
|
21698
|
+
}
|
|
21699
|
+
try {
|
|
21700
|
+
return JSON.parse(import_node_fs28.default.readFileSync(file, "utf8"));
|
|
21701
|
+
} catch (error) {
|
|
21702
|
+
fail(
|
|
21703
|
+
command,
|
|
21704
|
+
`${LANDING_PAGE_MANIFEST_FILE} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
21705
|
+
EXIT_CODES.validation,
|
|
21706
|
+
{
|
|
21707
|
+
code: "validation_failed"
|
|
21708
|
+
}
|
|
21709
|
+
);
|
|
21710
|
+
}
|
|
21711
|
+
}
|
|
21712
|
+
function declared(files) {
|
|
21713
|
+
return files.map(({ path: filePath, sha256: digest, bytes }) => ({
|
|
21714
|
+
path: filePath,
|
|
21715
|
+
sha256: digest,
|
|
21716
|
+
bytes
|
|
21717
|
+
}));
|
|
21718
|
+
}
|
|
21719
|
+
function printPlan(plan2) {
|
|
21720
|
+
const { files, manifest } = plan2;
|
|
21721
|
+
if (!plan2.would_change) {
|
|
21722
|
+
console.log("No changes.");
|
|
21723
|
+
return;
|
|
21724
|
+
}
|
|
21725
|
+
for (const file of files.added) console.log(` + ${file}`);
|
|
21726
|
+
for (const file of files.changed) console.log(` ~ ${file}`);
|
|
21727
|
+
for (const file of files.removed) console.log(` - ${file}`);
|
|
21728
|
+
console.log(` ${files.unchanged} unchanged \xB7 ${plan2.file_count} files \xB7 ${plan2.bytes} bytes`);
|
|
21729
|
+
if (manifest.fields.length) console.log(`Manifest: ${manifest.fields.join(", ")} changed`);
|
|
21730
|
+
const goals = manifest.goals;
|
|
21731
|
+
if (goals.added.length + goals.changed.length + goals.removed.length) {
|
|
21732
|
+
console.log(
|
|
21733
|
+
`Goals: ${[
|
|
21734
|
+
...goals.added.map((key) => `+${key}`),
|
|
21735
|
+
...goals.changed.map((key) => `~${key}`),
|
|
21736
|
+
...goals.removed.map((key) => `-${key} (kept in Apex)`)
|
|
21737
|
+
].join(" ")}`
|
|
21738
|
+
);
|
|
21739
|
+
}
|
|
21740
|
+
console.log(
|
|
21741
|
+
`Deploy target: ${plan2.deploy_target ?? "none: set host in apex-landing-page.json to a domain this shop holds"}`
|
|
21742
|
+
);
|
|
21743
|
+
}
|
|
21744
|
+
async function confirm2(message) {
|
|
21745
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) return null;
|
|
21746
|
+
const readline3 = await import("node:readline");
|
|
21747
|
+
const rl = readline3.createInterface({ input: process.stdin, output: process.stderr });
|
|
21748
|
+
const answer = await new Promise((resolve2) => rl.question(`${message} [y/N] `, resolve2));
|
|
21749
|
+
rl.close();
|
|
21750
|
+
return answer.trim().toLowerCase() === "y";
|
|
21751
|
+
}
|
|
21752
|
+
function manifestDigest(folder) {
|
|
21753
|
+
const file = import_node_path25.default.join(folder, LANDING_PAGE_MANIFEST_FILE);
|
|
21754
|
+
return import_node_fs28.default.existsSync(file) ? sha256(import_node_fs28.default.readFileSync(file)) : null;
|
|
21755
|
+
}
|
|
21756
|
+
function localChanges(folder, marker) {
|
|
21757
|
+
const local = new Map(readCheckoutFiles(folder).map((file) => [file.path, file.sha256]));
|
|
21758
|
+
const changed = /* @__PURE__ */ new Set();
|
|
21759
|
+
for (const [file, digest] of local) if (marker.files[file] !== digest) changed.add(file);
|
|
21760
|
+
for (const file of Object.keys(marker.files)) {
|
|
21761
|
+
if (isLandingPageCheckoutPath(file) && !local.has(file)) changed.add(file);
|
|
21762
|
+
}
|
|
21763
|
+
if (marker.manifestSha256 && manifestDigest(folder) !== marker.manifestSha256) {
|
|
21764
|
+
changed.add(LANDING_PAGE_MANIFEST_FILE);
|
|
21765
|
+
}
|
|
21766
|
+
return [...changed].sort();
|
|
21767
|
+
}
|
|
21768
|
+
function removeStaleFiles(folder, previous, next, added = []) {
|
|
21769
|
+
if (!previous) return;
|
|
21770
|
+
for (const file of [...Object.keys(previous.files), ...added]) {
|
|
21771
|
+
if (next.has(file) || !isLandingPageCheckoutPath(file)) continue;
|
|
21772
|
+
const absolute = import_node_path25.default.resolve(folder, file);
|
|
21773
|
+
if (absolute.startsWith(folder + import_node_path25.default.sep) && import_node_fs28.default.existsSync(absolute)) import_node_fs28.default.rmSync(absolute);
|
|
21774
|
+
}
|
|
21775
|
+
}
|
|
21776
|
+
async function pullCheckout(command, creds, id, folder, force = false) {
|
|
21777
|
+
const existing = readMarker(folder);
|
|
21778
|
+
if (existing && existing.landingPageId !== id) {
|
|
21779
|
+
fail(
|
|
21780
|
+
command,
|
|
21781
|
+
`${folder} is a checkout of another landing page (${existing.landingPageId}).`,
|
|
21782
|
+
EXIT_CODES.validation
|
|
21783
|
+
);
|
|
21784
|
+
}
|
|
21785
|
+
const changed = existing && !force ? localChanges(folder, existing) : [];
|
|
21786
|
+
if (changed.length) {
|
|
21787
|
+
fail(
|
|
21788
|
+
command,
|
|
21789
|
+
`${folder} has changes that are not applied: ${changed.slice(0, 5).join(", ")}${changed.length > 5 ? ` and ${changed.length - 5} more` : ""}.`,
|
|
21790
|
+
EXIT_CODES.conflict,
|
|
21791
|
+
{
|
|
21792
|
+
code: "landing_page_local_changes",
|
|
21793
|
+
next_steps: [
|
|
21794
|
+
{
|
|
21795
|
+
command: `apex landing-pages pull ${id} --out <new folder>`,
|
|
21796
|
+
why: "Pull the current version next to your checkout, then copy your changes over."
|
|
21797
|
+
},
|
|
21798
|
+
{
|
|
21799
|
+
command: `apex landing-pages apply ${folder} --yes --force`,
|
|
21800
|
+
why: "Replace the current version with your checkout."
|
|
21801
|
+
},
|
|
21802
|
+
{
|
|
21803
|
+
command: `apex landing-pages pull ${id} --out ${folder} --force`,
|
|
21804
|
+
why: "Discard your changes."
|
|
21805
|
+
}
|
|
21806
|
+
]
|
|
21807
|
+
}
|
|
21808
|
+
);
|
|
21809
|
+
}
|
|
21810
|
+
const api = landingPages(creds);
|
|
21811
|
+
const listing = await api.checkout(id);
|
|
21812
|
+
apiFailure(command, listing, "Failed to pull landing page");
|
|
21813
|
+
import_node_fs28.default.mkdirSync(folder, { recursive: true });
|
|
21814
|
+
const written = {};
|
|
21815
|
+
const downloaded = [];
|
|
21816
|
+
for (const batch of landingPageBatches(listing.data.files, (file) => file.bytes)) {
|
|
21817
|
+
const contents = await api.checkout(
|
|
21818
|
+
id,
|
|
21819
|
+
batch.map((file) => file.path)
|
|
21820
|
+
);
|
|
21821
|
+
apiFailure(command, contents, "Failed to pull landing page files");
|
|
21822
|
+
const returned2 = new Set(contents.data.files.map((file) => file.path));
|
|
21823
|
+
const missing = batch.filter((file) => !returned2.has(file.path)).map((file) => file.path);
|
|
21824
|
+
if (missing.length)
|
|
21825
|
+
fail(command, `The server did not return ${missing.slice(0, 5).join(", ")}. Pull again.`);
|
|
21826
|
+
for (const file of contents.data.files) {
|
|
21827
|
+
const target = import_node_path25.default.resolve(folder, file.path);
|
|
21828
|
+
if (!target.startsWith(folder + import_node_path25.default.sep))
|
|
21829
|
+
fail(command, `Refusing to write outside the checkout: ${file.path}`);
|
|
21830
|
+
const download = file.download_url ? await fetch(file.download_url, { signal: AbortSignal.timeout(6e4) }) : null;
|
|
21831
|
+
if (download && !download.ok)
|
|
21832
|
+
fail(command, `Failed to download ${file.path}: ${download.status}`);
|
|
21833
|
+
const body5 = download ? Buffer.from(await download.arrayBuffer()) : file.encoding === "base64" ? Buffer.from(file.content ?? "", "base64") : Buffer.from(file.content ?? "", "utf8");
|
|
21834
|
+
if (sha256(body5) !== file.sha256)
|
|
21835
|
+
fail(command, `The server returned ${file.path} incomplete. Pull again.`);
|
|
21836
|
+
downloaded.push({ path: file.path, target, body: body5, sha256: file.sha256 });
|
|
21837
|
+
}
|
|
21838
|
+
}
|
|
21839
|
+
if (!existing && !force) {
|
|
21840
|
+
const clobbered = downloaded.filter(
|
|
21841
|
+
({ target, sha256: digest }) => import_node_fs28.default.existsSync(target) && sha256(import_node_fs28.default.readFileSync(target)) !== digest
|
|
21842
|
+
).map((file) => file.path);
|
|
21843
|
+
if (clobbered.length) {
|
|
21844
|
+
fail(
|
|
21845
|
+
command,
|
|
21846
|
+
`${folder} already has files the pull would replace: ${clobbered.slice(0, 5).join(", ")}${clobbered.length > 5 ? ` and ${clobbered.length - 5} more` : ""}.`,
|
|
21847
|
+
EXIT_CODES.conflict,
|
|
21848
|
+
{
|
|
21849
|
+
code: "landing_page_folder_not_empty",
|
|
21850
|
+
next_steps: [
|
|
21851
|
+
{
|
|
21852
|
+
command: `apex landing-pages pull ${id} --out <new folder>`,
|
|
21853
|
+
why: "Pull into an empty folder."
|
|
21854
|
+
},
|
|
21855
|
+
{
|
|
21856
|
+
command: `apex landing-pages pull ${id} --out ${folder} --force`,
|
|
21857
|
+
why: "Replace those files."
|
|
21858
|
+
}
|
|
21859
|
+
]
|
|
21860
|
+
}
|
|
21861
|
+
);
|
|
21862
|
+
}
|
|
21863
|
+
}
|
|
21864
|
+
for (const file of downloaded) {
|
|
21865
|
+
import_node_fs28.default.mkdirSync(import_node_path25.default.dirname(file.target), { recursive: true });
|
|
21866
|
+
import_node_fs28.default.writeFileSync(file.target, file.body);
|
|
21867
|
+
written[file.path] = file.sha256;
|
|
21868
|
+
}
|
|
21869
|
+
removeStaleFiles(
|
|
21870
|
+
folder,
|
|
21871
|
+
existing,
|
|
21872
|
+
new Set(Object.keys(written)),
|
|
21873
|
+
force ? readCheckoutFiles(folder).map((file) => file.path) : []
|
|
21874
|
+
);
|
|
21875
|
+
import_node_fs28.default.writeFileSync(
|
|
21876
|
+
import_node_path25.default.join(folder, LANDING_PAGE_MANIFEST_FILE),
|
|
21877
|
+
`${JSON.stringify(listing.data.manifest ?? {}, null, 2)}
|
|
21878
|
+
`,
|
|
21879
|
+
"utf8"
|
|
21880
|
+
);
|
|
21881
|
+
const contextDir = import_node_path25.default.join(folder, LANDING_PAGE_CONTEXT_DIR);
|
|
21882
|
+
import_node_fs28.default.mkdirSync(contextDir, { recursive: true });
|
|
21883
|
+
const contextName = /^[a-z0-9_-]+\.json$/i;
|
|
21884
|
+
const contextEntries = Object.entries(listing.data.context ?? {}).filter(
|
|
21885
|
+
([name]) => contextName.test(name)
|
|
21886
|
+
);
|
|
21887
|
+
const returned = new Set(contextEntries.map(([name]) => name));
|
|
21888
|
+
for (const name of import_node_fs28.default.readdirSync(contextDir)) {
|
|
21889
|
+
if (contextName.test(name) && !returned.has(name)) import_node_fs28.default.rmSync(import_node_path25.default.join(contextDir, name));
|
|
21890
|
+
}
|
|
21891
|
+
for (const [name, value] of contextEntries) {
|
|
21892
|
+
import_node_fs28.default.writeFileSync(import_node_path25.default.join(contextDir, name), `${JSON.stringify(value, null, 2)}
|
|
21893
|
+
`, "utf8");
|
|
21894
|
+
}
|
|
21895
|
+
writeMarker(folder, {
|
|
21896
|
+
schemaVersion: 1,
|
|
21897
|
+
baseUrl: creds.baseUrl,
|
|
21898
|
+
landingPageId: id,
|
|
21899
|
+
versionId: listing.data.version?.id ?? null,
|
|
21900
|
+
versionNumber: listing.data.version?.version_number ?? null,
|
|
21901
|
+
pulledAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
21902
|
+
files: written,
|
|
21903
|
+
manifestSha256: manifestDigest(folder) ?? void 0
|
|
21904
|
+
});
|
|
21905
|
+
return {
|
|
21906
|
+
folder,
|
|
21907
|
+
landing_page: listing.data.landing_page,
|
|
21908
|
+
version: listing.data.version,
|
|
21909
|
+
files: Object.keys(written).length,
|
|
21910
|
+
notes: listing.data.notes ?? []
|
|
21911
|
+
};
|
|
21912
|
+
}
|
|
21913
|
+
var create9 = 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) {
|
|
21914
|
+
const creds = requireCredentials();
|
|
21915
|
+
const created = await landingPages(creds).create({
|
|
21916
|
+
name: opts.name,
|
|
21917
|
+
...opts.slug ? { slug: opts.slug } : {},
|
|
21918
|
+
...opts.idea ? { idea_key: opts.idea } : {}
|
|
21919
|
+
});
|
|
21920
|
+
apiFailure(this, created, "Failed to create landing page");
|
|
21921
|
+
const pulled = opts.out ? await pullCheckout(this, creds, created.data.id, import_node_path25.default.resolve(process.cwd(), opts.out)) : null;
|
|
21922
|
+
output(
|
|
21923
|
+
this,
|
|
21924
|
+
{ landing_page: created.data, ...pulled ? { folder: pulled.folder } : {} },
|
|
21925
|
+
() => {
|
|
21926
|
+
console.log(created.data.id);
|
|
21927
|
+
if (pulled) console.log(`Checkout: ${pulled.folder}`);
|
|
21928
|
+
}
|
|
21929
|
+
);
|
|
21930
|
+
});
|
|
21931
|
+
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) {
|
|
21932
|
+
const creds = requireCredentials();
|
|
21933
|
+
const folder = import_node_path25.default.resolve(process.cwd(), opts.out ?? id);
|
|
21934
|
+
const result = await pullCheckout(this, creds, id, folder, opts.force === true);
|
|
21935
|
+
output(this, result, () => {
|
|
21936
|
+
console.log(
|
|
21937
|
+
`Pulled ${result.landing_page.name} (version ${result.version?.version_number ?? "none"}, ${result.files} files)`
|
|
21938
|
+
);
|
|
21939
|
+
console.log(result.folder);
|
|
21940
|
+
for (const note of result.notes) console.log(note);
|
|
21941
|
+
});
|
|
21942
|
+
});
|
|
21943
|
+
var plan = new Command("plan").description("Show what applying a checkout would change").argument("[folder]", "Checkout folder", ".").action(async function(folderArg) {
|
|
21944
|
+
const creds = requireCredentials();
|
|
21945
|
+
const { folder, marker } = requireCheckout(this, folderArg);
|
|
21946
|
+
const files = readCheckoutFiles(folder);
|
|
21947
|
+
const response = await landingPages(creds).plan(marker.landingPageId, {
|
|
21948
|
+
manifest: readManifest2(this, folder),
|
|
21949
|
+
files: declared(files)
|
|
21950
|
+
});
|
|
21951
|
+
apiFailure(this, response, "Failed to plan landing page");
|
|
21952
|
+
output(
|
|
21953
|
+
this,
|
|
21954
|
+
{ folder, landingPageId: marker.landingPageId, plan: response.data },
|
|
21955
|
+
() => printPlan(response.data)
|
|
21956
|
+
);
|
|
21957
|
+
});
|
|
21958
|
+
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) {
|
|
21959
|
+
const creds = requireCredentials();
|
|
21960
|
+
const { folder, marker } = requireCheckout(this, folderArg);
|
|
21961
|
+
const api = landingPages(creds);
|
|
21962
|
+
const files = readCheckoutFiles(folder);
|
|
21963
|
+
const manifest = readManifest2(this, folder);
|
|
21964
|
+
const planned = await api.plan(marker.landingPageId, { manifest, files: declared(files) });
|
|
21965
|
+
apiFailure(this, planned, "Failed to plan landing page");
|
|
21966
|
+
if (!planned.data.would_change) {
|
|
21967
|
+
output(
|
|
21968
|
+
this,
|
|
21969
|
+
{ folder, applied: false, plan: planned.data, message: "No changes." },
|
|
21970
|
+
() => console.log("No changes.")
|
|
21971
|
+
);
|
|
21972
|
+
return;
|
|
21973
|
+
}
|
|
21974
|
+
if (!opts.yes) {
|
|
21975
|
+
if (!shouldUseJson(this)) printPlan(planned.data);
|
|
21976
|
+
const ok = await confirm2("Apply these changes as a new version?");
|
|
21977
|
+
if (ok === null) {
|
|
21978
|
+
confirmationRequired(
|
|
21979
|
+
this,
|
|
21980
|
+
"Applying a landing page needs confirmation.",
|
|
21981
|
+
`apex landing-pages apply ${folderArg} --yes`
|
|
21982
|
+
);
|
|
21983
|
+
}
|
|
21984
|
+
if (!ok) {
|
|
21985
|
+
output(this, { aborted: true }, () => console.log("Aborted."));
|
|
21986
|
+
return;
|
|
21987
|
+
}
|
|
21988
|
+
}
|
|
21989
|
+
const required = new Set(planned.data.files.upload_required);
|
|
21990
|
+
const toUpload = files.filter((file) => required.has(file.path));
|
|
21991
|
+
for (const file of toUpload.filter((file2) => file2.bytes > LANDING_PAGE_BATCH_BYTES)) {
|
|
21992
|
+
const granted = await api.uploadGrant(marker.landingPageId, {
|
|
21993
|
+
path: file.path,
|
|
21994
|
+
bytes: file.bytes,
|
|
21995
|
+
sha256: file.sha256,
|
|
21996
|
+
base_version_id: planned.data.base_version_id
|
|
21997
|
+
});
|
|
21998
|
+
apiFailure(this, granted, "Failed to grant video upload");
|
|
21999
|
+
await uploadLandingVideo(granted.data, file.body, {
|
|
22000
|
+
progress: (uploaded, total) => {
|
|
22001
|
+
console.error(`Uploading ${file.path}: ${uploaded}/${total} bytes`);
|
|
22002
|
+
}
|
|
22003
|
+
});
|
|
22004
|
+
}
|
|
22005
|
+
for (const batch of landingPageBatches(
|
|
22006
|
+
toUpload.filter((file) => file.bytes <= LANDING_PAGE_BATCH_BYTES),
|
|
22007
|
+
(file) => file.bytes
|
|
22008
|
+
)) {
|
|
22009
|
+
const uploaded = await api.upload(
|
|
22010
|
+
marker.landingPageId,
|
|
22011
|
+
batch.map((file) => ({
|
|
22012
|
+
path: file.path,
|
|
22013
|
+
content: file.body.toString("base64"),
|
|
22014
|
+
encoding: "base64"
|
|
22015
|
+
}))
|
|
22016
|
+
);
|
|
22017
|
+
apiFailure(this, uploaded, "Failed to upload landing page files");
|
|
22018
|
+
}
|
|
22019
|
+
const applied = await api.apply(marker.landingPageId, {
|
|
22020
|
+
manifest,
|
|
22021
|
+
files: declared(files),
|
|
22022
|
+
...opts.force ? {} : { base_version_id: marker.versionId }
|
|
22023
|
+
});
|
|
22024
|
+
if (!applied.ok && applied.status === 409 && applied.errorDetails?.code === "landing_page_version_conflict") {
|
|
22025
|
+
fail(
|
|
22026
|
+
this,
|
|
22027
|
+
applied.error ?? "The page changed since this checkout was pulled.",
|
|
22028
|
+
EXIT_CODES.conflict,
|
|
22029
|
+
{
|
|
22030
|
+
...applied.errorDetails,
|
|
22031
|
+
next_steps: [
|
|
22032
|
+
{
|
|
22033
|
+
command: `apex landing-pages pull ${marker.landingPageId} --out ${folderArg}`,
|
|
22034
|
+
why: "Bring the checkout up to date."
|
|
22035
|
+
},
|
|
22036
|
+
{
|
|
22037
|
+
command: `apex landing-pages apply ${folderArg} --yes --force`,
|
|
22038
|
+
why: "Replace the current version anyway."
|
|
22039
|
+
}
|
|
22040
|
+
]
|
|
22041
|
+
}
|
|
22042
|
+
);
|
|
22043
|
+
}
|
|
22044
|
+
apiFailure(this, applied, "Failed to apply landing page");
|
|
22045
|
+
writeMarker(folder, {
|
|
22046
|
+
...marker,
|
|
22047
|
+
versionId: applied.data.version_id,
|
|
22048
|
+
versionNumber: applied.data.version_number,
|
|
22049
|
+
pulledAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
22050
|
+
files: Object.fromEntries(files.map((file) => [file.path, file.sha256])),
|
|
22051
|
+
manifestSha256: manifestDigest(folder) ?? void 0
|
|
22052
|
+
});
|
|
22053
|
+
output(this, { folder, applied: true, ...applied.data }, () => {
|
|
22054
|
+
console.log(`Version ${applied.data.version_number}`);
|
|
22055
|
+
if (applied.data.preview_url) console.log(`Preview: ${applied.data.preview_url}`);
|
|
22056
|
+
console.log(
|
|
22057
|
+
`Deploy target: ${applied.data.deploy_target ?? "none: set host in apex-landing-page.json to a domain this shop holds"}`
|
|
22058
|
+
);
|
|
22059
|
+
});
|
|
22060
|
+
});
|
|
22061
|
+
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) {
|
|
22062
|
+
const versionNumber = opts.version === void 0 ? void 0 : Number(opts.version);
|
|
22063
|
+
if (versionNumber !== void 0 && (!Number.isInteger(versionNumber) || versionNumber < 1)) {
|
|
22064
|
+
fail(this, "--version must be a version number.", EXIT_CODES.validation);
|
|
22065
|
+
}
|
|
22066
|
+
const response = await landingPages(requireCredentials()).preview(
|
|
22067
|
+
resolvePageId(target),
|
|
22068
|
+
versionNumber
|
|
22069
|
+
);
|
|
22070
|
+
apiFailure(this, response, "Failed to get preview link");
|
|
22071
|
+
output(this, response.data, () => console.log(response.data.preview_url));
|
|
22072
|
+
});
|
|
22073
|
+
async function versionIdFor(command, creds, id, raw) {
|
|
22074
|
+
if (raw === void 0) return void 0;
|
|
22075
|
+
const number = Number(raw);
|
|
22076
|
+
if (!Number.isInteger(number) || number < 1)
|
|
22077
|
+
fail(command, "--version must be a version number.", EXIT_CODES.validation);
|
|
22078
|
+
const versions2 = await landingPages(creds).versions(id);
|
|
22079
|
+
apiFailure(command, versions2, "Failed to list versions");
|
|
22080
|
+
const match2 = versions2.data.find((version) => version.version_number === number);
|
|
22081
|
+
if (!match2) fail(command, `Version ${number} not found.`, EXIT_CODES.validation);
|
|
22082
|
+
return match2.id;
|
|
22083
|
+
}
|
|
22084
|
+
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";
|
|
22085
|
+
var deploy = new Command("deploy").description(
|
|
22086
|
+
"Deploy a version to the shop's instant host (<shop>.apex-page.com) or a domain this shop holds"
|
|
22087
|
+
).argument("[folder-or-id]", "Checkout folder or landing page id", ".").option(
|
|
22088
|
+
"--host <host>",
|
|
22089
|
+
"Domain claimed with apex domains claim (default: the manifest host, else the shop's apex-page.com subdomain)"
|
|
22090
|
+
).option("--path <path>", "Path on the host").option("--version <number>", "Version number (default: current)").action(async function(target, opts) {
|
|
22091
|
+
const creds = requireCredentials();
|
|
22092
|
+
const id = resolvePageId(target);
|
|
22093
|
+
const versionId = await versionIdFor(this, creds, id, opts.version);
|
|
22094
|
+
const response = await landingPages(creds).deploy(id, {
|
|
22095
|
+
...versionId ? { version_id: versionId } : {},
|
|
22096
|
+
...opts.host ? { host: opts.host } : {},
|
|
22097
|
+
...opts.path ? { path: opts.path } : {}
|
|
22098
|
+
});
|
|
22099
|
+
apiFailure(this, response, "Failed to deploy landing page");
|
|
22100
|
+
output(this, response.data, () => {
|
|
22101
|
+
console.log(response.data.url);
|
|
22102
|
+
console.log(`Version ${response.data.version_number} is live.`);
|
|
22103
|
+
});
|
|
22104
|
+
}).addHelpText("after", SPLIT_KEEPS_SERVING_HELP);
|
|
22105
|
+
var versions = new Command("versions").description("List versions").argument("[folder-or-id]", "Checkout folder or landing page id", ".").action(async function(target) {
|
|
22106
|
+
const response = await landingPages(requireCredentials()).versions(resolvePageId(target));
|
|
22107
|
+
apiFailure(this, response, "Failed to list versions");
|
|
22108
|
+
output(
|
|
22109
|
+
this,
|
|
22110
|
+
response.data,
|
|
22111
|
+
() => printTable(
|
|
22112
|
+
["VERSION", "SOURCE", "STATE", "FILES", "BYTES", "CREATED"],
|
|
22113
|
+
response.data.map((version) => [
|
|
22114
|
+
String(version.version_number),
|
|
22115
|
+
version.source,
|
|
22116
|
+
[version.published ? "live" : "", version.current ? "current" : ""].filter(Boolean).join(", ") || "-",
|
|
22117
|
+
version.file_count === null ? "-" : String(version.file_count),
|
|
22118
|
+
version.bytes === null ? "-" : String(version.bytes),
|
|
22119
|
+
version.created_at
|
|
22120
|
+
]),
|
|
22121
|
+
[8, 10, 14, 6, 10, 26]
|
|
22122
|
+
)
|
|
22123
|
+
);
|
|
22124
|
+
});
|
|
22125
|
+
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) {
|
|
22126
|
+
const number = Number(opts.version);
|
|
22127
|
+
if (!Number.isInteger(number) || number < 1)
|
|
22128
|
+
fail(this, "--version must be a version number.", EXIT_CODES.validation);
|
|
22129
|
+
const response = await landingPages(requireCredentials()).rollback(resolvePageId(target), {
|
|
22130
|
+
version_number: number
|
|
22131
|
+
});
|
|
22132
|
+
apiFailure(this, response, "Failed to roll back landing page");
|
|
22133
|
+
output(this, response.data, () => {
|
|
22134
|
+
console.log(response.data.url);
|
|
22135
|
+
console.log(`Version ${response.data.version_number} is live.`);
|
|
22136
|
+
});
|
|
22137
|
+
}).addHelpText("after", SPLIT_KEEPS_SERVING_HELP);
|
|
22138
|
+
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(
|
|
22139
|
+
"--control <number>",
|
|
22140
|
+
"Control version number (default: the manifest's experiment.control)"
|
|
22141
|
+
).option(
|
|
22142
|
+
"--variant <number>",
|
|
22143
|
+
"Variant version number (default: the manifest's experiment.variant)"
|
|
22144
|
+
).option("--name <name>", "Experiment name").option("--primary-goal <key>", "Manifest goal key to make primary (default: the first goal)").action(async function(target, opts) {
|
|
22145
|
+
const folder = import_node_path25.default.resolve(process.cwd(), target);
|
|
22146
|
+
const fromManifest = import_node_fs28.default.existsSync(import_node_path25.default.join(folder, LANDING_PAGE_MANIFEST_FILE)) ? readManifest2(this, folder)?.experiment ?? {} : {};
|
|
22147
|
+
const control = Number(opts.control ?? fromManifest.control);
|
|
22148
|
+
const variant = Number(opts.variant ?? fromManifest.variant);
|
|
22149
|
+
if (!Number.isInteger(control) || !Number.isInteger(variant) || control < 1 || variant < 1) {
|
|
22150
|
+
fail(
|
|
22151
|
+
this,
|
|
22152
|
+
"Name the control and variant version numbers (--control, --variant, or experiment in apex-landing-page.json).",
|
|
22153
|
+
EXIT_CODES.validation
|
|
22154
|
+
);
|
|
22155
|
+
}
|
|
22156
|
+
const response = await landingPages(requireCredentials()).experiment(resolvePageId(target), {
|
|
22157
|
+
control,
|
|
22158
|
+
variant,
|
|
22159
|
+
...opts.name ? { name: opts.name } : {},
|
|
22160
|
+
...opts.primaryGoal ? { primary_goal: opts.primaryGoal } : {}
|
|
22161
|
+
});
|
|
22162
|
+
apiFailure(this, response, "Failed to create experiment");
|
|
22163
|
+
output(this, response.data, () => {
|
|
22164
|
+
console.log(`Draft experiment ${String(response.data.experiment.id)} created.`);
|
|
22165
|
+
console.log("Start it in the Apex dashboard when it is ready.");
|
|
22166
|
+
});
|
|
22167
|
+
});
|
|
22168
|
+
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) {
|
|
22169
|
+
const url = `${requireCredentials().baseUrl.replace(/\/+$/, "")}/landing-pages/${encodeURIComponent(resolvePageId(target))}`;
|
|
22170
|
+
output(this, { url }, () => console.log(url));
|
|
22171
|
+
});
|
|
22172
|
+
var landingPageWorkspaceCommands = [
|
|
22173
|
+
create9,
|
|
22174
|
+
pull,
|
|
22175
|
+
plan,
|
|
22176
|
+
apply,
|
|
22177
|
+
preview,
|
|
22178
|
+
deploy,
|
|
22179
|
+
versions,
|
|
22180
|
+
rollback,
|
|
22181
|
+
experiment,
|
|
22182
|
+
open2
|
|
22183
|
+
];
|
|
22184
|
+
|
|
22185
|
+
// src/commands/config-resources.ts
|
|
21122
22186
|
function show(resource) {
|
|
21123
22187
|
process.stdout.write(`${JSON.stringify(resource, null, 2)}
|
|
21124
22188
|
`);
|
|
@@ -21184,12 +22248,12 @@ function readFamily(family, label) {
|
|
|
21184
22248
|
}
|
|
21185
22249
|
var assetReads = readFamily("assets", "assets");
|
|
21186
22250
|
var upload = new Command("upload").description("Upload an asset").argument("<file>").action(async function(file) {
|
|
21187
|
-
const absolute =
|
|
21188
|
-
if (!
|
|
21189
|
-
const fileStat =
|
|
22251
|
+
const absolute = import_node_path26.default.resolve(process.cwd(), file);
|
|
22252
|
+
if (!import_node_fs29.default.existsSync(absolute)) fail(this, `File not found: ${file}`, 3);
|
|
22253
|
+
const fileStat = import_node_fs29.default.statSync(absolute);
|
|
21190
22254
|
if (!fileStat.isFile()) fail(this, `File not found: ${file}`, 3);
|
|
21191
22255
|
if (fileStat.size > MAX_ASSET_SIZE_BYTES) fail(this, `${file} is larger than 10 MB`, 3);
|
|
21192
|
-
const mimeType = ASSET_MIME_TYPES[
|
|
22256
|
+
const mimeType = ASSET_MIME_TYPES[import_node_path26.default.extname(absolute).toLowerCase()];
|
|
21193
22257
|
if (!mimeType)
|
|
21194
22258
|
fail(
|
|
21195
22259
|
this,
|
|
@@ -21198,8 +22262,8 @@ var upload = new Command("upload").description("Upload an asset").argument("<fil
|
|
|
21198
22262
|
);
|
|
21199
22263
|
const r = await uploadAssets(requireCredentials(), [
|
|
21200
22264
|
{
|
|
21201
|
-
name:
|
|
21202
|
-
bytes:
|
|
22265
|
+
name: import_node_path26.default.basename(absolute),
|
|
22266
|
+
bytes: import_node_fs29.default.readFileSync(absolute),
|
|
21203
22267
|
type: mimeType
|
|
21204
22268
|
}
|
|
21205
22269
|
]);
|
|
@@ -21207,7 +22271,7 @@ var upload = new Command("upload").description("Upload an asset").argument("<fil
|
|
|
21207
22271
|
fail(this, r.error ?? "Failed to upload asset", classifyStatus(r.status), r.errorDetails);
|
|
21208
22272
|
output(this, r.data, () => r.data.assets.forEach(show));
|
|
21209
22273
|
});
|
|
21210
|
-
var
|
|
22274
|
+
var remove4 = new Command("delete").alias("rm").description("Delete an asset").argument("<id>").option("-f, --force", "Skip confirmation").action(async function(id, opts) {
|
|
21211
22275
|
if (!opts.force) {
|
|
21212
22276
|
const confirmed = await confirmAssetDelete(id);
|
|
21213
22277
|
if (!confirmed) {
|
|
@@ -21223,9 +22287,12 @@ var remove5 = new Command("delete").alias("rm").description("Delete an asset").a
|
|
|
21223
22287
|
fail(this, r.error ?? "Failed to delete asset", classifyStatus(r.status), r.errorDetails);
|
|
21224
22288
|
output(this, r.data, () => console.log(`Deleted ${id}`));
|
|
21225
22289
|
});
|
|
21226
|
-
var assetsCommand = new Command("assets").description("Manage assets").addCommand(assetReads.list).addCommand(assetReads.get).addCommand(upload).addCommand(
|
|
22290
|
+
var assetsCommand = new Command("assets").description("Manage assets").addCommand(assetReads.list).addCommand(assetReads.get).addCommand(upload).addCommand(remove4);
|
|
21227
22291
|
var landingReads = readFamily("landing-pages", "landing pages");
|
|
21228
|
-
var landingPagesCommand = new Command("landing-pages").description(
|
|
22292
|
+
var landingPagesCommand = new Command("landing-pages").description(
|
|
22293
|
+
"Build landing pages with your own tools, then deploy, track and test them with Apex"
|
|
22294
|
+
).addCommand(landingReads.list).addCommand(landingReads.get);
|
|
22295
|
+
for (const command of landingPageWorkspaceCommands) landingPagesCommand.addCommand(command);
|
|
21229
22296
|
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) {
|
|
21230
22297
|
const body5 = Object.fromEntries(
|
|
21231
22298
|
Object.entries(opts).filter(([, value]) => value !== void 0)
|