@drip-apex/apex-cli 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -1
- package/dist/cli.js +689 -1991
- package/package.json +4 -4
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 path21 = require("node:path");
|
|
1163
|
+
var fs25 = 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 (fs25.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 = path21.resolve(baseDir, baseName);
|
|
2163
|
+
if (fs25.existsSync(localBin)) return localBin;
|
|
2164
|
+
if (sourceExt.includes(path21.extname(baseName))) return void 0;
|
|
2165
2165
|
const foundExt = sourceExt.find(
|
|
2166
|
-
(ext) =>
|
|
2166
|
+
(ext) => fs25.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 = fs25.realpathSync(this._scriptPath);
|
|
2179
2179
|
} catch {
|
|
2180
2180
|
resolvedScriptPath = this._scriptPath;
|
|
2181
2181
|
}
|
|
2182
|
-
executableDir =
|
|
2183
|
-
|
|
2182
|
+
executableDir = path21.resolve(
|
|
2183
|
+
path21.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 = path21.basename(
|
|
2191
2191
|
this._scriptPath,
|
|
2192
|
-
|
|
2192
|
+
path21.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(path21.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 = path21.basename(filename, path21.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(path22) {
|
|
3065
|
+
if (path22 === void 0) return this._executableDir;
|
|
3066
|
+
this._executableDir = path22;
|
|
3067
3067
|
return this;
|
|
3068
3068
|
}
|
|
3069
3069
|
/**
|
|
@@ -3854,7 +3854,7 @@ var init_source = __esm({
|
|
|
3854
3854
|
// node_modules/is-docker/index.js
|
|
3855
3855
|
function hasDockerEnv() {
|
|
3856
3856
|
try {
|
|
3857
|
-
|
|
3857
|
+
import_node_fs3.default.statSync("/.dockerenv");
|
|
3858
3858
|
return true;
|
|
3859
3859
|
} catch {
|
|
3860
3860
|
return false;
|
|
@@ -3862,7 +3862,7 @@ function hasDockerEnv() {
|
|
|
3862
3862
|
}
|
|
3863
3863
|
function hasDockerCGroup() {
|
|
3864
3864
|
try {
|
|
3865
|
-
return
|
|
3865
|
+
return import_node_fs3.default.readFileSync("/proc/self/cgroup", "utf8").includes("docker");
|
|
3866
3866
|
} catch {
|
|
3867
3867
|
return false;
|
|
3868
3868
|
}
|
|
@@ -3873,10 +3873,10 @@ function isDocker() {
|
|
|
3873
3873
|
}
|
|
3874
3874
|
return isDockerCached;
|
|
3875
3875
|
}
|
|
3876
|
-
var
|
|
3876
|
+
var import_node_fs3, isDockerCached;
|
|
3877
3877
|
var init_is_docker = __esm({
|
|
3878
3878
|
"node_modules/is-docker/index.js"() {
|
|
3879
|
-
|
|
3879
|
+
import_node_fs3 = __toESM(require("node:fs"), 1);
|
|
3880
3880
|
}
|
|
3881
3881
|
});
|
|
3882
3882
|
|
|
@@ -3887,14 +3887,14 @@ function isInsideContainer() {
|
|
|
3887
3887
|
}
|
|
3888
3888
|
return cachedResult;
|
|
3889
3889
|
}
|
|
3890
|
-
var
|
|
3890
|
+
var import_node_fs4, cachedResult, hasContainerEnv;
|
|
3891
3891
|
var init_is_inside_container = __esm({
|
|
3892
3892
|
"node_modules/is-inside-container/index.js"() {
|
|
3893
|
-
|
|
3893
|
+
import_node_fs4 = __toESM(require("node:fs"), 1);
|
|
3894
3894
|
init_is_docker();
|
|
3895
3895
|
hasContainerEnv = () => {
|
|
3896
3896
|
try {
|
|
3897
|
-
|
|
3897
|
+
import_node_fs4.default.statSync("/run/.containerenv");
|
|
3898
3898
|
return true;
|
|
3899
3899
|
} catch {
|
|
3900
3900
|
return false;
|
|
@@ -3904,25 +3904,25 @@ var init_is_inside_container = __esm({
|
|
|
3904
3904
|
});
|
|
3905
3905
|
|
|
3906
3906
|
// node_modules/is-wsl/index.js
|
|
3907
|
-
var import_node_process2,
|
|
3907
|
+
var import_node_process2, import_node_os4, import_node_fs5, isWsl, is_wsl_default;
|
|
3908
3908
|
var init_is_wsl = __esm({
|
|
3909
3909
|
"node_modules/is-wsl/index.js"() {
|
|
3910
3910
|
import_node_process2 = __toESM(require("node:process"), 1);
|
|
3911
|
-
|
|
3912
|
-
|
|
3911
|
+
import_node_os4 = __toESM(require("node:os"), 1);
|
|
3912
|
+
import_node_fs5 = __toESM(require("node:fs"), 1);
|
|
3913
3913
|
init_is_inside_container();
|
|
3914
3914
|
isWsl = () => {
|
|
3915
3915
|
if (import_node_process2.default.platform !== "linux") {
|
|
3916
3916
|
return false;
|
|
3917
3917
|
}
|
|
3918
|
-
if (
|
|
3918
|
+
if (import_node_os4.default.release().toLowerCase().includes("microsoft")) {
|
|
3919
3919
|
if (isInsideContainer()) {
|
|
3920
3920
|
return false;
|
|
3921
3921
|
}
|
|
3922
3922
|
return true;
|
|
3923
3923
|
}
|
|
3924
3924
|
try {
|
|
3925
|
-
return
|
|
3925
|
+
return import_node_fs5.default.readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft") ? !isInsideContainer() : false;
|
|
3926
3926
|
} catch {
|
|
3927
3927
|
return false;
|
|
3928
3928
|
}
|
|
@@ -4191,12 +4191,12 @@ function detectPlatformBinary({ [platform]: platformBinary }, { wsl }) {
|
|
|
4191
4191
|
}
|
|
4192
4192
|
return detectArchBinary(platformBinary);
|
|
4193
4193
|
}
|
|
4194
|
-
var import_node_process7, import_node_buffer,
|
|
4194
|
+
var import_node_process7, import_node_buffer, import_node_path3, import_node_url, import_node_util5, import_node_child_process5, import_promises2, import_meta, execFile5, __dirname2, localXdgOpenPath, platform, arch, pTryEach, baseOpen, open, openApp, apps, open_default;
|
|
4195
4195
|
var init_open = __esm({
|
|
4196
4196
|
"node_modules/open/index.js"() {
|
|
4197
4197
|
import_node_process7 = __toESM(require("node:process"), 1);
|
|
4198
4198
|
import_node_buffer = require("node:buffer");
|
|
4199
|
-
|
|
4199
|
+
import_node_path3 = __toESM(require("node:path"), 1);
|
|
4200
4200
|
import_node_url = require("node:url");
|
|
4201
4201
|
import_node_util5 = require("node:util");
|
|
4202
4202
|
import_node_child_process5 = __toESM(require("node:child_process"), 1);
|
|
@@ -4207,8 +4207,8 @@ var init_open = __esm({
|
|
|
4207
4207
|
init_is_inside_container();
|
|
4208
4208
|
import_meta = {};
|
|
4209
4209
|
execFile5 = (0, import_node_util5.promisify)(import_node_child_process5.default.execFile);
|
|
4210
|
-
__dirname2 =
|
|
4211
|
-
localXdgOpenPath =
|
|
4210
|
+
__dirname2 = import_node_path3.default.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
|
|
4211
|
+
localXdgOpenPath = import_node_path3.default.join(__dirname2, "xdg-open");
|
|
4212
4212
|
({ platform, arch } = import_node_process7.default);
|
|
4213
4213
|
pTryEach = async (array, mapper) => {
|
|
4214
4214
|
let latestError;
|
|
@@ -6743,16 +6743,16 @@ __export(ora_exports, {
|
|
|
6743
6743
|
function ora(options) {
|
|
6744
6744
|
return new Ora(options);
|
|
6745
6745
|
}
|
|
6746
|
-
async function oraPromise(
|
|
6747
|
-
const actionIsFunction = typeof
|
|
6748
|
-
const actionIsPromise = typeof
|
|
6746
|
+
async function oraPromise(action69, options) {
|
|
6747
|
+
const actionIsFunction = typeof action69 === "function";
|
|
6748
|
+
const actionIsPromise = typeof action69.then === "function";
|
|
6749
6749
|
if (!actionIsFunction && !actionIsPromise) {
|
|
6750
6750
|
throw new TypeError("Parameter `action` must be a Function or a Promise");
|
|
6751
6751
|
}
|
|
6752
6752
|
const { successText, failText } = typeof options === "object" ? options : { successText: void 0, failText: void 0 };
|
|
6753
6753
|
const spinner = ora(options).start();
|
|
6754
6754
|
try {
|
|
6755
|
-
const promise = actionIsFunction ?
|
|
6755
|
+
const promise = actionIsFunction ? action69(spinner) : action69;
|
|
6756
6756
|
const result = await promise;
|
|
6757
6757
|
spinner.succeed(
|
|
6758
6758
|
successText === void 0 ? void 0 : typeof successText === "string" ? successText : successText(result)
|
|
@@ -7085,25 +7085,34 @@ var {
|
|
|
7085
7085
|
Help
|
|
7086
7086
|
} = import_index.default;
|
|
7087
7087
|
|
|
7088
|
+
// src/test-harness.ts
|
|
7089
|
+
var readSetting;
|
|
7090
|
+
function cliTestSetting(name) {
|
|
7091
|
+
return readSetting?.(name);
|
|
7092
|
+
}
|
|
7093
|
+
function cliTestHarnessActive() {
|
|
7094
|
+
return readSetting !== void 0;
|
|
7095
|
+
}
|
|
7096
|
+
|
|
7088
7097
|
// src/cli.ts
|
|
7089
7098
|
init_source();
|
|
7090
7099
|
|
|
7091
7100
|
// src/commands/login.ts
|
|
7092
7101
|
init_source();
|
|
7093
|
-
var
|
|
7102
|
+
var import_node_crypto = __toESM(require("node:crypto"));
|
|
7094
7103
|
var import_node_http = __toESM(require("node:http"));
|
|
7095
|
-
var
|
|
7104
|
+
var import_node_os5 = __toESM(require("node:os"));
|
|
7096
7105
|
var import_node_readline2 = __toESM(require("node:readline"));
|
|
7097
7106
|
|
|
7098
7107
|
// src/config.ts
|
|
7099
|
-
var
|
|
7100
|
-
var
|
|
7101
|
-
var
|
|
7108
|
+
var import_node_fs2 = __toESM(require("node:fs"));
|
|
7109
|
+
var import_node_path2 = __toESM(require("node:path"));
|
|
7110
|
+
var import_node_os3 = __toESM(require("node:os"));
|
|
7102
7111
|
|
|
7103
7112
|
// package.json
|
|
7104
7113
|
var package_default = {
|
|
7105
7114
|
name: "@drip-apex/apex-cli",
|
|
7106
|
-
version: "0.
|
|
7115
|
+
version: "0.5.0",
|
|
7107
7116
|
license: "MIT",
|
|
7108
7117
|
description: "Apex CLI for managing experiments, approved roadmap ideas, and draft workspaces.",
|
|
7109
7118
|
private: false,
|
|
@@ -7124,14 +7133,14 @@ var package_default = {
|
|
|
7124
7133
|
"package.json"
|
|
7125
7134
|
],
|
|
7126
7135
|
scripts: {
|
|
7127
|
-
build: "esbuild src/cli.ts --bundle --platform=node --target=node18 --format=cjs --outfile=dist/cli.js --banner:js='#!/usr/bin/env node' --external:esbuild --external:fsevents",
|
|
7136
|
+
build: "rm -rf dist dist-test && esbuild src/cli.ts --bundle --platform=node --target=node18 --format=cjs --outfile=dist/cli.js --banner:js='#!/usr/bin/env node' --external:esbuild --external:fsevents && esbuild src/cli-test.ts --bundle --platform=node --target=node18 --format=cjs --outfile=dist-test/cli.js --banner:js='#!/usr/bin/env node' --external:esbuild --external:fsevents",
|
|
7128
7137
|
"check:docs-command-index": "node scripts/check-docs-command-index.mjs",
|
|
7129
7138
|
dev: "tsx src/cli.ts",
|
|
7130
7139
|
test: 'npm run build && node --test "test/**/*.test.mjs"',
|
|
7131
7140
|
typecheck: "tsc --noEmit"
|
|
7132
7141
|
},
|
|
7133
7142
|
engines: {
|
|
7134
|
-
node: ">=
|
|
7143
|
+
node: ">=22.22.0"
|
|
7135
7144
|
},
|
|
7136
7145
|
publishConfig: {
|
|
7137
7146
|
access: "public"
|
|
@@ -7146,7 +7155,7 @@ var package_default = {
|
|
|
7146
7155
|
devDependencies: {
|
|
7147
7156
|
"@types/node": "^22.15.0",
|
|
7148
7157
|
tsx: "^4.22.4",
|
|
7149
|
-
typescript: "^5.
|
|
7158
|
+
typescript: "^5.9.3"
|
|
7150
7159
|
}
|
|
7151
7160
|
};
|
|
7152
7161
|
|
|
@@ -7337,6 +7346,18 @@ async function handleCliError(err, command) {
|
|
|
7337
7346
|
process.exit(cliError.exitCode);
|
|
7338
7347
|
}
|
|
7339
7348
|
|
|
7349
|
+
// ../api-client/src/credentials.ts
|
|
7350
|
+
var import_node_fs = __toESM(require("node:fs"));
|
|
7351
|
+
var import_node_os2 = __toESM(require("node:os"));
|
|
7352
|
+
var import_node_path = __toESM(require("node:path"));
|
|
7353
|
+
function readStoredCredentials(file = import_node_path.default.join(import_node_os2.default.homedir(), ".drip", "credentials.json")) {
|
|
7354
|
+
try {
|
|
7355
|
+
return JSON.parse(import_node_fs.default.readFileSync(file, "utf8"));
|
|
7356
|
+
} catch {
|
|
7357
|
+
return null;
|
|
7358
|
+
}
|
|
7359
|
+
}
|
|
7360
|
+
|
|
7340
7361
|
// src/config.ts
|
|
7341
7362
|
function argvOption(name) {
|
|
7342
7363
|
const flag = `--${name}`;
|
|
@@ -7347,6 +7368,21 @@ function argvOption(name) {
|
|
|
7347
7368
|
}
|
|
7348
7369
|
return void 0;
|
|
7349
7370
|
}
|
|
7371
|
+
var didWarnStoredContext = false;
|
|
7372
|
+
function warnStoredContextFallback(creds, flags, context) {
|
|
7373
|
+
if (didWarnStoredContext) return;
|
|
7374
|
+
const storedOrg = !flags.orgId && !process.env.DRIP_ORG && Boolean(creds?.context?.orgId);
|
|
7375
|
+
const storedShop = !flags.shopId && !process.env.DRIP_SHOP && Boolean(creds?.context?.shopId);
|
|
7376
|
+
if (!storedOrg && !storedShop) return;
|
|
7377
|
+
const selections = [
|
|
7378
|
+
storedOrg ? `org ${context.orgId}` : null,
|
|
7379
|
+
storedShop ? `shop ${context.shopId}` : null
|
|
7380
|
+
].filter(Boolean).join(" / ");
|
|
7381
|
+
console.error(
|
|
7382
|
+
`Using ${selections} from 'apex use' context (pass --org/--shop to override).`
|
|
7383
|
+
);
|
|
7384
|
+
didWarnStoredContext = true;
|
|
7385
|
+
}
|
|
7350
7386
|
function defaultCredentialKind(creds) {
|
|
7351
7387
|
return Boolean(process.env.DRIP_API_KEY) || !creds?.userApiKey ? "shop" : "user";
|
|
7352
7388
|
}
|
|
@@ -7361,9 +7397,12 @@ function resolveContext(creds = getCredentials(), flags = { orgId: argvOption("o
|
|
|
7361
7397
|
}
|
|
7362
7398
|
return { orgId: flags.orgId || process.env.DRIP_ORG || void 0, shopId: flags.shopId || process.env.DRIP_SHOP || void 0 };
|
|
7363
7399
|
}
|
|
7364
|
-
const
|
|
7365
|
-
|
|
7366
|
-
|
|
7400
|
+
const context = {
|
|
7401
|
+
orgId: flags.orgId || process.env.DRIP_ORG || creds?.context?.orgId,
|
|
7402
|
+
shopId: flags.shopId || process.env.DRIP_SHOP || creds?.context?.shopId
|
|
7403
|
+
};
|
|
7404
|
+
warnStoredContextFallback(creds, flags, context);
|
|
7405
|
+
return context;
|
|
7367
7406
|
}
|
|
7368
7407
|
function configureInvocationContext() {
|
|
7369
7408
|
const creds = getCredentials();
|
|
@@ -7371,14 +7410,10 @@ function configureInvocationContext() {
|
|
|
7371
7410
|
const context = resolveContext(creds, void 0, credential);
|
|
7372
7411
|
setEnvelopeContext({ credential, org_id: context.orgId ?? null, shop_id: context.shopId ?? null });
|
|
7373
7412
|
}
|
|
7374
|
-
var DRIP_DIR =
|
|
7375
|
-
var CREDS_FILE =
|
|
7413
|
+
var DRIP_DIR = import_node_path2.default.join(import_node_os3.default.homedir(), ".drip");
|
|
7414
|
+
var CREDS_FILE = import_node_path2.default.join(DRIP_DIR, "credentials.json");
|
|
7376
7415
|
function getStoredCredentials() {
|
|
7377
|
-
|
|
7378
|
-
return JSON.parse(import_node_fs.default.readFileSync(CREDS_FILE, "utf-8"));
|
|
7379
|
-
} catch {
|
|
7380
|
-
return null;
|
|
7381
|
-
}
|
|
7416
|
+
return readStoredCredentials(CREDS_FILE);
|
|
7382
7417
|
}
|
|
7383
7418
|
function getCredentials() {
|
|
7384
7419
|
const envKey = process.env.DRIP_API_KEY;
|
|
@@ -7390,1446 +7425,147 @@ function getCredentials() {
|
|
|
7390
7425
|
// An env-provided key pairs only with an env-provided URL or the
|
|
7391
7426
|
// production default — never a stored custom URL, so a credential is
|
|
7392
7427
|
// never silently sent to a host it was not issued for.
|
|
7393
|
-
baseUrl: envUrl ?? "https://app.drip-apex.com",
|
|
7394
|
-
userApiKey: fileCreds?.userApiKey,
|
|
7395
|
-
userBaseUrl: fileCreds?.userBaseUrl ?? fileCreds?.baseUrl,
|
|
7396
|
-
context: fileCreds?.context
|
|
7397
|
-
};
|
|
7398
|
-
}
|
|
7399
|
-
return fileCreds;
|
|
7400
|
-
}
|
|
7401
|
-
function saveCredentials(creds) {
|
|
7402
|
-
const { credentialKind: _kind, resolvedContext: _resolved, ...persisted } = creds;
|
|
7403
|
-
import_node_fs.default.mkdirSync(DRIP_DIR, { recursive: true });
|
|
7404
|
-
import_node_fs.default.writeFileSync(CREDS_FILE, JSON.stringify(persisted, null, 2) + "\n", {
|
|
7405
|
-
mode: 384
|
|
7406
|
-
});
|
|
7407
|
-
}
|
|
7408
|
-
function clearCredentials() {
|
|
7409
|
-
try {
|
|
7410
|
-
import_node_fs.default.unlinkSync(CREDS_FILE);
|
|
7411
|
-
} catch {
|
|
7412
|
-
}
|
|
7413
|
-
}
|
|
7414
|
-
function requireCredentials() {
|
|
7415
|
-
const creds = getCredentials();
|
|
7416
|
-
if (!creds) {
|
|
7417
|
-
throw new CliError(
|
|
7418
|
-
'Not logged in. Run "apex login" first, or set DRIP_API_KEY env var.',
|
|
7419
|
-
EXIT_CODES.auth
|
|
7420
|
-
);
|
|
7421
|
-
}
|
|
7422
|
-
const context = resolveContext(creds, void 0, defaultCredentialKind(creds));
|
|
7423
|
-
if (!process.env.DRIP_API_KEY && creds.userApiKey) {
|
|
7424
|
-
setEnvelopeContext({ credential: "user", org_id: context.orgId ?? null, shop_id: context.shopId ?? null });
|
|
7425
|
-
if (!context.shopId) {
|
|
7426
|
-
throw new CliError(
|
|
7427
|
-
'Shop context is required. Run "apex use", or pass --shop <id>.',
|
|
7428
|
-
EXIT_CODES.validation,
|
|
7429
|
-
{ code: "shop_context_required", retry: "apex use <org>/<shop>", flag: "--shop" }
|
|
7430
|
-
);
|
|
7431
|
-
}
|
|
7432
|
-
return { ...creds, apiKey: creds.userApiKey, baseUrl: creds.userBaseUrl ?? creds.baseUrl, credentialKind: "user", resolvedContext: context };
|
|
7433
|
-
}
|
|
7434
|
-
setEnvelopeContext({ credential: "shop", org_id: context.orgId ?? null, shop_id: context.shopId ?? null });
|
|
7435
|
-
return { ...creds, credentialKind: "shop", resolvedContext: context };
|
|
7436
|
-
}
|
|
7437
|
-
function requireUserCredentials() {
|
|
7438
|
-
const creds = getCredentials();
|
|
7439
|
-
if (!creds?.userApiKey) {
|
|
7440
|
-
throw new CliError(
|
|
7441
|
-
'This command needs a user credential (apx_uk_...). Run "apex login" first.',
|
|
7442
|
-
EXIT_CODES.auth
|
|
7443
|
-
);
|
|
7444
|
-
}
|
|
7445
|
-
const context = resolveContext(creds, void 0, "user");
|
|
7446
|
-
setEnvelopeContext({ credential: "user", org_id: context.orgId ?? null, shop_id: context.shopId ?? null });
|
|
7447
|
-
return { ...creds, apiKey: creds.userApiKey, baseUrl: creds.userBaseUrl ?? creds.baseUrl, credentialKind: "user", resolvedContext: context };
|
|
7448
|
-
}
|
|
7449
|
-
var CONFIG_NAMES = [
|
|
7450
|
-
".drip.config.ts",
|
|
7451
|
-
".drip.config.js",
|
|
7452
|
-
"drip.config.ts",
|
|
7453
|
-
"drip.config.js"
|
|
7454
|
-
];
|
|
7455
|
-
function findConfigPath() {
|
|
7456
|
-
const cwd = process.cwd();
|
|
7457
|
-
for (const name of CONFIG_NAMES) {
|
|
7458
|
-
const p = import_node_path.default.join(cwd, name);
|
|
7459
|
-
if (import_node_fs.default.existsSync(p)) return p;
|
|
7460
|
-
}
|
|
7461
|
-
return null;
|
|
7462
|
-
}
|
|
7463
|
-
|
|
7464
|
-
// src/api.ts
|
|
7465
|
-
var import_node_crypto = require("node:crypto");
|
|
7466
|
-
var APEX_API_VERSION_HEADER = "x-apex-api-version";
|
|
7467
|
-
var SUPPORTED_APEX_API_VERSION = "1";
|
|
7468
|
-
var warnedAboutApiVersionSkew = false;
|
|
7469
|
-
function warnAboutApiVersionSkew(headers) {
|
|
7470
|
-
const serverVersion = headers.get(APEX_API_VERSION_HEADER);
|
|
7471
|
-
if (!serverVersion || warnedAboutApiVersionSkew) return;
|
|
7472
|
-
const numericServerVersion = Number(serverVersion);
|
|
7473
|
-
const numericSupportedVersion = Number(SUPPORTED_APEX_API_VERSION);
|
|
7474
|
-
if (!Number.isFinite(numericServerVersion) || numericServerVersion === numericSupportedVersion) return;
|
|
7475
|
-
warnedAboutApiVersionSkew = true;
|
|
7476
|
-
process.stderr.write(
|
|
7477
|
-
numericServerVersion > numericSupportedVersion ? "CLI is behind the server \u2014 update.\n" : "Server API is older than this CLI supports.\n"
|
|
7478
|
-
);
|
|
7479
|
-
}
|
|
7480
|
-
var mockEnvironmentPatchAttempts = 0;
|
|
7481
|
-
var mockEnvironmentReads = 0;
|
|
7482
|
-
var mockDomainsPatchAttempts = 0;
|
|
7483
|
-
var mockScreenshotPolls = 0;
|
|
7484
|
-
function extractErrorMessage(json, status2) {
|
|
7485
|
-
if (!json) return `HTTP ${status2}`;
|
|
7486
|
-
const err = json.error;
|
|
7487
|
-
if (typeof err === "string" && err.trim()) return err;
|
|
7488
|
-
if (err && typeof err === "object") {
|
|
7489
|
-
const msg2 = err.message;
|
|
7490
|
-
if (typeof msg2 === "string" && msg2.trim()) return msg2;
|
|
7491
|
-
}
|
|
7492
|
-
const msg = json.message;
|
|
7493
|
-
if (typeof msg === "string" && msg.trim()) return msg;
|
|
7494
|
-
return `HTTP ${status2}`;
|
|
7495
|
-
}
|
|
7496
|
-
function extractErrorDetails(json) {
|
|
7497
|
-
if (!json) return void 0;
|
|
7498
|
-
const candidates = [
|
|
7499
|
-
json.details,
|
|
7500
|
-
json.meta,
|
|
7501
|
-
json.error && typeof json.error === "object" ? json.error.details : void 0
|
|
7502
|
-
];
|
|
7503
|
-
const nestedError = json.error && typeof json.error === "object" && !Array.isArray(json.error) ? json.error : null;
|
|
7504
|
-
const code = typeof json.code === "string" ? json.code : typeof nestedError?.code === "string" ? nestedError.code : void 0;
|
|
7505
|
-
for (const candidate of candidates) {
|
|
7506
|
-
if (candidate && typeof candidate === "object" && !Array.isArray(candidate)) {
|
|
7507
|
-
return { ...candidate, ...code ? { code } : {} };
|
|
7508
|
-
}
|
|
7509
|
-
}
|
|
7510
|
-
const limitKeys = ["limit", "current", "max", "actual", "field", "code"];
|
|
7511
|
-
const details = Object.fromEntries(
|
|
7512
|
-
limitKeys.filter((key) => key in json).map((key) => [key, json[key]])
|
|
7513
|
-
);
|
|
7514
|
-
if (code) details.code = code;
|
|
7515
|
-
return Object.keys(details).length > 0 ? details : void 0;
|
|
7516
|
-
}
|
|
7517
|
-
function mockExperiment() {
|
|
7518
|
-
const collisionFixture = ["collision", "collision-partial"].includes(process.env.APEX_CLI_MOCK_SCREENSHOTS ?? "");
|
|
7519
|
-
const collidingVariation = collisionFixture ? [{
|
|
7520
|
-
id: "deadbeef-0000-4000-8000-000000000002",
|
|
7521
|
-
name: "Trust Badge",
|
|
7522
|
-
weight: 0,
|
|
7523
|
-
is_control: false,
|
|
7524
|
-
mutations: []
|
|
7525
|
-
}] : [];
|
|
7526
|
-
return {
|
|
7527
|
-
id: "00000000-0000-4000-8000-000000000001",
|
|
7528
|
-
slug: "fixture-test",
|
|
7529
|
-
name: "Fixture Test",
|
|
7530
|
-
status: "draft",
|
|
7531
|
-
traffic_allocation: 1,
|
|
7532
|
-
store_url: "https://example.com/products/demo",
|
|
7533
|
-
created_at: "2026-07-04T00:00:00.000Z",
|
|
7534
|
-
archived_at: null,
|
|
7535
|
-
variations: [
|
|
7536
|
-
{
|
|
7537
|
-
id: "00000000-0000-4000-8000-0000000000c0",
|
|
7538
|
-
slug: "control",
|
|
7539
|
-
name: "Control",
|
|
7540
|
-
weight: 0.5,
|
|
7541
|
-
is_control: true,
|
|
7542
|
-
mutations: []
|
|
7543
|
-
},
|
|
7544
|
-
...collidingVariation,
|
|
7545
|
-
{
|
|
7546
|
-
id: "00000000-0000-4000-8000-0000000000b1",
|
|
7547
|
-
slug: collisionFixture ? void 0 : "badge",
|
|
7548
|
-
name: "Trust Badge",
|
|
7549
|
-
weight: 0.5,
|
|
7550
|
-
is_control: false,
|
|
7551
|
-
mutations: []
|
|
7552
|
-
}
|
|
7553
|
-
]
|
|
7554
|
-
};
|
|
7555
|
-
}
|
|
7556
|
-
var SECOND_PAGE_GOAL_ID = "00000000-0000-4000-8000-0000000000f2";
|
|
7557
|
-
function mockFunnel() {
|
|
7558
|
-
return {
|
|
7559
|
-
id: "00000000-0000-4000-8000-0000000000aa",
|
|
7560
|
-
name: "Fixture funnel",
|
|
7561
|
-
steps: [
|
|
7562
|
-
{ order: 1, label: "Collection", url_pattern: "/collections/*" },
|
|
7563
|
-
{ order: 2, label: "Product", url_pattern: "/products/*" }
|
|
7564
|
-
],
|
|
7565
|
-
created_at: "2026-07-04T00:00:00.000Z"
|
|
7566
|
-
};
|
|
7567
|
-
}
|
|
7568
|
-
function mockGoal() {
|
|
7569
|
-
return {
|
|
7570
|
-
id: "00000000-0000-4000-8000-0000000000f1",
|
|
7571
|
-
name: "Fixture purchase",
|
|
7572
|
-
type: "custom",
|
|
7573
|
-
measurement: "conversion",
|
|
7574
|
-
count_once: false,
|
|
7575
|
-
selector: null,
|
|
7576
|
-
text_match: null,
|
|
7577
|
-
url_pattern: null,
|
|
7578
|
-
page_id: null,
|
|
7579
|
-
event_name: "purchase",
|
|
7580
|
-
capture: null,
|
|
7581
|
-
created_at: "2026-07-04T00:00:00.000Z"
|
|
7582
|
-
};
|
|
7583
|
-
}
|
|
7584
|
-
var PAGE_SCOPED_GOAL_ID = "00000000-0000-4000-8000-0000000000f3";
|
|
7585
|
-
function mockPageScopedGoal() {
|
|
7586
|
-
return {
|
|
7587
|
-
...mockGoal(),
|
|
7588
|
-
id: PAGE_SCOPED_GOAL_ID,
|
|
7589
|
-
name: "Fixture page-scoped pageview",
|
|
7590
|
-
type: "pageview",
|
|
7591
|
-
event_name: null,
|
|
7592
|
-
url_pattern: "bestellung-abgeschlossen",
|
|
7593
|
-
page_id: "00000000-0000-4000-8000-0000000000e1"
|
|
7594
|
-
};
|
|
7595
|
-
}
|
|
7596
|
-
function mockBrandContext() {
|
|
7597
|
-
if (process.env.APEX_CLI_MOCK_BRAND_PROFILE === "none") {
|
|
7598
|
-
return {
|
|
7599
|
-
hasProfile: false,
|
|
7600
|
-
mode: "off",
|
|
7601
|
-
status: "",
|
|
7602
|
-
summary: "",
|
|
7603
|
-
voice: {
|
|
7604
|
-
adjectives: [],
|
|
7605
|
-
weAlways: [],
|
|
7606
|
-
weNever: [],
|
|
7607
|
-
bannedPhrases: [],
|
|
7608
|
-
ctaStyle: ""
|
|
7609
|
-
},
|
|
7610
|
-
guardrails: [],
|
|
7611
|
-
proofRequirements: [],
|
|
7612
|
-
clientNotes: "",
|
|
7613
|
-
clientAssetExcerpts: [],
|
|
7614
|
-
guidelinesText: "",
|
|
7615
|
-
promptBlock: ""
|
|
7616
|
-
};
|
|
7617
|
-
}
|
|
7618
|
-
return {
|
|
7619
|
-
hasProfile: true,
|
|
7620
|
-
mode: "balanced",
|
|
7621
|
-
status: "ready",
|
|
7622
|
-
summary: "Fixture brand profile",
|
|
7623
|
-
voice: {
|
|
7624
|
-
adjectives: ["clear", "practical"],
|
|
7625
|
-
weAlways: ["Use proof-backed claims"],
|
|
7626
|
-
weNever: ["Overpromise"],
|
|
7627
|
-
bannedPhrases: ["revolutionary"],
|
|
7628
|
-
ctaStyle: "Direct and specific"
|
|
7629
|
-
},
|
|
7630
|
-
guardrails: ["Avoid unsupported urgency"],
|
|
7631
|
-
proofRequirements: ["Mention only verified store facts"],
|
|
7632
|
-
clientNotes: "Prefer concise launch copy.",
|
|
7633
|
-
clientAssetExcerpts: [
|
|
7634
|
-
{ name: "brand.pdf", excerpt: "Use plain language." }
|
|
7635
|
-
],
|
|
7636
|
-
guidelinesText: "Keep customer-facing copy concrete.",
|
|
7637
|
-
promptBlock: [
|
|
7638
|
-
"## Brand context (mode: balanced)",
|
|
7639
|
-
"Summary: Fixture brand profile",
|
|
7640
|
-
"Voice: clear, practical",
|
|
7641
|
-
"Always: Use proof-backed claims"
|
|
7642
|
-
].join("\n")
|
|
7643
|
-
};
|
|
7644
|
-
}
|
|
7645
|
-
function mockResponse(method, path20, body5) {
|
|
7646
|
-
if (process.env.APEX_CLI_MOCK_API !== "1") return null;
|
|
7647
|
-
const url = new URL(path20, "https://mock.apex.local");
|
|
7648
|
-
const exp = mockExperiment();
|
|
7649
|
-
if (method === "POST" && url.pathname === `/api/v1/experiments/${exp.id}/screenshots`) {
|
|
7650
|
-
mockScreenshotPolls = 0;
|
|
7651
|
-
return { ok: true, status: 202, data: { job_id: "screenshot_job_1", status: "queued", poll_url: "/api/v1/screenshots/screenshot_job_1" } };
|
|
7652
|
-
}
|
|
7653
|
-
if (method === "GET" && url.pathname === "/api/v1/screenshots/screenshot_job_1") {
|
|
7654
|
-
mockScreenshotPolls += 1;
|
|
7655
|
-
if (process.env.APEX_CLI_MOCK_SCREENSHOTS === "transient" && mockScreenshotPolls === 1) {
|
|
7656
|
-
return { ok: false, status: 503, error: "Service temporarily unavailable" };
|
|
7657
|
-
}
|
|
7658
|
-
if (process.env.APEX_CLI_MOCK_SCREENSHOTS === "timeout" || process.env.APEX_CLI_MOCK_SCREENSHOTS === "deadline-processing" || mockScreenshotPolls === 1 && process.env.APEX_CLI_MOCK_SCREENSHOTS !== "deadline-completed") {
|
|
7659
|
-
return { ok: true, status: 200, data: { job_id: "screenshot_job_1", experiment_id: exp.id, status: "processing", devices: ["desktop_chrome"], variation_ids: exp.variations.map((variation) => variation.id) } };
|
|
7660
|
-
}
|
|
7661
|
-
const collisionPartial = process.env.APEX_CLI_MOCK_SCREENSHOTS === "collision-partial";
|
|
7662
|
-
const results2 = {
|
|
7663
|
-
[exp.variations[0].id]: { desktop_chrome: "data:image/png;base64,iVBORw0KGgo=" }
|
|
7664
|
-
};
|
|
7665
|
-
if (process.env.APEX_CLI_MOCK_SCREENSHOTS !== "partial") {
|
|
7666
|
-
for (const variation of exp.variations.slice(collisionPartial ? 2 : 1)) {
|
|
7667
|
-
results2[variation.id] = { desktop_chrome: "data:image/png;base64,iVBORw0KGgo=" };
|
|
7668
|
-
}
|
|
7669
|
-
}
|
|
7670
|
-
return { ok: true, status: 200, data: { job_id: "screenshot_job_1", experiment_id: exp.id, status: "completed", devices: ["desktop_chrome"], variation_ids: exp.variations.map((variation) => variation.id), results: results2 } };
|
|
7671
|
-
}
|
|
7672
|
-
if (url.pathname.startsWith("/api/v1/webhooks")) {
|
|
7673
|
-
const input = body5 ?? {};
|
|
7674
|
-
const webhook = {
|
|
7675
|
-
id: url.pathname.split("/")[4] || "webhook_1",
|
|
7676
|
-
url: String(input.url ?? "https://hooks.example.test/apex"),
|
|
7677
|
-
events: Array.isArray(input.events) ? input.events.map(String) : ["project.published"],
|
|
7678
|
-
active: input.active === void 0 ? true : input.active === true,
|
|
7679
|
-
hasSecret: true,
|
|
7680
|
-
consecutiveFailures: process.env.APEX_CLI_MOCK_WEBHOOK_DISABLED === "1" ? 3 : 1,
|
|
7681
|
-
disabledAt: process.env.APEX_CLI_MOCK_WEBHOOK_DISABLED === "1" ? "2026-08-05T12:00:00.000Z" : null,
|
|
7682
|
-
created_at: "2026-08-05T00:00:00.000Z"
|
|
7683
|
-
};
|
|
7684
|
-
const availableEvents = ["experiment.started", "experiment.completed", "experiment.significant", "experiment.guardian_triggered", "experiment.guardian_paused", "variation.winning", "screenshots.completed", "project.published"];
|
|
7685
|
-
if (method === "GET" && url.pathname === "/api/v1/webhooks") return { ok: true, status: 200, data: { data: [webhook], availableEvents, pagination: { has_more: false, next_cursor: null } } };
|
|
7686
|
-
if (method === "POST" && url.pathname === "/api/v1/webhooks") return { ok: true, status: 201, data: { ...webhook, secret: "whsec_mock_secret" } };
|
|
7687
|
-
if (method === "PATCH") return { ok: true, status: 200, data: webhook };
|
|
7688
|
-
if (method === "DELETE") return { ok: true, status: 200, data: { deleted: true, id: webhook.id } };
|
|
7689
|
-
if (method === "GET" && url.pathname.endsWith("/deliveries")) return { ok: true, status: 200, data: { webhook: { id: webhook.id, active: webhook.active, consecutiveFailures: webhook.consecutiveFailures, disabledAt: webhook.disabledAt }, data: [{ id: "delivery_1", webhook_id: webhook.id, event: "project.published", status: process.env.APEX_CLI_MOCK_WEBHOOK_DISABLED === "1" ? "dead" : "delivered", attempts: 1, response_status: process.env.APEX_CLI_MOCK_WEBHOOK_DISABLED === "1" ? 500 : 200, last_error: process.env.APEX_CLI_MOCK_WEBHOOK_DISABLED === "1" ? "Webhook responded with 500" : null, next_attempt_at: null, processing_started_at: null, delivered_at: "2026-08-05T00:01:00.000Z", created_at: "2026-08-05T00:00:00.000Z", updated_at: "2026-08-05T00:01:00.000Z" }], pagination: { has_more: false, next_cursor: null } } };
|
|
7690
|
-
if (method === "POST" && url.pathname.endsWith("/test")) return { ok: true, status: 200, data: { webhook: { id: webhook.id, active: webhook.active, consecutiveFailures: webhook.consecutiveFailures, disabledAt: webhook.disabledAt }, delivery: { id: "delivery_test", webhook_id: webhook.id, event: "test", status: "delivered", attempts: 1, response_status: 200, last_error: null, next_attempt_at: null, processing_started_at: null, delivered_at: "2026-08-05T00:01:00.000Z", created_at: "2026-08-05T00:00:00.000Z", updated_at: "2026-08-05T00:01:00.000Z" } } };
|
|
7691
|
-
}
|
|
7692
|
-
const forcedStatus = Number(process.env.APEX_CLI_MOCK_ERROR_STATUS ?? 0);
|
|
7693
|
-
if (forcedStatus > 0) {
|
|
7694
|
-
return {
|
|
7695
|
-
ok: false,
|
|
7696
|
-
status: forcedStatus,
|
|
7697
|
-
error: forcedStatus === 401 ? "Invalid API key" : "Resource not found",
|
|
7698
|
-
errorDetails: { mocked: true }
|
|
7699
|
-
};
|
|
7700
|
-
}
|
|
7701
|
-
if (method === "POST" && url.pathname.endsWith("/build") && process.env.APEX_CLI_MOCK_WORKSPACE_BUILD === "blocked-evidence") {
|
|
7702
|
-
return {
|
|
7703
|
-
ok: false,
|
|
7704
|
-
status: 400,
|
|
7705
|
-
error: "Generated custom JavaScript includes an external network or asset call.",
|
|
7706
|
-
data: {
|
|
7707
|
-
ok: false,
|
|
7708
|
-
error: "Generated custom JavaScript includes an external network or asset call.",
|
|
7709
|
-
blockingIssues: [
|
|
7710
|
-
{
|
|
7711
|
-
code: "external_network_call",
|
|
7712
|
-
severity: "blocking",
|
|
7713
|
-
field: "custom_js",
|
|
7714
|
-
message: "Generated custom JavaScript includes an external network or asset call.",
|
|
7715
|
-
evidence: {
|
|
7716
|
-
construct: "String-capable timer execution (fail-closed)",
|
|
7717
|
-
snippet: "setTimeout(unknownHandler, 10)",
|
|
7718
|
-
line: 7,
|
|
7719
|
-
column: 2
|
|
7720
|
-
}
|
|
7721
|
-
},
|
|
7722
|
-
{
|
|
7723
|
-
code: "external_network_call",
|
|
7724
|
-
severity: "blocking",
|
|
7725
|
-
field: "mutations[1].html",
|
|
7726
|
-
message: "Generated HTML includes an external network or asset URL.",
|
|
7727
|
-
evidence: {
|
|
7728
|
-
construct: "External HTML asset URL",
|
|
7729
|
-
url: "https://assets.example.test/pixel.png",
|
|
7730
|
-
matchIndex: 14
|
|
7731
|
-
}
|
|
7732
|
-
},
|
|
7733
|
-
{
|
|
7734
|
-
code: "invalid_custom_code_type",
|
|
7735
|
-
severity: "blocking",
|
|
7736
|
-
field: "custom_css",
|
|
7737
|
-
message: "custom_css must be a string."
|
|
7738
|
-
},
|
|
7739
|
-
{
|
|
7740
|
-
code: "price_node_mutation",
|
|
7741
|
-
severity: "blocking",
|
|
7742
|
-
field: ".product-price",
|
|
7743
|
-
message: "This mutation replaces a price-display node.",
|
|
7744
|
-
evidence: {
|
|
7745
|
-
selector: ".product-price"
|
|
7746
|
-
}
|
|
7747
|
-
},
|
|
7748
|
-
{
|
|
7749
|
-
code: "unknown_validation_family",
|
|
7750
|
-
severity: "blocking",
|
|
7751
|
-
field: "future_guardrail_field",
|
|
7752
|
-
message: "A future guardrail blocked this workspace."
|
|
7753
|
-
}
|
|
7754
|
-
]
|
|
7755
|
-
}
|
|
7756
|
-
};
|
|
7757
|
-
}
|
|
7758
|
-
if (method === "GET" && url.pathname === "/api/v1/roadmap/approved") {
|
|
7759
|
-
return {
|
|
7760
|
-
ok: true,
|
|
7761
|
-
status: 200,
|
|
7762
|
-
data: {
|
|
7763
|
-
data: [{ id: "roadmap_1", title: "Fixture roadmap idea", linked_experiments: [] }],
|
|
7764
|
-
pagination: { has_more: false, next_cursor: null }
|
|
7765
|
-
}
|
|
7766
|
-
};
|
|
7767
|
-
}
|
|
7768
|
-
if (method === "GET" && url.pathname === "/api/v1/roadmap/items/roadmap_1/handoff") {
|
|
7769
|
-
return {
|
|
7770
|
-
ok: true,
|
|
7771
|
-
status: 200,
|
|
7772
|
-
data: {
|
|
7773
|
-
id: "roadmap_1",
|
|
7774
|
-
title: "Fixture roadmap idea",
|
|
7775
|
-
hypothesis: "The fixture improves conversion",
|
|
7776
|
-
instructions: "Build the fixture"
|
|
7777
|
-
}
|
|
7778
|
-
};
|
|
7779
|
-
}
|
|
7780
|
-
if (method === "GET" && url.pathname === `/api/v1/experiments/${exp.id}/filesystem`) {
|
|
7781
|
-
if (process.env.APEX_CLI_MOCK_FILESYSTEM_REFRESH === "failed") {
|
|
7782
|
-
throw new Error("Mock filesystem refresh failed");
|
|
7783
|
-
}
|
|
7784
|
-
const fixtures = [
|
|
7785
|
-
["experiment.json", '{"schemaVersion":1,"name":"Fixture Test"}\n'],
|
|
7786
|
-
["targeting.json", '{"url":[],"segments":[]}\n'],
|
|
7787
|
-
["experiment/index.js", "window.__apexExperiment = true;\n"],
|
|
7788
|
-
["experiment/reset.js", "delete window.__apexExperiment;\n"],
|
|
7789
|
-
["experiment/index.css", ".apex-experiment { display: block; }\n"],
|
|
7790
|
-
["metrics.json", '{"primary":null,"secondary":[]}\n'],
|
|
7791
|
-
["qa.json", '{"status":"pending"}\n'],
|
|
7792
|
-
["launch.json", '{"status":"draft"}\n'],
|
|
7793
|
-
["variations/control/variation.json", '{"slug":"control","isControl":true}\n'],
|
|
7794
|
-
["variations/control/mutations.json", "[]\n"],
|
|
7795
|
-
["variations/control/url-blocks.json", "[]\n"],
|
|
7796
|
-
["variations/control/index.css", ".control { color: inherit; }\n"],
|
|
7797
|
-
["variations/control/index.js", "window.__apexControl = true;\n"],
|
|
7798
|
-
["variations/control/reset.js", "delete window.__apexControl;\n"],
|
|
7799
|
-
["variations/control/anti-flicker.json", "[]\n"]
|
|
7800
|
-
];
|
|
7801
|
-
const invalidPaths = {
|
|
7802
|
-
invalid: "../secret",
|
|
7803
|
-
traversal: "variations/../../x",
|
|
7804
|
-
absolute: "/abs/path"
|
|
7805
|
-
};
|
|
7806
|
-
const invalidPath = invalidPaths[process.env.APEX_CLI_MOCK_FILESYSTEM_PATHS ?? ""];
|
|
7807
|
-
if (invalidPath) {
|
|
7808
|
-
fixtures.push([invalidPath, "must not be written\n"]);
|
|
7809
|
-
}
|
|
7810
|
-
return {
|
|
7811
|
-
ok: true,
|
|
7812
|
-
status: 200,
|
|
7813
|
-
data: {
|
|
7814
|
-
rootName: "fixture-test",
|
|
7815
|
-
files: fixtures.map(([filePath, content]) => ({
|
|
7816
|
-
path: filePath,
|
|
7817
|
-
content,
|
|
7818
|
-
sha256: (0, import_node_crypto.createHash)("sha256").update(content).digest("hex")
|
|
7819
|
-
}))
|
|
7820
|
-
}
|
|
7821
|
-
};
|
|
7822
|
-
}
|
|
7823
|
-
const workspaceMatch = url.pathname.match(
|
|
7824
|
-
/^\/api\/v1\/experiments\/([^/]+)\/variations\/([^/]+)\/workspace$/
|
|
7825
|
-
);
|
|
7826
|
-
if (method === "GET" && workspaceMatch) {
|
|
7827
|
-
const content = "window.__apexOperator = true;\n";
|
|
7828
|
-
return {
|
|
7829
|
-
ok: true,
|
|
7830
|
-
status: 200,
|
|
7831
|
-
data: {
|
|
7832
|
-
workspace: {
|
|
7833
|
-
id: "workspace_1",
|
|
7834
|
-
schemaVersion: 1,
|
|
7835
|
-
status: "draft",
|
|
7836
|
-
currentRevisionId: "revision_1",
|
|
7837
|
-
updatedAt: "2026-08-05T00:00:00.000Z"
|
|
7838
|
-
},
|
|
7839
|
-
files: [
|
|
7840
|
-
{
|
|
7841
|
-
path: "src/index.js",
|
|
7842
|
-
kind: "custom_js",
|
|
7843
|
-
contentType: "application/javascript",
|
|
7844
|
-
content,
|
|
7845
|
-
sha256: (0, import_node_crypto.createHash)("sha256").update(content).digest("hex"),
|
|
7846
|
-
sizeBytes: Buffer.byteLength(content),
|
|
7847
|
-
updatedAt: "2026-08-05T00:00:00.000Z"
|
|
7848
|
-
}
|
|
7849
|
-
]
|
|
7850
|
-
}
|
|
7851
|
-
};
|
|
7852
|
-
}
|
|
7853
|
-
const configMatch = url.pathname.match(
|
|
7854
|
-
/^\/api\/v1\/(assets|landing-pages)(?:\/([^/]+))?$/
|
|
7855
|
-
);
|
|
7856
|
-
if (configMatch) {
|
|
7857
|
-
const [, family, id = family === "assets" ? "asset_1" : "landing_page_1"] = configMatch;
|
|
7858
|
-
const item = {
|
|
7859
|
-
id,
|
|
7860
|
-
name: family === "assets" ? "fixture.png" : "Fixture landing page",
|
|
7861
|
-
status: "draft",
|
|
7862
|
-
created_at: "2026-07-20T00:00:00.000Z"
|
|
7863
|
-
};
|
|
7864
|
-
if (method === "GET" && !configMatch[2])
|
|
7865
|
-
return {
|
|
7866
|
-
ok: true,
|
|
7867
|
-
status: 200,
|
|
7868
|
-
data: {
|
|
7869
|
-
data: [item],
|
|
7870
|
-
pagination: {
|
|
7871
|
-
has_more: false,
|
|
7872
|
-
next_cursor: url.searchParams.get("cursor")
|
|
7873
|
-
}
|
|
7874
|
-
}
|
|
7875
|
-
};
|
|
7876
|
-
if (method === "POST" && family === "assets" && body5 instanceof FormData) {
|
|
7877
|
-
if (process.env.APEX_CLI_MOCK_ASSET_STATUS === "413") {
|
|
7878
|
-
return {
|
|
7879
|
-
ok: false,
|
|
7880
|
-
status: 413,
|
|
7881
|
-
error: "Asset exceeds the upload size limit"
|
|
7882
|
-
};
|
|
7883
|
-
}
|
|
7884
|
-
const file = body5.get("files");
|
|
7885
|
-
return {
|
|
7886
|
-
ok: true,
|
|
7887
|
-
status: 201,
|
|
7888
|
-
data: {
|
|
7889
|
-
assets: [{ ...item, mime_type: file instanceof Blob ? file.type : "" }]
|
|
7890
|
-
}
|
|
7891
|
-
};
|
|
7892
|
-
}
|
|
7893
|
-
if (method === "DELETE" && family === "assets")
|
|
7894
|
-
return { ok: true, status: 200, data: { ok: true } };
|
|
7895
|
-
return { ok: true, status: 200, data: item };
|
|
7896
|
-
}
|
|
7897
|
-
const targetingMatch = url.pathname.match(
|
|
7898
|
-
/^\/api\/v1\/(pages|segments|exclusion-groups)(?:\/([^/]+))?(?:\/members)?$/
|
|
7899
|
-
);
|
|
7900
|
-
if (targetingMatch) {
|
|
7901
|
-
const [, kind5, id = `${kind5.replace(/s$/, "")}_1`] = targetingMatch;
|
|
7902
|
-
const input = body5 ?? {};
|
|
7903
|
-
const item = {
|
|
7904
|
-
id,
|
|
7905
|
-
name: String(input.name ?? `Fixture ${kind5}`),
|
|
7906
|
-
...kind5 === "exclusion-groups" ? { updatedAt: "2026-07-20T00:00:00.000Z" } : { updated_at: "2026-07-20T00:00:00.000Z" },
|
|
7907
|
-
...input
|
|
7908
|
-
};
|
|
7909
|
-
if (method === "GET" && !targetingMatch[2]) {
|
|
7910
|
-
return {
|
|
7911
|
-
ok: true,
|
|
7912
|
-
status: 200,
|
|
7913
|
-
data: {
|
|
7914
|
-
data: [item],
|
|
7915
|
-
pagination: {
|
|
7916
|
-
has_more: false,
|
|
7917
|
-
next_cursor: url.searchParams.get("cursor")
|
|
7918
|
-
}
|
|
7919
|
-
}
|
|
7920
|
-
};
|
|
7921
|
-
}
|
|
7922
|
-
return { ok: true, status: method === "POST" ? 201 : 200, data: item };
|
|
7923
|
-
}
|
|
7924
|
-
if (method === "POST" && url.pathname === "/api/v1/external-pixels/snippet")
|
|
7925
|
-
return {
|
|
7926
|
-
ok: true,
|
|
7927
|
-
status: 200,
|
|
7928
|
-
data: {
|
|
7929
|
-
snippet: "window.apexExternalPurchase=function(){};",
|
|
7930
|
-
provider: "external"
|
|
7931
|
-
}
|
|
7932
|
-
};
|
|
7933
|
-
const membershipMatch = url.pathname.match(/^\/api\/v1\/(org|team)(?:\/(members|invitations))?(?:\/([^/]+))?$/);
|
|
7934
|
-
if (membershipMatch) {
|
|
7935
|
-
const [, plane, collection, id] = membershipMatch;
|
|
7936
|
-
const input = body5 ?? {};
|
|
7937
|
-
const org = { id: "org_1", name: String(input.name ?? "Fixture organization"), slug: String(input.slug ?? "fixture-organization") };
|
|
7938
|
-
const member = { id: id ?? "member_1", user_id: "user_1", role: String(input.role ?? "member") };
|
|
7939
|
-
const invitation = { id: id ?? "invite_1", email: String(input.email ?? "member@example.com"), role: String(input.role ?? input.org_role ?? "viewer"), status: "pending" };
|
|
7940
|
-
if (plane === "org" && !collection) return { ok: true, status: 200, data: { data: org } };
|
|
7941
|
-
if (method === "DELETE") return { ok: true, status: 200, data: { success: true, id } };
|
|
7942
|
-
const value = collection === "invitations" ? invitation : member;
|
|
7943
|
-
return { ok: true, status: method === "POST" ? 201 : 200, data: method === "GET" ? { data: [value] } : { data: value } };
|
|
7944
|
-
}
|
|
7945
|
-
if (method === "POST" && url.pathname === "/api/v1/preview-sessions") return { ok: true, status: 200, data: { session_id: "preview_fixture", preview_url: "https://preview.drip-apex.com/api/preview?pst=fixture", expires_at: "2026-07-20T11:00:00.000Z" } };
|
|
7946
|
-
if (url.pathname === "/api/v1/admin/settings") return { ok: true, status: 200, data: { data: { guardian_enabled: method === "PATCH" ? Boolean(body5?.guardian_enabled ?? true) : true, srm_check_enabled: method === "PATCH" ? Boolean(body5?.srm_check_enabled ?? true) : true } } };
|
|
7947
|
-
if (method === "GET" && url.pathname === "/api/v1/admin/tracking/quarantine") return { ok: true, status: 200, data: { rows: [{ id: "q_1", shop_name: "Fixture shop", reason_code: "invalid_payload", verdict: "quarantined", created_at: "2026-07-20T10:00:00.000Z" }] } };
|
|
7948
|
-
if (method === "GET" && url.pathname === "/api/v1/audit-logs") return { ok: true, status: 200, data: { data: [{ id: "audit_1", created_at: "2026-07-20T10:00:00.000Z", action: "experiment.updated", entity_type: "experiment", shop_id: "11111111-1111-4111-8111-111111111112", org_id: null, user_id: "11111111-1111-4111-8111-111111111111", entity_id: exp.id, entity_name: exp.name, diff: null }], pagination: { total: 1, limit: 50, offset: 0, has_more: false } } };
|
|
7949
|
-
const shopMatch = url.pathname.match(
|
|
7950
|
-
/^\/api\/v1\/shops(?:\/([^/]+))?(?:\/(.*))?$/
|
|
7951
|
-
);
|
|
7952
|
-
if (shopMatch) {
|
|
7953
|
-
const input = body5 ?? {};
|
|
7954
|
-
const shop = {
|
|
7955
|
-
id: shopMatch[1] ?? "11111111-1111-4111-8111-111111111112",
|
|
7956
|
-
name: String(input.name ?? "Fixture Shop"),
|
|
7957
|
-
domain: String(input.domain ?? "fixture.example.com"),
|
|
7958
|
-
...input.platform ? { platform: String(input.platform) } : {},
|
|
7959
|
-
...input.plan ? { plan: String(input.plan) } : {},
|
|
7960
|
-
tier: String(input.tier ?? (input.plan === "self_serve" ? "self_managed" : "apex_managed")),
|
|
7961
|
-
created_at: "2026-07-20T00:00:00.000Z"
|
|
7962
|
-
};
|
|
7963
|
-
if (!shopMatch[1] && method === "GET")
|
|
7964
|
-
return { ok: true, status: 200, data: { data: [shop] } };
|
|
7965
|
-
if (shopMatch[2] === "store-profile")
|
|
7966
|
-
return {
|
|
7967
|
-
ok: true,
|
|
7968
|
-
status: 200,
|
|
7969
|
-
data: { schemaReady: true, shopId: shop.id, facts: [] }
|
|
7970
|
-
};
|
|
7971
|
-
if (shopMatch[2])
|
|
7972
|
-
return {
|
|
7973
|
-
ok: true,
|
|
7974
|
-
status: method === "POST" ? 201 : 200,
|
|
7975
|
-
data: { ok: true, area: shopMatch[2], ...input }
|
|
7976
|
-
};
|
|
7977
|
-
return {
|
|
7978
|
-
ok: true,
|
|
7979
|
-
status: method === "POST" ? 201 : 200,
|
|
7980
|
-
data: method === "PATCH" ? { ok: true, shop } : shop
|
|
7981
|
-
};
|
|
7982
|
-
}
|
|
7983
|
-
if (method === "GET" && url.pathname === "/api/v1/audit-logs")
|
|
7984
|
-
return {
|
|
7985
|
-
ok: true,
|
|
7986
|
-
status: 200,
|
|
7987
|
-
data: {
|
|
7988
|
-
data: [
|
|
7989
|
-
{
|
|
7990
|
-
id: "audit_1",
|
|
7991
|
-
created_at: "2026-07-20T10:00:00.000Z",
|
|
7992
|
-
action: "experiment.updated",
|
|
7993
|
-
entity_type: "experiment",
|
|
7994
|
-
shop_id: "11111111-1111-4111-8111-111111111112",
|
|
7995
|
-
org_id: null,
|
|
7996
|
-
user_id: "11111111-1111-4111-8111-111111111111",
|
|
7997
|
-
entity_id: exp.id,
|
|
7998
|
-
entity_name: exp.name,
|
|
7999
|
-
diff: null
|
|
8000
|
-
}
|
|
8001
|
-
],
|
|
8002
|
-
pagination: { total: 1, limit: 50, offset: 0, has_more: false }
|
|
8003
|
-
}
|
|
8004
|
-
};
|
|
8005
|
-
if (url.pathname === "/api/v1/inbox/messages") {
|
|
8006
|
-
const input = body5 ?? {};
|
|
8007
|
-
const message = {
|
|
8008
|
-
id: "message_1",
|
|
8009
|
-
role: "user",
|
|
8010
|
-
body: String(input.body ?? "Fixture inbox message"),
|
|
8011
|
-
sender_id: "11111111-1111-4111-8111-111111111111",
|
|
8012
|
-
created_at: "2026-07-20T10:00:00.000Z"
|
|
8013
|
-
};
|
|
8014
|
-
return {
|
|
8015
|
-
ok: true,
|
|
8016
|
-
status: method === "POST" ? 201 : 200,
|
|
8017
|
-
data: method === "POST" ? { message } : { messages: [message] }
|
|
8018
|
-
};
|
|
8019
|
-
}
|
|
8020
|
-
if (method === "POST" && url.pathname === "/api/v1/inbox/read-state")
|
|
8021
|
-
return {
|
|
8022
|
-
ok: true,
|
|
8023
|
-
status: 200,
|
|
8024
|
-
data: {
|
|
8025
|
-
ok: true,
|
|
8026
|
-
channelKeys: body5.channelKey ? [body5.channelKey] : ["account:account"],
|
|
8027
|
-
lastReadAt: "2026-07-20T10:00:00.000Z"
|
|
8028
|
-
}
|
|
8029
|
-
};
|
|
8030
|
-
const managedMatch = url.pathname.match(
|
|
8031
|
-
/^\/api\/v1\/(flags|personalizations)(?:\/([^/]+))?(?:\/(activate|pause|archive|status))?$/
|
|
8032
|
-
);
|
|
8033
|
-
if (managedMatch) {
|
|
8034
|
-
const [, kind5, id = `${kind5.slice(0, -1)}_1`, action70] = managedMatch;
|
|
8035
|
-
const input = body5 ?? {};
|
|
8036
|
-
const item = {
|
|
8037
|
-
id,
|
|
8038
|
-
key: String(input.key ?? `fixture-${kind5.slice(0, -1)}`),
|
|
8039
|
-
name: String(input.name ?? `Fixture ${kind5.slice(0, -1)}`),
|
|
8040
|
-
status: action70 === "activate" ? "active" : action70 === "pause" ? "paused" : action70 === "archive" ? "archived" : "draft",
|
|
8041
|
-
updated_at: "2026-07-19T00:00:00.000Z"
|
|
8042
|
-
};
|
|
8043
|
-
if (kind5 === "flags" && action70 === "status")
|
|
8044
|
-
return {
|
|
8045
|
-
ok: true,
|
|
8046
|
-
status: 200,
|
|
8047
|
-
data: {
|
|
8048
|
-
flag: item,
|
|
8049
|
-
publication_revision: 2,
|
|
8050
|
-
generated_at: "2026-07-19T00:00:00.000Z",
|
|
8051
|
-
config_urls: [
|
|
8052
|
-
"data:application/json,%7B%22publication_revision%22%3A2%2C%22generated_at%22%3A%222026-07-19T00%3A00%3A00.000Z%22%7D"
|
|
8053
|
-
]
|
|
8054
|
-
}
|
|
8055
|
-
};
|
|
8056
|
-
if (method === "GET" && !managedMatch[2]) {
|
|
8057
|
-
return {
|
|
8058
|
-
ok: true,
|
|
8059
|
-
status: 200,
|
|
8060
|
-
data: {
|
|
8061
|
-
data: [item],
|
|
8062
|
-
pagination: { has_more: false, next_cursor: null }
|
|
8063
|
-
}
|
|
8064
|
-
};
|
|
8065
|
-
}
|
|
8066
|
-
return {
|
|
8067
|
-
ok: true,
|
|
8068
|
-
status: method === "POST" && !managedMatch[2] ? 201 : 200,
|
|
8069
|
-
data: item
|
|
8070
|
-
};
|
|
8071
|
-
}
|
|
8072
|
-
const insightMatch = url.pathname.match(
|
|
8073
|
-
/^\/api\/v1\/(backlog|signals)(?:\/([^/]+))?(?:\/(archive|restore))?$/
|
|
8074
|
-
);
|
|
8075
|
-
if (insightMatch) {
|
|
8076
|
-
const [, kind5, id = `${kind5}_1`, action70] = insightMatch;
|
|
8077
|
-
const input = body5 ?? {};
|
|
8078
|
-
if (action70 && (kind5 !== "backlog" || method !== "POST"))
|
|
8079
|
-
return { ok: false, status: 405, error: `${action70} requires POST` };
|
|
8080
|
-
const archived = action70 === "archive" || method === "DELETE" || url.searchParams.get("include_archived") === "true";
|
|
8081
|
-
const item = {
|
|
8082
|
-
id,
|
|
8083
|
-
...kind5 === "backlog" ? {
|
|
8084
|
-
title: String(input.title ?? "Fixture backlog item"),
|
|
8085
|
-
archived_at: archived ? "2026-07-20T00:00:00.000Z" : null
|
|
8086
|
-
} : {
|
|
8087
|
-
key: String(input.key ?? "fixture-signal"),
|
|
8088
|
-
label: String(input.label ?? "Fixture signal")
|
|
8089
|
-
},
|
|
8090
|
-
status: archived ? "archived" : "active",
|
|
8091
|
-
updated_at: "2026-07-20T00:00:00.000Z"
|
|
8092
|
-
};
|
|
8093
|
-
if (method === "GET" && !insightMatch[2])
|
|
8094
|
-
return {
|
|
8095
|
-
ok: true,
|
|
8096
|
-
status: 200,
|
|
8097
|
-
data: {
|
|
8098
|
-
data: [item],
|
|
8099
|
-
pagination: { has_more: false, next_cursor: null }
|
|
8100
|
-
}
|
|
8101
|
-
};
|
|
8102
|
-
if (method === "DELETE")
|
|
8103
|
-
return { ok: true, status: 200, data: { ok: true } };
|
|
8104
|
-
return {
|
|
8105
|
-
ok: true,
|
|
8106
|
-
status: method === "POST" && !action70 ? 201 : 200,
|
|
8107
|
-
data: item
|
|
8108
|
-
};
|
|
8109
|
-
}
|
|
8110
|
-
if (method === "GET" && url.pathname === "/api/v1/experiments") {
|
|
8111
|
-
const experiment = url.searchParams.get("archived") === "1" ? { ...exp, archived_at: "2026-07-20T00:00:00.000Z" } : { ...exp, archived_at: null };
|
|
8112
|
-
return {
|
|
8113
|
-
ok: true,
|
|
8114
|
-
status: 200,
|
|
8115
|
-
data: {
|
|
8116
|
-
data: [experiment],
|
|
8117
|
-
pagination: { has_more: false, next_cursor: null }
|
|
8118
|
-
}
|
|
8119
|
-
};
|
|
8120
|
-
}
|
|
8121
|
-
if (method === "POST" && url.pathname === `/api/v1/experiments/${exp.id}/start`) {
|
|
8122
|
-
return { ok: true, status: 200, data: { ...exp, status: "running" } };
|
|
8123
|
-
}
|
|
8124
|
-
if (method === "POST" && url.pathname === "/api/v1/experiments") {
|
|
8125
|
-
const input = body5 ?? {};
|
|
8126
|
-
if (input.experiment_type === "redirect" && process.env.APEX_CLI_MOCK_REDIRECT_DISABLED === "1") {
|
|
8127
|
-
return {
|
|
8128
|
-
ok: false,
|
|
8129
|
-
status: 403,
|
|
8130
|
-
error: "Redirect tests are not enabled for this shop"
|
|
8131
|
-
};
|
|
8132
|
-
}
|
|
8133
|
-
const requestedVariations = Array.isArray(input.variations) ? input.variations : exp.variations ?? [];
|
|
8134
|
-
return {
|
|
8135
|
-
ok: true,
|
|
8136
|
-
status: 201,
|
|
8137
|
-
data: {
|
|
8138
|
-
...exp,
|
|
8139
|
-
name: String(input.name ?? exp.name),
|
|
8140
|
-
slug: typeof input.slug === "string" || input.slug === null ? input.slug : exp.slug,
|
|
8141
|
-
experiment_type: input.experiment_type === "redirect" ? "redirect" : "mutation",
|
|
8142
|
-
traffic_allocation: typeof input.traffic_allocation === "number" ? input.traffic_allocation : exp.traffic_allocation,
|
|
8143
|
-
store_url: typeof input.store_url === "string" ? input.store_url : null,
|
|
8144
|
-
targeting: input.targeting ?? null,
|
|
8145
|
-
variations: requestedVariations.map((variation, index) => ({
|
|
8146
|
-
id: `00000000-0000-4000-8000-${String(index + 1).padStart(12, "0")}`,
|
|
8147
|
-
name: String(variation.name ?? `Variation ${index + 1}`),
|
|
8148
|
-
weight: typeof variation.weight === "number" ? variation.weight : 0,
|
|
8149
|
-
is_control: variation.is_control === true,
|
|
8150
|
-
redirect_url: typeof variation.redirect_url === "string" ? variation.redirect_url : null,
|
|
8151
|
-
mutations: []
|
|
8152
|
-
}))
|
|
8153
|
-
}
|
|
8154
|
-
};
|
|
8155
|
-
}
|
|
8156
|
-
if (method === "GET" && (url.pathname === `/api/v1/experiments/${exp.id}` || url.pathname === `/api/v1/experiments/by-slug/${exp.slug}`)) {
|
|
8157
|
-
return { ok: true, status: 200, data: exp };
|
|
8158
|
-
}
|
|
8159
|
-
if (method === "GET" && url.pathname === `/api/v1/experiments/${exp.id}/results`) {
|
|
8160
|
-
const withheld = process.env.APEX_CLI_MOCK_RESULTS_STATE === "untrustworthy";
|
|
8161
|
-
return {
|
|
8162
|
-
ok: true,
|
|
8163
|
-
status: 200,
|
|
8164
|
-
data: {
|
|
8165
|
-
experiment: { id: exp.id, name: exp.name, status: exp.status },
|
|
8166
|
-
totals: { visitors: 128, primaryConversions: 18, revenue: 4200 },
|
|
8167
|
-
arms: [
|
|
8168
|
-
{
|
|
8169
|
-
variationId: exp.variations?.[0]?.id,
|
|
8170
|
-
name: "Control",
|
|
8171
|
-
isControl: true,
|
|
8172
|
-
visitors: 64,
|
|
8173
|
-
primaryConversions: 8,
|
|
8174
|
-
primaryRate: 0.125,
|
|
8175
|
-
revenue: 2e3
|
|
8176
|
-
},
|
|
8177
|
-
{
|
|
8178
|
-
variationId: exp.variations?.[1]?.id,
|
|
8179
|
-
name: "Trust Badge",
|
|
8180
|
-
isControl: false,
|
|
8181
|
-
visitors: 64,
|
|
8182
|
-
primaryConversions: 10,
|
|
8183
|
-
primaryRate: 0.15625,
|
|
8184
|
-
revenue: 2200
|
|
8185
|
-
}
|
|
8186
|
-
],
|
|
8187
|
-
decision: {
|
|
8188
|
-
state: withheld ? "untrustworthy" : "collecting",
|
|
8189
|
-
treatmentName: "Trust Badge",
|
|
8190
|
-
lift: 25,
|
|
8191
|
-
sequentialConfidence: withheld ? 0 : 62.5,
|
|
8192
|
-
fixedConfidence: withheld ? 0 : 84,
|
|
8193
|
-
judgedConfidence: withheld ? 0 : 84,
|
|
8194
|
-
basis: withheld ? "sequential" : "fixed_horizon",
|
|
8195
|
-
blockingGates: withheld ? ["mixed-currency"] : []
|
|
8196
|
-
},
|
|
8197
|
-
effective_outcome: {
|
|
8198
|
-
outcome: withheld ? "needs_readout" : "winner",
|
|
8199
|
-
statisticsOutcome: withheld ? "needs_readout" : "collecting",
|
|
8200
|
-
provenance: withheld ? "statistics" : "manager_decided"
|
|
8201
|
-
},
|
|
8202
|
-
manager_decision: withheld ? null : {
|
|
8203
|
-
outcome: "winner",
|
|
8204
|
-
reason: "The business case is strong enough to ship.",
|
|
8205
|
-
decided_by: { id: "user_1", name: "Maya Manager" },
|
|
8206
|
-
decided_at: "2026-07-31T09:00:00.000Z",
|
|
8207
|
-
version: 1
|
|
8208
|
-
},
|
|
8209
|
-
metrics: [],
|
|
8210
|
-
series: [],
|
|
8211
|
-
warnings: withheld ? [{ code: "mixed-currency", message: "Mixed currencies block the verdict." }] : []
|
|
8212
|
-
}
|
|
8213
|
-
};
|
|
8214
|
-
}
|
|
8215
|
-
if (method === "GET" && url.pathname === `/api/v1/experiments/${exp.id}/qa/run`) {
|
|
8216
|
-
return {
|
|
8217
|
-
ok: true,
|
|
8218
|
-
status: 200,
|
|
8219
|
-
data: {
|
|
8220
|
-
run: {
|
|
8221
|
-
id: "qa_run_1",
|
|
8222
|
-
status: "in_review",
|
|
8223
|
-
checklist: [],
|
|
8224
|
-
urls: [],
|
|
8225
|
-
automatedEvidence: null,
|
|
8226
|
-
acceptanceCriteriaSummary: null,
|
|
8227
|
-
goalVerification: null,
|
|
8228
|
-
summary: {
|
|
8229
|
-
status: "in_review",
|
|
8230
|
-
required: 0,
|
|
8231
|
-
completedRequired: 0,
|
|
8232
|
-
failedRequired: 0,
|
|
8233
|
-
optional: 0,
|
|
8234
|
-
completedOptional: 0,
|
|
8235
|
-
blockers: []
|
|
8236
|
-
},
|
|
8237
|
-
notes: null,
|
|
8238
|
-
createdAt: "2026-07-04T00:00:00.000Z",
|
|
8239
|
-
updatedAt: "2026-07-04T00:00:00.000Z",
|
|
8240
|
-
approvedAt: null,
|
|
8241
|
-
approvedBy: null,
|
|
8242
|
-
completedAt: null
|
|
8243
|
-
}
|
|
8244
|
-
}
|
|
8245
|
-
};
|
|
8246
|
-
}
|
|
8247
|
-
if (method === "PUT" && url.pathname === `/api/v1/experiments/by-slug/${exp.slug}`) {
|
|
8248
|
-
const failureStatus = Number(
|
|
8249
|
-
process.env.APEX_CLI_MOCK_UPSERT_FAILURE_STATUS ?? 0
|
|
8250
|
-
);
|
|
8251
|
-
if (failureStatus > 0) {
|
|
8252
|
-
return {
|
|
8253
|
-
ok: false,
|
|
8254
|
-
status: failureStatus,
|
|
8255
|
-
error: `Mock upsert failed with HTTP ${failureStatus}`
|
|
8256
|
-
};
|
|
8257
|
-
}
|
|
8258
|
-
return { ok: true, status: 200, data: exp };
|
|
8259
|
-
}
|
|
8260
|
-
if (method === "POST" && url.pathname.endsWith("/filesystem/plan")) {
|
|
8261
|
-
const files = body5?.files ?? [];
|
|
8262
|
-
if (process.env.APEX_CLI_MOCK_FILESYSTEM_PLAN === "invalid") {
|
|
8263
|
-
return {
|
|
8264
|
-
ok: true,
|
|
8265
|
-
status: 200,
|
|
8266
|
-
data: {
|
|
8267
|
-
ok: false,
|
|
8268
|
-
experimentId: exp.id,
|
|
8269
|
-
status: "invalid",
|
|
8270
|
-
changes: [],
|
|
8271
|
-
errors: ["Mock filesystem plan error"],
|
|
8272
|
-
warnings: [],
|
|
8273
|
-
invalidatesQa: false,
|
|
8274
|
-
precompileRequired: false
|
|
8275
|
-
}
|
|
8276
|
-
};
|
|
8277
|
-
}
|
|
8278
|
-
if (["blocking-only", "blocking-many"].includes(process.env.APEX_CLI_MOCK_FILESYSTEM_PLAN ?? "")) {
|
|
8279
|
-
const blockingIssues = [{
|
|
8280
|
-
code: "custom_js_parse_error",
|
|
8281
|
-
severity: "blocking",
|
|
8282
|
-
message: "Custom JavaScript does not parse.",
|
|
8283
|
-
path: "variations/control/index.js",
|
|
8284
|
-
variation: "control"
|
|
8285
|
-
}];
|
|
8286
|
-
if (process.env.APEX_CLI_MOCK_FILESYSTEM_PLAN === "blocking-many") {
|
|
8287
|
-
blockingIssues.push(
|
|
8288
|
-
{ ...blockingIssues[0], code: "second_error", message: "Second blocking issue." },
|
|
8289
|
-
{ ...blockingIssues[0], code: "third_error", message: "Third blocking issue." }
|
|
8290
|
-
);
|
|
8291
|
-
}
|
|
8292
|
-
return {
|
|
8293
|
-
ok: true,
|
|
8294
|
-
status: 200,
|
|
8295
|
-
data: {
|
|
8296
|
-
ok: false,
|
|
8297
|
-
experimentId: exp.id,
|
|
8298
|
-
status: "invalid",
|
|
8299
|
-
changes: [],
|
|
8300
|
-
errors: [],
|
|
8301
|
-
warnings: [],
|
|
8302
|
-
blockingIssues,
|
|
8303
|
-
invalidatesQa: false,
|
|
8304
|
-
precompileRequired: false
|
|
8305
|
-
}
|
|
8306
|
-
};
|
|
8307
|
-
}
|
|
8308
|
-
const observedPaths = files.map((file) => file.path).filter((filePath) => typeof filePath === "string");
|
|
8309
|
-
return {
|
|
8310
|
-
ok: true,
|
|
8311
|
-
status: 200,
|
|
8312
|
-
data: {
|
|
8313
|
-
ok: true,
|
|
8314
|
-
experimentId: exp.id,
|
|
8315
|
-
status: "ok",
|
|
8316
|
-
changes: files.length ? [
|
|
8317
|
-
{
|
|
8318
|
-
path: "experiment.json",
|
|
8319
|
-
entity: "experiment",
|
|
8320
|
-
action: "update",
|
|
8321
|
-
summary: "Fixture plan change",
|
|
8322
|
-
invalidatesQa: false
|
|
8323
|
-
}
|
|
8324
|
-
] : [],
|
|
8325
|
-
errors: [],
|
|
8326
|
-
warnings: [],
|
|
8327
|
-
invalidatesQa: false,
|
|
8328
|
-
precompileRequired: false,
|
|
8329
|
-
...process.env.APEX_CLI_MOCK_FILESYSTEM_PLAN === "echo-paths" ? { observedPaths } : {}
|
|
8330
|
-
}
|
|
8331
|
-
};
|
|
8332
|
-
}
|
|
8333
|
-
if (method === "POST" && url.pathname.endsWith("/filesystem/apply")) {
|
|
8334
|
-
if (process.env.APEX_CLI_MOCK_FILESYSTEM_APPLY === "failed-with-plan") {
|
|
8335
|
-
return {
|
|
8336
|
-
ok: false,
|
|
8337
|
-
status: 409,
|
|
8338
|
-
error: "Mock apply failed",
|
|
8339
|
-
data: {
|
|
8340
|
-
applied: false,
|
|
8341
|
-
plan: {
|
|
8342
|
-
ok: false,
|
|
8343
|
-
experimentId: exp.id,
|
|
8344
|
-
status: "invalid",
|
|
8345
|
-
changes: [],
|
|
8346
|
-
errors: ["Mock apply plan error"],
|
|
8347
|
-
warnings: [],
|
|
8348
|
-
invalidatesQa: false,
|
|
8349
|
-
precompileRequired: false
|
|
8350
|
-
}
|
|
8351
|
-
}
|
|
8352
|
-
};
|
|
8353
|
-
}
|
|
8354
|
-
return {
|
|
8355
|
-
ok: true,
|
|
8356
|
-
status: 200,
|
|
8357
|
-
data: {
|
|
8358
|
-
applied: true,
|
|
8359
|
-
plan: {
|
|
8360
|
-
ok: true,
|
|
8361
|
-
experimentId: exp.id,
|
|
8362
|
-
status: "ok",
|
|
8363
|
-
changes: [],
|
|
8364
|
-
errors: [],
|
|
8365
|
-
warnings: [],
|
|
8366
|
-
invalidatesQa: false,
|
|
8367
|
-
precompileRequired: false
|
|
8368
|
-
}
|
|
8369
|
-
}
|
|
8370
|
-
};
|
|
8371
|
-
}
|
|
8372
|
-
if (url.pathname === "/api/v1/environments" && (method === "GET" || method === "PATCH")) {
|
|
8373
|
-
if (method === "GET") mockEnvironmentReads += 1;
|
|
8374
|
-
if (method === "PATCH") {
|
|
8375
|
-
mockEnvironmentPatchAttempts += 1;
|
|
8376
|
-
const conflictMode = process.env.APEX_CLI_MOCK_ENVIRONMENT_CONFLICT;
|
|
8377
|
-
if (conflictMode === "always" || conflictMode === "once" && mockEnvironmentPatchAttempts === 1) {
|
|
8378
|
-
return {
|
|
8379
|
-
ok: false,
|
|
8380
|
-
status: 409,
|
|
8381
|
-
error: "Runtime settings changed since read \u2014 re-read and retry"
|
|
8382
|
-
};
|
|
8383
|
-
}
|
|
8384
|
-
}
|
|
8385
|
-
const defaultItems = [
|
|
8386
|
-
{
|
|
8387
|
-
key: "production",
|
|
8388
|
-
name: "Production",
|
|
8389
|
-
isDefault: true,
|
|
8390
|
-
subSnippetEnabled: false,
|
|
8391
|
-
domains: ["www.example.com"]
|
|
8392
|
-
},
|
|
8393
|
-
{
|
|
8394
|
-
key: "staging",
|
|
8395
|
-
name: "Staging",
|
|
8396
|
-
isDefault: false,
|
|
8397
|
-
subSnippetEnabled: true,
|
|
8398
|
-
// The dev-bound doctor fixture binds a development host to a
|
|
8399
|
-
// non-production environment so the doctor can prove it reports the
|
|
8400
|
-
// binding instead of staying silent.
|
|
8401
|
-
domains: process.env.APEX_CLI_MOCK_DOCTOR_STATE === "dev-bound" ? ["staging.example.dev"] : []
|
|
8402
|
-
}
|
|
8403
|
-
];
|
|
8404
|
-
const items = body5?.environments?.items ?? defaultItems;
|
|
8405
|
-
const precompile = process.env.APEX_CLI_MOCK_PRECOMPILE_ERROR === "1" ? { status: "error", summary: "Mock edge precompile failed" } : { status: "ok", summary: "Mock edge precompile succeeded" };
|
|
8406
|
-
return {
|
|
8407
|
-
ok: true,
|
|
8408
|
-
status: 200,
|
|
8409
|
-
data: {
|
|
8410
|
-
environments: { activeKey: "production", items },
|
|
8411
|
-
version: `2026-07-19T10:0${mockEnvironmentReads}:00.000Z`,
|
|
8412
|
-
...method === "PATCH" ? { precompile } : {}
|
|
8413
|
-
}
|
|
8414
|
-
};
|
|
8415
|
-
}
|
|
8416
|
-
if (method === "GET" && url.pathname === "/api/v1/analytics/url-inventory") {
|
|
8417
|
-
if (process.env.APEX_CLI_MOCK_URL_INVENTORY === "paged") {
|
|
8418
|
-
const offset2 = Number(url.searchParams.get("offset") ?? "0");
|
|
8419
|
-
const pages = {
|
|
8420
|
-
"0": { items: [{ host: "example.com", path: "/products/page-one", pageviews: 42, visitors: 30 }], total: 2, truncated: true },
|
|
8421
|
-
"200": { items: [{ host: "example.com", path: "/products/page-two", pageviews: 8, visitors: 6 }], total: 2, truncated: false }
|
|
8422
|
-
};
|
|
8423
|
-
const page = pages[String(offset2)] ?? { items: [{ host: "example.com", path: "/products/overrun", pageviews: 1, visitors: 1 }], total: 2, truncated: true };
|
|
8424
|
-
return { ok: true, status: 200, data: page };
|
|
8425
|
-
}
|
|
8426
|
-
const state = process.env.APEX_CLI_MOCK_DOCTOR_STATE ?? "green";
|
|
8427
|
-
const inventories = {
|
|
8428
|
-
prefix: [
|
|
8429
|
-
{ host: "getkong.shop", path: "/", pageviews: 6e4, visitors: 2e4 },
|
|
8430
|
-
{ host: "getkong.delivery", path: "/checkout", pageviews: 48e3, visitors: 17e3 },
|
|
8431
|
-
{ host: "preview-abc-drip.vercel.app", path: "/", pageviews: 42, visitors: 12 }
|
|
8432
|
-
],
|
|
8433
|
-
// Production-labeled traffic from one development host bound to the
|
|
8434
|
-
// staging environment and one bound nowhere at all.
|
|
8435
|
-
"dev-bound": [
|
|
8436
|
-
{ host: "www.example.com", path: "/", pageviews: 100, visitors: 80 },
|
|
8437
|
-
// A production storefront on the public .dev TLD: real traffic, not
|
|
8438
|
-
// development pollution.
|
|
8439
|
-
{ host: "store.example.dev", path: "/", pageviews: 90, visitors: 70 },
|
|
8440
|
-
{ host: "staging.example.dev", path: "/", pageviews: 22, visitors: 9 },
|
|
8441
|
-
{ host: "preview-xyz-drip.vercel.app", path: "/", pageviews: 15, visitors: 6 }
|
|
8442
|
-
]
|
|
8443
|
-
};
|
|
8444
|
-
const offset = Number(url.searchParams.get("offset") ?? 0);
|
|
8445
|
-
if (state === "paginated") {
|
|
8446
|
-
const pages = [
|
|
8447
|
-
{ items: [{ host: "www.example.com", path: "/", pageviews: 100, visitors: 80 }], truncated: true },
|
|
8448
|
-
{ items: [{ host: "preview-page-two.vercel.app", path: "/", pageviews: 12, visitors: 5 }], truncated: false }
|
|
8449
|
-
];
|
|
8450
|
-
const page = pages[Math.min(Math.floor(offset / 500), pages.length - 1)];
|
|
8451
|
-
return { ok: true, status: 200, data: { items: page.items, total: 2, truncated: page.truncated } };
|
|
8452
|
-
}
|
|
8453
|
-
if (state === "truncated") {
|
|
8454
|
-
const items2 = Array.from({ length: 500 }, (_, index) => ({ host: "www.example.com", path: `/p/${offset + index}`, pageviews: 3, visitors: 2 }));
|
|
8455
|
-
return { ok: true, status: 200, data: { items: items2, total: 1e5, truncated: true } };
|
|
8456
|
-
}
|
|
8457
|
-
const baseItems = [
|
|
8458
|
-
{ host: "www.example.com", path: "/", pageviews: 100, visitors: 80 },
|
|
8459
|
-
{ host: "example.com", path: "/products/demo", pageviews: 40, visitors: 30 }
|
|
8460
|
-
];
|
|
8461
|
-
const bestellung = url.searchParams.get("days") === "30" ? { host: "example.com", path: "/bestellung-abgeschlossen", pageviews: 12, visitors: 10 } : { host: "getkong.test", path: "/bestellung-abgeschlossen", pageviews: 12, visitors: 10 };
|
|
8462
|
-
const items = inventories[state] ?? [...baseItems, bestellung];
|
|
8463
|
-
const hostFilter = url.searchParams.get("host")?.trim().toLowerCase();
|
|
8464
|
-
const pathContains = url.searchParams.get("pathContains") ?? "";
|
|
8465
|
-
const filtered = items.filter(
|
|
8466
|
-
(item) => (!hostFilter || item.host.toLowerCase() === hostFilter) && (!pathContains || item.path.includes(pathContains))
|
|
8467
|
-
);
|
|
8468
|
-
return { ok: true, status: 200, data: { items: filtered, total: filtered.length, truncated: false } };
|
|
8469
|
-
}
|
|
8470
|
-
if (method === "GET" && url.pathname === "/api/v1/analytics/visitor-series") {
|
|
8471
|
-
if (process.env.APEX_CLI_MOCK_DOCTOR_STATE === "series-down") {
|
|
8472
|
-
return { ok: false, status: 503, error: "Analytics is temporarily unavailable" };
|
|
8473
|
-
}
|
|
8474
|
-
return { ok: true, status: 200, data: { items: [
|
|
8475
|
-
{ date: "2026-07-28", visitors: 100, pageviews: 200 },
|
|
8476
|
-
{ date: "2026-07-29", visitors: 110, pageviews: 220 },
|
|
8477
|
-
{ date: "2026-08-03", visitors: 70, pageviews: 140 },
|
|
8478
|
-
{ date: "2026-08-04", visitors: 77, pageviews: 154 }
|
|
8479
|
-
] } };
|
|
8480
|
-
}
|
|
8481
|
-
if (url.pathname === "/api/v1/consent" && (method === "GET" || method === "PATCH")) {
|
|
8482
|
-
const patch = body5;
|
|
8483
|
-
const enabled = typeof patch?.consentMode?.enabled === "boolean" ? patch.consentMode.enabled : true;
|
|
8484
|
-
const provider = typeof patch?.consentMode?.provider === "string" ? patch.consentMode.provider : "cookiescript";
|
|
8485
|
-
const requiredGroup = typeof patch?.consentMode?.requiredGroup === "string" ? patch.consentMode.requiredGroup : "analytics";
|
|
8486
|
-
const getter = typeof patch?.consentMode?.getter === "string" ? patch.consentMode.getter : "";
|
|
8487
|
-
if (method === "PATCH" && patch?.applyCanonicalSplit === false) {
|
|
8488
|
-
return { ok: false, status: 403, error: "applyCanonicalSplit: false is not permitted with the consent:write scope \u2014 clear those prerequisites in the dashboard (shop:settings)." };
|
|
8489
|
-
}
|
|
8490
|
-
return { ok: true, status: 200, data: {
|
|
8491
|
-
consentMode: { enabled, provider, requiredGroup, getter },
|
|
8492
|
-
prerequisites: {
|
|
8493
|
-
showVariationsRule: "return true;",
|
|
8494
|
-
startTrackingRule: "return drip.get('trackingConsentEnabled');"
|
|
8495
|
-
},
|
|
8496
|
-
...method === "PATCH" ? { precompile: { status: "ok", summary: "Mock edge precompile succeeded" } } : {}
|
|
8497
|
-
} };
|
|
8498
|
-
}
|
|
8499
|
-
if (url.pathname === "/api/v1/install-domains" && (method === "GET" || method === "PATCH")) {
|
|
8500
|
-
const conflictMode = process.env.APEX_CLI_MOCK_DOMAINS_CONFLICT;
|
|
8501
|
-
const serverDomains = conflictMode === "once" && mockDomainsPatchAttempts >= 1 ? ["shop.example.com", "peer.example.com"] : ["shop.example.com"];
|
|
8502
|
-
if (method === "PATCH") {
|
|
8503
|
-
mockDomainsPatchAttempts += 1;
|
|
8504
|
-
if (conflictMode === "always" || conflictMode === "once" && mockDomainsPatchAttempts === 1) {
|
|
8505
|
-
return { ok: false, status: 409, error: "Install domains changed since read \u2014 re-read and retry" };
|
|
8506
|
-
}
|
|
8507
|
-
}
|
|
8508
|
-
const domains = body5?.domains ?? serverDomains;
|
|
8509
|
-
const additionalDomains = method === "PATCH" ? domains : serverDomains;
|
|
8510
|
-
return { ok: true, status: 200, data: {
|
|
8511
|
-
domain: "example.com",
|
|
8512
|
-
additionalDomains,
|
|
8513
|
-
storeOrigins: ["https://example.com", "https://www.example.com", ...additionalDomains.map((domain) => `https://${domain}`)],
|
|
8514
|
-
revision: method === "PATCH" ? "rev_patched" : `rev_${serverDomains.length}`,
|
|
8515
|
-
...method === "PATCH" ? { precompile: { status: "ok", summary: "Mock edge precompile succeeded" } } : {}
|
|
8516
|
-
} };
|
|
8517
|
-
}
|
|
8518
|
-
if (url.pathname === "/api/v1/external-tracking" && (method === "GET" || method === "PATCH")) {
|
|
8519
|
-
const current = { domains: ["www.example.com", "shop.example.com"], handoverQueryParam: "drip_bucket", handoverTtlSeconds: 120, handoverMintTimeoutMs: 250, signed: true };
|
|
8520
|
-
const patch = body5?.externalTracking;
|
|
8521
|
-
const next = { ...current, ...patch ?? {} };
|
|
8522
|
-
return { ok: true, status: 200, data: { externalTracking: next, revision: "2026-08-05T10:00:00.000Z", ...method === "PATCH" ? { precompile: { status: "ok", summary: "Mock edge precompile succeeded" } } : {} } };
|
|
8523
|
-
}
|
|
8524
|
-
const analyticsMatch = url.pathname.match(/^\/api\/v1\/analytics\/(stats|timeseries|traffic|pages|geo|sessions|web-vitals|realtime|entry-exit)$/);
|
|
8525
|
-
if (method === "GET" && analyticsMatch) {
|
|
8526
|
-
const fixtures = {
|
|
8527
|
-
stats: [{ date: "2026-07-19", visitors: 120, conversions: 12, revenue: 540, foreign_revenue_buckets: [], converted_revenue: null, excluded_foreign_revenue: null, unconverted_revenue: null, unknown_currency_orders: null, unknown_currency_revenue: null }],
|
|
8528
|
-
timeseries: [{ date: "2026-07-19", visitors: 120, sessions: 132, pageviews: 280, conversions: 12, revenue: 540 }],
|
|
8529
|
-
traffic: [{ referrer: "google.com", utm_source: "google", utm_medium: "organic", utm_campaign: "", visitors: 80, sessions: 88, pageviews: 160 }],
|
|
8530
|
-
pages: [{ page_url: "/products/demo", unique_visitors: 90, pageviews: 140, planning_days: Number(url.searchParams.get("planningDays") ?? 180) }],
|
|
8531
|
-
geo: [{ country: "DE", device: "desktop", visitors: 70, sessions: 76, pageviews: 150 }],
|
|
8532
|
-
sessions: [{ total_sessions: 132, avg_pages_per_session: 2.1, avg_session_duration_seconds: 94, bounce_rate: 0.32, median_pages_per_session: 2 }],
|
|
8533
|
-
"web-vitals": [{ metric: "LCP", route: "/products/demo", p75: 1800, sample_count: 40, good_count: 35, poor_count: 2 }],
|
|
8534
|
-
realtime: { summary: { active_visitors: 4, active_sessions: 5, events_last_5min: 18 }, activeVisitors: [], recentEvents: [] },
|
|
8535
|
-
"entry-exit": [{ type: "entry", page_url: "/", sessions: 45 }]
|
|
8536
|
-
};
|
|
8537
|
-
return { ok: true, status: 200, data: { data: fixtures[analyticsMatch[1]] } };
|
|
8538
|
-
}
|
|
8539
|
-
if (method === "POST" && url.pathname === "/api/v1/analytics/suggest-goals") {
|
|
8540
|
-
return { ok: true, status: 200, data: { goals: [{ name: "Purchase", type: "pageview", urlPattern: "/bestellung-abgeschlossen", explanation: "Fixture" }], funnels: [], triggers: [] } };
|
|
8541
|
-
}
|
|
8542
|
-
if (method === "GET" && url.pathname === "/api/v1/funnels") {
|
|
8543
|
-
return { ok: true, status: 200, data: { data: [mockFunnel()] } };
|
|
8544
|
-
}
|
|
8545
|
-
if (method === "POST" && url.pathname === "/api/v1/funnels") {
|
|
8546
|
-
return { ok: true, status: 201, data: { ...mockFunnel(), ...body5 } };
|
|
8547
|
-
}
|
|
8548
|
-
if (method === "GET" && url.pathname === `/api/v1/funnels/${mockFunnel().id}`) {
|
|
8549
|
-
return { ok: true, status: 200, data: mockFunnel() };
|
|
8550
|
-
}
|
|
8551
|
-
if (method === "PATCH" && url.pathname === `/api/v1/funnels/${mockFunnel().id}`) {
|
|
8552
|
-
return { ok: true, status: 200, data: { ...mockFunnel(), ...body5 } };
|
|
8553
|
-
}
|
|
8554
|
-
if (method === "DELETE" && url.pathname === `/api/v1/funnels/${mockFunnel().id}`) {
|
|
8555
|
-
return { ok: true, status: 200, data: { ok: true } };
|
|
8556
|
-
}
|
|
8557
|
-
if (method === "GET" && url.pathname === `/api/v1/funnels/${mockFunnel().id}/run`) {
|
|
8558
|
-
return { ok: true, status: 200, data: { total_sessions_analyzed: 2, steps: [{ step: 1, label: "Collection", url_pattern: "/collections/*", visitors: 2, drop_off_rate: 0, matched_pageviews: 2 }] } };
|
|
8559
|
-
}
|
|
8560
|
-
if (method === "GET" && url.pathname.endsWith("/workspace")) {
|
|
8561
|
-
const files = [
|
|
8562
|
-
["experiment.config.json", JSON.stringify({ name: exp.name, storeUrl: exp.store_url, experimentSlug: exp.slug, variationSlug: "badge" }, null, 2) + "\n", "application/json", "config"],
|
|
8563
|
-
["metrics.json", '{"primary":null,"secondary":[]}\n', "application/json", "metrics"],
|
|
8564
|
-
["targeting.json", '{"url":[],"segments":[]}\n', "application/json", "targeting"],
|
|
8565
|
-
["src/index.js", "window.__apexOperatorFixture = true;\n", "application/javascript", "custom_js"],
|
|
8566
|
-
["src/index.scss", ".apex-operator-fixture { display: block; }\n", "text/x-scss", "custom_css"],
|
|
8567
|
-
["src/mutations.json", "[]\n", "application/json", "mutations"],
|
|
8568
|
-
["dev-doc.md", "# Fixture workspace\n", "text/markdown", "doc"],
|
|
8569
|
-
["brand.md", "# Fixture brand\n", "text/markdown", "brand"]
|
|
8570
|
-
];
|
|
8571
|
-
return {
|
|
8572
|
-
ok: true,
|
|
8573
|
-
status: 200,
|
|
8574
|
-
data: {
|
|
8575
|
-
workspace: {
|
|
8576
|
-
id: "workspace_1",
|
|
8577
|
-
schemaVersion: 1,
|
|
8578
|
-
status: "draft",
|
|
8579
|
-
currentRevisionId: "revision_1",
|
|
8580
|
-
updatedAt: "2026-07-30T00:00:00.000Z"
|
|
8581
|
-
},
|
|
8582
|
-
files: files.map(([filePath, content, contentType, kind5]) => ({
|
|
8583
|
-
path: filePath,
|
|
8584
|
-
kind: kind5,
|
|
8585
|
-
contentType,
|
|
8586
|
-
content,
|
|
8587
|
-
sha256: (0, import_node_crypto.createHash)("sha256").update(content).digest("hex"),
|
|
8588
|
-
sizeBytes: Buffer.byteLength(content, "utf8"),
|
|
8589
|
-
updatedAt: "2026-07-30T00:00:00.000Z"
|
|
8590
|
-
}))
|
|
8591
|
-
}
|
|
8592
|
-
};
|
|
8593
|
-
}
|
|
8594
|
-
if (method === "PUT" && url.pathname.includes("/variations/") && url.pathname.includes("/files/")) {
|
|
8595
|
-
return { ok: true, status: 200, data: { ok: true } };
|
|
8596
|
-
}
|
|
8597
|
-
if (method === "POST" && url.pathname.endsWith("/build")) {
|
|
8598
|
-
return {
|
|
8599
|
-
ok: true,
|
|
8600
|
-
status: 200,
|
|
8601
|
-
data: { ok: true, mutationCount: 0, customCssBytes: 42, customJsBytes: 58 }
|
|
8602
|
-
};
|
|
8603
|
-
}
|
|
8604
|
-
if (method === "POST" && url.pathname === "/api/v1/harness/sessions") {
|
|
8605
|
-
return { ok: true, status: 201, data: { sessionId: "harness_1", sessionToken: "token", experimentId: exp.id, variationId: exp.variations?.[1]?.id ?? "variation_1", expiresAt: "2026-07-30T01:00:00.000Z", previewUrl: "https://preview.drip-apex.com/api/preview?pst=harness_1" } };
|
|
8606
|
-
}
|
|
8607
|
-
if (method === "POST" && url.pathname === "/api/v1/harness/sessions/harness_1/versions") {
|
|
8608
|
-
return { ok: true, status: 201, data: { sessionId: "harness_1", version: 1, expiresAt: "2026-07-30T01:00:00.000Z", jsArtifactId: "asset_js", cssArtifactId: "asset_css" } };
|
|
8609
|
-
}
|
|
8610
|
-
if (method === "POST" && url.pathname === "/api/v1/harness/sessions/harness_1/publish") {
|
|
8611
|
-
return { ok: true, status: 200, data: { ok: true } };
|
|
8612
|
-
}
|
|
8613
|
-
if (method === "GET" && url.pathname === "/api/v1/goals") {
|
|
8614
|
-
if (process.env.APEX_CLI_MOCK_DOCTOR_STATE === "goals-paginated") {
|
|
8615
|
-
const cursor = url.searchParams.get("cursor");
|
|
8616
|
-
return cursor === "goal-page-2" ? {
|
|
8617
|
-
ok: true,
|
|
8618
|
-
status: 200,
|
|
8619
|
-
data: {
|
|
8620
|
-
data: [{ ...mockGoal(), id: "00000000-0000-4000-8000-0000000000f2", name: "Unreachable landing", type: "pageview", url_pattern: "^/never-visited-page$" }],
|
|
8621
|
-
pagination: { has_more: false, next_cursor: null }
|
|
8622
|
-
}
|
|
8623
|
-
} : {
|
|
8624
|
-
ok: true,
|
|
8625
|
-
status: 200,
|
|
8626
|
-
data: {
|
|
8627
|
-
data: [mockGoal()],
|
|
8628
|
-
pagination: { has_more: true, next_cursor: "goal-page-2" }
|
|
8629
|
-
}
|
|
8630
|
-
};
|
|
8631
|
-
}
|
|
8632
|
-
if (process.env.APEX_CLI_MOCK_GOAL_PAGES === "2") {
|
|
8633
|
-
if (!url.searchParams.get("cursor")) {
|
|
8634
|
-
return {
|
|
8635
|
-
ok: true,
|
|
8636
|
-
status: 200,
|
|
8637
|
-
data: {
|
|
8638
|
-
data: [mockGoal()],
|
|
8639
|
-
pagination: { has_more: true, next_cursor: "goals_page_2" }
|
|
8640
|
-
}
|
|
8641
|
-
};
|
|
8642
|
-
}
|
|
8643
|
-
return {
|
|
8644
|
-
ok: true,
|
|
8645
|
-
status: 200,
|
|
8646
|
-
data: {
|
|
8647
|
-
data: [{
|
|
8648
|
-
...mockGoal(),
|
|
8649
|
-
id: SECOND_PAGE_GOAL_ID,
|
|
8650
|
-
name: "Fixture pageview",
|
|
8651
|
-
type: "pageview",
|
|
8652
|
-
event_name: null,
|
|
8653
|
-
url_pattern: "bestellung-abgeschlossen"
|
|
8654
|
-
}],
|
|
8655
|
-
pagination: { has_more: false, next_cursor: null }
|
|
8656
|
-
}
|
|
8657
|
-
};
|
|
8658
|
-
}
|
|
8659
|
-
return {
|
|
8660
|
-
ok: true,
|
|
8661
|
-
status: 200,
|
|
8662
|
-
data: {
|
|
8663
|
-
data: [mockGoal(), mockPageScopedGoal()],
|
|
8664
|
-
pagination: { has_more: false, next_cursor: null }
|
|
8665
|
-
}
|
|
8666
|
-
};
|
|
8667
|
-
}
|
|
8668
|
-
if (method === "GET" && url.pathname === "/api/v1/brand-context") {
|
|
8669
|
-
return {
|
|
8670
|
-
ok: true,
|
|
8671
|
-
status: 200,
|
|
8672
|
-
data: { data: mockBrandContext() }
|
|
8673
|
-
};
|
|
8674
|
-
}
|
|
8675
|
-
if (method === "POST" && url.pathname === "/api/v1/goals") {
|
|
8676
|
-
return {
|
|
8677
|
-
ok: true,
|
|
8678
|
-
status: 201,
|
|
8679
|
-
data: { ...mockGoal(), ...body5 }
|
|
8680
|
-
};
|
|
8681
|
-
}
|
|
8682
|
-
if (method === "PATCH" && url.pathname === `/api/v1/goals/${mockGoal().id}`) {
|
|
8683
|
-
return {
|
|
8684
|
-
ok: true,
|
|
8685
|
-
status: 200,
|
|
8686
|
-
data: { ...mockGoal(), ...body5 }
|
|
8687
|
-
};
|
|
8688
|
-
}
|
|
8689
|
-
if (method === "GET" && url.pathname === "/api/v1/tracking/verify") {
|
|
8690
|
-
if (process.env.APEX_CLI_MOCK_TRACKING_VERIFY === "unreachable") {
|
|
8691
|
-
throw new Error("Mock tracking verify transport failure");
|
|
8692
|
-
}
|
|
8693
|
-
return {
|
|
8694
|
-
ok: true,
|
|
8695
|
-
status: 200,
|
|
8696
|
-
data: {
|
|
8697
|
-
ok: true,
|
|
8698
|
-
window: { minutes: Number(url.searchParams.get("minutes") ?? 120) },
|
|
8699
|
-
filters: {
|
|
8700
|
-
experiment_id: url.searchParams.get("experiment_id"),
|
|
8701
|
-
event_types: []
|
|
8702
|
-
},
|
|
8703
|
-
totals: {
|
|
8704
|
-
events: 2,
|
|
8705
|
-
unique_visitors: 2,
|
|
8706
|
-
last_event_at: "2026-07-04T12:00:00.000Z",
|
|
8707
|
-
event_types: { pageview: 2 }
|
|
8708
|
-
},
|
|
8709
|
-
recent_events: [
|
|
8710
|
-
{
|
|
8711
|
-
event_type: "pageview",
|
|
8712
|
-
visitor_id: "visitor_1",
|
|
8713
|
-
url: "https://example.com/products/demo",
|
|
8714
|
-
timestamp: "2026-07-04T12:00:00.000Z"
|
|
8715
|
-
}
|
|
8716
|
-
]
|
|
8717
|
-
}
|
|
8718
|
-
};
|
|
8719
|
-
}
|
|
8720
|
-
if (method === "GET" && url.pathname === "/api/v1/targeting/test") {
|
|
8721
|
-
return {
|
|
8722
|
-
ok: true,
|
|
8723
|
-
status: 200,
|
|
8724
|
-
data: {
|
|
8725
|
-
url: url.searchParams.get("url"),
|
|
8726
|
-
statuses: ["draft", "running", "paused"],
|
|
8727
|
-
matching_count: 1,
|
|
8728
|
-
experiments: [
|
|
8729
|
-
{
|
|
8730
|
-
experiment: exp,
|
|
8731
|
-
matches: true,
|
|
8732
|
-
has_url_rules: true,
|
|
8733
|
-
rules: [
|
|
8734
|
-
{
|
|
8735
|
-
source: "url",
|
|
8736
|
-
pattern: "/products/*",
|
|
8737
|
-
type: "simple",
|
|
8738
|
-
include: true,
|
|
8739
|
-
matched: true,
|
|
8740
|
-
matchType: "wildcard"
|
|
8741
|
-
}
|
|
8742
|
-
],
|
|
8743
|
-
caveats: [],
|
|
8744
|
-
variations: exp.variations
|
|
8745
|
-
}
|
|
8746
|
-
]
|
|
8747
|
-
}
|
|
8748
|
-
};
|
|
8749
|
-
}
|
|
8750
|
-
if (method === "POST" && url.pathname === "/api/v1/sos") {
|
|
8751
|
-
return {
|
|
8752
|
-
ok: true,
|
|
8753
|
-
status: 201,
|
|
8754
|
-
data: {
|
|
8755
|
-
ticket: {
|
|
8756
|
-
id: "00000000-0000-4000-8000-0000000000aa",
|
|
8757
|
-
jira_key: "AWF-123",
|
|
8758
|
-
jira_status: "Queued",
|
|
8759
|
-
mapped_status: "queued",
|
|
8760
|
-
type: body5?.type ?? "qa",
|
|
8761
|
-
sub_class: null,
|
|
8762
|
-
loom_url: body5?.loom_url ?? null,
|
|
8763
|
-
description: body5?.description ?? "Fixture SOS",
|
|
8764
|
-
created_at: "2026-07-04T12:00:00.000Z",
|
|
8765
|
-
updated_at: "2026-07-04T12:00:00.000Z"
|
|
8766
|
-
},
|
|
8767
|
-
reused: false,
|
|
8768
|
-
jira: {
|
|
8769
|
-
key: "AWF-123",
|
|
8770
|
-
url: "https://jira.example.test/browse/AWF-123"
|
|
8771
|
-
}
|
|
8772
|
-
}
|
|
8773
|
-
};
|
|
8774
|
-
}
|
|
8775
|
-
if (method === "GET" && url.pathname === "/api/v1/sos/mine") {
|
|
8776
|
-
return {
|
|
8777
|
-
ok: true,
|
|
8778
|
-
status: 200,
|
|
8779
|
-
data: {
|
|
8780
|
-
tickets: [
|
|
8781
|
-
{
|
|
8782
|
-
id: "00000000-0000-4000-8000-0000000000aa",
|
|
8783
|
-
jira_key: "AWF-123",
|
|
8784
|
-
mapped_status: "queued",
|
|
8785
|
-
my_status: "queued",
|
|
8786
|
-
type: "qa",
|
|
8787
|
-
description: "Fixture SOS",
|
|
8788
|
-
created_at: "2026-07-04T12:00:00.000Z",
|
|
8789
|
-
updated_at: "2026-07-04T12:00:00.000Z"
|
|
8790
|
-
}
|
|
8791
|
-
]
|
|
8792
|
-
}
|
|
7428
|
+
baseUrl: envUrl ?? "https://app.drip-apex.com",
|
|
7429
|
+
userApiKey: fileCreds?.userApiKey,
|
|
7430
|
+
userBaseUrl: fileCreds?.userBaseUrl ?? fileCreds?.baseUrl,
|
|
7431
|
+
context: fileCreds?.context
|
|
8793
7432
|
};
|
|
8794
7433
|
}
|
|
8795
|
-
|
|
8796
|
-
|
|
8797
|
-
|
|
8798
|
-
|
|
8799
|
-
|
|
8800
|
-
|
|
8801
|
-
|
|
8802
|
-
|
|
8803
|
-
|
|
8804
|
-
|
|
8805
|
-
|
|
8806
|
-
|
|
8807
|
-
|
|
8808
|
-
};
|
|
7434
|
+
return fileCreds;
|
|
7435
|
+
}
|
|
7436
|
+
function saveCredentials(creds) {
|
|
7437
|
+
const { credentialKind: _kind, resolvedContext: _resolved, ...persisted } = creds;
|
|
7438
|
+
import_node_fs2.default.mkdirSync(DRIP_DIR, { recursive: true });
|
|
7439
|
+
import_node_fs2.default.writeFileSync(CREDS_FILE, JSON.stringify(persisted, null, 2) + "\n", {
|
|
7440
|
+
mode: 384
|
|
7441
|
+
});
|
|
7442
|
+
}
|
|
7443
|
+
function clearCredentials() {
|
|
7444
|
+
try {
|
|
7445
|
+
import_node_fs2.default.unlinkSync(CREDS_FILE);
|
|
7446
|
+
} catch {
|
|
8809
7447
|
}
|
|
8810
|
-
|
|
8811
|
-
|
|
7448
|
+
}
|
|
7449
|
+
function requireCredentials() {
|
|
7450
|
+
const creds = getCredentials();
|
|
7451
|
+
if (!creds) {
|
|
7452
|
+
throw new CliError(
|
|
7453
|
+
'Not logged in. Run "apex login" first, or set DRIP_API_KEY env var.',
|
|
7454
|
+
EXIT_CODES.auth
|
|
7455
|
+
);
|
|
8812
7456
|
}
|
|
8813
|
-
|
|
8814
|
-
|
|
7457
|
+
const context = resolveContext(creds, void 0, defaultCredentialKind(creds));
|
|
7458
|
+
if (!process.env.DRIP_API_KEY && creds.userApiKey) {
|
|
7459
|
+
setEnvelopeContext({ credential: "user", org_id: context.orgId ?? null, shop_id: context.shopId ?? null });
|
|
7460
|
+
if (!context.shopId) {
|
|
7461
|
+
throw new CliError(
|
|
7462
|
+
'Shop context is required. Run "apex use", or pass --shop <id>.',
|
|
7463
|
+
EXIT_CODES.validation,
|
|
7464
|
+
{ code: "shop_context_required", retry: "apex use <org>/<shop>", flag: "--shop" }
|
|
7465
|
+
);
|
|
7466
|
+
}
|
|
7467
|
+
return { ...creds, apiKey: creds.userApiKey, baseUrl: creds.userBaseUrl ?? creds.baseUrl, credentialKind: "user", resolvedContext: context };
|
|
8815
7468
|
}
|
|
8816
|
-
|
|
8817
|
-
|
|
7469
|
+
setEnvelopeContext({ credential: "shop", org_id: context.orgId ?? null, shop_id: context.shopId ?? null });
|
|
7470
|
+
return { ...creds, credentialKind: "shop", resolvedContext: context };
|
|
7471
|
+
}
|
|
7472
|
+
function requireUserCredentials() {
|
|
7473
|
+
const creds = getCredentials();
|
|
7474
|
+
if (!creds?.userApiKey) {
|
|
7475
|
+
throw new CliError(
|
|
7476
|
+
'This command needs a user credential (apx_uk_...). Run "apex login" first.',
|
|
7477
|
+
EXIT_CODES.auth
|
|
7478
|
+
);
|
|
8818
7479
|
}
|
|
8819
|
-
|
|
8820
|
-
|
|
7480
|
+
const context = resolveContext(creds, void 0, "user");
|
|
7481
|
+
setEnvelopeContext({ credential: "user", org_id: context.orgId ?? null, shop_id: context.shopId ?? null });
|
|
7482
|
+
return { ...creds, apiKey: creds.userApiKey, baseUrl: creds.userBaseUrl ?? creds.baseUrl, credentialKind: "user", resolvedContext: context };
|
|
7483
|
+
}
|
|
7484
|
+
var CONFIG_NAMES = [
|
|
7485
|
+
".drip.config.ts",
|
|
7486
|
+
".drip.config.js",
|
|
7487
|
+
"drip.config.ts",
|
|
7488
|
+
"drip.config.js"
|
|
7489
|
+
];
|
|
7490
|
+
function findConfigPath() {
|
|
7491
|
+
const cwd = process.cwd();
|
|
7492
|
+
for (const name of CONFIG_NAMES) {
|
|
7493
|
+
const p = import_node_path2.default.join(cwd, name);
|
|
7494
|
+
if (import_node_fs2.default.existsSync(p)) return p;
|
|
8821
7495
|
}
|
|
8822
|
-
|
|
8823
|
-
|
|
7496
|
+
return null;
|
|
7497
|
+
}
|
|
7498
|
+
|
|
7499
|
+
// ../api-client/src/http.ts
|
|
7500
|
+
var APEX_API_VERSION_HEADER = "x-apex-api-version";
|
|
7501
|
+
var SUPPORTED_APEX_API_VERSION = "1";
|
|
7502
|
+
function extractErrorMessage(json, status2) {
|
|
7503
|
+
if (!json) return `HTTP ${status2}`;
|
|
7504
|
+
const error = json.error;
|
|
7505
|
+
if (typeof error === "string" && error.trim()) return error;
|
|
7506
|
+
if (error && typeof error === "object") {
|
|
7507
|
+
const message = error.message;
|
|
7508
|
+
if (typeof message === "string" && message.trim()) return message;
|
|
8824
7509
|
}
|
|
8825
|
-
|
|
8826
|
-
|
|
7510
|
+
return typeof json.message === "string" && json.message.trim() ? json.message : `HTTP ${status2}`;
|
|
7511
|
+
}
|
|
7512
|
+
function extractErrorDetails(json) {
|
|
7513
|
+
if (!json) return void 0;
|
|
7514
|
+
const nested = json.error && typeof json.error === "object" && !Array.isArray(json.error) ? json.error : null;
|
|
7515
|
+
const code = typeof json.code === "string" ? json.code : typeof nested?.code === "string" ? nested.code : void 0;
|
|
7516
|
+
for (const value of [json.details, json.meta, nested?.details]) if (value && typeof value === "object" && !Array.isArray(value)) return { ...value, ...code ? { code } : {} };
|
|
7517
|
+
const details = Object.fromEntries(["limit", "current", "max", "actual", "field", "code"].filter((key) => key in json).map((key) => [key, json[key]]));
|
|
7518
|
+
if (code) details.code = code;
|
|
7519
|
+
return Object.keys(details).length ? details : void 0;
|
|
7520
|
+
}
|
|
7521
|
+
var warnedAboutVersionSkew = false;
|
|
7522
|
+
function createHttpClient(creds, options) {
|
|
7523
|
+
const warn3 = options.warn ?? ((message) => process.stderr.write(message));
|
|
7524
|
+
function inspectVersion(headers) {
|
|
7525
|
+
const value = headers.get(APEX_API_VERSION_HEADER);
|
|
7526
|
+
if (!value || warnedAboutVersionSkew) return;
|
|
7527
|
+
const version = Number(value);
|
|
7528
|
+
if (!Number.isFinite(version) || version === Number(SUPPORTED_APEX_API_VERSION)) return;
|
|
7529
|
+
warnedAboutVersionSkew = true;
|
|
7530
|
+
warn3(version > 1 ? "CLI is behind the server \u2014 update.\n" : "Server API is older than this CLI supports.\n");
|
|
7531
|
+
}
|
|
7532
|
+
async function request2(method, path21, body5, requestTimeoutMs, form = false) {
|
|
7533
|
+
const mock = options.transport?.(method, path21, body5);
|
|
7534
|
+
if (mock !== void 0) {
|
|
7535
|
+
const version = options.testTransportVersion?.();
|
|
7536
|
+
if (version) inspectVersion(new Headers({ [APEX_API_VERSION_HEADER]: version }));
|
|
7537
|
+
return mock;
|
|
7538
|
+
}
|
|
7539
|
+
const fallback2 = form ? 12e4 : 3e4;
|
|
7540
|
+
const configured = Number(process.env.APEX_TIMEOUT_MS ?? fallback2);
|
|
7541
|
+
const defaultMs = Number.isFinite(configured) && configured > 0 ? configured : fallback2;
|
|
7542
|
+
const timeoutMs = requestTimeoutMs === void 0 ? defaultMs : Math.max(1, Math.min(defaultMs, requestTimeoutMs));
|
|
7543
|
+
const headers = { Authorization: `Bearer ${creds.apiKey}` };
|
|
7544
|
+
if (!form) headers["Content-Type"] = "application/json";
|
|
7545
|
+
if (creds.credentialKind === "user" || creds.apiKey.startsWith("apx_uk_")) {
|
|
7546
|
+
if (creds.resolvedContext?.orgId) headers["X-Apex-Org"] = creds.resolvedContext.orgId;
|
|
7547
|
+
if (creds.resolvedContext?.shopId) headers["X-Apex-Shop"] = creds.resolvedContext.shopId;
|
|
7548
|
+
}
|
|
7549
|
+
let response;
|
|
7550
|
+
try {
|
|
7551
|
+
response = await fetch(`${creds.baseUrl}${path21}`, { method, headers, body: form ? body5 : body5 ? JSON.stringify(body5) : void 0, signal: AbortSignal.timeout(timeoutMs) });
|
|
7552
|
+
} catch (error) {
|
|
7553
|
+
return options.onNetworkError({ baseUrl: creds.baseUrl, timeoutMs, cause: error instanceof Error ? error.message : String(error) });
|
|
7554
|
+
}
|
|
7555
|
+
inspectVersion(response.headers);
|
|
7556
|
+
const json = await response.json().catch(() => null);
|
|
7557
|
+
if (!response.ok) return { ok: false, status: response.status, data: form ? void 0 : json, error: extractErrorMessage(json, response.status), errorDetails: extractErrorDetails(json), requestId: response.headers.get("x-request-id"), rateLimit: { limit: response.headers.get("x-ratelimit-limit"), remaining: response.headers.get("x-ratelimit-remaining"), reset: response.headers.get("x-ratelimit-reset") } };
|
|
7558
|
+
return { ok: true, status: response.status, data: json };
|
|
8827
7559
|
}
|
|
8828
|
-
return {
|
|
8829
|
-
|
|
8830
|
-
|
|
8831
|
-
|
|
8832
|
-
|
|
7560
|
+
return { request: (method, path21, body5, timeoutMs) => request2(method, path21, body5, timeoutMs), requestForm: (method, path21, body5) => request2(method, path21, body5, void 0, true) };
|
|
7561
|
+
}
|
|
7562
|
+
|
|
7563
|
+
// src/api.ts
|
|
7564
|
+
var testTransport;
|
|
7565
|
+
var testScreenshotDelay;
|
|
7566
|
+
var testScreenshotNow;
|
|
7567
|
+
function screenshotNow() {
|
|
7568
|
+
return testScreenshotNow?.() ?? Date.now();
|
|
8833
7569
|
}
|
|
8834
7570
|
function getUserKeyIdentity(creds) {
|
|
8835
7571
|
return request(
|
|
@@ -8845,8 +7581,8 @@ function getUserKeyIdentity(creds) {
|
|
|
8845
7581
|
function getAccountContext(creds) {
|
|
8846
7582
|
return userRequest(creds, "GET", "/api/v1/me/context");
|
|
8847
7583
|
}
|
|
8848
|
-
function userRequest(creds, method,
|
|
8849
|
-
return request({ ...creds, apiKey: creds.userApiKey ?? "", baseUrl: creds.userBaseUrl ?? creds.baseUrl }, method,
|
|
7584
|
+
function userRequest(creds, method, path21, body5) {
|
|
7585
|
+
return request({ ...creds, apiKey: creds.userApiKey ?? "", baseUrl: creds.userBaseUrl ?? creds.baseUrl }, method, path21, body5);
|
|
8850
7586
|
}
|
|
8851
7587
|
function getAccountProfile(creds) {
|
|
8852
7588
|
return userRequest(creds, "GET", "/api/v1/account/profile");
|
|
@@ -8863,114 +7599,30 @@ function createUserManagedKey(creds, body5) {
|
|
|
8863
7599
|
function revokeUserManagedKey(creds, id, shopId) {
|
|
8864
7600
|
return userRequest(creds, "DELETE", `/api/v1/keys/${encodeURIComponent(id)}?shop_id=${encodeURIComponent(shopId)}`);
|
|
8865
7601
|
}
|
|
8866
|
-
|
|
8867
|
-
|
|
8868
|
-
|
|
8869
|
-
|
|
8870
|
-
|
|
8871
|
-
|
|
8872
|
-
|
|
8873
|
-
|
|
8874
|
-
|
|
8875
|
-
|
|
8876
|
-
|
|
8877
|
-
|
|
8878
|
-
|
|
8879
|
-
"Content-Type": "application/json"
|
|
8880
|
-
};
|
|
8881
|
-
if (creds.credentialKind === "user" || creds.apiKey.startsWith("apx_uk_")) {
|
|
8882
|
-
if (creds.resolvedContext?.orgId) headers["X-Apex-Org"] = creds.resolvedContext.orgId;
|
|
8883
|
-
if (creds.resolvedContext?.shopId) headers["X-Apex-Shop"] = creds.resolvedContext.shopId;
|
|
8884
|
-
}
|
|
8885
|
-
let res;
|
|
8886
|
-
try {
|
|
8887
|
-
res = await fetch(url, {
|
|
8888
|
-
method,
|
|
8889
|
-
headers,
|
|
8890
|
-
body: body5 ? JSON.stringify(body5) : void 0,
|
|
8891
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
8892
|
-
});
|
|
8893
|
-
} catch (error) {
|
|
8894
|
-
const seconds = timeoutMs / 1e3;
|
|
8895
|
-
throw new CliError(
|
|
8896
|
-
`Could not reach ${creds.baseUrl}: check DRIP_BASE_URL / network; server did not respond within ${seconds}s.`,
|
|
8897
|
-
EXIT_CODES.general,
|
|
8898
|
-
{ baseUrl: creds.baseUrl, timeoutMs, cause: error instanceof Error ? error.message : String(error) }
|
|
8899
|
-
);
|
|
8900
|
-
}
|
|
8901
|
-
warnAboutApiVersionSkew(res.headers);
|
|
8902
|
-
const json = await res.json().catch(() => null);
|
|
8903
|
-
if (!res.ok) {
|
|
8904
|
-
return {
|
|
8905
|
-
ok: false,
|
|
8906
|
-
status: res.status,
|
|
8907
|
-
data: json,
|
|
8908
|
-
error: extractErrorMessage(json, res.status),
|
|
8909
|
-
errorDetails: extractErrorDetails(json),
|
|
8910
|
-
requestId: res.headers.get("x-request-id"),
|
|
8911
|
-
rateLimit: {
|
|
8912
|
-
limit: res.headers.get("x-ratelimit-limit"),
|
|
8913
|
-
remaining: res.headers.get("x-ratelimit-remaining"),
|
|
8914
|
-
reset: res.headers.get("x-ratelimit-reset")
|
|
7602
|
+
var sharedClients = /* @__PURE__ */ new WeakMap();
|
|
7603
|
+
function sharedClient(creds) {
|
|
7604
|
+
let client = sharedClients.get(creds);
|
|
7605
|
+
if (!client) {
|
|
7606
|
+
client = createHttpClient(creds, {
|
|
7607
|
+
transport: testTransport,
|
|
7608
|
+
testTransportVersion: () => testTransport ? process.env[["APEX", "CLI", "MOCK", "API", "VERSION"].join("_")] : void 0,
|
|
7609
|
+
onNetworkError: (info) => {
|
|
7610
|
+
throw new CliError(
|
|
7611
|
+
`Could not reach ${info.baseUrl}: check DRIP_BASE_URL / network; server did not respond within ${info.timeoutMs / 1e3}s.`,
|
|
7612
|
+
EXIT_CODES.general,
|
|
7613
|
+
{ ...info }
|
|
7614
|
+
);
|
|
8915
7615
|
}
|
|
8916
|
-
};
|
|
8917
|
-
}
|
|
8918
|
-
return {
|
|
8919
|
-
ok: true,
|
|
8920
|
-
status: res.status,
|
|
8921
|
-
// Do not unwrap `json.data` automatically: some endpoints return
|
|
8922
|
-
// `{ data, pagination }`, and callers need the full shape.
|
|
8923
|
-
data: json
|
|
8924
|
-
};
|
|
8925
|
-
}
|
|
8926
|
-
async function requestForm(creds, method, path20, body5) {
|
|
8927
|
-
const mock = mockResponse(method, path20, body5);
|
|
8928
|
-
if (mock) {
|
|
8929
|
-
const mockVersion = process.env.APEX_CLI_MOCK_API_VERSION;
|
|
8930
|
-
if (mockVersion) warnAboutApiVersionSkew(new Headers({ [APEX_API_VERSION_HEADER]: mockVersion }));
|
|
8931
|
-
return mock;
|
|
8932
|
-
}
|
|
8933
|
-
const url = `${creds.baseUrl}${path20}`;
|
|
8934
|
-
const configuredTimeout = Number(process.env.APEX_TIMEOUT_MS ?? "120000");
|
|
8935
|
-
const timeoutMs = Number.isFinite(configuredTimeout) && configuredTimeout > 0 ? configuredTimeout : 12e4;
|
|
8936
|
-
let res;
|
|
8937
|
-
try {
|
|
8938
|
-
const headers = { Authorization: `Bearer ${creds.apiKey}` };
|
|
8939
|
-
if (creds.credentialKind === "user" || creds.apiKey.startsWith("apx_uk_")) {
|
|
8940
|
-
if (creds.resolvedContext?.orgId) headers["X-Apex-Org"] = creds.resolvedContext.orgId;
|
|
8941
|
-
if (creds.resolvedContext?.shopId) headers["X-Apex-Shop"] = creds.resolvedContext.shopId;
|
|
8942
|
-
}
|
|
8943
|
-
res = await fetch(url, {
|
|
8944
|
-
method,
|
|
8945
|
-
headers,
|
|
8946
|
-
body: body5,
|
|
8947
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
8948
7616
|
});
|
|
8949
|
-
|
|
8950
|
-
const seconds = timeoutMs / 1e3;
|
|
8951
|
-
throw new CliError(
|
|
8952
|
-
`Could not reach ${creds.baseUrl}: check DRIP_BASE_URL / network; server did not respond within ${seconds}s.`,
|
|
8953
|
-
EXIT_CODES.general,
|
|
8954
|
-
{ baseUrl: creds.baseUrl, timeoutMs, cause: error instanceof Error ? error.message : String(error) }
|
|
8955
|
-
);
|
|
8956
|
-
}
|
|
8957
|
-
warnAboutApiVersionSkew(res.headers);
|
|
8958
|
-
const json = await res.json().catch(() => null);
|
|
8959
|
-
if (!res.ok) {
|
|
8960
|
-
return {
|
|
8961
|
-
ok: false,
|
|
8962
|
-
status: res.status,
|
|
8963
|
-
error: extractErrorMessage(json, res.status),
|
|
8964
|
-
errorDetails: extractErrorDetails(json),
|
|
8965
|
-
requestId: res.headers.get("x-request-id"),
|
|
8966
|
-
rateLimit: {
|
|
8967
|
-
limit: res.headers.get("x-ratelimit-limit"),
|
|
8968
|
-
remaining: res.headers.get("x-ratelimit-remaining"),
|
|
8969
|
-
reset: res.headers.get("x-ratelimit-reset")
|
|
8970
|
-
}
|
|
8971
|
-
};
|
|
7617
|
+
sharedClients.set(creds, client);
|
|
8972
7618
|
}
|
|
8973
|
-
return
|
|
7619
|
+
return client;
|
|
7620
|
+
}
|
|
7621
|
+
async function request(creds, method, path21, body5, requestTimeoutMs) {
|
|
7622
|
+
return sharedClient(creds).request(method, path21, body5, requestTimeoutMs);
|
|
7623
|
+
}
|
|
7624
|
+
async function requestForm(creds, method, path21, body5) {
|
|
7625
|
+
return sharedClient(creds).requestForm(method, path21, body5);
|
|
8974
7626
|
}
|
|
8975
7627
|
function listExperiments(creds, opts) {
|
|
8976
7628
|
const params = new URLSearchParams();
|
|
@@ -9006,9 +7658,6 @@ function upsertExperimentBySlug(creds, slug, body5) {
|
|
|
9006
7658
|
function createExperiment(creds, body5) {
|
|
9007
7659
|
return request(creds, "POST", "/api/v1/experiments", body5);
|
|
9008
7660
|
}
|
|
9009
|
-
function startExperiment(creds, id) {
|
|
9010
|
-
return request(creds, "POST", `/api/v1/experiments/${id}/start`);
|
|
9011
|
-
}
|
|
9012
7661
|
function pauseExperiment(creds, id) {
|
|
9013
7662
|
return request(creds, "POST", `/api/v1/experiments/${id}/pause`);
|
|
9014
7663
|
}
|
|
@@ -9043,9 +7692,7 @@ function createExperimentScreenshots(creds, id, body5) {
|
|
|
9043
7692
|
return request(creds, "POST", `/api/v1/experiments/${id}/screenshots`, body5);
|
|
9044
7693
|
}
|
|
9045
7694
|
async function getScreenshotJob(creds, jobId, timeoutMs) {
|
|
9046
|
-
|
|
9047
|
-
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
9048
|
-
}
|
|
7695
|
+
await testScreenshotDelay?.(timeoutMs);
|
|
9049
7696
|
return request(creds, "GET", `/api/v1/screenshots/${jobId}`, void 0, timeoutMs);
|
|
9050
7697
|
}
|
|
9051
7698
|
function planExperimentFilesystem(creds, id, body5) {
|
|
@@ -9259,8 +7906,8 @@ function getRoadmapItemHandoff(creds, id) {
|
|
|
9259
7906
|
`/api/v1/roadmap/items/${encodeURIComponent(id)}/handoff`
|
|
9260
7907
|
);
|
|
9261
7908
|
}
|
|
9262
|
-
function createProxyPreviewSession(creds, experimentId, variationId, targetUrl) {
|
|
9263
|
-
return request(creds, "POST", "/api/v1/preview-sessions", { experiment_id: experimentId, ...variationId ? { variation_id: variationId } : {}, ...targetUrl ? { target_url: targetUrl } : {} });
|
|
7909
|
+
function createProxyPreviewSession(creds, experimentId, variationId, targetUrl, ttlHours) {
|
|
7910
|
+
return request(creds, "POST", "/api/v1/preview-sessions", { experiment_id: experimentId, ...variationId ? { variation_id: variationId } : {}, ...targetUrl ? { target_url: targetUrl } : {}, ...ttlHours !== void 0 ? { ttl_hours: ttlHours } : {} });
|
|
9264
7911
|
}
|
|
9265
7912
|
function listAuditLogs(creds, opts) {
|
|
9266
7913
|
const p = new URLSearchParams();
|
|
@@ -9336,11 +7983,11 @@ function requestTargetingResource(creds, method, kind5, id, body5, limit, cursor
|
|
|
9336
7983
|
if (limit) params.set("limit", String(limit));
|
|
9337
7984
|
if (cursor) params.set("cursor", cursor);
|
|
9338
7985
|
const query = params.toString();
|
|
9339
|
-
const
|
|
9340
|
-
return request(creds, method,
|
|
7986
|
+
const path21 = `/api/v1/${kind5}${id ? `/${id.split("/").map(encodeURIComponent).join("/")}` : ""}${query ? `?${query}` : ""}`;
|
|
7987
|
+
return request(creds, method, path21, body5);
|
|
9341
7988
|
}
|
|
9342
|
-
function managedPath(kind5, id,
|
|
9343
|
-
return `/api/v1/${kind5}${id ? `/${encodeURIComponent(id)}` : ""}${
|
|
7989
|
+
function managedPath(kind5, id, action69) {
|
|
7990
|
+
return `/api/v1/${kind5}${id ? `/${encodeURIComponent(id)}` : ""}${action69 ? `/${action69}` : ""}`;
|
|
9344
7991
|
}
|
|
9345
7992
|
function listManagedDeliveries(creds, kind5, limit) {
|
|
9346
7993
|
const q = limit ? `?limit=${limit}` : "";
|
|
@@ -9362,8 +8009,8 @@ function createManagedDelivery(creds, kind5, body5) {
|
|
|
9362
8009
|
function updateManagedDelivery(creds, kind5, id, body5) {
|
|
9363
8010
|
return request(creds, "PATCH", managedPath(kind5, id), body5);
|
|
9364
8011
|
}
|
|
9365
|
-
function transitionManagedDelivery(creds, kind5, id,
|
|
9366
|
-
return request(creds, "POST", managedPath(kind5, id,
|
|
8012
|
+
function transitionManagedDelivery(creds, kind5, id, action69) {
|
|
8013
|
+
return request(creds, "POST", managedPath(kind5, id, action69));
|
|
9367
8014
|
}
|
|
9368
8015
|
function listConfigResources(creds, family, opts) {
|
|
9369
8016
|
const params = new URLSearchParams();
|
|
@@ -9620,6 +8267,11 @@ async function pickContext(tree) {
|
|
|
9620
8267
|
init_source();
|
|
9621
8268
|
var STATUS_COLORS = {
|
|
9622
8269
|
draft: source_default.gray,
|
|
8270
|
+
awaiting_client_approval: source_default.magenta,
|
|
8271
|
+
changes_requested: source_default.red,
|
|
8272
|
+
client_approved: source_default.magentaBright,
|
|
8273
|
+
qa: source_default.cyan,
|
|
8274
|
+
ready_to_launch: source_default.greenBright,
|
|
9623
8275
|
running: source_default.green,
|
|
9624
8276
|
paused: source_default.yellow,
|
|
9625
8277
|
completed: source_default.blue
|
|
@@ -9710,8 +8362,8 @@ function parseFormBody(body5) {
|
|
|
9710
8362
|
return new URLSearchParams(body5);
|
|
9711
8363
|
}
|
|
9712
8364
|
async function waitForBrowserLogin(baseUrl, userMode = false) {
|
|
9713
|
-
const state =
|
|
9714
|
-
const clientName =
|
|
8365
|
+
const state = import_node_crypto.default.randomBytes(24).toString("base64url");
|
|
8366
|
+
const clientName = import_node_os5.default.hostname() || "Local machine";
|
|
9715
8367
|
return new Promise((resolve, reject) => {
|
|
9716
8368
|
let settled = false;
|
|
9717
8369
|
let timeout = null;
|
|
@@ -9962,9 +8614,41 @@ var useCommand = new Command("use").description("Set the current Apex organizati
|
|
|
9962
8614
|
|
|
9963
8615
|
// src/commands/experiments.ts
|
|
9964
8616
|
init_source();
|
|
9965
|
-
var
|
|
9966
|
-
var
|
|
9967
|
-
var
|
|
8617
|
+
var import_node_crypto2 = __toESM(require("node:crypto"));
|
|
8618
|
+
var import_node_fs6 = __toESM(require("node:fs"));
|
|
8619
|
+
var import_node_path4 = __toESM(require("node:path"));
|
|
8620
|
+
|
|
8621
|
+
// ../api-client/src/qa-preview-url.ts
|
|
8622
|
+
function applyQaPreviewParams(url, params) {
|
|
8623
|
+
url.searchParams.set("drip_force", `${params.experimentId}:${params.variationId}`);
|
|
8624
|
+
url.searchParams.set("drip_experiment_id", params.experimentId);
|
|
8625
|
+
url.searchParams.set("drip_variation_id", params.variationId);
|
|
8626
|
+
url.searchParams.set("drip_qa", "1");
|
|
8627
|
+
if (params.debug !== false) url.searchParams.set("drip_debug", "1");
|
|
8628
|
+
if (params.devtools) url.searchParams.set("drip_devtools", "1");
|
|
8629
|
+
if (params.qaSessionId) url.searchParams.set("drip_qa_session", params.qaSessionId);
|
|
8630
|
+
if (params.harnessToken) url.searchParams.set("drip_harness", params.harnessToken);
|
|
8631
|
+
return url;
|
|
8632
|
+
}
|
|
8633
|
+
function buildQaPreviewUrl(experiment, options = {}) {
|
|
8634
|
+
const variation = options.variationName ? experiment.variations?.find((v) => v.name === options.variationName) : experiment.variations?.find((v) => !v.is_control) ?? experiment.variations?.[0];
|
|
8635
|
+
const target = options.storeUrl ?? experiment.store_url;
|
|
8636
|
+
if (!target || !variation) return null;
|
|
8637
|
+
try {
|
|
8638
|
+
return applyQaPreviewParams(new URL(target), { experimentId: experiment.id, variationId: variation.id, ...options });
|
|
8639
|
+
} catch {
|
|
8640
|
+
return null;
|
|
8641
|
+
}
|
|
8642
|
+
}
|
|
8643
|
+
|
|
8644
|
+
// src/lib/qa-preview-url.ts
|
|
8645
|
+
function buildQaPreviewUrl2(storeUrl, experimentId, variationId) {
|
|
8646
|
+
const url = buildQaPreviewUrl({ id: experimentId, store_url: storeUrl, variations: [{ id: variationId, is_control: false }] });
|
|
8647
|
+
if (!url) throw new TypeError("Invalid QA preview URL");
|
|
8648
|
+
return url;
|
|
8649
|
+
}
|
|
8650
|
+
|
|
8651
|
+
// src/commands/experiments.ts
|
|
9968
8652
|
function formatDate(iso) {
|
|
9969
8653
|
return new Date(iso).toLocaleDateString("en-US", {
|
|
9970
8654
|
month: "short",
|
|
@@ -10070,25 +8754,25 @@ var FILESYSTEM_FILE_RE = new RegExp(
|
|
|
10070
8754
|
`^(?:${pathAlternation(EXPERIMENT_FILESYSTEM_PATH_SCHEMA.topLevel)}|experiment/(?:${pathAlternation(EXPERIMENT_FILESYSTEM_PATH_SCHEMA.experiment)})|variations/[^/]+/(?:${pathAlternation(EXPERIMENT_FILESYSTEM_PATH_SCHEMA.variation)}))$`
|
|
10071
8755
|
);
|
|
10072
8756
|
function sha256Text(value) {
|
|
10073
|
-
return
|
|
8757
|
+
return import_node_crypto2.default.createHash("sha256").update(value).digest("hex");
|
|
10074
8758
|
}
|
|
10075
8759
|
function folderPath(folder) {
|
|
10076
|
-
return
|
|
8760
|
+
return import_node_path4.default.resolve(process.cwd(), folder);
|
|
10077
8761
|
}
|
|
10078
8762
|
function filesystemApexDir(folder) {
|
|
10079
|
-
return
|
|
8763
|
+
return import_node_path4.default.join(folder, ".apex");
|
|
10080
8764
|
}
|
|
10081
8765
|
function filesystemSessionPath(folder) {
|
|
10082
|
-
return
|
|
8766
|
+
return import_node_path4.default.join(filesystemApexDir(folder), "experiment-filesystem.json");
|
|
10083
8767
|
}
|
|
10084
8768
|
function writeFilesystemSession(folder, session) {
|
|
10085
|
-
|
|
10086
|
-
|
|
10087
|
-
|
|
8769
|
+
import_node_fs6.default.mkdirSync(filesystemApexDir(folder), { recursive: true });
|
|
8770
|
+
import_node_fs6.default.writeFileSync(
|
|
8771
|
+
import_node_path4.default.join(filesystemApexDir(folder), ".gitignore"),
|
|
10088
8772
|
"experiment-filesystem.json\n",
|
|
10089
8773
|
"utf8"
|
|
10090
8774
|
);
|
|
10091
|
-
|
|
8775
|
+
import_node_fs6.default.writeFileSync(
|
|
10092
8776
|
filesystemSessionPath(folder),
|
|
10093
8777
|
`${JSON.stringify(session, null, 2)}
|
|
10094
8778
|
`,
|
|
@@ -10097,14 +8781,14 @@ function writeFilesystemSession(folder, session) {
|
|
|
10097
8781
|
}
|
|
10098
8782
|
function readFilesystemSession(folder) {
|
|
10099
8783
|
const filePath = filesystemSessionPath(folder);
|
|
10100
|
-
if (!
|
|
10101
|
-
const workspaceMarker =
|
|
10102
|
-
if (
|
|
8784
|
+
if (!import_node_fs6.default.existsSync(filePath)) {
|
|
8785
|
+
const workspaceMarker = import_node_path4.default.join(filesystemApexDir(folder), "session.json");
|
|
8786
|
+
if (import_node_fs6.default.existsSync(workspaceMarker)) {
|
|
10103
8787
|
throw new Error("This folder is an operator workspace (apex workspace \u2026), not an experiment filesystem \u2014 run `apex workspace status` here, or `apex experiments pull` into a different folder.");
|
|
10104
8788
|
}
|
|
10105
8789
|
throw new Error(`Missing ${filePath}. Run apex experiments pull first.`);
|
|
10106
8790
|
}
|
|
10107
|
-
return JSON.parse(
|
|
8791
|
+
return JSON.parse(import_node_fs6.default.readFileSync(filePath, "utf8"));
|
|
10108
8792
|
}
|
|
10109
8793
|
function validateFilesystemPath(relativePath) {
|
|
10110
8794
|
const normalized = relativePath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
@@ -10120,38 +8804,38 @@ function writeExperimentFilesystemFiles(folder, files) {
|
|
|
10120
8804
|
}));
|
|
10121
8805
|
for (const file of validatedFiles) {
|
|
10122
8806
|
const relativePath = file.relativePath;
|
|
10123
|
-
const absPath =
|
|
10124
|
-
|
|
10125
|
-
|
|
8807
|
+
const absPath = import_node_path4.default.join(folder, relativePath);
|
|
8808
|
+
import_node_fs6.default.mkdirSync(import_node_path4.default.dirname(absPath), { recursive: true });
|
|
8809
|
+
import_node_fs6.default.writeFileSync(absPath, file.content, "utf8");
|
|
10126
8810
|
}
|
|
10127
8811
|
}
|
|
10128
8812
|
function listExperimentFilesystemFiles(folder) {
|
|
10129
8813
|
const files = [];
|
|
10130
8814
|
for (const fileName of EXPERIMENT_FILESYSTEM_PATH_SCHEMA.topLevel) {
|
|
10131
|
-
const absPath =
|
|
10132
|
-
if (!
|
|
10133
|
-
const content =
|
|
8815
|
+
const absPath = import_node_path4.default.join(folder, fileName);
|
|
8816
|
+
if (!import_node_fs6.default.existsSync(absPath)) continue;
|
|
8817
|
+
const content = import_node_fs6.default.readFileSync(absPath, "utf8");
|
|
10134
8818
|
files.push({ path: fileName, content, sha256: sha256Text(content) });
|
|
10135
8819
|
}
|
|
10136
8820
|
for (const fileName of EXPERIMENT_FILESYSTEM_PATH_SCHEMA.experiment) {
|
|
10137
8821
|
const relativePath = `experiment/${fileName}`;
|
|
10138
|
-
const absPath =
|
|
10139
|
-
if (!
|
|
10140
|
-
const content =
|
|
8822
|
+
const absPath = import_node_path4.default.join(folder, relativePath);
|
|
8823
|
+
if (!import_node_fs6.default.existsSync(absPath)) continue;
|
|
8824
|
+
const content = import_node_fs6.default.readFileSync(absPath, "utf8");
|
|
10141
8825
|
files.push({ path: relativePath, content, sha256: sha256Text(content) });
|
|
10142
8826
|
}
|
|
10143
|
-
const variationsRoot =
|
|
10144
|
-
if (
|
|
10145
|
-
for (const variationDir of
|
|
8827
|
+
const variationsRoot = import_node_path4.default.join(folder, "variations");
|
|
8828
|
+
if (import_node_fs6.default.existsSync(variationsRoot)) {
|
|
8829
|
+
for (const variationDir of import_node_fs6.default.readdirSync(variationsRoot, { withFileTypes: true })) {
|
|
10146
8830
|
if (!variationDir.isDirectory()) continue;
|
|
10147
|
-
const base =
|
|
8831
|
+
const base = import_node_path4.default.join(variationsRoot, variationDir.name);
|
|
10148
8832
|
for (const fileName of EXPERIMENT_FILESYSTEM_PATH_SCHEMA.variation) {
|
|
10149
|
-
const absPath =
|
|
10150
|
-
if (!
|
|
8833
|
+
const absPath = import_node_path4.default.join(base, fileName);
|
|
8834
|
+
if (!import_node_fs6.default.existsSync(absPath)) continue;
|
|
10151
8835
|
const relativePath = validateFilesystemPath(
|
|
10152
|
-
|
|
8836
|
+
import_node_path4.default.relative(folder, absPath)
|
|
10153
8837
|
);
|
|
10154
|
-
const content =
|
|
8838
|
+
const content = import_node_fs6.default.readFileSync(absPath, "utf8");
|
|
10155
8839
|
files.push({ path: relativePath, content, sha256: sha256Text(content) });
|
|
10156
8840
|
}
|
|
10157
8841
|
}
|
|
@@ -10193,6 +8877,39 @@ function blockingIssueSummary(issues) {
|
|
|
10193
8877
|
const remaining = issues.length - 1;
|
|
10194
8878
|
return `Experiment filesystem plan has errors. ${issues[0].message}${remaining ? ` and ${remaining} more` : ""}`;
|
|
10195
8879
|
}
|
|
8880
|
+
async function getQaPreviewUrls(creds, experimentId) {
|
|
8881
|
+
try {
|
|
8882
|
+
const res = await getExperiment(creds, experimentId);
|
|
8883
|
+
const storeUrl = res.data?.store_url;
|
|
8884
|
+
if (!res.ok || !res.data || !storeUrl) {
|
|
8885
|
+
return { previewUrls: [], unavailable: true };
|
|
8886
|
+
}
|
|
8887
|
+
const experiment = res.data;
|
|
8888
|
+
const previewUrls = (experiment.variations ?? []).filter((variation) => !variation.is_control).map((variation) => ({
|
|
8889
|
+
variationId: variation.id,
|
|
8890
|
+
variationName: variation.name,
|
|
8891
|
+
url: buildQaPreviewUrl2(
|
|
8892
|
+
storeUrl,
|
|
8893
|
+
experiment.id,
|
|
8894
|
+
variation.id
|
|
8895
|
+
).toString()
|
|
8896
|
+
}));
|
|
8897
|
+
return { previewUrls, unavailable: false };
|
|
8898
|
+
} catch {
|
|
8899
|
+
return { previewUrls: [], unavailable: true };
|
|
8900
|
+
}
|
|
8901
|
+
}
|
|
8902
|
+
function printQaPreviewUrls(experimentId, result) {
|
|
8903
|
+
if (result.unavailable) {
|
|
8904
|
+
console.log(source_default.dim(`Preview: apex experiments get ${experimentId} / drip preview ${experimentId}`));
|
|
8905
|
+
return;
|
|
8906
|
+
}
|
|
8907
|
+
for (const preview of result.previewUrls) {
|
|
8908
|
+
console.log(
|
|
8909
|
+
`Preview (interactive, works while draft \u2014 no on-site editing needed): ${preview.url}`
|
|
8910
|
+
);
|
|
8911
|
+
}
|
|
8912
|
+
}
|
|
10196
8913
|
function printQaRun(run3) {
|
|
10197
8914
|
console.log();
|
|
10198
8915
|
console.log(source_default.bold(`QA run ${run3.id}`));
|
|
@@ -10236,7 +8953,7 @@ async function confirmApply(message) {
|
|
|
10236
8953
|
function parseEvidenceInput(value) {
|
|
10237
8954
|
if (!value) return void 0;
|
|
10238
8955
|
const source = value.startsWith("@") ? value.slice(1) : value;
|
|
10239
|
-
const text = value.startsWith("@") ||
|
|
8956
|
+
const text = value.startsWith("@") || import_node_fs6.default.existsSync(source) ? import_node_fs6.default.readFileSync(import_node_path4.default.resolve(process.cwd(), source), "utf8") : value;
|
|
10240
8957
|
const parsed = JSON.parse(text);
|
|
10241
8958
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
10242
8959
|
throw new Error("QA evidence must be a JSON object");
|
|
@@ -10260,7 +8977,7 @@ async function resolveExperimentForFilesystemOrId(value) {
|
|
|
10260
8977
|
return {
|
|
10261
8978
|
experimentId: session.experimentId,
|
|
10262
8979
|
folder: candidate,
|
|
10263
|
-
label:
|
|
8980
|
+
label: import_node_path4.default.basename(candidate)
|
|
10264
8981
|
};
|
|
10265
8982
|
}
|
|
10266
8983
|
const exp = await resolveExperiment(value);
|
|
@@ -10306,7 +9023,7 @@ async function resolveExperiment(idOrSlug) {
|
|
|
10306
9023
|
res.errorDetails
|
|
10307
9024
|
);
|
|
10308
9025
|
}
|
|
10309
|
-
var list = new Command("list").alias("ls").description("List experiments (archived experiments are hidden by default)").option("-s, --status <status>", "Filter by status (draft|running|paused|completed)").option("-n, --limit <n>", "Max results", "20").option("--include-archived", "Include archived experiments").action(async function action3(opts) {
|
|
9026
|
+
var list = new Command("list").alias("ls").description("List experiments (archived experiments are hidden by default)").option("-s, --status <status>", "Filter by status (draft|awaiting_client_approval|changes_requested|client_approved|qa|ready_to_launch|running|paused|completed)").option("-n, --limit <n>", "Max results", "20").option("--include-archived", "Include archived experiments").action(async function action3(opts) {
|
|
10310
9027
|
const creds = requireCredentials();
|
|
10311
9028
|
const ora2 = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default;
|
|
10312
9029
|
const spinner = process.stderr.isTTY ? ora2("Fetching experiments...").start() : null;
|
|
@@ -10506,25 +9223,7 @@ var create = new Command("create").description("Create a new experiment").requir
|
|
|
10506
9223
|
spinner?.succeed("Experiment created");
|
|
10507
9224
|
output(this, { experiment: res.data }, () => printExperiment(res.data));
|
|
10508
9225
|
});
|
|
10509
|
-
var
|
|
10510
|
-
const creds = requireCredentials();
|
|
10511
|
-
const ora2 = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default;
|
|
10512
|
-
const spinner = ora2("Starting experiment...").start();
|
|
10513
|
-
const exp = await resolveExperiment(idOrSlug);
|
|
10514
|
-
if (!exp) {
|
|
10515
|
-
spinner.fail(`Experiment "${idOrSlug}" not found`);
|
|
10516
|
-
fail(this, `Experiment "${idOrSlug}" not found`);
|
|
10517
|
-
}
|
|
10518
|
-
const res = await startExperiment(creds, exp.id);
|
|
10519
|
-
if (!res.ok) {
|
|
10520
|
-
spinner.fail("Failed to start experiment");
|
|
10521
|
-
fail(this, res.error ?? "Failed to start experiment", classifyStatus(res.status), res.errorDetails);
|
|
10522
|
-
}
|
|
10523
|
-
output(this, { experiment: { id: exp.id, name: exp.name, status: "running" } }, () => {
|
|
10524
|
-
spinner.succeed(`Experiment "${exp.name}" is now ${statusBadge("running")}`);
|
|
10525
|
-
});
|
|
10526
|
-
});
|
|
10527
|
-
var pause = new Command("pause").description("Pause a running experiment").argument("<id-or-slug>", "Experiment ID or slug").action(async function action8(idOrSlug) {
|
|
9226
|
+
var pause = new Command("pause").description("Pause a running experiment").argument("<id-or-slug>", "Experiment ID or slug").action(async function action7(idOrSlug) {
|
|
10528
9227
|
const creds = requireCredentials();
|
|
10529
9228
|
const ora2 = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default;
|
|
10530
9229
|
const spinner = ora2("Pausing experiment...").start();
|
|
@@ -10542,7 +9241,7 @@ var pause = new Command("pause").description("Pause a running experiment").argum
|
|
|
10542
9241
|
spinner.succeed(`Experiment "${exp.name}" is now ${statusBadge("paused")}`);
|
|
10543
9242
|
});
|
|
10544
9243
|
});
|
|
10545
|
-
var complete = new Command("complete").description("Complete/stop an experiment").argument("<id-or-slug>", "Experiment ID or slug").action(async function
|
|
9244
|
+
var complete = new Command("complete").description("Complete/stop an experiment").argument("<id-or-slug>", "Experiment ID or slug").action(async function action8(idOrSlug) {
|
|
10546
9245
|
const creds = requireCredentials();
|
|
10547
9246
|
const ora2 = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default;
|
|
10548
9247
|
const spinner = ora2("Completing experiment...").start();
|
|
@@ -10560,7 +9259,7 @@ var complete = new Command("complete").description("Complete/stop an experiment"
|
|
|
10560
9259
|
spinner.succeed(`Experiment "${exp.name}" is now ${statusBadge("completed")}`);
|
|
10561
9260
|
});
|
|
10562
9261
|
});
|
|
10563
|
-
var del = new Command("delete").alias("rm").description("Delete an experiment").argument("<id-or-slug>", "Experiment ID or slug").option("-f, --force", "Skip confirmation").action(async function
|
|
9262
|
+
var del = new Command("delete").alias("rm").description("Delete an experiment").argument("<id-or-slug>", "Experiment ID or slug").option("-f, --force", "Skip confirmation").action(async function action9(idOrSlug, opts) {
|
|
10564
9263
|
const creds = requireCredentials();
|
|
10565
9264
|
const ora2 = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default;
|
|
10566
9265
|
const exp = await resolveExperiment(idOrSlug);
|
|
@@ -10602,7 +9301,7 @@ var del = new Command("delete").alias("rm").description("Delete an experiment").
|
|
|
10602
9301
|
}
|
|
10603
9302
|
output(this, { experiment: { id: exp.id, name: exp.name }, deleted: true }, () => spinner.succeed(`Experiment "${exp.name}" deleted.`));
|
|
10604
9303
|
});
|
|
10605
|
-
var pullFilesystem = new Command("pull").description("Pull an experiment as an editable filesystem").argument("<id-or-slug>", "Experiment ID or slug").option("--out <path>", "Target folder").addOption(new Option("--dir <folder>", "Legacy alias for --out").hideHelp()).action(async function
|
|
9304
|
+
var pullFilesystem = new Command("pull").description("Pull an experiment as an editable filesystem").argument("<id-or-slug>", "Experiment ID or slug").option("--out <path>", "Target folder").addOption(new Option("--dir <folder>", "Legacy alias for --out").hideHelp()).action(async function action10(idOrSlug, opts) {
|
|
10606
9305
|
const creds = requireCredentials();
|
|
10607
9306
|
const ora2 = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default;
|
|
10608
9307
|
const spinner = ora2("Pulling experiment filesystem...").start();
|
|
@@ -10622,7 +9321,7 @@ var pullFilesystem = new Command("pull").description("Pull an experiment as an e
|
|
|
10622
9321
|
res.data.files.map((file) => [file.path, { sha256: file.sha256 }])
|
|
10623
9322
|
)
|
|
10624
9323
|
});
|
|
10625
|
-
output(this, { folder, experimentId: exp.id, files: res.data.files.map((file) =>
|
|
9324
|
+
output(this, { folder, experimentId: exp.id, files: res.data.files.map((file) => import_node_path4.default.join(folder, file.path)) }, () => {
|
|
10626
9325
|
spinner.succeed(`Pulled ${exp.name}`);
|
|
10627
9326
|
console.log(source_default.cyan(folder));
|
|
10628
9327
|
});
|
|
@@ -10631,7 +9330,7 @@ var pullFilesystem = new Command("pull").description("Pull an experiment as an e
|
|
|
10631
9330
|
throw error;
|
|
10632
9331
|
}
|
|
10633
9332
|
});
|
|
10634
|
-
var planFilesystem = new Command("plan").description("Plan experiment filesystem changes").argument("[folder]", "Experiment filesystem folder", ".").action(async function
|
|
9333
|
+
var planFilesystem = new Command("plan").description("Plan experiment filesystem changes").argument("[folder]", "Experiment filesystem folder", ".").action(async function action11(folderArg) {
|
|
10635
9334
|
const creds = requireCredentials();
|
|
10636
9335
|
try {
|
|
10637
9336
|
const folder = folderPath(folderArg);
|
|
@@ -10654,7 +9353,7 @@ var planFilesystem = new Command("plan").description("Plan experiment filesystem
|
|
|
10654
9353
|
fail(this, error instanceof Error ? error.message : String(error));
|
|
10655
9354
|
}
|
|
10656
9355
|
});
|
|
10657
|
-
var applyFilesystem = new Command("apply").description("Apply experiment filesystem changes to Apex").argument("[folder]", "Experiment filesystem folder", ".").option("-f, --force", "Skip confirmation when QA will be invalidated").action(async function
|
|
9356
|
+
var applyFilesystem = new Command("apply").description("Apply experiment filesystem changes to Apex").argument("[folder]", "Experiment filesystem folder", ".").option("-f, --force", "Skip confirmation when QA will be invalidated").action(async function action12(folderArg, opts) {
|
|
10658
9357
|
const creds = requireCredentials();
|
|
10659
9358
|
const ora2 = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default;
|
|
10660
9359
|
try {
|
|
@@ -10711,15 +9410,18 @@ var applyFilesystem = new Command("apply").description("Apply experiment filesys
|
|
|
10711
9410
|
folder,
|
|
10712
9411
|
session.experimentId
|
|
10713
9412
|
);
|
|
10714
|
-
|
|
9413
|
+
const previewResult = await getQaPreviewUrls(creds, session.experimentId);
|
|
9414
|
+
const { previewUrls } = previewResult;
|
|
9415
|
+
output(this, { folder, experimentId: session.experimentId, applied: true, plan: planRes.data, result: res.data, previewUrls }, () => {
|
|
10715
9416
|
printFilesystemPlan(planRes.data);
|
|
9417
|
+
printQaPreviewUrls(session.experimentId, previewResult);
|
|
10716
9418
|
});
|
|
10717
9419
|
} catch (error) {
|
|
10718
9420
|
if (error instanceof CliError) throw error;
|
|
10719
9421
|
fail(this, error instanceof Error ? error.message : String(error));
|
|
10720
9422
|
}
|
|
10721
9423
|
});
|
|
10722
|
-
var qaStatus = new Command("status").description("Show or create the latest experiment QA run").argument("[folder-or-id]", "Experiment filesystem folder, ID, or slug", ".").option("--json", "Print the QA run in the standard JSON envelope").action(async function
|
|
9424
|
+
var qaStatus = new Command("status").description("Show or create the latest experiment QA run").argument("[folder-or-id]", "Experiment filesystem folder, ID, or slug", ".").option("--json", "Print the QA run in the standard JSON envelope").action(async function action13(target) {
|
|
10723
9425
|
const creds = requireCredentials();
|
|
10724
9426
|
const ora2 = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default;
|
|
10725
9427
|
const spinner = ora2("Fetching QA run...").start();
|
|
@@ -10740,7 +9442,7 @@ var qaStatus = new Command("status").description("Show or create the latest expe
|
|
|
10740
9442
|
}
|
|
10741
9443
|
});
|
|
10742
9444
|
var qaCheck = new Command("check").description("Update one QA checklist item with status, notes, and evidence").argument("[folder-or-id]", "Experiment filesystem folder, ID, or slug", ".").requiredOption("--item <id>", "Checklist item id").requiredOption("--status <status>", "pending|pass|warning|fail|accepted|skipped").option("--notes <text>", "Checklist notes").option("--evidence <json-or-path>", "Evidence JSON object or @path/to/evidence.json").option("--json", "Print raw QA run JSON").action(
|
|
10743
|
-
async function
|
|
9445
|
+
async function action14(target, opts) {
|
|
10744
9446
|
const creds = requireCredentials();
|
|
10745
9447
|
const ora2 = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default;
|
|
10746
9448
|
const spinner = ora2("Updating QA checklist...").start();
|
|
@@ -10779,7 +9481,7 @@ var qaCheck = new Command("check").description("Update one QA checklist item wit
|
|
|
10779
9481
|
}
|
|
10780
9482
|
);
|
|
10781
9483
|
var qaNotes = new Command("notes").description("Update QA run notes or acceptance criteria summary").argument("[folder-or-id]", "Experiment filesystem folder, ID, or slug", ".").option("--notes <text>", "QA run notes").option("--acceptance <text>", "Acceptance criteria summary").option("--json", "Print raw QA run JSON").action(
|
|
10782
|
-
async function
|
|
9484
|
+
async function action15(target, opts) {
|
|
10783
9485
|
const creds = requireCredentials();
|
|
10784
9486
|
const ora2 = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default;
|
|
10785
9487
|
const spinner = ora2("Updating QA notes...").start();
|
|
@@ -10814,7 +9516,7 @@ var qaNotes = new Command("notes").description("Update QA run notes or acceptanc
|
|
|
10814
9516
|
}
|
|
10815
9517
|
}
|
|
10816
9518
|
);
|
|
10817
|
-
var qaApprove = new Command("approve").description("Mark QA done after the checklist gates are ready").argument("[folder-or-id]", "Experiment filesystem folder, ID, or slug", ".").action(async function
|
|
9519
|
+
var qaApprove = new Command("approve").description("Mark QA done after the checklist gates are ready").argument("[folder-or-id]", "Experiment filesystem folder, ID, or slug", ".").action(async function action16(target) {
|
|
10818
9520
|
const creds = requireCredentials();
|
|
10819
9521
|
const ora2 = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default;
|
|
10820
9522
|
const spinner = ora2("Approving QA...").start();
|
|
@@ -10833,7 +9535,7 @@ var qaApprove = new Command("approve").description("Mark QA done after the check
|
|
|
10833
9535
|
throw error;
|
|
10834
9536
|
}
|
|
10835
9537
|
});
|
|
10836
|
-
var qaReopen = new Command("reopen").description("Reopen QA and clear scheduled launch state").argument("[folder-or-id]", "Experiment filesystem folder, ID, or slug", ".").action(async function
|
|
9538
|
+
var qaReopen = new Command("reopen").description("Reopen QA and clear scheduled launch state").argument("[folder-or-id]", "Experiment filesystem folder, ID, or slug", ".").action(async function action17(target) {
|
|
10837
9539
|
const creds = requireCredentials();
|
|
10838
9540
|
const ora2 = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default;
|
|
10839
9541
|
const spinner = ora2("Reopening QA...").start();
|
|
@@ -10856,7 +9558,7 @@ var qa = new Command("qa").description("Manage experiment QA runs from the CLI")
|
|
|
10856
9558
|
var SCREENSHOT_DEVICES = ["desktop_chrome", "iphone_15_pro", "galaxy_s24", "ipad_pro", "desktop_safari"];
|
|
10857
9559
|
var safeFilenamePart = (value) => value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "screenshot";
|
|
10858
9560
|
var commaList = (value) => value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
10859
|
-
var screenshots = new Command("screenshots").description("Force-assign every capture, including control, to its variation server-side for deterministic screenshots").argument("<experimentRef>", "Experiment ID or slug").option("--url <target>", "Target URL override (not supported by the current v1 API)").option("--devices <list>", `Comma-separated devices (default: ${SCREENSHOT_DEVICES.join(",")})`).option("--variations <list>", "Comma-separated treatment IDs, slugs, or names; control is always included and force-assigned").option("--out <dir>", "Download directory", ".").option("--json", "Print the completed raw job payload with signed URLs").option("--timeout <seconds>", "Maximum wait for completion; finished results are always downloaded", "300").action(async function
|
|
9561
|
+
var screenshots = new Command("screenshots").description("Force-assign every capture, including control, to its variation server-side for deterministic screenshots").argument("<experimentRef>", "Experiment ID or slug").option("--url <target>", "Target URL override (not supported by the current v1 API)").option("--devices <list>", `Comma-separated devices (default: ${SCREENSHOT_DEVICES.join(",")})`).option("--variations <list>", "Comma-separated treatment IDs, slugs, or names; control is always included and force-assigned").option("--out <dir>", "Download directory", ".").option("--json", "Print the completed raw job payload with signed URLs").option("--timeout <seconds>", "Maximum wait for completion; finished results are always downloaded", "300").action(async function action18(experimentRef, opts) {
|
|
10860
9562
|
if (opts.url) fail(this, "--url is not supported by the current screenshot v1 API.", EXIT_CODES.validation);
|
|
10861
9563
|
const timeoutSeconds = Number(opts.timeout);
|
|
10862
9564
|
if (!Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0) fail(this, "--timeout must be a positive number of seconds.", EXIT_CODES.validation);
|
|
@@ -10889,25 +9591,25 @@ var screenshots = new Command("screenshots").description("Force-assign every cap
|
|
|
10889
9591
|
const created = await createExperimentScreenshots(creds, experiment.id, { devices, variation_ids: variationIds });
|
|
10890
9592
|
if (!created.ok || !created.data) fail(this, created.error ?? "Failed to create screenshot job", classifyStatus(created.status), created.errorDetails);
|
|
10891
9593
|
let job = created.data;
|
|
10892
|
-
const deadline =
|
|
10893
|
-
let delayMs =
|
|
9594
|
+
const deadline = screenshotNow() + timeoutSeconds * 1e3;
|
|
9595
|
+
let delayMs = cliTestSetting("API") === "1" ? 1 : 1e3;
|
|
10894
9596
|
let consecutiveTransientFailures = 0;
|
|
10895
9597
|
while (job.status === "queued" || job.status === "processing") {
|
|
10896
|
-
if (
|
|
10897
|
-
await new Promise((resolve) => setTimeout(resolve, Math.min(delayMs, Math.max(0, deadline -
|
|
10898
|
-
if (
|
|
9598
|
+
if (screenshotNow() >= deadline) fail(this, `Screenshot job ${job.job_id} timed out after ${timeoutSeconds}s.`);
|
|
9599
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(delayMs, Math.max(0, deadline - screenshotNow()))));
|
|
9600
|
+
if (screenshotNow() >= deadline) fail(this, `Screenshot job ${job.job_id} timed out after ${timeoutSeconds}s.`);
|
|
10899
9601
|
let polled;
|
|
10900
9602
|
try {
|
|
10901
|
-
polled = await getScreenshotJob(creds, job.job_id, Math.max(1, deadline -
|
|
9603
|
+
polled = await getScreenshotJob(creds, job.job_id, Math.max(1, deadline - screenshotNow()));
|
|
10902
9604
|
} catch (error) {
|
|
10903
|
-
if (
|
|
9605
|
+
if (screenshotNow() >= deadline) fail(this, `Screenshot job ${job.job_id} timed out after ${timeoutSeconds}s.`);
|
|
10904
9606
|
consecutiveTransientFailures += 1;
|
|
10905
9607
|
if (consecutiveTransientFailures > 3) throw error;
|
|
10906
9608
|
delayMs = Math.min(Math.round(delayMs * 1.6), 1e4);
|
|
10907
9609
|
continue;
|
|
10908
9610
|
}
|
|
10909
9611
|
const polledToTerminalState = polled.ok && polled.data && polled.data.status !== "queued" && polled.data.status !== "processing";
|
|
10910
|
-
if (!polledToTerminalState &&
|
|
9612
|
+
if (!polledToTerminalState && screenshotNow() >= deadline) {
|
|
10911
9613
|
fail(this, `Screenshot job ${job.job_id} timed out after ${timeoutSeconds}s.`);
|
|
10912
9614
|
}
|
|
10913
9615
|
if (!polled.ok || !polled.data) {
|
|
@@ -10923,8 +9625,8 @@ var screenshots = new Command("screenshots").description("Force-assign every cap
|
|
|
10923
9625
|
delayMs = Math.min(Math.round(delayMs * 1.6), 1e4);
|
|
10924
9626
|
}
|
|
10925
9627
|
if (job.status === "failed") fail(this, `Screenshot job failed: ${job.error ?? "unknown error"}`);
|
|
10926
|
-
const outputDir =
|
|
10927
|
-
|
|
9628
|
+
const outputDir = import_node_path4.default.resolve(process.cwd(), opts.out);
|
|
9629
|
+
import_node_fs6.default.mkdirSync(outputDir, { recursive: true });
|
|
10928
9630
|
const rows = [];
|
|
10929
9631
|
const errors = [];
|
|
10930
9632
|
const usedFilenames = /* @__PURE__ */ new Set();
|
|
@@ -10961,7 +9663,7 @@ var screenshots = new Command("screenshots").description("Force-assign every cap
|
|
|
10961
9663
|
try {
|
|
10962
9664
|
const response = await fetch(url);
|
|
10963
9665
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
10964
|
-
await
|
|
9666
|
+
await import_node_fs6.default.promises.writeFile(import_node_path4.default.join(outputDir, filename), Buffer.from(await response.arrayBuffer()));
|
|
10965
9667
|
workerResults.push({ index, row: [variation.name, device, filename] });
|
|
10966
9668
|
} catch (error) {
|
|
10967
9669
|
workerResults.push({ index, error: `${variation.name}/${device}: ${error instanceof Error ? error.message : String(error)}` });
|
|
@@ -10982,7 +9684,7 @@ var screenshots = new Command("screenshots").description("Force-assign every cap
|
|
|
10982
9684
|
printTable(["VARIATION", "DEVICE", "FILE"], rows, [24, 20, 48]);
|
|
10983
9685
|
});
|
|
10984
9686
|
});
|
|
10985
|
-
var experimentsCommand = new Command("experiments").alias("exp").description("Manage experiments").addCommand(list).addCommand(get).addCommand(results).addCommand(create).addCommand(
|
|
9687
|
+
var experimentsCommand = new Command("experiments").alias("exp").description("Manage experiments").addCommand(list).addCommand(get).addCommand(results).addCommand(create).addCommand(pause).addCommand(complete).addCommand(del).addCommand(pullFilesystem).addCommand(planFilesystem).addCommand(applyFilesystem).addCommand(screenshots).addCommand(qa).addHelpText("after", `
|
|
10986
9688
|
Examples:
|
|
10987
9689
|
apex experiments list --status running
|
|
10988
9690
|
apex experiments get checkout-banner
|
|
@@ -10992,9 +9694,9 @@ Examples:
|
|
|
10992
9694
|
|
|
10993
9695
|
// src/commands/push.ts
|
|
10994
9696
|
init_source();
|
|
10995
|
-
var
|
|
10996
|
-
var
|
|
10997
|
-
var
|
|
9697
|
+
var import_node_fs7 = __toESM(require("node:fs"));
|
|
9698
|
+
var import_node_os6 = __toESM(require("node:os"));
|
|
9699
|
+
var import_node_path5 = __toESM(require("node:path"));
|
|
10998
9700
|
var import_node_url2 = require("node:url");
|
|
10999
9701
|
function toApiBody(exp) {
|
|
11000
9702
|
return {
|
|
@@ -11018,7 +9720,7 @@ function toApiBody(exp) {
|
|
|
11018
9720
|
};
|
|
11019
9721
|
}
|
|
11020
9722
|
async function importConfigModule(absPath) {
|
|
11021
|
-
const ext =
|
|
9723
|
+
const ext = import_node_path5.default.extname(absPath).toLowerCase();
|
|
11022
9724
|
if (ext === ".js" || ext === ".mjs" || ext === ".cjs") {
|
|
11023
9725
|
return await import((0, import_node_url2.pathToFileURL)(absPath).href);
|
|
11024
9726
|
}
|
|
@@ -11032,8 +9734,8 @@ async function importConfigModule(absPath) {
|
|
|
11032
9734
|
` + (err instanceof Error ? err.message : String(err))
|
|
11033
9735
|
);
|
|
11034
9736
|
}
|
|
11035
|
-
const tmpDir =
|
|
11036
|
-
const outFile =
|
|
9737
|
+
const tmpDir = import_node_fs7.default.mkdtempSync(import_node_path5.default.join(import_node_os6.default.tmpdir(), "drip-config-"));
|
|
9738
|
+
const outFile = import_node_path5.default.join(tmpDir, "config.mjs");
|
|
11037
9739
|
try {
|
|
11038
9740
|
await esbuild.build({
|
|
11039
9741
|
entryPoints: [absPath],
|
|
@@ -11049,7 +9751,7 @@ async function importConfigModule(absPath) {
|
|
|
11049
9751
|
return await import(href);
|
|
11050
9752
|
} finally {
|
|
11051
9753
|
try {
|
|
11052
|
-
|
|
9754
|
+
import_node_fs7.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
11053
9755
|
} catch {
|
|
11054
9756
|
}
|
|
11055
9757
|
}
|
|
@@ -11063,7 +9765,7 @@ function isRecord(value) {
|
|
|
11063
9765
|
}
|
|
11064
9766
|
var pushCommand = new Command("push").description(
|
|
11065
9767
|
"Push experiment definitions from config file to the API (idempotent by slug)"
|
|
11066
|
-
).option("-c, --config <path>", "Config file path").option("--dry-run", "Show what would be pushed without making changes").action(async function
|
|
9768
|
+
).option("-c, --config <path>", "Config file path").option("--dry-run", "Show what would be pushed without making changes").action(async function action19(opts) {
|
|
11067
9769
|
const creds = requireCredentials();
|
|
11068
9770
|
const ora2 = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default;
|
|
11069
9771
|
const configPath = opts.config ?? findConfigPath();
|
|
@@ -11077,7 +9779,7 @@ var pushCommand = new Command("push").description(
|
|
|
11077
9779
|
const spinner = process.stderr.isTTY ? ora2(`Loading config from ${source_default.cyan(configPath)}...`).start() : null;
|
|
11078
9780
|
let experiments;
|
|
11079
9781
|
try {
|
|
11080
|
-
const absPath =
|
|
9782
|
+
const absPath = import_node_path5.default.resolve(configPath);
|
|
11081
9783
|
const mod = await importConfigModule(absPath);
|
|
11082
9784
|
const exported = isRecord(mod) && "default" in mod ? mod.default : mod;
|
|
11083
9785
|
if (Array.isArray(exported)) {
|
|
@@ -11181,17 +9883,22 @@ var pushCommand = new Command("push").description(
|
|
|
11181
9883
|
|
|
11182
9884
|
// src/commands/preview.ts
|
|
11183
9885
|
init_source();
|
|
11184
|
-
var previewCommand = new Command("preview").description("Open QA preview URL for an experiment variation").argument("<id-or-slug>", "Experiment ID or slug").argument("[variation-id]", "Variation ID (defaults to first non-control)").option("--store-url <url>", "Override experiment store_url (target website URL)").option("--proxy", "Create an artifact-injecting proxy preview").option("--no-open", "Print URL instead of opening browser").action(async (idOrSlug, variationId, opts) => {
|
|
9886
|
+
var previewCommand = new Command("preview").description("Open QA preview URL for an experiment variation").argument("<id-or-slug>", "Experiment ID or slug").argument("[variation-id]", "Variation ID (defaults to first non-control)").option("--store-url <url>", "Override experiment store_url (target website URL)").option("--proxy", "Create an artifact-injecting proxy preview").option("--ttl <hours>", "Proxy preview lifetime in hours (1-168)").option("--no-open", "Print URL instead of opening browser").action(async (idOrSlug, variationId, opts) => {
|
|
11185
9887
|
const creds = requireCredentials();
|
|
11186
9888
|
const ora2 = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default;
|
|
11187
9889
|
if (opts?.proxy) {
|
|
9890
|
+
const ttlHours = opts.ttl === void 0 ? void 0 : Number(opts.ttl);
|
|
9891
|
+
if (ttlHours !== void 0 && (!Number.isInteger(ttlHours) || ttlHours < 1 || ttlHours > 168)) {
|
|
9892
|
+
fail(previewCommand, "--ttl must be an integer between 1 and 168 hours");
|
|
9893
|
+
}
|
|
11188
9894
|
const identifier = idOrSlug.startsWith("slug:") ? idOrSlug.slice(5) : idOrSlug;
|
|
11189
9895
|
const proxySpinner = ora2("Creating proxy preview session...").start();
|
|
11190
9896
|
const created = await createProxyPreviewSession(
|
|
11191
9897
|
creds,
|
|
11192
9898
|
identifier,
|
|
11193
9899
|
variationId,
|
|
11194
|
-
opts.storeUrl
|
|
9900
|
+
opts.storeUrl,
|
|
9901
|
+
ttlHours
|
|
11195
9902
|
);
|
|
11196
9903
|
proxySpinner.stop();
|
|
11197
9904
|
if (!created.ok || !created.data) {
|
|
@@ -11202,7 +9909,15 @@ var previewCommand = new Command("preview").description("Open QA preview URL for
|
|
|
11202
9909
|
);
|
|
11203
9910
|
}
|
|
11204
9911
|
const data = created.data;
|
|
11205
|
-
output(previewCommand, data, () =>
|
|
9912
|
+
output(previewCommand, data, () => {
|
|
9913
|
+
console.log(source_default.cyan(data.preview_url));
|
|
9914
|
+
const expiresAt = new Date(data.expires_at);
|
|
9915
|
+
const remainingMs = expiresAt.getTime() - Date.now();
|
|
9916
|
+
const value = remainingMs < 864e5 ? Math.max(1, Math.round(remainingMs / 36e5)) : Math.round(remainingMs / 864e5);
|
|
9917
|
+
const unit = remainingMs < 864e5 ? "hour" : "day";
|
|
9918
|
+
const duration = `${value} ${unit}${value === 1 ? "" : "s"}`;
|
|
9919
|
+
console.log(`Valid until ${expiresAt.toISOString()} (${duration}). Re-run this command for a fresh link.`);
|
|
9920
|
+
});
|
|
11206
9921
|
if (opts.open !== false && !shouldUseJson(previewCommand)) {
|
|
11207
9922
|
try {
|
|
11208
9923
|
const open2 = (await Promise.resolve().then(() => (init_open(), open_exports))).default;
|
|
@@ -11250,21 +9965,16 @@ var previewCommand = new Command("preview").description("Open QA preview URL for
|
|
|
11250
9965
|
variation = exp.variations.find((v) => !v.is_control) ?? exp.variations[0];
|
|
11251
9966
|
}
|
|
11252
9967
|
spinner.stop();
|
|
11253
|
-
const storeUrl = exp.store_url
|
|
9968
|
+
const storeUrl = opts?.storeUrl ?? exp.store_url;
|
|
11254
9969
|
if (!storeUrl) {
|
|
11255
9970
|
fail(previewCommand, 'Experiment is missing "store_url". Set it on the experiment or pass --store-url <url>.');
|
|
11256
9971
|
}
|
|
11257
9972
|
let previewUrl;
|
|
11258
9973
|
try {
|
|
11259
|
-
previewUrl =
|
|
9974
|
+
previewUrl = buildQaPreviewUrl2(storeUrl, exp.id, variation.id);
|
|
11260
9975
|
} catch {
|
|
11261
9976
|
fail(previewCommand, 'Invalid store URL. Pass an absolute URL like "https://example.com/products/item".');
|
|
11262
9977
|
}
|
|
11263
|
-
previewUrl.searchParams.set("drip_force", `${exp.id}:${variation.id}`);
|
|
11264
|
-
previewUrl.searchParams.set("drip_experiment_id", exp.id);
|
|
11265
|
-
previewUrl.searchParams.set("drip_variation_id", variation.id);
|
|
11266
|
-
previewUrl.searchParams.set("drip_qa", "1");
|
|
11267
|
-
previewUrl.searchParams.set("drip_debug", "1");
|
|
11268
9978
|
output(previewCommand, { experimentId: exp.id, variationId: variation.id, previewUrl: previewUrl.toString() }, () => {
|
|
11269
9979
|
console.log();
|
|
11270
9980
|
console.log(source_default.bold(`Preview: ${exp.name}`));
|
|
@@ -11286,25 +9996,25 @@ var previewCommand = new Command("preview").description("Open QA preview URL for
|
|
|
11286
9996
|
|
|
11287
9997
|
// src/commands/harness.ts
|
|
11288
9998
|
init_source();
|
|
11289
|
-
var
|
|
11290
|
-
var
|
|
9999
|
+
var import_node_fs8 = __toESM(require("node:fs"));
|
|
10000
|
+
var import_node_path6 = __toESM(require("node:path"));
|
|
11291
10001
|
var import_node_child_process6 = require("node:child_process");
|
|
11292
10002
|
function slugify(value, fallback2 = "variation") {
|
|
11293
10003
|
const slug = value.normalize("NFKD").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
11294
10004
|
return slug || fallback2;
|
|
11295
10005
|
}
|
|
11296
10006
|
function readJson(filePath) {
|
|
11297
|
-
if (!
|
|
11298
|
-
return JSON.parse(
|
|
10007
|
+
if (!import_node_fs8.default.existsSync(filePath)) return null;
|
|
10008
|
+
return JSON.parse(import_node_fs8.default.readFileSync(filePath, "utf8"));
|
|
11299
10009
|
}
|
|
11300
10010
|
function resolveFolder(folder) {
|
|
11301
|
-
return
|
|
10011
|
+
return import_node_path6.default.resolve(process.cwd(), folder);
|
|
11302
10012
|
}
|
|
11303
10013
|
function experimentConfigPath(folder) {
|
|
11304
|
-
return
|
|
10014
|
+
return import_node_path6.default.join(folder, "experiment.config.json");
|
|
11305
10015
|
}
|
|
11306
10016
|
function apexConfigPath(folder) {
|
|
11307
|
-
return
|
|
10017
|
+
return import_node_path6.default.join(folder, "apex.harness.json");
|
|
11308
10018
|
}
|
|
11309
10019
|
function loadHarnessConfig(folder) {
|
|
11310
10020
|
const experiment = readJson(experimentConfigPath(folder)) ?? {};
|
|
@@ -11313,8 +10023,8 @@ function loadHarnessConfig(folder) {
|
|
|
11313
10023
|
const targetOrigin = experiment.targetOrigin?.replace(/\/+$/, "") ?? "";
|
|
11314
10024
|
const defaultPath = experiment.defaultPath ?? "/";
|
|
11315
10025
|
const targetUrl = targetOrigin ? new URL(defaultPath, targetOrigin).toString() : "";
|
|
11316
|
-
const id = experiment.id ??
|
|
11317
|
-
const variation =
|
|
10026
|
+
const id = experiment.id ?? import_node_path6.default.basename(import_node_path6.default.dirname(folder));
|
|
10027
|
+
const variation = import_node_path6.default.basename(folder);
|
|
11318
10028
|
return {
|
|
11319
10029
|
experiment,
|
|
11320
10030
|
apex: {
|
|
@@ -11346,35 +10056,35 @@ function runAbHarness(args, cwd) {
|
|
|
11346
10056
|
function artifactPaths(folder, experiment) {
|
|
11347
10057
|
const variant = experiment.variant;
|
|
11348
10058
|
if (variant?.endsWith(".bundle.js")) {
|
|
11349
|
-
const jsPath =
|
|
11350
|
-
const cssPath =
|
|
11351
|
-
return { jsPath, cssPath:
|
|
10059
|
+
const jsPath = import_node_path6.default.join(folder, variant.replace(/\.bundle\.js$/, ".js"));
|
|
10060
|
+
const cssPath = import_node_path6.default.join(folder, variant.replace(/\.bundle\.js$/, ".css"));
|
|
10061
|
+
return { jsPath, cssPath: import_node_fs8.default.existsSync(cssPath) ? cssPath : null };
|
|
11352
10062
|
}
|
|
11353
|
-
const buildDir =
|
|
11354
|
-
const files =
|
|
10063
|
+
const buildDir = import_node_path6.default.join(folder, "build");
|
|
10064
|
+
const files = import_node_fs8.default.existsSync(buildDir) ? import_node_fs8.default.readdirSync(buildDir) : [];
|
|
11355
10065
|
const js = files.find((file) => file.endsWith(".js") && !file.endsWith(".bundle.js") && !file.endsWith(".min.js"));
|
|
11356
10066
|
const css = files.find((file) => file.endsWith(".css") && !file.endsWith(".min.css"));
|
|
11357
10067
|
if (!js) throw new Error("Could not find harness JS artifact in build/");
|
|
11358
10068
|
return {
|
|
11359
|
-
jsPath:
|
|
11360
|
-
cssPath: css ?
|
|
10069
|
+
jsPath: import_node_path6.default.join(buildDir, js),
|
|
10070
|
+
cssPath: css ? import_node_path6.default.join(buildDir, css) : null
|
|
11361
10071
|
};
|
|
11362
10072
|
}
|
|
11363
10073
|
async function uploadVersion(params) {
|
|
11364
10074
|
const creds = requireCredentials();
|
|
11365
10075
|
const { jsPath, cssPath } = artifactPaths(params.folder, params.experiment);
|
|
11366
|
-
if (!
|
|
10076
|
+
if (!import_node_fs8.default.existsSync(jsPath)) throw new Error(`Missing JS artifact: ${jsPath}`);
|
|
11367
10077
|
const form = new FormData();
|
|
11368
10078
|
form.append(
|
|
11369
10079
|
"js",
|
|
11370
|
-
new Blob([
|
|
11371
|
-
|
|
10080
|
+
new Blob([import_node_fs8.default.readFileSync(jsPath)], { type: "application/javascript" }),
|
|
10081
|
+
import_node_path6.default.basename(jsPath)
|
|
11372
10082
|
);
|
|
11373
|
-
if (cssPath &&
|
|
10083
|
+
if (cssPath && import_node_fs8.default.existsSync(cssPath) && import_node_fs8.default.statSync(cssPath).size > 0) {
|
|
11374
10084
|
form.append(
|
|
11375
10085
|
"css",
|
|
11376
|
-
new Blob([
|
|
11377
|
-
|
|
10086
|
+
new Blob([import_node_fs8.default.readFileSync(cssPath)], { type: "text/css" }),
|
|
10087
|
+
import_node_path6.default.basename(cssPath)
|
|
11378
10088
|
);
|
|
11379
10089
|
}
|
|
11380
10090
|
form.append(
|
|
@@ -11383,8 +10093,8 @@ async function uploadVersion(params) {
|
|
|
11383
10093
|
...params.apex,
|
|
11384
10094
|
resetJs: cleanupResetJs(params.experiment),
|
|
11385
10095
|
builtAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11386
|
-
jsPath:
|
|
11387
|
-
cssPath: cssPath ?
|
|
10096
|
+
jsPath: import_node_path6.default.relative(params.folder, jsPath),
|
|
10097
|
+
cssPath: cssPath ? import_node_path6.default.relative(params.folder, cssPath) : null
|
|
11388
10098
|
})
|
|
11389
10099
|
);
|
|
11390
10100
|
const res = await uploadHarnessVersion(creds, params.session.sessionId, form);
|
|
@@ -11410,18 +10120,18 @@ async function createSession(folder) {
|
|
|
11410
10120
|
return { session: res.data, apex, experiment };
|
|
11411
10121
|
}
|
|
11412
10122
|
var harnessCommand = new Command("harness").description("Develop AB Test Harness projects through Apex live QA sessions");
|
|
11413
|
-
harnessCommand.command("init").argument("<folder>", "AB Test Harness experiment folder").option("--force", "Overwrite existing apex.harness.json").action(function
|
|
10123
|
+
harnessCommand.command("init").argument("<folder>", "AB Test Harness experiment folder").option("--force", "Overwrite existing apex.harness.json").action(function action20(folderArg, opts) {
|
|
11414
10124
|
const folder = resolveFolder(folderArg);
|
|
11415
10125
|
const targetPath = apexConfigPath(folder);
|
|
11416
|
-
if (
|
|
10126
|
+
if (import_node_fs8.default.existsSync(targetPath) && !opts.force) {
|
|
11417
10127
|
fail(this, `${targetPath} already exists. Use --force to overwrite.`, EXIT_CODES.conflict);
|
|
11418
10128
|
}
|
|
11419
10129
|
const { apex } = loadHarnessConfig(folder);
|
|
11420
|
-
|
|
10130
|
+
import_node_fs8.default.writeFileSync(targetPath, `${JSON.stringify(apex, null, 2)}
|
|
11421
10131
|
`, "utf8");
|
|
11422
10132
|
output(this, { folder, configPath: targetPath, experiment: apex.experimentSlug, variation: apex.variationSlug }, () => printSuccess(`Created ${targetPath}`));
|
|
11423
10133
|
});
|
|
11424
|
-
harnessCommand.command("dev").argument("<folder>", "AB Test Harness experiment folder").option("--no-open", "Print preview URL instead of opening it").action(async function
|
|
10134
|
+
harnessCommand.command("dev").argument("<folder>", "AB Test Harness experiment folder").option("--no-open", "Print preview URL instead of opening it").action(async function action21(folderArg, opts) {
|
|
11425
10135
|
const folder = resolveFolder(folderArg);
|
|
11426
10136
|
const created = await createSession(folder);
|
|
11427
10137
|
await runAbHarness(["build", folder], folder);
|
|
@@ -11463,7 +10173,7 @@ harnessCommand.command("dev").argument("<folder>", "AB Test Harness experiment f
|
|
|
11463
10173
|
}
|
|
11464
10174
|
};
|
|
11465
10175
|
let debounce = null;
|
|
11466
|
-
const watcher =
|
|
10176
|
+
const watcher = import_node_fs8.default.watch(folder, { recursive: true }, (_event, filename) => {
|
|
11467
10177
|
const name = String(filename ?? "");
|
|
11468
10178
|
if (!name || name.startsWith("build/") || name.includes("/build/")) return;
|
|
11469
10179
|
if (debounce) clearTimeout(debounce);
|
|
@@ -11477,7 +10187,7 @@ harnessCommand.command("dev").argument("<folder>", "AB Test Harness experiment f
|
|
|
11477
10187
|
process.exitCode = 0;
|
|
11478
10188
|
});
|
|
11479
10189
|
});
|
|
11480
|
-
harnessCommand.command("publish").argument("<folder>", "AB Test Harness experiment folder").action(async function
|
|
10190
|
+
harnessCommand.command("publish").argument("<folder>", "AB Test Harness experiment folder").action(async function action22(folderArg) {
|
|
11481
10191
|
const folder = resolveFolder(folderArg);
|
|
11482
10192
|
const created = await createSession(folder);
|
|
11483
10193
|
await runAbHarness(["verify", folder], folder);
|
|
@@ -11490,9 +10200,9 @@ harnessCommand.command("publish").argument("<folder>", "AB Test Harness experime
|
|
|
11490
10200
|
|
|
11491
10201
|
// src/commands/operator.ts
|
|
11492
10202
|
init_source();
|
|
11493
|
-
var
|
|
11494
|
-
var
|
|
11495
|
-
var
|
|
10203
|
+
var import_node_crypto3 = __toESM(require("node:crypto"));
|
|
10204
|
+
var import_node_fs9 = __toESM(require("node:fs"));
|
|
10205
|
+
var import_node_path7 = __toESM(require("node:path"));
|
|
11496
10206
|
var CANONICAL_FILE_RE = /^(src\/(?:index\.js|index\.scss|info\.js|mutations\.json)|experiment\.config\.json|metrics\.json|targeting\.json|dev-doc\.md|brand\.md|goals\/[a-zA-Z0-9_.-]+\.json|segments\/[a-zA-Z0-9_.-]+\.json|network\/[a-zA-Z0-9_.-]+\.recipe\.json)$/;
|
|
11497
10207
|
function workspaceIssueFile(field, evidence) {
|
|
11498
10208
|
if (field === "custom_js" || field === "reset_js") return "src/index.js";
|
|
@@ -11536,7 +10246,7 @@ function formatWorkspaceBlockingIssues(value) {
|
|
|
11536
10246
|
}).join("\n");
|
|
11537
10247
|
}
|
|
11538
10248
|
function sha256Text2(value) {
|
|
11539
|
-
return
|
|
10249
|
+
return import_node_crypto3.default.createHash("sha256").update(value).digest("hex");
|
|
11540
10250
|
}
|
|
11541
10251
|
function byteLength(value) {
|
|
11542
10252
|
return Buffer.byteLength(value, "utf8");
|
|
@@ -11546,28 +10256,28 @@ function slugify2(value, fallback2 = "operator") {
|
|
|
11546
10256
|
return slug || fallback2;
|
|
11547
10257
|
}
|
|
11548
10258
|
function folderPath2(folder) {
|
|
11549
|
-
return
|
|
10259
|
+
return import_node_path7.default.resolve(process.cwd(), folder);
|
|
11550
10260
|
}
|
|
11551
10261
|
function apexDir(folder) {
|
|
11552
|
-
return
|
|
10262
|
+
return import_node_path7.default.join(folder, ".apex");
|
|
11553
10263
|
}
|
|
11554
10264
|
function sessionPath(folder) {
|
|
11555
|
-
return
|
|
10265
|
+
return import_node_path7.default.join(apexDir(folder), "session.json");
|
|
11556
10266
|
}
|
|
11557
10267
|
function readJsonFile(filePath) {
|
|
11558
|
-
if (!
|
|
11559
|
-
return JSON.parse(
|
|
10268
|
+
if (!import_node_fs9.default.existsSync(filePath)) return null;
|
|
10269
|
+
return JSON.parse(import_node_fs9.default.readFileSync(filePath, "utf8"));
|
|
11560
10270
|
}
|
|
11561
10271
|
function writeJsonFile(filePath, value) {
|
|
11562
|
-
|
|
11563
|
-
|
|
10272
|
+
import_node_fs9.default.mkdirSync(import_node_path7.default.dirname(filePath), { recursive: true });
|
|
10273
|
+
import_node_fs9.default.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}
|
|
11564
10274
|
`, "utf8");
|
|
11565
10275
|
}
|
|
11566
10276
|
function readSession(folder) {
|
|
11567
10277
|
const session = readJsonFile(sessionPath(folder));
|
|
11568
10278
|
if (!session) {
|
|
11569
|
-
const filesystemMarker =
|
|
11570
|
-
if (
|
|
10279
|
+
const filesystemMarker = import_node_path7.default.join(apexDir(folder), "experiment-filesystem.json");
|
|
10280
|
+
if (import_node_fs9.default.existsSync(filesystemMarker)) {
|
|
11571
10281
|
throw new Error("This folder is an experiment filesystem (apex experiments \u2026), not an operator workspace \u2014 run `apex experiments plan` here, or `apex workspace checkout` into a different folder.");
|
|
11572
10282
|
}
|
|
11573
10283
|
throw new Error(`Missing ${sessionPath(folder)}. Run apex workspace checkout first.`);
|
|
@@ -11575,9 +10285,9 @@ function readSession(folder) {
|
|
|
11575
10285
|
return session;
|
|
11576
10286
|
}
|
|
11577
10287
|
function writeSession(folder, session) {
|
|
11578
|
-
|
|
11579
|
-
|
|
11580
|
-
|
|
10288
|
+
import_node_fs9.default.mkdirSync(apexDir(folder), { recursive: true });
|
|
10289
|
+
import_node_fs9.default.writeFileSync(
|
|
10290
|
+
import_node_path7.default.join(apexDir(folder), ".gitignore"),
|
|
11581
10291
|
"session.json\nharness-session.json\n",
|
|
11582
10292
|
"utf8"
|
|
11583
10293
|
);
|
|
@@ -11595,15 +10305,15 @@ function listLocalWorkspaceFiles(folder) {
|
|
|
11595
10305
|
const roots = ["src", "goals", "segments", "network"];
|
|
11596
10306
|
const topLevel = ["experiment.config.json", "metrics.json", "targeting.json", "dev-doc.md", "brand.md"];
|
|
11597
10307
|
function visit(dir) {
|
|
11598
|
-
if (!
|
|
11599
|
-
for (const entry of
|
|
11600
|
-
const absPath =
|
|
10308
|
+
if (!import_node_fs9.default.existsSync(dir)) return;
|
|
10309
|
+
for (const entry of import_node_fs9.default.readdirSync(dir, { withFileTypes: true })) {
|
|
10310
|
+
const absPath = import_node_path7.default.join(dir, entry.name);
|
|
11601
10311
|
if (entry.isDirectory()) {
|
|
11602
10312
|
visit(absPath);
|
|
11603
10313
|
continue;
|
|
11604
10314
|
}
|
|
11605
|
-
const relativePath = validateCanonicalPath(
|
|
11606
|
-
const content =
|
|
10315
|
+
const relativePath = validateCanonicalPath(import_node_path7.default.relative(folder, absPath));
|
|
10316
|
+
const content = import_node_fs9.default.readFileSync(absPath, "utf8");
|
|
11607
10317
|
files.push({
|
|
11608
10318
|
path: relativePath,
|
|
11609
10319
|
absPath,
|
|
@@ -11613,11 +10323,11 @@ function listLocalWorkspaceFiles(folder) {
|
|
|
11613
10323
|
});
|
|
11614
10324
|
}
|
|
11615
10325
|
}
|
|
11616
|
-
for (const root of roots) visit(
|
|
10326
|
+
for (const root of roots) visit(import_node_path7.default.join(folder, root));
|
|
11617
10327
|
for (const file of topLevel) {
|
|
11618
|
-
const absPath =
|
|
11619
|
-
if (!
|
|
11620
|
-
const content =
|
|
10328
|
+
const absPath = import_node_path7.default.join(folder, file);
|
|
10329
|
+
if (!import_node_fs9.default.existsSync(absPath)) continue;
|
|
10330
|
+
const content = import_node_fs9.default.readFileSync(absPath, "utf8");
|
|
11621
10331
|
files.push({
|
|
11622
10332
|
path: file,
|
|
11623
10333
|
absPath,
|
|
@@ -11641,7 +10351,7 @@ function validateJsonFiles(files) {
|
|
|
11641
10351
|
}
|
|
11642
10352
|
}
|
|
11643
10353
|
function readExperimentConfig(folder) {
|
|
11644
|
-
return readJsonFile(
|
|
10354
|
+
return readJsonFile(import_node_path7.default.join(folder, "experiment.config.json")) ?? {};
|
|
11645
10355
|
}
|
|
11646
10356
|
function resolveVariation(exp, variationArg) {
|
|
11647
10357
|
const variations = exp.variations ?? [];
|
|
@@ -11658,9 +10368,9 @@ function resolveVariation(exp, variationArg) {
|
|
|
11658
10368
|
function writeWorkspaceFiles(folder, workspace) {
|
|
11659
10369
|
for (const file of workspace.files) {
|
|
11660
10370
|
const relativePath = validateCanonicalPath(file.path);
|
|
11661
|
-
const absPath =
|
|
11662
|
-
|
|
11663
|
-
|
|
10371
|
+
const absPath = import_node_path7.default.join(folder, relativePath);
|
|
10372
|
+
import_node_fs9.default.mkdirSync(import_node_path7.default.dirname(absPath), { recursive: true });
|
|
10373
|
+
import_node_fs9.default.writeFileSync(absPath, file.content, "utf8");
|
|
11664
10374
|
}
|
|
11665
10375
|
}
|
|
11666
10376
|
function sessionFromWorkspace(input) {
|
|
@@ -11724,10 +10434,10 @@ async function uploadCurrentArtifactVersion(folder, sessionId) {
|
|
|
11724
10434
|
form.append(
|
|
11725
10435
|
"js",
|
|
11726
10436
|
new Blob([js.content], { type: "application/javascript" }),
|
|
11727
|
-
|
|
10437
|
+
import_node_path7.default.basename(js.path)
|
|
11728
10438
|
);
|
|
11729
10439
|
if (css && css.content.trim()) {
|
|
11730
|
-
form.append("css", new Blob([css.content], { type: "text/css" }),
|
|
10440
|
+
form.append("css", new Blob([css.content], { type: "text/css" }), import_node_path7.default.basename(css.path));
|
|
11731
10441
|
}
|
|
11732
10442
|
form.append("manifest", JSON.stringify(manifest));
|
|
11733
10443
|
const res = await uploadHarnessVersion(creds, sessionId, form);
|
|
@@ -11809,7 +10519,7 @@ Examples:
|
|
|
11809
10519
|
apex workspace dev ./workspace --no-open
|
|
11810
10520
|
apex workspace publish ./workspace
|
|
11811
10521
|
`);
|
|
11812
|
-
operatorCommand.command("checkout").argument("<experiment-id>", "Apex experiment ID").requiredOption("--variation <id-or-slug>", "Variation ID, slug, or name").option("--out <path>", "Target folder").addOption(new Option("--dir <folder>", "Legacy alias for --out").hideHelp()).action(async function
|
|
10522
|
+
operatorCommand.command("checkout").argument("<experiment-id>", "Apex experiment ID").requiredOption("--variation <id-or-slug>", "Variation ID, slug, or name").option("--out <path>", "Target folder").addOption(new Option("--dir <folder>", "Legacy alias for --out").hideHelp()).action(async function action23(experimentId, opts) {
|
|
11813
10523
|
try {
|
|
11814
10524
|
const creds = requireCredentials();
|
|
11815
10525
|
const expRes = await getExperiment(creds, experimentId);
|
|
@@ -11828,7 +10538,7 @@ operatorCommand.command("checkout").argument("<experiment-id>", "Apex experiment
|
|
|
11828
10538
|
const folder = folderPath2(opts.out ?? opts.dir ?? slugify2(`${experiment.name}-${variation.name}`, "apex-operator"));
|
|
11829
10539
|
const workspace = await getOperatorWorkspace(creds, experiment.id, variation.id);
|
|
11830
10540
|
if (!workspace.ok || !workspace.data) throw new CliError(workspace.error ?? "Could not load workspace", classifyStatus(workspace.status), workspace.errorDetails);
|
|
11831
|
-
|
|
10541
|
+
import_node_fs9.default.mkdirSync(folder, { recursive: true });
|
|
11832
10542
|
writeWorkspaceFiles(folder, workspace.data);
|
|
11833
10543
|
writeSession(folder, sessionFromWorkspace({
|
|
11834
10544
|
credsBaseUrl: creds.baseUrl,
|
|
@@ -11844,7 +10554,7 @@ operatorCommand.command("checkout").argument("<experiment-id>", "Apex experiment
|
|
|
11844
10554
|
throw error;
|
|
11845
10555
|
}
|
|
11846
10556
|
});
|
|
11847
|
-
operatorCommand.command("status").argument("[folder]", "Operator workspace folder", ".").action(function
|
|
10557
|
+
operatorCommand.command("status").argument("[folder]", "Operator workspace folder", ".").action(function action24(folderArg) {
|
|
11848
10558
|
try {
|
|
11849
10559
|
const folder = folderPath2(folderArg);
|
|
11850
10560
|
const session = readSession(folder);
|
|
@@ -11872,14 +10582,14 @@ operatorCommand.command("status").argument("[folder]", "Operator workspace folde
|
|
|
11872
10582
|
fail(this, error instanceof Error ? error.message : String(error));
|
|
11873
10583
|
}
|
|
11874
10584
|
});
|
|
11875
|
-
operatorCommand.command("dev").argument("[folder]", "Operator workspace folder", ".").option("--no-open", "Print preview URL instead of opening it").option("--once", "Upload one preview version and exit").action(async function
|
|
10585
|
+
operatorCommand.command("dev").argument("[folder]", "Operator workspace folder", ".").option("--no-open", "Print preview URL instead of opening it").option("--once", "Upload one preview version and exit").action(async function action25(folderArg, opts) {
|
|
11876
10586
|
try {
|
|
11877
10587
|
const folder = folderPath2(folderArg);
|
|
11878
10588
|
const session = readSession(folder);
|
|
11879
10589
|
const harnessSession = await createExactHarnessSession(folder, session);
|
|
11880
10590
|
const version = await uploadCurrentArtifactVersion(folder, harnessSession.sessionId);
|
|
11881
|
-
|
|
11882
|
-
writeJsonFile(
|
|
10591
|
+
import_node_fs9.default.mkdirSync(apexDir(folder), { recursive: true });
|
|
10592
|
+
writeJsonFile(import_node_path7.default.join(apexDir(folder), "harness-session.json"), {
|
|
11883
10593
|
sessionId: harnessSession.sessionId,
|
|
11884
10594
|
experimentId: harnessSession.experimentId,
|
|
11885
10595
|
variationId: harnessSession.variationId,
|
|
@@ -11924,7 +10634,7 @@ operatorCommand.command("dev").argument("[folder]", "Operator workspace folder",
|
|
|
11924
10634
|
}
|
|
11925
10635
|
};
|
|
11926
10636
|
let timer = null;
|
|
11927
|
-
const watcher =
|
|
10637
|
+
const watcher = import_node_fs9.default.watch(folder, { recursive: true }, (_event, filename) => {
|
|
11928
10638
|
const name = String(filename ?? "");
|
|
11929
10639
|
if (!name || name.startsWith(".apex/") || name.includes("/.apex/")) return;
|
|
11930
10640
|
if (timer) clearTimeout(timer);
|
|
@@ -11941,7 +10651,7 @@ operatorCommand.command("dev").argument("[folder]", "Operator workspace folder",
|
|
|
11941
10651
|
throw error;
|
|
11942
10652
|
}
|
|
11943
10653
|
});
|
|
11944
|
-
operatorCommand.command("publish").argument("[folder]", "Operator workspace folder", ".").option("--allow-goal-writes", "Allow metrics.json to create or update shared shop-level goals").action(async function
|
|
10654
|
+
operatorCommand.command("publish").argument("[folder]", "Operator workspace folder", ".").option("--allow-goal-writes", "Allow metrics.json to create or update shared shop-level goals").action(async function action26(folderArg, opts) {
|
|
11945
10655
|
try {
|
|
11946
10656
|
const result = await runPublish(folderArg, opts);
|
|
11947
10657
|
const build = result.build;
|
|
@@ -11959,8 +10669,8 @@ operatorCommand.command("publish").argument("[folder]", "Operator workspace fold
|
|
|
11959
10669
|
|
|
11960
10670
|
// src/commands/roadmap.ts
|
|
11961
10671
|
init_source();
|
|
11962
|
-
var
|
|
11963
|
-
var
|
|
10672
|
+
var import_node_fs10 = __toESM(require("node:fs"));
|
|
10673
|
+
var import_node_path8 = __toESM(require("node:path"));
|
|
11964
10674
|
function truncate(value, max) {
|
|
11965
10675
|
const text = typeof value === "string" ? value : value == null ? "" : String(value);
|
|
11966
10676
|
if (text.length <= max) return text;
|
|
@@ -11984,7 +10694,7 @@ function briefLine(item) {
|
|
|
11984
10694
|
].filter(Boolean);
|
|
11985
10695
|
return parts.join("\n");
|
|
11986
10696
|
}
|
|
11987
|
-
var approved = new Command("approved").description("List approved roadmap ideas ready to build in Apex").option("-n, --limit <n>", "Max results", "20").option("--cursor <id>", "Pagination cursor").option("-q, --query <text>", "Search title, hypothesis, brief, and notes").option("--unbuilt", "Only show approved ideas without a linked experiment").option("--built", "Only show approved ideas with a linked experiment").option("--json", "Print raw JSON").option("--brief", "Print each item with hypothesis and target URL").action(async function
|
|
10697
|
+
var approved = new Command("approved").description("List approved roadmap ideas ready to build in Apex").option("-n, --limit <n>", "Max results", "20").option("--cursor <id>", "Pagination cursor").option("-q, --query <text>", "Search title, hypothesis, brief, and notes").option("--unbuilt", "Only show approved ideas without a linked experiment").option("--built", "Only show approved ideas with a linked experiment").option("--json", "Print raw JSON").option("--brief", "Print each item with hypothesis and target URL").action(async function action27(opts) {
|
|
11988
10698
|
const creds = requireCredentials();
|
|
11989
10699
|
const limit = parseInt(opts.limit, 10);
|
|
11990
10700
|
const hasExperiment = opts.unbuilt ? false : opts.built ? true : void 0;
|
|
@@ -12028,7 +10738,7 @@ More results: --cursor ${res.data.pagination.next_cursor}`));
|
|
|
12028
10738
|
}
|
|
12029
10739
|
});
|
|
12030
10740
|
});
|
|
12031
|
-
var handoff = new Command("handoff").description("Get the full build brief for an approved roadmap item").argument("<id>", "Roadmap/backlog item ID").option("--out <path>", "Write handoff JSON to a file").addOption(new Option("-o, --output <path>", "Legacy alias for --out").hideHelp()).option("--instructions", "Print only the build instructions").action(async function
|
|
10741
|
+
var handoff = new Command("handoff").description("Get the full build brief for an approved roadmap item").argument("<id>", "Roadmap/backlog item ID").option("--out <path>", "Write handoff JSON to a file").addOption(new Option("-o, --output <path>", "Legacy alias for --out").hideHelp()).option("--instructions", "Print only the build instructions").action(async function action28(id, opts) {
|
|
12032
10742
|
const creds = requireCredentials();
|
|
12033
10743
|
const res = await getRoadmapItemHandoff(creds, id);
|
|
12034
10744
|
if (!res.ok) fail(this, res.error ?? "Failed to fetch roadmap handoff", classifyStatus(res.status), res.errorDetails);
|
|
@@ -12037,9 +10747,9 @@ var handoff = new Command("handoff").description("Get the full build brief for a
|
|
|
12037
10747
|
`;
|
|
12038
10748
|
const outputData = opts.instructions ? { roadmapItemId: id, instructions: String(payload.instructions ?? "") } : payload;
|
|
12039
10749
|
if (opts.out || opts.output) {
|
|
12040
|
-
const target =
|
|
12041
|
-
|
|
12042
|
-
|
|
10750
|
+
const target = import_node_path8.default.resolve(process.cwd(), opts.out ?? opts.output);
|
|
10751
|
+
import_node_fs10.default.mkdirSync(import_node_path8.default.dirname(target), { recursive: true });
|
|
10752
|
+
import_node_fs10.default.writeFileSync(target, rendered, "utf8");
|
|
12043
10753
|
output(this, { path: target, roadmapItemId: id }, () => printSuccess(`Wrote ${target}`));
|
|
12044
10754
|
return;
|
|
12045
10755
|
}
|
|
@@ -12051,8 +10761,8 @@ roadmapCommand.addCommand(handoff);
|
|
|
12051
10761
|
|
|
12052
10762
|
// src/commands/tracking.ts
|
|
12053
10763
|
init_source();
|
|
12054
|
-
var
|
|
12055
|
-
var
|
|
10764
|
+
var import_node_fs11 = __toESM(require("node:fs"));
|
|
10765
|
+
var import_node_path9 = __toESM(require("node:path"));
|
|
12056
10766
|
function normalizeUrl(input) {
|
|
12057
10767
|
const value = input.trim();
|
|
12058
10768
|
if (!value) throw new Error("URL cannot be empty");
|
|
@@ -12098,17 +10808,17 @@ function commonCookieDomain(hostnames) {
|
|
|
12098
10808
|
return suffix && suffixes.every((candidate) => candidate === suffix) ? `.${suffix}` : null;
|
|
12099
10809
|
}
|
|
12100
10810
|
function defaultManifestPath() {
|
|
12101
|
-
return
|
|
10811
|
+
return import_node_path9.default.resolve(process.cwd(), ".apex", "tracking-manifest.json");
|
|
12102
10812
|
}
|
|
12103
10813
|
function readManifest(filePath) {
|
|
12104
|
-
if (!
|
|
12105
|
-
const parsed = JSON.parse(
|
|
10814
|
+
if (!import_node_fs11.default.existsSync(filePath)) throw new Error(`Manifest not found: ${filePath}`);
|
|
10815
|
+
const parsed = JSON.parse(import_node_fs11.default.readFileSync(filePath, "utf8"));
|
|
12106
10816
|
if (parsed.schemaVersion !== 1) throw new Error("Unsupported tracking manifest schema");
|
|
12107
10817
|
return parsed;
|
|
12108
10818
|
}
|
|
12109
10819
|
function writeManifest(filePath, manifest) {
|
|
12110
|
-
|
|
12111
|
-
|
|
10820
|
+
import_node_fs11.default.mkdirSync(import_node_path9.default.dirname(filePath), { recursive: true });
|
|
10821
|
+
import_node_fs11.default.writeFileSync(filePath, `${JSON.stringify(manifest, null, 2)}
|
|
12112
10822
|
`, "utf8");
|
|
12113
10823
|
}
|
|
12114
10824
|
function buildSnippet(shopId, endpoint) {
|
|
@@ -12175,7 +10885,7 @@ function buildManifest(urlArgs, opts) {
|
|
|
12175
10885
|
};
|
|
12176
10886
|
}
|
|
12177
10887
|
function manifestPath(input) {
|
|
12178
|
-
return
|
|
10888
|
+
return import_node_path9.default.resolve(process.cwd(), input ?? defaultManifestPath());
|
|
12179
10889
|
}
|
|
12180
10890
|
function printManifestPlan(manifest) {
|
|
12181
10891
|
console.log(source_default.bold("Apex tracking ingestion plan"));
|
|
@@ -12223,7 +10933,7 @@ trackingCommand.command("qa").description("Print the QA checklist for a tracking
|
|
|
12223
10933
|
console.log(`${index + 1}. ${check}`);
|
|
12224
10934
|
});
|
|
12225
10935
|
});
|
|
12226
|
-
trackingCommand.command("verify").description("Verify live tracking events for the current shop").option("--minutes <n>", "Lookback window in minutes", "120").option("--limit <n>", "Recent events to include", "20").option("--experiment <id>", "Filter to one experiment ID").option("--event-type <type>", "Filter to one event type; repeat with comma-separated values").action(async function
|
|
10936
|
+
trackingCommand.command("verify").description("Verify live tracking events for the current shop").option("--minutes <n>", "Lookback window in minutes", "120").option("--limit <n>", "Recent events to include", "20").option("--experiment <id>", "Filter to one experiment ID").option("--event-type <type>", "Filter to one event type; repeat with comma-separated values").action(async function action29(opts) {
|
|
12227
10937
|
const creds = requireCredentials();
|
|
12228
10938
|
const res = await verifyTracking(creds, {
|
|
12229
10939
|
minutes: Number.parseInt(opts.minutes, 10),
|
|
@@ -12261,10 +10971,10 @@ trackingCommand.command("verify").description("Verify live tracking events for t
|
|
|
12261
10971
|
|
|
12262
10972
|
// src/commands/goals.ts
|
|
12263
10973
|
init_source();
|
|
12264
|
-
var
|
|
12265
|
-
var
|
|
10974
|
+
var import_node_fs12 = __toESM(require("node:fs"));
|
|
10975
|
+
var import_node_path10 = __toESM(require("node:path"));
|
|
12266
10976
|
|
|
12267
|
-
// src/
|
|
10977
|
+
// ../sdk/src/safe-regex.ts
|
|
12268
10978
|
var MAX_PATTERN_LENGTH = 1024;
|
|
12269
10979
|
var MAX_CACHE_ENTRIES = 256;
|
|
12270
10980
|
var regexCache = /* @__PURE__ */ new Map();
|
|
@@ -12291,10 +11001,16 @@ function safeRegexTest(pattern, input) {
|
|
|
12291
11001
|
}
|
|
12292
11002
|
}
|
|
12293
11003
|
|
|
11004
|
+
// ../sdk/src/goal-eligibility.ts
|
|
11005
|
+
function goalIsEligible(goal, pageActiveIds, href) {
|
|
11006
|
+
if (goal.pageId) return pageActiveIds.has(goal.pageId);
|
|
11007
|
+
return !goal.urlPattern || safeRegexTest(goal.urlPattern, href);
|
|
11008
|
+
}
|
|
11009
|
+
|
|
12294
11010
|
// src/lib/sdk-goal-url-match.ts
|
|
11011
|
+
var NO_ACTIVE_PAGES = /* @__PURE__ */ new Set();
|
|
12295
11012
|
function sdkPageviewGoalMatch(urlPattern, href) {
|
|
12296
|
-
|
|
12297
|
-
return safeRegexTest(urlPattern, href);
|
|
11013
|
+
return goalIsEligible({ urlPattern: urlPattern ?? null }, NO_ACTIVE_PAGES, href);
|
|
12298
11014
|
}
|
|
12299
11015
|
|
|
12300
11016
|
// src/commands/goals.ts
|
|
@@ -12411,7 +11127,7 @@ function printSuggestions(data) {
|
|
|
12411
11127
|
function parseCapture(command, value) {
|
|
12412
11128
|
if (!value) return void 0;
|
|
12413
11129
|
const source = value.startsWith("@") ? value.slice(1) : value;
|
|
12414
|
-
const text = value.startsWith("@") ||
|
|
11130
|
+
const text = value.startsWith("@") || import_node_fs12.default.existsSync(source) ? import_node_fs12.default.readFileSync(import_node_path10.default.resolve(process.cwd(), source), "utf8") : value;
|
|
12415
11131
|
try {
|
|
12416
11132
|
return JSON.parse(text);
|
|
12417
11133
|
} catch (error) {
|
|
@@ -12430,6 +11146,8 @@ function printGoal(goal) {
|
|
|
12430
11146
|
console.log(` ID: ${source_default.dim(goal.id)}`);
|
|
12431
11147
|
console.log(` Type: ${goal.type}`);
|
|
12432
11148
|
console.log(` Measurement: ${goal.measurement === "value" ? "value" : "conversion"}`);
|
|
11149
|
+
if (goal.value_property) console.log(` Value source: ${goal.value_property}`);
|
|
11150
|
+
if (goal.value_unit) console.log(` Value unit: ${goal.value_unit}`);
|
|
12433
11151
|
if (goal.selector) console.log(` Selector: ${goal.selector}`);
|
|
12434
11152
|
if (goal.text_match) console.log(` Text match: ${goal.text_match}`);
|
|
12435
11153
|
if (goal.url_pattern) console.log(` URL: ${goal.url_pattern}`);
|
|
@@ -12437,7 +11155,7 @@ function printGoal(goal) {
|
|
|
12437
11155
|
console.log(` Count once: ${goal.count_once ? "yes" : "no"}`);
|
|
12438
11156
|
console.log();
|
|
12439
11157
|
}
|
|
12440
|
-
var list2 = new Command("list").alias("ls").description("List goals").option("-n, --limit <n>", "Max results", "50").action(async function
|
|
11158
|
+
var list2 = new Command("list").alias("ls").description("List goals").option("-n, --limit <n>", "Max results", "50").action(async function action30(opts) {
|
|
12441
11159
|
const creds = requireCredentials();
|
|
12442
11160
|
const res = await listGoals(creds, { limit: Number.parseInt(opts.limit, 10) });
|
|
12443
11161
|
if (!res.ok || !res.data) {
|
|
@@ -12459,12 +11177,14 @@ var list2 = new Command("list").alias("ls").description("List goals").option("-n
|
|
|
12459
11177
|
);
|
|
12460
11178
|
});
|
|
12461
11179
|
});
|
|
12462
|
-
var create2 = new Command("create").description("Create a goal").requiredOption("--name <name>", "Goal name").requiredOption("--type <type>", "click|pageview|custom|revenue").option("--measurement <measurement>", "conversion|value", "conversion").option("--selector <selector>", "CSS selector for click goals").option("--text-match <text>", "Text match for click goals").option("--url-pattern <pattern>", "URL pattern for pageview goals").option("--event-name <name>", "Event name for custom goals").option("--count-once", "Count only once per visitor").option("--capture <json-or-path>", "Capture JSON array or @path").action(async function
|
|
11180
|
+
var create2 = new Command("create").description("Create a goal").requiredOption("--name <name>", "Goal name").requiredOption("--type <type>", "click|pageview|custom|revenue").option("--measurement <measurement>", "conversion|value", "conversion").option("--value-property <property>", "Numeric event property for value goals").option("--value-unit <unit>", "currency|number|duration_seconds|percent").option("--selector <selector>", "CSS selector for click goals").option("--text-match <text>", "Text match for click goals").option("--url-pattern <pattern>", "URL pattern for pageview goals").option("--event-name <name>", "Event name for custom goals").option("--count-once", "Count only once per visitor").option("--capture <json-or-path>", "Capture JSON array or @path").action(async function action31(opts) {
|
|
12463
11181
|
const creds = requireCredentials();
|
|
12464
11182
|
const body5 = cleanBody({
|
|
12465
11183
|
name: opts.name,
|
|
12466
11184
|
type: opts.type,
|
|
12467
11185
|
measurement: opts.measurement,
|
|
11186
|
+
value_property: opts.valueProperty,
|
|
11187
|
+
value_unit: opts.valueUnit,
|
|
12468
11188
|
selector: opts.selector,
|
|
12469
11189
|
text_match: opts.textMatch,
|
|
12470
11190
|
url_pattern: opts.urlPattern,
|
|
@@ -12478,12 +11198,14 @@ var create2 = new Command("create").description("Create a goal").requiredOption(
|
|
|
12478
11198
|
}
|
|
12479
11199
|
output(this, { goal: res.data }, () => printGoal(res.data));
|
|
12480
11200
|
});
|
|
12481
|
-
var update = new Command("update").description("Update a goal").argument("<id>", "Goal ID").option("--name <name>", "Goal name").option("--type <type>", "click|pageview|custom|revenue").option("--measurement <measurement>", "conversion|value").option("--selector <selector>", "CSS selector for click goals").option("--text-match <text>", "Text match for click goals").option("--url-pattern <pattern>", "URL pattern for pageview goals").option("--event-name <name>", "Event name for custom goals").option("--count-once", "Count only once per visitor").option("--no-count-once", "Do not count only once per visitor").option("--capture <json-or-path>", "Capture JSON array or @path").action(async function
|
|
11201
|
+
var update = new Command("update").description("Update a goal").argument("<id>", "Goal ID").option("--name <name>", "Goal name").option("--type <type>", "click|pageview|custom|revenue").option("--measurement <measurement>", "conversion|value").option("--value-property <property>", "Numeric event property for value goals; pass null to clear").option("--value-unit <unit>", "currency|number|duration_seconds|percent; pass null to clear").option("--selector <selector>", "CSS selector for click goals").option("--text-match <text>", "Text match for click goals").option("--url-pattern <pattern>", "URL pattern for pageview goals").option("--event-name <name>", "Event name for custom goals").option("--count-once", "Count only once per visitor").option("--no-count-once", "Do not count only once per visitor").option("--capture <json-or-path>", "Capture JSON array or @path").action(async function action32(id, opts) {
|
|
12482
11202
|
const creds = requireCredentials();
|
|
12483
11203
|
const body5 = cleanBody({
|
|
12484
11204
|
name: opts.name,
|
|
12485
11205
|
type: opts.type,
|
|
12486
11206
|
measurement: opts.measurement,
|
|
11207
|
+
value_property: opts.valueProperty === "null" ? null : opts.valueProperty,
|
|
11208
|
+
value_unit: opts.valueUnit === "null" ? null : opts.valueUnit,
|
|
12487
11209
|
selector: opts.selector,
|
|
12488
11210
|
text_match: opts.textMatch,
|
|
12489
11211
|
url_pattern: opts.urlPattern,
|
|
@@ -12500,7 +11222,7 @@ var update = new Command("update").description("Update a goal").argument("<id>",
|
|
|
12500
11222
|
}
|
|
12501
11223
|
output(this, { goal: res.data }, () => printGoal(res.data));
|
|
12502
11224
|
});
|
|
12503
|
-
var test = new Command("test").description("Dry run a goal URL pattern against traffic").option("--pattern <regex>", "URL regex pattern to test (matched against the full URL, as the SDK matches pageview goals)").option("--goal <id>", "Existing goal ID whose URL pattern should be tested").option("--days <n>", "Traffic lookback days", "14").action(async function
|
|
11225
|
+
var test = new Command("test").description("Dry run a goal URL pattern against traffic").option("--pattern <regex>", "URL regex pattern to test (matched against the full URL, as the SDK matches pageview goals)").option("--goal <id>", "Existing goal ID whose URL pattern should be tested").option("--days <n>", "Traffic lookback days", "14").action(async function action33(opts) {
|
|
12504
11226
|
if (!opts.pattern && !opts.goal) fail(this, "Provide --pattern or --goal.", EXIT_CODES.validation);
|
|
12505
11227
|
if (opts.pattern && opts.goal) fail(this, "Use only one of --pattern or --goal.", EXIT_CODES.validation);
|
|
12506
11228
|
const creds = requireCredentials();
|
|
@@ -12545,7 +11267,7 @@ var test = new Command("test").description("Dry run a goal URL pattern against t
|
|
|
12545
11267
|
printTable(["URL", "PAGEVIEWS", "VISITORS"], matches.map((item) => [`${item.host}${item.path}`.slice(0, 64), String(item.pageviews), String(item.visitors)]), [64, 10, 10]);
|
|
12546
11268
|
});
|
|
12547
11269
|
});
|
|
12548
|
-
var suggest = new Command("suggest").description("Suggest goals and funnels from analytics").option("--create", "Create selected goals after confirmation").option("--yes", "Skip prompts when used with create").action(async function
|
|
11270
|
+
var suggest = new Command("suggest").description("Suggest goals and funnels from analytics").option("--create", "Create selected goals after confirmation").option("--yes", "Skip prompts when used with create").action(async function action34(opts) {
|
|
12549
11271
|
const creds = requireCredentials();
|
|
12550
11272
|
const res = await suggestGoals(creds);
|
|
12551
11273
|
if (!res.ok || !res.data) fail(this, res.error ?? "Failed to suggest goals", classifyStatus(res.status), res.errorDetails);
|
|
@@ -12574,12 +11296,12 @@ Examples:
|
|
|
12574
11296
|
|
|
12575
11297
|
// src/commands/funnels.ts
|
|
12576
11298
|
init_source();
|
|
12577
|
-
var
|
|
12578
|
-
var
|
|
11299
|
+
var import_node_fs13 = __toESM(require("node:fs"));
|
|
11300
|
+
var import_node_path11 = __toESM(require("node:path"));
|
|
12579
11301
|
function readSteps(command, file) {
|
|
12580
11302
|
let parsed;
|
|
12581
11303
|
try {
|
|
12582
|
-
parsed = JSON.parse(
|
|
11304
|
+
parsed = JSON.parse(import_node_fs13.default.readFileSync(import_node_path11.default.resolve(process.cwd(), file), "utf8"));
|
|
12583
11305
|
} catch (error) {
|
|
12584
11306
|
fail(command, `Invalid --steps-file JSON: ${error instanceof Error ? error.message : String(error)}`, EXIT_CODES.validation);
|
|
12585
11307
|
}
|
|
@@ -12598,27 +11320,27 @@ function printFunnel(funnel) {
|
|
|
12598
11320
|
for (const step of [...funnel.steps].sort((a, b) => a.order - b.order)) console.log(` ${step.order}. ${step.label} \u2014 ${step.url_pattern}`);
|
|
12599
11321
|
console.log();
|
|
12600
11322
|
}
|
|
12601
|
-
var list3 = new Command("list").alias("ls").description("List funnels").action(async function
|
|
11323
|
+
var list3 = new Command("list").alias("ls").description("List funnels").action(async function action35() {
|
|
12602
11324
|
const res = await listFunnels(requireCredentials());
|
|
12603
11325
|
if (!res.ok || !res.data) fail(this, res.error ?? "Failed to list funnels", classifyStatus(res.status), res.errorDetails);
|
|
12604
11326
|
output(this, { funnels: res.data.data }, () => printTable(["NAME", "ID", "STEPS"], res.data.data.map((f) => [f.name.slice(0, 36), f.id, String(f.steps.length)]), [36, 36, 5]));
|
|
12605
11327
|
});
|
|
12606
|
-
var create3 = new Command("create").description("Create a funnel").requiredOption("--name <name>", "Funnel name").requiredOption("--steps-file <file>", "JSON array of { label, pattern }").action(async function
|
|
11328
|
+
var create3 = new Command("create").description("Create a funnel").requiredOption("--name <name>", "Funnel name").requiredOption("--steps-file <file>", "JSON array of { label, pattern }").action(async function action36(opts) {
|
|
12607
11329
|
const res = await createFunnel(requireCredentials(), { name: opts.name, steps: readSteps(this, opts.stepsFile) });
|
|
12608
11330
|
if (!res.ok || !res.data) fail(this, res.error ?? "Failed to create funnel", classifyStatus(res.status), res.errorDetails);
|
|
12609
11331
|
output(this, { funnel: res.data }, () => printFunnel(res.data));
|
|
12610
11332
|
});
|
|
12611
|
-
var update2 = new Command("update").description("Replace a funnel's steps").argument("<id>", "Funnel ID").requiredOption("--steps-file <file>", "JSON array of { label, pattern }").action(async function
|
|
11333
|
+
var update2 = new Command("update").description("Replace a funnel's steps").argument("<id>", "Funnel ID").requiredOption("--steps-file <file>", "JSON array of { label, pattern }").action(async function action37(id, opts) {
|
|
12612
11334
|
const res = await updateFunnel(requireCredentials(), id, { steps: readSteps(this, opts.stepsFile) });
|
|
12613
11335
|
if (!res.ok || !res.data) fail(this, res.error ?? "Failed to update funnel", classifyStatus(res.status), res.errorDetails);
|
|
12614
11336
|
output(this, { funnel: res.data }, () => printFunnel(res.data));
|
|
12615
11337
|
});
|
|
12616
|
-
var del2 = new Command("delete").description("Delete a funnel").argument("<id>", "Funnel ID").action(async function
|
|
11338
|
+
var del2 = new Command("delete").description("Delete a funnel").argument("<id>", "Funnel ID").action(async function action38(id) {
|
|
12617
11339
|
const res = await deleteFunnel(requireCredentials(), id);
|
|
12618
11340
|
if (!res.ok) fail(this, res.error ?? "Failed to delete funnel", classifyStatus(res.status), res.errorDetails);
|
|
12619
11341
|
output(this, { deleted: true, id }, () => console.log(`Deleted funnel ${id}`));
|
|
12620
11342
|
});
|
|
12621
|
-
var run = new Command("run").description("Run funnel analysis").argument("<id>", "Funnel ID").option("--days <n>", "Analysis window", "30").action(async function
|
|
11343
|
+
var run = new Command("run").description("Run funnel analysis").argument("<id>", "Funnel ID").option("--days <n>", "Analysis window", "30").action(async function action39(id, opts) {
|
|
12622
11344
|
if (!/^[1-9]\d*$/.test(opts.days)) fail(this, "--days must be a positive integer", EXIT_CODES.validation);
|
|
12623
11345
|
const days = Number.parseInt(opts.days, 10);
|
|
12624
11346
|
if (!Number.isSafeInteger(days)) fail(this, "--days must be a positive integer", EXIT_CODES.validation);
|
|
@@ -12626,7 +11348,7 @@ var run = new Command("run").description("Run funnel analysis").argument("<id>",
|
|
|
12626
11348
|
if (!res.ok || !res.data) fail(this, res.error ?? "Failed to run funnel", classifyStatus(res.status), res.errorDetails);
|
|
12627
11349
|
output(this, { run: res.data }, () => printTable(["STEP", "LABEL", "VISITORS", "DROP-OFF"], res.data.steps.map((s) => [String(s.step), s.label, String(s.visitors), `${s.drop_off_rate}%`]), [5, 36, 8, 8]));
|
|
12628
11350
|
});
|
|
12629
|
-
var validate2 = new Command("validate").description("Validate funnel steps without writing").requiredOption("--steps-file <file>", "JSON array of { label, pattern }").action(function
|
|
11351
|
+
var validate2 = new Command("validate").description("Validate funnel steps without writing").requiredOption("--steps-file <file>", "JSON array of { label, pattern }").action(function action40(opts) {
|
|
12630
11352
|
const steps = readSteps(this, opts.stepsFile);
|
|
12631
11353
|
output(this, { valid: true, steps }, () => console.log(`Valid funnel steps: ${steps.length}`));
|
|
12632
11354
|
});
|
|
@@ -12683,11 +11405,11 @@ async function load2(command) {
|
|
|
12683
11405
|
}
|
|
12684
11406
|
return result.data;
|
|
12685
11407
|
}
|
|
12686
|
-
var list4 = new Command("list").alias("ls").description("List runtime environments and mapped domains").action(async function
|
|
11408
|
+
var list4 = new Command("list").alias("ls").description("List runtime environments and mapped domains").action(async function action41() {
|
|
12687
11409
|
const { environments } = await load2(this);
|
|
12688
11410
|
output(this, { environments }, () => printEnvironments(environments.items));
|
|
12689
11411
|
});
|
|
12690
|
-
var setDomain = new Command("set-domain").description("Map a domain to a runtime environment").argument("<domain>", "Domain to map").requiredOption("--environment <env>", "Environment key").action(async function
|
|
11412
|
+
var setDomain = new Command("set-domain").description("Map a domain to a runtime environment").argument("<domain>", "Domain to map").requiredOption("--environment <env>", "Environment key").action(async function action42(domain, opts) {
|
|
12691
11413
|
const normalizedDomain = normalizeEnvironmentDomain(domain);
|
|
12692
11414
|
if (!normalizedDomain) {
|
|
12693
11415
|
fail(this, "Domain must be a valid hostname containing a dot", EXIT_CODES.validation);
|
|
@@ -12730,7 +11452,7 @@ var setDomain = new Command("set-domain").description("Map a domain to a runtime
|
|
|
12730
11452
|
() => printEnvironments(result.data.environments.items)
|
|
12731
11453
|
);
|
|
12732
11454
|
});
|
|
12733
|
-
var removeDomain = new Command("remove-domain").description("Remove a domain mapping").argument("<domain>", "Domain to remove").action(async function
|
|
11455
|
+
var removeDomain = new Command("remove-domain").description("Remove a domain mapping").argument("<domain>", "Domain to remove").action(async function action43(domain) {
|
|
12734
11456
|
const normalizedDomain = normalizeEnvironmentDomain(domain);
|
|
12735
11457
|
if (!normalizedDomain) {
|
|
12736
11458
|
fail(this, "Domain must be a valid hostname containing a dot", EXIT_CODES.validation);
|
|
@@ -12784,11 +11506,11 @@ function printConsent(data) {
|
|
|
12784
11506
|
console.log(`Getter: ${data.consentMode.getter || "-"}`);
|
|
12785
11507
|
console.log(`Split: ${data.prerequisites.showVariationsRule && data.prerequisites.startTrackingRule ? "canonical" : "off"}`);
|
|
12786
11508
|
}
|
|
12787
|
-
var get2 = new Command("get").description("Show consent settings").action(async function
|
|
11509
|
+
var get2 = new Command("get").description("Show consent settings").action(async function action44() {
|
|
12788
11510
|
const data = await load3(this);
|
|
12789
11511
|
output(this, data, () => printConsent(data));
|
|
12790
11512
|
});
|
|
12791
|
-
var set = new Command("set").description("Enable and configure consent gating").requiredOption("--provider <provider>", "Consent provider").requiredOption("--group <group>", "Required consent group").option("--getter <path>", "Custom consent getter path").action(async function
|
|
11513
|
+
var set = new Command("set").description("Enable and configure consent gating").requiredOption("--provider <provider>", "Consent provider").requiredOption("--group <group>", "Required consent group").option("--getter <path>", "Custom consent getter path").action(async function action45(opts) {
|
|
12792
11514
|
const consentMode = { enabled: true, provider: opts.provider, requiredGroup: opts.group };
|
|
12793
11515
|
if (opts.getter !== void 0) consentMode.getter = opts.getter;
|
|
12794
11516
|
const result = await updateConsent(requireCredentials(), { consentMode, applyCanonicalSplit: true });
|
|
@@ -12796,7 +11518,7 @@ var set = new Command("set").description("Enable and configure consent gating").
|
|
|
12796
11518
|
warn(this, result.data.precompile);
|
|
12797
11519
|
output(this, result.data, () => printConsent(result.data));
|
|
12798
11520
|
});
|
|
12799
|
-
var off = new Command("off").description("Disable consent gating").action(async function
|
|
11521
|
+
var off = new Command("off").description("Disable consent gating").action(async function action46() {
|
|
12800
11522
|
const result = await updateConsent(requireCredentials(), { consentMode: { enabled: false } });
|
|
12801
11523
|
if (!result.ok || !result.data) fail(this, result.error ?? "Failed to disable consent settings", classifyStatus(result.status), result.errorDetails);
|
|
12802
11524
|
warn(this, result.data.precompile);
|
|
@@ -12831,21 +11553,21 @@ async function patchWithRetry(command, build) {
|
|
|
12831
11553
|
warn2(command, result.data.precompile);
|
|
12832
11554
|
return result.data;
|
|
12833
11555
|
}
|
|
12834
|
-
var list5 = new Command("list").alias("ls").description("List install domains").action(async function
|
|
11556
|
+
var list5 = new Command("list").alias("ls").description("List install domains").action(async function action47() {
|
|
12835
11557
|
const data = await load4(this);
|
|
12836
11558
|
output(this, data, () => printDomains(data));
|
|
12837
11559
|
});
|
|
12838
|
-
var add = new Command("add").description("Add install domains").argument("<domain...>").action(async function
|
|
11560
|
+
var add = new Command("add").description("Add install domains").argument("<domain...>").action(async function action48(domain) {
|
|
12839
11561
|
const added = domain.map(normalizeDomain);
|
|
12840
11562
|
const data = await patchWithRetry(this, (current) => Array.from(/* @__PURE__ */ new Set([...current.additionalDomains, ...added])));
|
|
12841
11563
|
output(this, data, () => printDomains(data));
|
|
12842
11564
|
});
|
|
12843
|
-
var remove = new Command("remove").description("Remove install domains").argument("<domain...>").action(async function
|
|
11565
|
+
var remove = new Command("remove").description("Remove install domains").argument("<domain...>").action(async function action49(domain) {
|
|
12844
11566
|
const removeSet = new Set(domain.map(normalizeDomain));
|
|
12845
11567
|
const data = await patchWithRetry(this, (current) => current.additionalDomains.filter((d) => !removeSet.has(normalizeDomain(d))));
|
|
12846
11568
|
output(this, data, () => printDomains(data));
|
|
12847
11569
|
});
|
|
12848
|
-
var set2 = new Command("set").description("Replace install domains").argument("<domain...>").action(async function
|
|
11570
|
+
var set2 = new Command("set").description("Replace install domains").argument("<domain...>").action(async function action50(domain) {
|
|
12849
11571
|
const replacement = domain.map(normalizeDomain);
|
|
12850
11572
|
const data = await patchWithRetry(this, () => replacement);
|
|
12851
11573
|
output(this, data, () => printDomains(data));
|
|
@@ -12864,7 +11586,11 @@ function normalizeDomain2(value) {
|
|
|
12864
11586
|
return "";
|
|
12865
11587
|
}
|
|
12866
11588
|
}
|
|
12867
|
-
var QA_FETCH_TIMEOUT_MS =
|
|
11589
|
+
var QA_FETCH_TIMEOUT_MS = 15e3;
|
|
11590
|
+
function qaFetchTimeoutMs() {
|
|
11591
|
+
const testOverride = Number(cliTestSetting("QA_FETCH_TIMEOUT_MS"));
|
|
11592
|
+
return Number.isFinite(testOverride) && testOverride > 0 ? testOverride : QA_FETCH_TIMEOUT_MS;
|
|
11593
|
+
}
|
|
12868
11594
|
var NAMED_HTML_ENTITIES = {
|
|
12869
11595
|
amp: "&",
|
|
12870
11596
|
lt: "<",
|
|
@@ -12902,29 +11628,29 @@ async function patchWithRetry2(command, build) {
|
|
|
12902
11628
|
if (!shouldUseJson(command) && result.data.precompile?.status && result.data.precompile.status !== "ok") console.error(source_default.yellow(`Warning (${result.data.precompile.status}): ${result.data.precompile.summary}. Edge config may be stale until republish succeeds.`));
|
|
12903
11629
|
return result.data;
|
|
12904
11630
|
}
|
|
12905
|
-
var get3 = new Command("get").description("Show cross-domain handover settings").action(async function
|
|
11631
|
+
var get3 = new Command("get").description("Show cross-domain handover settings").action(async function action51() {
|
|
12906
11632
|
const data = await load5(this);
|
|
12907
11633
|
output(this, data, () => printSettings(data.externalTracking));
|
|
12908
11634
|
});
|
|
12909
|
-
var set3 = new Command("set").description("Enable cross-domain identity handover").requiredOption("--domain <domain...>", "Bound handover domain(s)").option("--param <param>", "Handover query parameter").option("--signed", "Require signed handover tokens (the default; unsigning is a platform-support action)").action(async function
|
|
11635
|
+
var set3 = new Command("set").description("Enable cross-domain identity handover").requiredOption("--domain <domain...>", "Bound handover domain(s)").option("--param <param>", "Handover query parameter").option("--signed", "Require signed handover tokens (the default; unsigning is a platform-support action)").action(async function action52(opts) {
|
|
12910
11636
|
const domains = opts.domain.map(normalizeDomain2);
|
|
12911
11637
|
if (domains.some((domain) => !domain)) fail(this, "Every --domain value must be a valid hostname containing a dot", EXIT_CODES.validation);
|
|
12912
11638
|
if (opts.param && !/^[a-z_][a-z0-9_]{0,31}$/.test(opts.param)) fail(this, "--param must match ^[a-z_][a-z0-9_]{0,31}$", EXIT_CODES.validation);
|
|
12913
11639
|
const data = await patchWithRetry2(this, (current) => ({ externalTracking: { domains: Array.from(new Set(domains)), handoverQueryParam: opts.param ?? current.externalTracking.handoverQueryParam, signed: true }, ifRevision: current.revision }));
|
|
12914
11640
|
output(this, data, () => printSettings(data.externalTracking));
|
|
12915
11641
|
});
|
|
12916
|
-
var off2 = new Command("off").description("Disable cross-domain handover").action(async function
|
|
11642
|
+
var off2 = new Command("off").description("Disable cross-domain handover").action(async function action53() {
|
|
12917
11643
|
const data = await patchWithRetry2(this, (current) => ({ externalTracking: { domains: [], signed: true }, ifRevision: current.revision }));
|
|
12918
11644
|
output(this, data, () => printSettings(data.externalTracking));
|
|
12919
11645
|
});
|
|
12920
|
-
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
|
|
11646
|
+
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 action54(opts) {
|
|
12921
11647
|
const config = await load5(this);
|
|
12922
11648
|
const target = normalizeDomain2(opts.toDomain);
|
|
12923
11649
|
if (!target) fail(this, "--to-domain must be a valid hostname", EXIT_CODES.validation);
|
|
12924
11650
|
let response;
|
|
12925
11651
|
let html;
|
|
12926
11652
|
try {
|
|
12927
|
-
response = await fetch(opts.from, { signal: AbortSignal.timeout(
|
|
11653
|
+
response = await fetch(opts.from, { signal: AbortSignal.timeout(qaFetchTimeoutMs()), redirect: "follow" });
|
|
12928
11654
|
if (!response.ok) fail(this, `Fetching ${opts.from} returned HTTP ${response.status}`, classifyStatus(response.status));
|
|
12929
11655
|
html = await response.text();
|
|
12930
11656
|
} catch (error) {
|
|
@@ -12934,7 +11660,10 @@ var qa2 = new Command("qa").description("Check static link/form handover paramet
|
|
|
12934
11660
|
}
|
|
12935
11661
|
const param = config.externalTracking.handoverQueryParam;
|
|
12936
11662
|
const baseUrl = response.url || opts.from;
|
|
12937
|
-
const hrefs = [
|
|
11663
|
+
const hrefs = [
|
|
11664
|
+
...html.matchAll(/<(?:a|form)\b[^>]*\s(?:href|action)\s*=\s*["']([^"']+)["'][^>]*>/gi),
|
|
11665
|
+
...html.matchAll(/<[a-z][^>]*\s(?:data-href|data-url)\s*=\s*["']([^"']+)["'][^>]*>/gi)
|
|
11666
|
+
].map((m) => decodeHtmlEntities(m[1]));
|
|
12938
11667
|
const targetUrls = hrefs.map((raw) => {
|
|
12939
11668
|
try {
|
|
12940
11669
|
return new URL(raw, baseUrl);
|
|
@@ -13033,8 +11762,8 @@ function goalMatches(goal, rows) {
|
|
|
13033
11762
|
var CHALLENGE_STATUSES = [401, 403, 429, 503];
|
|
13034
11763
|
var PAGE_FETCH_TIMEOUT_MS = 12e3;
|
|
13035
11764
|
async function fetchPrimaryHtml(host) {
|
|
13036
|
-
if (
|
|
13037
|
-
const state =
|
|
11765
|
+
if (cliTestSetting("API") === "1") {
|
|
11766
|
+
const state = cliTestSetting("DOCTOR_STATE");
|
|
13038
11767
|
if (state === "protected") return { protected: true };
|
|
13039
11768
|
if (state === "page-error") return { error: "http 404" };
|
|
13040
11769
|
if (state === "page-timeout") return { error: "The operation was aborted due to timeout" };
|
|
@@ -13092,8 +11821,8 @@ function servedConfigUrl(workerBase, shopId) {
|
|
|
13092
11821
|
return `${workerBase}/config?shopId=${encodeURIComponent(shopId)}&env=${PRODUCTION_ENVIRONMENT_KEY}`;
|
|
13093
11822
|
}
|
|
13094
11823
|
async function fetchServedConfig(workerBase, shopId) {
|
|
13095
|
-
if (
|
|
13096
|
-
const state =
|
|
11824
|
+
if (cliTestSetting("API") === "1") {
|
|
11825
|
+
const state = cliTestSetting("DOCTOR_SERVED_CONFIG") ?? "agree";
|
|
13097
11826
|
if (state === "unreachable") return { error: "mock worker is unreachable" };
|
|
13098
11827
|
if (state === "diverge") return { config: { storeOrigins: ["https://example.com"], runtime: { consentMode: { enabled: false, provider: "custom" } } } };
|
|
13099
11828
|
return { config: { storeOrigins: ["https://example.com", "https://www.example.com", "https://shop.example.com"], runtime: { consentMode: { enabled: true, provider: "cookiescript" } } } };
|
|
@@ -13190,7 +11919,7 @@ async function runDoctor(options = {}) {
|
|
|
13190
11919
|
checks.push(unbound.length ? { checkId: "unbound_hosts", status: "FAIL", evidence: [...unbound.map((h) => `${h} sent pageviews but is not bound`), ...inventoryCaveat], fix: `apex domains add ${unbound[0]}` } : { checkId: "unbound_hosts", status: "PASS", evidence: ["All event hosts are bound to shop or environment domains.", ...inventoryCaveat] });
|
|
13191
11920
|
checks.push(withCaveat(devPollutionCheck(eventHosts, envsRes.data), inventoryCaveat));
|
|
13192
11921
|
const primary = hostOnly(domainsRes.data.domain) ?? eventHosts[0];
|
|
13193
|
-
const shopId = resolveShopId(options);
|
|
11922
|
+
const shopId = resolveShopId(options) ?? domainsRes.data.shop_id;
|
|
13194
11923
|
const workerBase = resolveWorkerBase(options);
|
|
13195
11924
|
const [page, servedResult] = await Promise.all([
|
|
13196
11925
|
primary ? fetchPrimaryHtml(primary) : Promise.resolve({}),
|
|
@@ -13200,10 +11929,11 @@ async function runDoctor(options = {}) {
|
|
|
13200
11929
|
if (!primary) checks.push({ checkId: "cmp_mismatch", status: "SKIP", evidence: ["No storefront host is configured for this shop yet; add a domain to run the CMP check."] });
|
|
13201
11930
|
else if (page.protected) checks.push({ checkId: "cmp_mismatch", status: "SKIP", evidence: [`Primary storefront ${primary} appears protected or bot-challenged; verify in browser.`] });
|
|
13202
11931
|
else if (page.error !== void 0 || page.html === void 0) checks.push({ checkId: "cmp_mismatch", status: "SKIP", evidence: [`Could not read https://${primary}: ${page.error ?? "no response body"}`, "No CMP verdict was formed; an error page carries no consent markers."] });
|
|
11932
|
+
else if (consent.enabled && !consent.provider) checks.push({ checkId: "cmp_mismatch", status: "WARN", evidence: ["Consent mode is on, but no consent provider is configured", "Configure a consent provider before relying on consent gating"], fix: "apex consent set" });
|
|
13203
11933
|
else {
|
|
13204
11934
|
const cmpMarker = matchDetectionMarkers(page.html, "cmp")[0];
|
|
13205
11935
|
if (!cmpMarker) {
|
|
13206
|
-
checks.push(consent.enabled
|
|
11936
|
+
checks.push(consent.enabled ? { checkId: "cmp_mismatch", status: "WARN", evidence: [`Consent mode is on with provider ${consent.provider}, but the served page carries no CMP marker`, `Expected CMP ${consent.provider} not found in served page`], fix: "apex consent set" } : { checkId: "cmp_mismatch", status: "PASS", evidence: ["No CMP marker detected."] });
|
|
13207
11937
|
} else {
|
|
13208
11938
|
const vendor = cmpMarker.verdict;
|
|
13209
11939
|
const mismatch = !consent.enabled || consent.provider && consent.provider.toLowerCase() !== vendor.toLowerCase();
|
|
@@ -13257,7 +11987,7 @@ function consentRateCheck(items) {
|
|
|
13257
11987
|
const estimate = estimateConsentRate(items);
|
|
13258
11988
|
return { checkId: "consent_rate", status: "PASS", evidence: [`estimatedAcceptRate=${estimate.estimatedAcceptRate ?? "unknown"}`, `Baseline ${describeWindow(estimate.baselineWindow)}; gated ${describeWindow(estimate.gatedWindow)}`, CAVEAT] };
|
|
13259
11989
|
}
|
|
13260
|
-
var doctorCommand = new Command("doctor").description("Check shop configuration against observed analytics").option("--shop-id <uuid>", "
|
|
11990
|
+
var doctorCommand = new Command("doctor").description("Check shop configuration against observed analytics").option("--shop-id <uuid>", "Override the shop ID discovered from the API (DRIP_SHOP_ID also overrides it)").option("--worker <url>", `Apex Worker base URL; defaults to DRIP_WORKER_URL or ${PRODUCTION_WORKER_BASE_URL}`).action(async function action55(options) {
|
|
13261
11991
|
const data = await runDoctor(options);
|
|
13262
11992
|
output(this, data, () => printDoctorReport(data.checks));
|
|
13263
11993
|
if (data.checks.some((c) => c.status === "FAIL")) process.exitCode = EXIT_CODES.general;
|
|
@@ -13271,7 +12001,7 @@ function printDoctorReport(checks) {
|
|
|
13271
12001
|
if (check.fix) console.log(` fix: ${check.fix}`);
|
|
13272
12002
|
}
|
|
13273
12003
|
}
|
|
13274
|
-
var consentRateCommand = new Command("consent-rate").description("Estimate analytics consent acceptance from visitor series").option("--since <date>", "Consent gating start date (YYYY-MM-DD); splits the baseline and gated windows").action(async function
|
|
12004
|
+
var consentRateCommand = new Command("consent-rate").description("Estimate analytics consent acceptance from visitor series").option("--since <date>", "Consent gating start date (YYYY-MM-DD); splits the baseline and gated windows").action(async function action56(opts) {
|
|
13275
12005
|
if (opts.since !== void 0 && parseDay(opts.since) === null) fail(this, "--since must be a calendar date in YYYY-MM-DD form", EXIT_CODES.validation);
|
|
13276
12006
|
const result = await getVisitorSeries(requireCredentials(), { days: VISITOR_SERIES_DAYS });
|
|
13277
12007
|
if (!result.ok || !result.data) fail(this, result.error ?? "Failed to load visitor series", classifyStatus(result.status), result.errorDetails);
|
|
@@ -13310,14 +12040,14 @@ function analyticsSubcommand(name, description, defaultDays) {
|
|
|
13310
12040
|
command.option("--planning-days <n>", "Planning window in days", "180");
|
|
13311
12041
|
command.option("--limit <n>", "Maximum page rows", "50");
|
|
13312
12042
|
}
|
|
13313
|
-
return command.action(async function
|
|
12043
|
+
return command.action(async function action69(opts) {
|
|
13314
12044
|
const result = await getAnalytics(requireCredentials(), name, { days: positiveInteger(this, opts.days, "--days"), planningDays: positiveInteger(this, opts.planningDays, "--planning-days"), limit: positiveInteger(this, opts.limit, "--limit") });
|
|
13315
12045
|
if (!result.ok || !result.data) fail(this, result.error ?? `Failed to load ${name} analytics`, classifyStatus(result.status), result.errorDetails);
|
|
13316
12046
|
output(this, { data: result.data.data }, () => printAnalytics(result.data.data));
|
|
13317
12047
|
});
|
|
13318
12048
|
}
|
|
13319
12049
|
function urlsCommand() {
|
|
13320
|
-
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("--match <regex>", "Client-side regex filter; exhausts inventory pages before filtering").option("--limit <n>", "Maximum rows to print", "50").action(async function
|
|
12050
|
+
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("--match <regex>", "Client-side regex filter; exhausts inventory pages before filtering").option("--limit <n>", "Maximum rows to print", "50").action(async function action69(opts) {
|
|
13321
12051
|
const creds = requireCredentials();
|
|
13322
12052
|
const days = positiveInteger(this, opts.days, "--days") ?? 14;
|
|
13323
12053
|
const displayLimit = Math.min(200, positiveInteger(this, opts.limit, "--limit") ?? 50);
|
|
@@ -13348,7 +12078,7 @@ function urlsCommand() {
|
|
|
13348
12078
|
});
|
|
13349
12079
|
});
|
|
13350
12080
|
}
|
|
13351
|
-
var analyticsCommand = new Command("analytics").description("Read shop analytics").addCommand(analyticsSubcommand("stats", "Show dashboard analytics stats", 7)).addCommand(analyticsSubcommand("timeseries", "Show analytics timeseries", 30)).addCommand(analyticsSubcommand("traffic", "Show traffic sources", 30)).addCommand(analyticsSubcommand("pages", "Show page analytics", 30)).addCommand(analyticsSubcommand("geo", "Show geography and device analytics", 30)).addCommand(analyticsSubcommand("sessions", "Show session analytics", 30)).addCommand(analyticsSubcommand("
|
|
12081
|
+
var analyticsCommand = new Command("analytics").description("Read shop analytics").addCommand(analyticsSubcommand("stats", "Show dashboard analytics stats", 7)).addCommand(analyticsSubcommand("timeseries", "Show analytics timeseries", 30)).addCommand(analyticsSubcommand("traffic", "Show traffic sources", 30)).addCommand(analyticsSubcommand("pages", "Show page analytics", 30)).addCommand(analyticsSubcommand("geo", "Show geography and device analytics", 30)).addCommand(analyticsSubcommand("sessions", "Show session analytics", 30)).addCommand(analyticsSubcommand("realtime", "Show realtime analytics")).addCommand(analyticsSubcommand("entry-exit", "Show entry and exit page analytics", 30)).addCommand(consentRateCommand).addCommand(urlsCommand()).addHelpText("after", `
|
|
13352
12082
|
Examples:
|
|
13353
12083
|
apex analytics stats --days 7
|
|
13354
12084
|
apex analytics pages --planning-days 180 --limit 25
|
|
@@ -13394,18 +12124,10 @@ function privateIp(rawHost) {
|
|
|
13394
12124
|
const [a, b] = host.split(".").map(Number);
|
|
13395
12125
|
return a === 0 || a === 10 || a === 127 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 169 && b === 254;
|
|
13396
12126
|
}
|
|
13397
|
-
|
|
13398
|
-
|
|
13399
|
-
const mode = process.env.APEX_CLI_MOCK_DNS ?? "public";
|
|
13400
|
-
if (mode === "fail") throw new Error(`getaddrinfo EAI_AGAIN ${hostname}`);
|
|
13401
|
-
if (mode === "empty") return [];
|
|
13402
|
-
if (mode === "mapped-loopback") return [{ address: "::ffff:127.0.0.1", family: 6 }];
|
|
13403
|
-
if (mode === "mapped-private-hex") return [{ address: "::ffff:0a00:0001", family: 6 }];
|
|
13404
|
-
if (mode === "unspecified-v6") return [{ address: "::", family: 6 }];
|
|
13405
|
-
return [{ address: "93.184.216.34", family: 4 }];
|
|
13406
|
-
}
|
|
12127
|
+
var testResolver;
|
|
12128
|
+
var testFetch;
|
|
13407
12129
|
async function resolveHost(hostname) {
|
|
13408
|
-
const mocked =
|
|
12130
|
+
const mocked = testResolver?.(hostname) ?? null;
|
|
13409
12131
|
const addresses = mocked ?? await (0, import_promises3.lookup)(hostname, { all: true });
|
|
13410
12132
|
return addresses.map((entry) => entry.address);
|
|
13411
12133
|
}
|
|
@@ -13430,44 +12152,6 @@ async function validateUrl(raw, allowedHosts) {
|
|
|
13430
12152
|
if (privateAddress) throw new UrlPolicyError(`Refusing ${url.hostname}; DNS resolves to a private address (${privateAddress})`);
|
|
13431
12153
|
return { url, addresses };
|
|
13432
12154
|
}
|
|
13433
|
-
function mockFetch(url) {
|
|
13434
|
-
if (process.env.APEX_CLI_MOCK_API !== "1") return null;
|
|
13435
|
-
const oversizedFiller = "x".repeat(BODY_LIMIT_BYTES + 1024);
|
|
13436
|
-
if (url.pathname === "/config") {
|
|
13437
|
-
const mode = process.env.APEX_CLI_MOCK_CONFIG ?? "ok";
|
|
13438
|
-
if (mode === "unreachable") throw new TypeError("fetch failed");
|
|
13439
|
-
if (mode === "oversized") return new Response(JSON.stringify({ publicationRevision: 7, experiments: [], filler: oversizedFiller }), { status: 200, headers: { "content-type": "application/json" } });
|
|
13440
|
-
if (mode === "gzipped") return new Response((0, import_node_zlib.gzipSync)(Buffer.from(JSON.stringify({ publicationRevision: 7, publication_revision: 7, experiments: [{ id: "experiment_1" }], runtime: { consentMode: { provider: "usercentrics", requiredGroup: "marketing" } }, delivery: { revision: 7, scriptUrl: `${url.origin}/s/shop_1.js?v=7`, deliveryState: "no_visual_tests" } }))), { status: 200, headers: { "content-type": "application/json", "content-encoding": "gzip" } });
|
|
13441
|
-
if (mode === "unknown-encoding") return new Response("compressed-with-something-else", { status: 200, headers: { "content-type": "application/json", "content-encoding": "zstd" } });
|
|
13442
|
-
if (mode === "http-error") return new Response(JSON.stringify({ error: "Failed to fetch config" }), { status: 502, headers: { "content-type": "application/json" } });
|
|
13443
|
-
if (mode === "unrecognized") return new Response(JSON.stringify({ unexpected: true }), { status: 200, headers: { "content-type": "application/json" } });
|
|
13444
|
-
const shopId = url.searchParams.get("shopId") ?? "shop_1";
|
|
13445
|
-
return new Response(JSON.stringify({
|
|
13446
|
-
publication_revision: 7,
|
|
13447
|
-
publicationRevision: 7,
|
|
13448
|
-
generated_at: "2026-08-05T00:00:00.000Z",
|
|
13449
|
-
experiments: [{ id: "experiment_1" }],
|
|
13450
|
-
runtime: { consentMode: { enabled: true, provider: "usercentrics", getter: "", requiredGroup: "marketing" } },
|
|
13451
|
-
delivery: { version: 1, revision: 7, scriptUrl: `${url.origin}/s/${shopId}.js?v=7`, deliveryState: "no_visual_tests" },
|
|
13452
|
-
geo: { country: "DE" }
|
|
13453
|
-
}), { status: 200, headers: { "content-type": "application/json" } });
|
|
13454
|
-
}
|
|
13455
|
-
const page = process.env.APEX_CLI_MOCK_PAGE ?? "installed";
|
|
13456
|
-
const installedHtml = `<html><head><script src="${PRODUCTION_WORKER_BASE_URL2}/s/shop_1.js?v=7"></script></head><body>ok</body></html>`;
|
|
13457
|
-
if (page === "not-found") return new Response(`<html><body>Not found</body>${installedHtml}</html>`, { status: 404, headers: { "content-type": "text/html" } });
|
|
13458
|
-
if (page === "forbidden") return new Response("<html><body>Forbidden</body></html>", { status: 403, headers: { "content-type": "text/html" } });
|
|
13459
|
-
if (page === "challenge") return new Response('<html><title>Just a moment...</title><script src="/cdn-cgi/challenge-platform/h/b/orchestrate/chl_page/v1"></script></html>', { status: 403, headers: { "content-type": "text/html" } });
|
|
13460
|
-
if (page === "oversized") return new Response(`${installedHtml}<!--${oversizedFiller}-->`, { status: 200, headers: { "content-type": "text/html" } });
|
|
13461
|
-
if (page === "gzipped") return new Response((0, import_node_zlib.gzipSync)(Buffer.from(installedHtml)), { status: 200, headers: { "content-type": "text/html", "content-encoding": "gzip" } });
|
|
13462
|
-
if (page === "corrupt-gzip-trailer") {
|
|
13463
|
-
const archive2 = (0, import_node_zlib.gzipSync)(Buffer.from(`${installedHtml}<!--${"x".repeat(64 * 1024)}-->`));
|
|
13464
|
-
archive2[archive2.length - 8] ^= 255;
|
|
13465
|
-
return new Response(archive2, { status: 200, headers: { "content-type": "text/html", "content-encoding": "gzip" } });
|
|
13466
|
-
}
|
|
13467
|
-
if (page === "unknown-encoding") return new Response("compressed-with-something-else", { status: 200, headers: { "content-type": "text/html", "content-encoding": "zstd" } });
|
|
13468
|
-
if (page === "prose-shopid") return new Response("<html><body><p>Set your shopId to shop_1 in the docs.</p></body></html>", { status: 200, headers: { "content-type": "text/html" } });
|
|
13469
|
-
return new Response(installedHtml, { status: 200, headers: { "content-type": "text/html" } });
|
|
13470
|
-
}
|
|
13471
12155
|
var REQUEST_TIMEOUT_MS = 1e4;
|
|
13472
12156
|
var RESPONSE_DEADLINE_MS = 15e3;
|
|
13473
12157
|
var BODY_LIMIT_BYTES = 2 * 1024 * 1024;
|
|
@@ -13496,10 +12180,10 @@ function decodeBody(raw, header, limits, hostname, inputTruncated = false) {
|
|
|
13496
12180
|
const chunks = [];
|
|
13497
12181
|
let bytes = 0;
|
|
13498
12182
|
let settled = false;
|
|
13499
|
-
const settle = (
|
|
12183
|
+
const settle = (action69) => {
|
|
13500
12184
|
if (settled) return;
|
|
13501
12185
|
settled = true;
|
|
13502
|
-
|
|
12186
|
+
action69();
|
|
13503
12187
|
};
|
|
13504
12188
|
stream.on("data", (chunk) => {
|
|
13505
12189
|
if (settled) return;
|
|
@@ -13550,11 +12234,11 @@ function pinnedFetch(url, addresses, limits) {
|
|
|
13550
12234
|
else callback(null, addresses[0], (0, import_node_net.isIP)(addresses[0]));
|
|
13551
12235
|
};
|
|
13552
12236
|
let settled = false;
|
|
13553
|
-
const settle = (
|
|
12237
|
+
const settle = (action69) => {
|
|
13554
12238
|
if (settled) return;
|
|
13555
12239
|
settled = true;
|
|
13556
12240
|
clearTimeout(deadline);
|
|
13557
|
-
|
|
12241
|
+
action69();
|
|
13558
12242
|
};
|
|
13559
12243
|
const req = send2(url, {
|
|
13560
12244
|
method: "GET",
|
|
@@ -13631,7 +12315,7 @@ async function enforceBodyLimit(url, response, limits) {
|
|
|
13631
12315
|
async function fetchSafe(raw, allowedHosts, limits) {
|
|
13632
12316
|
let target = await validateUrl(raw, allowedHosts);
|
|
13633
12317
|
for (let hop = 0; hop <= 5; hop++) {
|
|
13634
|
-
const mocked =
|
|
12318
|
+
const mocked = testFetch?.(target.url) ?? null;
|
|
13635
12319
|
const result = mocked ? await enforceBodyLimit(target.url, mocked, limits) : await pinnedFetch(target.url, target.addresses, limits);
|
|
13636
12320
|
if (![301, 302, 303, 307, 308].includes(result.response.status)) return result;
|
|
13637
12321
|
const location = result.response.headers.get("location");
|
|
@@ -13743,13 +12427,14 @@ async function checkServedConfig(configUrl, workerBaseUrl, workerBaseSource) {
|
|
|
13743
12427
|
message: "Worker served a recognized config document for this shop."
|
|
13744
12428
|
};
|
|
13745
12429
|
}
|
|
13746
|
-
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("--worker-url <url>", "Apex Worker base URL that serves /config (defaults to the URL in the served snippet, then DRIP_WORKER_URL, then the production worker)").option("--environment <key>", "Environment key to request from the worker", "production").action(async function
|
|
12430
|
+
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("--worker-url <url>", "Apex Worker base URL that serves /config (defaults to the URL in the served snippet, then DRIP_WORKER_URL, then the production worker)").option("--environment <key>", "Environment key to request from the worker", "production").action(async function action57(opts) {
|
|
13747
12431
|
const creds = requireCredentials();
|
|
13748
12432
|
const shopId = opts.shopId?.trim() || process.env.DRIP_SHOP_ID?.trim() || null;
|
|
13749
12433
|
if (!shopId) fail(this, "Shop ID is required: pass --shop-id <id> or set DRIP_SHOP_ID", EXIT_CODES.validation, { field: "shop-id" });
|
|
13750
12434
|
const domains = await getInstallDomains(creds);
|
|
13751
12435
|
if (!domains.ok || !domains.data) fail(this, domains.error ?? "Failed to load install domains", classifyStatus(domains.status), domains.errorDetails);
|
|
13752
|
-
const
|
|
12436
|
+
const mockStoreOrigins = cliTestSetting("STORE_ORIGINS");
|
|
12437
|
+
const storeOrigins = cliTestSetting("API") === "1" && mockStoreOrigins ? mockStoreOrigins.split(",") : domains.data.storeOrigins;
|
|
13753
12438
|
const origins = opts.url ? [opts.url] : storeOrigins;
|
|
13754
12439
|
const allowedHosts = new Set(storeOrigins.map((origin) => normalizeHostname(new URL(origin).hostname)));
|
|
13755
12440
|
const pages = [];
|
|
@@ -13808,11 +12493,11 @@ var installCommand = new Command("install").description("Check Apex storefront i
|
|
|
13808
12493
|
|
|
13809
12494
|
// src/commands/flags.ts
|
|
13810
12495
|
init_source();
|
|
13811
|
-
var
|
|
13812
|
-
var
|
|
12496
|
+
var import_node_fs14 = __toESM(require("node:fs"));
|
|
12497
|
+
var import_node_path12 = __toESM(require("node:path"));
|
|
13813
12498
|
|
|
13814
12499
|
// src/edge-status.ts
|
|
13815
|
-
function getEdgeDeliveryStatus(deliveryType, expectedWorstCaseSeconds =
|
|
12500
|
+
function getEdgeDeliveryStatus(deliveryType, expectedWorstCaseSeconds = 360) {
|
|
13816
12501
|
return deliveryType === "api" ? { edge_delivery: "not_applicable", message: `API-delivered flag \u2014 edge delivery is not applicable. A previously cached visual payload may remain at some PoPs for up to ~${expectedWorstCaseSeconds}s; a single-vantage status cannot prove global propagation.` } : { edge_delivery: "applicable", message: null };
|
|
13817
12502
|
}
|
|
13818
12503
|
function parsePublicationMetadata(value) {
|
|
@@ -13837,7 +12522,7 @@ function compareEdgePublication(currentValue, edgeValue, expectedWorstCaseSecond
|
|
|
13837
12522
|
return { ...base, verdict: "live", relationship: edge.publication_revision > current.publication_revision ? "edge_ahead" : "equal" };
|
|
13838
12523
|
}
|
|
13839
12524
|
function formatEdgePropagationStatus(status2) {
|
|
13840
|
-
const caveat = "This checks one CDN vantage point; other PoPs may lag by up to max-age
|
|
12525
|
+
const caveat = "This checks one CDN vantage point; other PoPs may lag by up to max-age.";
|
|
13841
12526
|
if (status2.verdict === "pending_publication") return `Pending publication \u2014 flag updated ${status2.flag_updated_at}, after the latest publication generated ${status2.current.generated_at ?? "never"}; the latest change is not live. After publication, allow ~${status2.expected_worst_case_seconds}s for propagation. ${caveat}`;
|
|
13842
12527
|
if (status2.verdict === "unknown") return `Unknown \u2014 edge payload predates publication metadata. ${caveat}`;
|
|
13843
12528
|
if (status2.verdict === "propagating") return `Propagating \u2014 edge revision ${status2.edge.publication_revision} (generated ${status2.edge.generated_at}) vs current revision ${status2.current.publication_revision} (generated ${status2.current.generated_at}); expected within ~${status2.expected_worst_case_seconds}s after generation. ${caveat}`;
|
|
@@ -13851,7 +12536,7 @@ function body(command, value, opts) {
|
|
|
13851
12536
|
let parsed = {};
|
|
13852
12537
|
if (value) {
|
|
13853
12538
|
const source = value.startsWith("@") ? value.slice(1) : value;
|
|
13854
|
-
const text = value.startsWith("@") ||
|
|
12539
|
+
const text = value.startsWith("@") || import_node_fs14.default.existsSync(source) ? import_node_fs14.default.readFileSync(import_node_path12.default.resolve(process.cwd(), source), "utf8") : value;
|
|
13855
12540
|
try {
|
|
13856
12541
|
parsed = JSON.parse(text);
|
|
13857
12542
|
} catch (e) {
|
|
@@ -13979,18 +12664,18 @@ function write(name) {
|
|
|
13979
12664
|
});
|
|
13980
12665
|
return c;
|
|
13981
12666
|
}
|
|
13982
|
-
function lifecycle(
|
|
13983
|
-
return new Command(
|
|
12667
|
+
function lifecycle(action69) {
|
|
12668
|
+
return new Command(action69).description(`${action69[0].toUpperCase()}${action69.slice(1)} a flag`).argument("<id>").action(async function(id) {
|
|
13984
12669
|
const r = await transitionManagedDelivery(
|
|
13985
12670
|
requireCredentials(),
|
|
13986
12671
|
kind,
|
|
13987
12672
|
id,
|
|
13988
|
-
|
|
12673
|
+
action69
|
|
13989
12674
|
);
|
|
13990
12675
|
if (!r.ok || !r.data)
|
|
13991
12676
|
fail(
|
|
13992
12677
|
this,
|
|
13993
|
-
r.error ?? `Failed to ${
|
|
12678
|
+
r.error ?? `Failed to ${action69}`,
|
|
13994
12679
|
classifyStatus(r.status),
|
|
13995
12680
|
r.errorDetails
|
|
13996
12681
|
);
|
|
@@ -14006,8 +12691,8 @@ Examples:
|
|
|
14006
12691
|
`);
|
|
14007
12692
|
|
|
14008
12693
|
// src/commands/backlog.ts
|
|
14009
|
-
var
|
|
14010
|
-
var
|
|
12694
|
+
var import_node_fs15 = __toESM(require("node:fs"));
|
|
12695
|
+
var import_node_path13 = __toESM(require("node:path"));
|
|
14011
12696
|
init_source();
|
|
14012
12697
|
var kind2 = "backlog";
|
|
14013
12698
|
var singular = "backlog item";
|
|
@@ -14015,7 +12700,7 @@ function parse(command, value) {
|
|
|
14015
12700
|
if (!value) return {};
|
|
14016
12701
|
try {
|
|
14017
12702
|
const source = value.startsWith("@") ? value.slice(1) : value;
|
|
14018
|
-
const text = value.startsWith("@") ||
|
|
12703
|
+
const text = value.startsWith("@") || import_node_fs15.default.existsSync(source) ? import_node_fs15.default.readFileSync(import_node_path13.default.resolve(process.cwd(), source), "utf8") : value;
|
|
14019
12704
|
const body5 = JSON.parse(text);
|
|
14020
12705
|
if (!body5 || typeof body5 !== "object" || Array.isArray(body5)) throw new Error("body must be an object");
|
|
14021
12706
|
return body5;
|
|
@@ -14040,18 +12725,18 @@ var get5 = new Command("get").description("Get a backlog item").argument("<id>",
|
|
|
14040
12725
|
if (!r.ok || !r.data) fail(this, r.error ?? "Failed to get", classifyStatus(r.status), r.errorDetails);
|
|
14041
12726
|
output(this, { backlog_item: r.data }, () => detail2(r.data));
|
|
14042
12727
|
});
|
|
14043
|
-
function write2(
|
|
14044
|
-
const c = new Command(
|
|
14045
|
-
if (
|
|
12728
|
+
function write2(action69) {
|
|
12729
|
+
const c = new Command(action69).description(`${action69 === "create" ? "Create" : "Update"} a ${singular}`);
|
|
12730
|
+
if (action69 === "update") c.argument("<id>", `${singular} ID`);
|
|
14046
12731
|
c.argument("[json]", "Inline JSON, @file, or file path").option("--file <path>", "JSON body file");
|
|
14047
12732
|
c.action(async function(...args) {
|
|
14048
12733
|
const o = this.opts();
|
|
14049
|
-
const id =
|
|
14050
|
-
const raw = o.file ? `@${o.file}` : String(args[
|
|
12734
|
+
const id = action69 === "update" ? String(args[0]) : void 0;
|
|
12735
|
+
const raw = o.file ? `@${o.file}` : String(args[action69 === "update" ? 1 : 0] ?? "") || void 0;
|
|
14051
12736
|
const body5 = parse(this, raw);
|
|
14052
12737
|
if (!Object.keys(body5).length) fail(this, "Provide a JSON body.", EXIT_CODES.validation);
|
|
14053
|
-
const r =
|
|
14054
|
-
if (!r.ok || !r.data) fail(this, r.error ?? `Failed to ${
|
|
12738
|
+
const r = action69 === "create" ? await createInsight(requireCredentials(), kind2, body5) : await updateInsight(requireCredentials(), kind2, id, body5);
|
|
12739
|
+
if (!r.ok || !r.data) fail(this, r.error ?? `Failed to ${action69}`, classifyStatus(r.status), r.errorDetails);
|
|
14055
12740
|
output(this, { backlog_item: r.data }, () => detail2(r.data));
|
|
14056
12741
|
});
|
|
14057
12742
|
return c;
|
|
@@ -14086,8 +12771,8 @@ var remove2 = new Command("delete").description("Delete a backlog item").argumen
|
|
|
14086
12771
|
var backlogCommand = new Command("backlog").description("Manage backlog").addCommand(list7).addCommand(get5).addCommand(write2("create")).addCommand(write2("update")).addCommand(archive).addCommand(restore).addCommand(remove2);
|
|
14087
12772
|
|
|
14088
12773
|
// src/commands/signals.ts
|
|
14089
|
-
var
|
|
14090
|
-
var
|
|
12774
|
+
var import_node_fs16 = __toESM(require("node:fs"));
|
|
12775
|
+
var import_node_path14 = __toESM(require("node:path"));
|
|
14091
12776
|
init_source();
|
|
14092
12777
|
var kind3 = "signals";
|
|
14093
12778
|
var singular2 = "signal";
|
|
@@ -14095,7 +12780,7 @@ function parse2(command, value) {
|
|
|
14095
12780
|
if (!value) return {};
|
|
14096
12781
|
try {
|
|
14097
12782
|
const source = value.startsWith("@") ? value.slice(1) : value;
|
|
14098
|
-
const text = value.startsWith("@") ||
|
|
12783
|
+
const text = value.startsWith("@") || import_node_fs16.default.existsSync(source) ? import_node_fs16.default.readFileSync(import_node_path14.default.resolve(process.cwd(), source), "utf8") : value;
|
|
14099
12784
|
const body5 = JSON.parse(text);
|
|
14100
12785
|
if (!body5 || typeof body5 !== "object" || Array.isArray(body5)) throw new Error("body must be an object");
|
|
14101
12786
|
return body5;
|
|
@@ -14120,18 +12805,18 @@ var get6 = new Command("get").description("Get a signal").argument("<id>", "sign
|
|
|
14120
12805
|
if (!r.ok || !r.data) fail(this, r.error ?? "Failed to get", classifyStatus(r.status), r.errorDetails);
|
|
14121
12806
|
output(this, { signal: r.data }, () => detail3(r.data));
|
|
14122
12807
|
});
|
|
14123
|
-
function write3(
|
|
14124
|
-
const c = new Command(
|
|
14125
|
-
if (
|
|
12808
|
+
function write3(action69) {
|
|
12809
|
+
const c = new Command(action69).description(`${action69 === "create" ? "Create" : "Update"} a ${singular2}`);
|
|
12810
|
+
if (action69 === "update") c.argument("<id>", `${singular2} ID`);
|
|
14126
12811
|
c.argument("[json]", "Inline JSON, @file, or file path").option("--file <path>", "JSON body file");
|
|
14127
12812
|
c.action(async function(...args) {
|
|
14128
12813
|
const o = this.opts();
|
|
14129
|
-
const id =
|
|
14130
|
-
const raw = o.file ? `@${o.file}` : String(args[
|
|
12814
|
+
const id = action69 === "update" ? String(args[0]) : void 0;
|
|
12815
|
+
const raw = o.file ? `@${o.file}` : String(args[action69 === "update" ? 1 : 0] ?? "") || void 0;
|
|
14131
12816
|
const body5 = parse2(this, raw);
|
|
14132
12817
|
if (!Object.keys(body5).length) fail(this, "Provide a JSON body.", EXIT_CODES.validation);
|
|
14133
|
-
const r =
|
|
14134
|
-
if (!r.ok || !r.data) fail(this, r.error ?? `Failed to ${
|
|
12818
|
+
const r = action69 === "create" ? await createInsight(requireCredentials(), kind3, body5) : await updateInsight(requireCredentials(), kind3, id, body5);
|
|
12819
|
+
if (!r.ok || !r.data) fail(this, r.error ?? `Failed to ${action69}`, classifyStatus(r.status), r.errorDetails);
|
|
14135
12820
|
output(this, { signal: r.data }, () => detail3(r.data));
|
|
14136
12821
|
});
|
|
14137
12822
|
return c;
|
|
@@ -14145,14 +12830,14 @@ var signalsCommand = new Command("signals").description("Manage signals").addCom
|
|
|
14145
12830
|
|
|
14146
12831
|
// src/commands/personalizations.ts
|
|
14147
12832
|
init_source();
|
|
14148
|
-
var
|
|
14149
|
-
var
|
|
12833
|
+
var import_node_fs17 = __toESM(require("node:fs"));
|
|
12834
|
+
var import_node_path15 = __toESM(require("node:path"));
|
|
14150
12835
|
var kind4 = "personalizations";
|
|
14151
12836
|
function body2(command, value, opts) {
|
|
14152
12837
|
let parsed = {};
|
|
14153
12838
|
if (value) {
|
|
14154
12839
|
const source = value.startsWith("@") ? value.slice(1) : value;
|
|
14155
|
-
const text = value.startsWith("@") ||
|
|
12840
|
+
const text = value.startsWith("@") || import_node_fs17.default.existsSync(source) ? import_node_fs17.default.readFileSync(import_node_path15.default.resolve(process.cwd(), source), "utf8") : value;
|
|
14156
12841
|
try {
|
|
14157
12842
|
parsed = JSON.parse(text);
|
|
14158
12843
|
} catch (e) {
|
|
@@ -14249,20 +12934,20 @@ function write4(name) {
|
|
|
14249
12934
|
});
|
|
14250
12935
|
return c;
|
|
14251
12936
|
}
|
|
14252
|
-
function lifecycle2(
|
|
14253
|
-
return new Command(
|
|
14254
|
-
`${
|
|
12937
|
+
function lifecycle2(action69) {
|
|
12938
|
+
return new Command(action69).description(
|
|
12939
|
+
`${action69[0].toUpperCase()}${action69.slice(1)} a personalization`
|
|
14255
12940
|
).argument("<id>").action(async function(id) {
|
|
14256
12941
|
const r = await transitionManagedDelivery(
|
|
14257
12942
|
requireCredentials(),
|
|
14258
12943
|
kind4,
|
|
14259
12944
|
id,
|
|
14260
|
-
|
|
12945
|
+
action69
|
|
14261
12946
|
);
|
|
14262
12947
|
if (!r.ok || !r.data)
|
|
14263
12948
|
fail(
|
|
14264
12949
|
this,
|
|
14265
|
-
r.error ?? `Failed to ${
|
|
12950
|
+
r.error ?? `Failed to ${action69}`,
|
|
14266
12951
|
classifyStatus(r.status),
|
|
14267
12952
|
r.errorDetails
|
|
14268
12953
|
);
|
|
@@ -14273,13 +12958,13 @@ var personalizationsCommand = new Command("personalizations").description("Manag
|
|
|
14273
12958
|
|
|
14274
12959
|
// src/commands/targeting-resources.ts
|
|
14275
12960
|
init_source();
|
|
14276
|
-
var
|
|
14277
|
-
var
|
|
12961
|
+
var import_node_fs18 = __toESM(require("node:fs"));
|
|
12962
|
+
var import_node_path16 = __toESM(require("node:path"));
|
|
14278
12963
|
function body3(command, value) {
|
|
14279
12964
|
if (!value) return {};
|
|
14280
12965
|
const source = value.startsWith("@") ? value.slice(1) : value;
|
|
14281
12966
|
try {
|
|
14282
|
-
const text = value.startsWith("@") ||
|
|
12967
|
+
const text = value.startsWith("@") || import_node_fs18.default.existsSync(source) ? import_node_fs18.default.readFileSync(import_node_path16.default.resolve(process.cwd(), source), "utf8") : value;
|
|
14283
12968
|
return JSON.parse(text);
|
|
14284
12969
|
} catch (error) {
|
|
14285
12970
|
fail(
|
|
@@ -14358,17 +13043,17 @@ function targetingResourceCommand(kind5) {
|
|
|
14358
13043
|
);
|
|
14359
13044
|
})
|
|
14360
13045
|
);
|
|
14361
|
-
for (const
|
|
14362
|
-
const command = new Command(
|
|
14363
|
-
`${
|
|
13046
|
+
for (const action69 of ["create", "update"]) {
|
|
13047
|
+
const command = new Command(action69).description(
|
|
13048
|
+
`${action69 === "create" ? "Create" : "Update"} a ${singular3}`
|
|
14364
13049
|
);
|
|
14365
|
-
if (
|
|
13050
|
+
if (action69 === "update") command.argument("<id>");
|
|
14366
13051
|
command.argument("[json]", "Inline JSON, @file, or file path").option("--file <path>", "JSON body file").action(async function(...args) {
|
|
14367
13052
|
const opts = this.opts();
|
|
14368
|
-
const id =
|
|
14369
|
-
const value = opts.file ? `@${opts.file}` : String(args[
|
|
13053
|
+
const id = action69 === "update" ? String(args[0]) : void 0;
|
|
13054
|
+
const value = opts.file ? `@${opts.file}` : String(args[action69 === "update" ? 1 : 0] ?? "") || void 0;
|
|
14370
13055
|
const requestBody = body3(this, value);
|
|
14371
|
-
if (
|
|
13056
|
+
if (action69 === "update" && typeof requestBody === "object" && requestBody !== null && !Array.isArray(requestBody) && Object.keys(requestBody).length === 0)
|
|
14372
13057
|
fail(
|
|
14373
13058
|
this,
|
|
14374
13059
|
`Updating a ${singular3} requires at least one field.`,
|
|
@@ -14376,7 +13061,7 @@ function targetingResourceCommand(kind5) {
|
|
|
14376
13061
|
);
|
|
14377
13062
|
const response = await requestTargetingResource(
|
|
14378
13063
|
requireCredentials(),
|
|
14379
|
-
|
|
13064
|
+
action69 === "create" ? "POST" : "PATCH",
|
|
14380
13065
|
kind5,
|
|
14381
13066
|
id,
|
|
14382
13067
|
requestBody
|
|
@@ -14384,7 +13069,7 @@ function targetingResourceCommand(kind5) {
|
|
|
14384
13069
|
if (!response.ok || !response.data)
|
|
14385
13070
|
fail(
|
|
14386
13071
|
this,
|
|
14387
|
-
response.error ?? `Failed to ${
|
|
13072
|
+
response.error ?? `Failed to ${action69}`,
|
|
14388
13073
|
classifyStatus(response.status),
|
|
14389
13074
|
response.errorDetails
|
|
14390
13075
|
);
|
|
@@ -14393,7 +13078,7 @@ function targetingResourceCommand(kind5) {
|
|
|
14393
13078
|
{
|
|
14394
13079
|
[kind5 === "exclusion-groups" ? "exclusion_group" : singular3]: response.data
|
|
14395
13080
|
},
|
|
14396
|
-
() => console.log(source_default.green(`${
|
|
13081
|
+
() => console.log(source_default.green(`${action69}d ${singular3}`))
|
|
14397
13082
|
);
|
|
14398
13083
|
});
|
|
14399
13084
|
root.addCommand(command);
|
|
@@ -14480,7 +13165,7 @@ var exclusionGroupsCommand = targetingResourceCommand("exclusion-groups");
|
|
|
14480
13165
|
// src/commands/brand.ts
|
|
14481
13166
|
init_source();
|
|
14482
13167
|
var NO_PROFILE_MESSAGE = "No brand profile yet \u2014 run onboarding or a site check";
|
|
14483
|
-
var brandCommand = new Command("brand").description("Print the shop brand context for external agents").action(async function
|
|
13168
|
+
var brandCommand = new Command("brand").description("Print the shop brand context for external agents").action(async function action58() {
|
|
14484
13169
|
const creds = requireCredentials();
|
|
14485
13170
|
const res = await getBrandContext(creds);
|
|
14486
13171
|
if (res.status === 404) {
|
|
@@ -14513,7 +13198,7 @@ var brandCommand = new Command("brand").description("Print the shop brand contex
|
|
|
14513
13198
|
|
|
14514
13199
|
// src/commands/targeting.ts
|
|
14515
13200
|
init_source();
|
|
14516
|
-
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
|
|
13201
|
+
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 action59(url, opts) {
|
|
14517
13202
|
const creds = requireCredentials();
|
|
14518
13203
|
const res = await testTargeting(creds, url, { status: opts.status });
|
|
14519
13204
|
if (!res.ok || !res.data) {
|
|
@@ -14553,13 +13238,13 @@ var targetingCommand = new Command("targeting").description("Test experiment tar
|
|
|
14553
13238
|
|
|
14554
13239
|
// src/commands/sos.ts
|
|
14555
13240
|
init_source();
|
|
14556
|
-
var
|
|
14557
|
-
var
|
|
13241
|
+
var import_node_fs19 = __toESM(require("node:fs"));
|
|
13242
|
+
var import_node_path17 = __toESM(require("node:path"));
|
|
14558
13243
|
var SOS_TYPES = /* @__PURE__ */ new Set(["full_handover", "design", "development", "qa", "config", "bug"]);
|
|
14559
13244
|
function readText(value) {
|
|
14560
13245
|
if (!value) return void 0;
|
|
14561
13246
|
if (!value.startsWith("@")) return value;
|
|
14562
|
-
return
|
|
13247
|
+
return import_node_fs19.default.readFileSync(import_node_path17.default.resolve(process.cwd(), value.slice(1)), "utf8");
|
|
14563
13248
|
}
|
|
14564
13249
|
function printTicket(ticket, reused) {
|
|
14565
13250
|
console.log();
|
|
@@ -14570,7 +13255,7 @@ function printTicket(ticket, reused) {
|
|
|
14570
13255
|
console.log(` Status: ${ticket.my_status ?? ticket.mapped_status}`);
|
|
14571
13256
|
console.log();
|
|
14572
13257
|
}
|
|
14573
|
-
var create4 = new Command("create").description("Create an Apex SOS ticket").option("--type <type>", "full_handover|design|development|qa|config|bug", "qa").option("--loom <url>", "Loom URL with reproduction or QA context").option("--experiment <id>", "Experiment ID").option("--sub-class <text>", "SOS sub-classification").option("--description <text-or-@file>", "Description text or @path").argument("[description]", "Description text").action(async function
|
|
13258
|
+
var create4 = new Command("create").description("Create an Apex SOS ticket").option("--type <type>", "full_handover|design|development|qa|config|bug", "qa").option("--loom <url>", "Loom URL with reproduction or QA context").option("--experiment <id>", "Experiment ID").option("--sub-class <text>", "SOS sub-classification").option("--description <text-or-@file>", "Description text or @path").argument("[description]", "Description text").action(async function action60(descriptionArg, opts) {
|
|
14574
13259
|
if (!SOS_TYPES.has(opts.type)) {
|
|
14575
13260
|
fail(this, "Invalid SOS type. Use full_handover, design, development, qa, config, or bug.", 3);
|
|
14576
13261
|
}
|
|
@@ -14588,7 +13273,7 @@ var create4 = new Command("create").description("Create an Apex SOS ticket").opt
|
|
|
14588
13273
|
}
|
|
14589
13274
|
output(this, res.data, () => printTicket(res.data.ticket, res.data.reused));
|
|
14590
13275
|
});
|
|
14591
|
-
var mine = new Command("mine").description("List SOS tickets created by this API key").action(async function
|
|
13276
|
+
var mine = new Command("mine").description("List SOS tickets created by this API key").action(async function action61() {
|
|
14592
13277
|
const creds = requireCredentials();
|
|
14593
13278
|
const res = await listMySos(creds);
|
|
14594
13279
|
if (!res.ok || !res.data) {
|
|
@@ -14641,13 +13326,13 @@ var markRead = new Command("mark-read").option("--channel <key>", "Mark one chan
|
|
|
14641
13326
|
var inboxCommand = new Command("inbox").description("Read and send inbox messages").addCommand(list11).addCommand(send).addCommand(markRead);
|
|
14642
13327
|
|
|
14643
13328
|
// src/commands/shops.ts
|
|
14644
|
-
var
|
|
14645
|
-
var
|
|
13329
|
+
var import_node_fs20 = __toESM(require("node:fs"));
|
|
13330
|
+
var import_node_path18 = __toESM(require("node:path"));
|
|
14646
13331
|
function body4(command, raw) {
|
|
14647
13332
|
if (!raw) return {};
|
|
14648
13333
|
try {
|
|
14649
13334
|
const source = raw.startsWith("@") ? raw.slice(1) : raw;
|
|
14650
|
-
const text = raw.startsWith("@") ||
|
|
13335
|
+
const text = raw.startsWith("@") || import_node_fs20.default.existsSync(source) ? import_node_fs20.default.readFileSync(import_node_path18.default.resolve(source), "utf8") : raw;
|
|
14651
13336
|
const value = JSON.parse(text);
|
|
14652
13337
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
14653
13338
|
throw new Error("body must be an object");
|
|
@@ -14909,7 +13594,7 @@ function commandSchema(command) {
|
|
|
14909
13594
|
};
|
|
14910
13595
|
}
|
|
14911
13596
|
function createSchemaCommand(program3) {
|
|
14912
|
-
return new Command("schema").description("Print the current Apex CLI command and flag schema").option("--json", "Print JSON schema").action(function
|
|
13597
|
+
return new Command("schema").description("Print the current Apex CLI command and flag schema").option("--json", "Print JSON schema").action(function action69() {
|
|
14913
13598
|
const schema = commandSchema(program3);
|
|
14914
13599
|
output(this, schema, () => {
|
|
14915
13600
|
process.stdout.write(`${JSON.stringify(schema, null, 2)}
|
|
@@ -14919,16 +13604,16 @@ function createSchemaCommand(program3) {
|
|
|
14919
13604
|
}
|
|
14920
13605
|
|
|
14921
13606
|
// src/commands/skills.ts
|
|
14922
|
-
var
|
|
14923
|
-
var
|
|
13607
|
+
var import_node_fs21 = __toESM(require("node:fs"));
|
|
13608
|
+
var import_node_path19 = __toESM(require("node:path"));
|
|
14924
13609
|
function packageRoot() {
|
|
14925
13610
|
const candidates = [
|
|
14926
|
-
|
|
14927
|
-
|
|
14928
|
-
|
|
13611
|
+
import_node_path19.default.resolve(__dirname, ".."),
|
|
13612
|
+
import_node_path19.default.resolve(import_node_path19.default.dirname(process.argv[1] ?? process.cwd()), ".."),
|
|
13613
|
+
import_node_path19.default.resolve(process.cwd(), "packages/cli")
|
|
14929
13614
|
];
|
|
14930
13615
|
for (const candidate of candidates) {
|
|
14931
|
-
if (
|
|
13616
|
+
if (import_node_fs21.default.existsSync(import_node_path19.default.join(candidate, "AGENTS.md"))) return candidate;
|
|
14932
13617
|
}
|
|
14933
13618
|
throw new Error(
|
|
14934
13619
|
`Apex CLI package files not found. Looked for AGENTS.md in: ${candidates.join(", ")}`
|
|
@@ -14937,24 +13622,24 @@ function packageRoot() {
|
|
|
14937
13622
|
function skillPaths() {
|
|
14938
13623
|
const root = packageRoot();
|
|
14939
13624
|
return {
|
|
14940
|
-
agents:
|
|
14941
|
-
skill:
|
|
13625
|
+
agents: import_node_path19.default.join(root, "AGENTS.md"),
|
|
13626
|
+
skill: import_node_path19.default.join(root, "skills", "apex-cli", "SKILL.md")
|
|
14942
13627
|
};
|
|
14943
13628
|
}
|
|
14944
13629
|
function installSkill(paths, targetRoot) {
|
|
14945
|
-
const missing = [paths.skill, paths.agents].filter((filePath) => !
|
|
13630
|
+
const missing = [paths.skill, paths.agents].filter((filePath) => !import_node_fs21.default.existsSync(filePath));
|
|
14946
13631
|
if (missing.length > 0) {
|
|
14947
13632
|
throw new Error(
|
|
14948
13633
|
`Apex CLI package files not found. Missing: ${missing.join(", ")}`
|
|
14949
13634
|
);
|
|
14950
13635
|
}
|
|
14951
|
-
const target =
|
|
14952
|
-
|
|
14953
|
-
|
|
14954
|
-
|
|
13636
|
+
const target = import_node_path19.default.resolve(targetRoot, "apex-cli");
|
|
13637
|
+
import_node_fs21.default.mkdirSync(target, { recursive: true });
|
|
13638
|
+
import_node_fs21.default.copyFileSync(paths.skill, import_node_path19.default.join(target, "SKILL.md"));
|
|
13639
|
+
import_node_fs21.default.copyFileSync(paths.agents, import_node_path19.default.join(target, "AGENTS.md"));
|
|
14955
13640
|
return target;
|
|
14956
13641
|
}
|
|
14957
|
-
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
|
|
13642
|
+
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 action62(opts) {
|
|
14958
13643
|
let paths;
|
|
14959
13644
|
try {
|
|
14960
13645
|
paths = skillPaths();
|
|
@@ -15169,8 +13854,8 @@ async function fetchText(url) {
|
|
|
15169
13854
|
const res = await fetch(url, { headers: { "user-agent": DESKTOP_UA2, accept: "text/html,application/xhtml+xml,application/json;q=0.9,*/*;q=0.8" }, redirect: "follow", signal: AbortSignal.timeout(12e3) });
|
|
15170
13855
|
return { status: res.status, text: await readResponseText(res), finalUrl: res.url };
|
|
15171
13856
|
}
|
|
15172
|
-
function abs(base,
|
|
15173
|
-
return new URL(
|
|
13857
|
+
function abs(base, path21) {
|
|
13858
|
+
return new URL(path21, base).toString();
|
|
15174
13859
|
}
|
|
15175
13860
|
function looksLikeShopifyCart(text) {
|
|
15176
13861
|
try {
|
|
@@ -15190,9 +13875,9 @@ async function collectSignals(url) {
|
|
|
15190
13875
|
const chunks = [`__apex_primary_status_${primary.status}__`, primary.text];
|
|
15191
13876
|
const protectedHit = primary.status === 403 || primary.status === 429 || matchDetectionMarkers(primary.text, "protected").length > 0;
|
|
15192
13877
|
if (!protectedHit) {
|
|
15193
|
-
for (const
|
|
13878
|
+
for (const path21 of ["/cart.js"]) {
|
|
15194
13879
|
try {
|
|
15195
|
-
const secondary = await fetchText(abs(primary.finalUrl || url,
|
|
13880
|
+
const secondary = await fetchText(abs(primary.finalUrl || url, path21));
|
|
15196
13881
|
if (secondary.status >= 200 && secondary.status < 300 && looksLikeShopifyCart(secondary.text)) {
|
|
15197
13882
|
chunks.push("__apex_secondary_shopify_cart_js__", secondary.text.slice(0, 2e3));
|
|
15198
13883
|
}
|
|
@@ -15267,7 +13952,7 @@ var prospectCommand = new Command("prospect").description("Pre-sales storefront
|
|
|
15267
13952
|
|
|
15268
13953
|
// src/commands/webhooks.ts
|
|
15269
13954
|
init_source();
|
|
15270
|
-
var SIGNATURE_DOC = "https://app.drip-apex.com/docs/
|
|
13955
|
+
var SIGNATURE_DOC = "https://app.drip-apex.com/docs/developers/webhooks#signatures";
|
|
15271
13956
|
function requireShopSecretKey(_command) {
|
|
15272
13957
|
requireCredentials();
|
|
15273
13958
|
}
|
|
@@ -15325,11 +14010,11 @@ function printDeliveries(deliveries2, webhook) {
|
|
|
15325
14010
|
function selectedEvents(opts) {
|
|
15326
14011
|
return [...new Set((opts.event ?? []).map((event) => event.trim()).filter(Boolean))];
|
|
15327
14012
|
}
|
|
15328
|
-
var list14 = new Command("list").alias("ls").description("List webhooks. JSON includes availableEvents; secrets are never returned after create.").action(async function
|
|
14013
|
+
var list14 = new Command("list").alias("ls").description("List webhooks. JSON includes availableEvents; secrets are never returned after create.").action(async function action63() {
|
|
15329
14014
|
const data = await loadWebhookIndex(this);
|
|
15330
14015
|
output(this, data, () => printWebhooks(data.data));
|
|
15331
14016
|
});
|
|
15332
|
-
var create7 = new Command("create").description(`Create a webhook. Prints the signing secret once. Signature docs: ${SIGNATURE_DOC}`).requiredOption("--url <url>", "HTTPS callback URL").option("--event <event>", "Webhook event to subscribe to; repeat for multiple events", (value, previous = []) => [...previous, value], []).action(async function
|
|
14017
|
+
var create7 = new Command("create").description(`Create a webhook. Prints the signing secret once. Signature docs: ${SIGNATURE_DOC}`).requiredOption("--url <url>", "HTTPS callback URL").option("--event <event>", "Webhook event to subscribe to; repeat for multiple events", (value, previous = []) => [...previous, value], []).action(async function action64(opts) {
|
|
15333
14018
|
const events = selectedEvents(opts);
|
|
15334
14019
|
if (events.length === 0) fail(this, "At least one --event is required", EXIT_CODES.validation);
|
|
15335
14020
|
const index = await loadWebhookIndex(this);
|
|
@@ -15346,19 +14031,19 @@ var create7 = new Command("create").description(`Create a webhook. Prints the si
|
|
|
15346
14031
|
console.log(`Signature verification: ${SIGNATURE_DOC}`);
|
|
15347
14032
|
});
|
|
15348
14033
|
});
|
|
15349
|
-
var remove4 = new Command("delete").alias("rm").description("Delete a webhook").argument("<id>", "Webhook id").action(async function
|
|
14034
|
+
var remove4 = new Command("delete").alias("rm").description("Delete a webhook").argument("<id>", "Webhook id").action(async function action65(id) {
|
|
15350
14035
|
requireShopSecretKey(this);
|
|
15351
14036
|
const result = handleResult(this, await deleteWebhook(requireCredentials(), id), "Failed to delete webhook");
|
|
15352
14037
|
output(this, result, () => console.log(source_default.green(`Deleted webhook ${id}`)));
|
|
15353
14038
|
});
|
|
15354
|
-
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
|
|
14039
|
+
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 action66(id, opts) {
|
|
15355
14040
|
const limit = Number.parseInt(opts.limit, 10);
|
|
15356
14041
|
if (!Number.isInteger(limit) || limit < 1) fail(this, "--limit must be a positive integer", EXIT_CODES.validation);
|
|
15357
14042
|
requireShopSecretKey(this);
|
|
15358
14043
|
const result = handleResult(this, await getWebhookDeliveries(requireCredentials(), id, limit), "Failed to load deliveries");
|
|
15359
14044
|
output(this, result, () => printDeliveries(result.data, result.webhook));
|
|
15360
14045
|
});
|
|
15361
|
-
var update4 = new Command("update").description("Update a webhook URL, events, or active state").argument("<id>", "Webhook id").option("--url <url>", "HTTPS callback URL").option("--event <event>", "Replacement event; repeat for multiple events", (value, previous = []) => [...previous, value], []).option("--active", "Enable the webhook").option("--inactive", "Disable the webhook").action(async function
|
|
14046
|
+
var update4 = new Command("update").description("Update a webhook URL, events, or active state").argument("<id>", "Webhook id").option("--url <url>", "HTTPS callback URL").option("--event <event>", "Replacement event; repeat for multiple events", (value, previous = []) => [...previous, value], []).option("--active", "Enable the webhook").option("--inactive", "Disable the webhook").action(async function action67(id, opts) {
|
|
15362
14047
|
if (opts.active && opts.inactive) fail(this, "Use only one of --active or --inactive", EXIT_CODES.validation);
|
|
15363
14048
|
const events = selectedEvents(opts);
|
|
15364
14049
|
const body5 = {};
|
|
@@ -15378,7 +14063,7 @@ var update4 = new Command("update").description("Update a webhook URL, events, o
|
|
|
15378
14063
|
const result = handleResult(this, await updateWebhook(requireCredentials(), id, body5), "Failed to update webhook");
|
|
15379
14064
|
output(this, { webhook: result }, () => printWebhooks([result]));
|
|
15380
14065
|
});
|
|
15381
|
-
var test3 = new Command("test").description("Send a signed test event to a webhook").argument("<id>", "Webhook id").action(async function
|
|
14066
|
+
var test3 = new Command("test").description("Send a signed test event to a webhook").argument("<id>", "Webhook id").action(async function action68(id) {
|
|
15382
14067
|
requireShopSecretKey(this);
|
|
15383
14068
|
const result = handleResult(this, await testWebhook(requireCredentials(), id), "Failed to send test webhook");
|
|
15384
14069
|
output(this, result, () => console.log(source_default.green(`Sent test delivery ${result.delivery.id}: ${result.delivery.status}`)));
|
|
@@ -15386,8 +14071,8 @@ var test3 = new Command("test").description("Send a signed test event to a webho
|
|
|
15386
14071
|
var webhooksCommand = new Command("webhooks").description("Manage shop webhooks").addCommand(list14).addCommand(create7).addCommand(update4).addCommand(remove4).addCommand(deliveries).addCommand(test3);
|
|
15387
14072
|
|
|
15388
14073
|
// src/commands/config-resources.ts
|
|
15389
|
-
var
|
|
15390
|
-
var
|
|
14074
|
+
var import_node_fs22 = __toESM(require("node:fs"));
|
|
14075
|
+
var import_node_path20 = __toESM(require("node:path"));
|
|
15391
14076
|
function show(resource) {
|
|
15392
14077
|
process.stdout.write(`${JSON.stringify(resource, null, 2)}
|
|
15393
14078
|
`);
|
|
@@ -15472,14 +14157,14 @@ function readFamily(family, label) {
|
|
|
15472
14157
|
}
|
|
15473
14158
|
var assetReads = readFamily("assets", "assets");
|
|
15474
14159
|
var upload = new Command("upload").description("Upload an asset").argument("<file>").action(async function(file) {
|
|
15475
|
-
const absolute =
|
|
15476
|
-
if (!
|
|
14160
|
+
const absolute = import_node_path20.default.resolve(process.cwd(), file);
|
|
14161
|
+
if (!import_node_fs22.default.existsSync(absolute))
|
|
15477
14162
|
fail(this, `File not found: ${file}`, 3);
|
|
15478
|
-
const fileStat =
|
|
14163
|
+
const fileStat = import_node_fs22.default.statSync(absolute);
|
|
15479
14164
|
if (!fileStat.isFile()) fail(this, `File not found: ${file}`, 3);
|
|
15480
14165
|
if (fileStat.size > MAX_ASSET_SIZE_BYTES)
|
|
15481
14166
|
fail(this, `${file} is larger than 10 MB`, 3);
|
|
15482
|
-
const mimeType = ASSET_MIME_TYPES[
|
|
14167
|
+
const mimeType = ASSET_MIME_TYPES[import_node_path20.default.extname(absolute).toLowerCase()];
|
|
15483
14168
|
if (!mimeType)
|
|
15484
14169
|
fail(
|
|
15485
14170
|
this,
|
|
@@ -15488,8 +14173,8 @@ var upload = new Command("upload").description("Upload an asset").argument("<fil
|
|
|
15488
14173
|
);
|
|
15489
14174
|
const r = await uploadAssets(requireCredentials(), [
|
|
15490
14175
|
{
|
|
15491
|
-
name:
|
|
15492
|
-
bytes:
|
|
14176
|
+
name: import_node_path20.default.basename(absolute),
|
|
14177
|
+
bytes: import_node_fs22.default.readFileSync(absolute),
|
|
15493
14178
|
type: mimeType
|
|
15494
14179
|
}
|
|
15495
14180
|
]);
|
|
@@ -15543,6 +14228,19 @@ var snippet = new Command("snippet").description("Generate an external purchase
|
|
|
15543
14228
|
var externalPixelsCommand = new Command("external-pixels").description("Manage external pixels").addCommand(snippet);
|
|
15544
14229
|
|
|
15545
14230
|
// src/cli.ts
|
|
14231
|
+
var mockApiEnvironmentKey = ["APEX", "CLI", "MOCK", "API"].join("_");
|
|
14232
|
+
if (process.env[mockApiEnvironmentKey] === "1" && !cliTestHarnessActive()) {
|
|
14233
|
+
const message = "mock harness is not part of the published CLI; use the test artifact";
|
|
14234
|
+
const guardTerminatorIndex = process.argv.indexOf("--");
|
|
14235
|
+
const guardArgv = guardTerminatorIndex === -1 ? process.argv : process.argv.slice(0, guardTerminatorIndex);
|
|
14236
|
+
if (guardArgv.includes("--json") || !process.stdout.isTTY) {
|
|
14237
|
+
writeErrorEnvelope(new CliError(message, EXIT_CODES.general));
|
|
14238
|
+
} else {
|
|
14239
|
+
process.stderr.write(`${message}
|
|
14240
|
+
`);
|
|
14241
|
+
}
|
|
14242
|
+
process.exit(EXIT_CODES.general);
|
|
14243
|
+
}
|
|
15546
14244
|
function addJsonOption(command) {
|
|
15547
14245
|
if (!command.options.some((option) => option.long === "--json")) {
|
|
15548
14246
|
command.option("--json", "Print JSON output");
|