@drip-apex/apex-cli 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/cli.js +313 -101
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,6 +28,9 @@ DRIP_API_KEY=apx_your_key_here apex experiments list --json
|
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
30
|
apex experiments list
|
|
31
|
+
apex experiments create --name "Redirect test" --type redirect \
|
|
32
|
+
--source-url "https://shop.example/products/source" \
|
|
33
|
+
--destination "https://landing.example/products/treatment"
|
|
31
34
|
apex experiments pull <id-or-slug> --dir ./my-test
|
|
32
35
|
apex experiments plan ./my-test --json
|
|
33
36
|
apex experiments apply ./my-test --json
|
package/dist/cli.js
CHANGED
|
@@ -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(action25, options) {
|
|
6747
|
+
const actionIsFunction = typeof action25 === "function";
|
|
6748
|
+
const actionIsPromise = typeof action25.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 ? action25(spinner) : action25;
|
|
6756
6756
|
const result = await promise;
|
|
6757
6757
|
spinner.succeed(
|
|
6758
6758
|
successText === void 0 ? void 0 : typeof successText === "string" ? successText : successText(result)
|
|
@@ -7090,7 +7090,7 @@ init_source();
|
|
|
7090
7090
|
|
|
7091
7091
|
// src/commands/login.ts
|
|
7092
7092
|
init_source();
|
|
7093
|
-
var
|
|
7093
|
+
var import_node_crypto2 = __toESM(require("node:crypto"));
|
|
7094
7094
|
var import_node_http = __toESM(require("node:http"));
|
|
7095
7095
|
var import_node_os4 = __toESM(require("node:os"));
|
|
7096
7096
|
var import_node_readline = __toESM(require("node:readline"));
|
|
@@ -7356,6 +7356,7 @@ function findConfigPath() {
|
|
|
7356
7356
|
}
|
|
7357
7357
|
|
|
7358
7358
|
// src/api.ts
|
|
7359
|
+
var import_node_crypto = require("node:crypto");
|
|
7359
7360
|
var mockEnvironmentPatchAttempts = 0;
|
|
7360
7361
|
var mockEnvironmentReads = 0;
|
|
7361
7362
|
function extractErrorMessage(json, status2) {
|
|
@@ -7484,6 +7485,45 @@ function mockResponse(method, path20, body5) {
|
|
|
7484
7485
|
if (process.env.APEX_CLI_MOCK_API !== "1") return null;
|
|
7485
7486
|
const url = new URL(path20, "https://mock.apex.local");
|
|
7486
7487
|
const exp = mockExperiment();
|
|
7488
|
+
if (method === "GET" && url.pathname === `/api/v1/experiments/${exp.id}/filesystem`) {
|
|
7489
|
+
const fixtures = [
|
|
7490
|
+
["experiment.json", '{"schemaVersion":1,"name":"Fixture Test"}\n'],
|
|
7491
|
+
["targeting.json", '{"url":[],"segments":[]}\n'],
|
|
7492
|
+
["experiment/index.js", "window.__apexExperiment = true;\n"],
|
|
7493
|
+
["experiment/reset.js", "delete window.__apexExperiment;\n"],
|
|
7494
|
+
["experiment/index.css", ".apex-experiment { display: block; }\n"],
|
|
7495
|
+
["metrics.json", '{"primary":null,"secondary":[]}\n'],
|
|
7496
|
+
["qa.json", '{"status":"pending"}\n'],
|
|
7497
|
+
["launch.json", '{"status":"draft"}\n'],
|
|
7498
|
+
["variations/control/variation.json", '{"slug":"control","isControl":true}\n'],
|
|
7499
|
+
["variations/control/mutations.json", "[]\n"],
|
|
7500
|
+
["variations/control/index.css", ".control { color: inherit; }\n"],
|
|
7501
|
+
["variations/control/index.js", "window.__apexControl = true;\n"],
|
|
7502
|
+
["variations/control/reset.js", "delete window.__apexControl;\n"],
|
|
7503
|
+
["variations/control/anti-flicker.json", "[]\n"]
|
|
7504
|
+
];
|
|
7505
|
+
const invalidPaths = {
|
|
7506
|
+
invalid: "../secret",
|
|
7507
|
+
traversal: "variations/../../x",
|
|
7508
|
+
absolute: "/abs/path"
|
|
7509
|
+
};
|
|
7510
|
+
const invalidPath = invalidPaths[process.env.APEX_CLI_MOCK_FILESYSTEM_PATHS ?? ""];
|
|
7511
|
+
if (invalidPath) {
|
|
7512
|
+
fixtures.push([invalidPath, "must not be written\n"]);
|
|
7513
|
+
}
|
|
7514
|
+
return {
|
|
7515
|
+
ok: true,
|
|
7516
|
+
status: 200,
|
|
7517
|
+
data: {
|
|
7518
|
+
rootName: "fixture-test",
|
|
7519
|
+
files: fixtures.map(([filePath, content]) => ({
|
|
7520
|
+
path: filePath,
|
|
7521
|
+
content,
|
|
7522
|
+
sha256: (0, import_node_crypto.createHash)("sha256").update(content).digest("hex")
|
|
7523
|
+
}))
|
|
7524
|
+
}
|
|
7525
|
+
};
|
|
7526
|
+
}
|
|
7487
7527
|
const configMatch = url.pathname.match(
|
|
7488
7528
|
/^\/api\/v1\/(assets|landing-pages)(?:\/([^/]+))?$/
|
|
7489
7529
|
);
|
|
@@ -7663,16 +7703,16 @@ function mockResponse(method, path20, body5) {
|
|
|
7663
7703
|
/^\/api\/v1\/(flags|personalizations)(?:\/([^/]+))?(?:\/(activate|pause|archive|status))?$/
|
|
7664
7704
|
);
|
|
7665
7705
|
if (managedMatch) {
|
|
7666
|
-
const [, kind5, id = `${kind5.slice(0, -1)}_1`,
|
|
7706
|
+
const [, kind5, id = `${kind5.slice(0, -1)}_1`, action25] = managedMatch;
|
|
7667
7707
|
const input = body5 ?? {};
|
|
7668
7708
|
const item = {
|
|
7669
7709
|
id,
|
|
7670
7710
|
key: String(input.key ?? `fixture-${kind5.slice(0, -1)}`),
|
|
7671
7711
|
name: String(input.name ?? `Fixture ${kind5.slice(0, -1)}`),
|
|
7672
|
-
status:
|
|
7712
|
+
status: action25 === "activate" ? "active" : action25 === "pause" ? "paused" : action25 === "archive" ? "archived" : "draft",
|
|
7673
7713
|
updated_at: "2026-07-19T00:00:00.000Z"
|
|
7674
7714
|
};
|
|
7675
|
-
if (kind5 === "flags" &&
|
|
7715
|
+
if (kind5 === "flags" && action25 === "status")
|
|
7676
7716
|
return {
|
|
7677
7717
|
ok: true,
|
|
7678
7718
|
status: 200,
|
|
@@ -7705,11 +7745,11 @@ function mockResponse(method, path20, body5) {
|
|
|
7705
7745
|
/^\/api\/v1\/(backlog|signals)(?:\/([^/]+))?(?:\/(archive|restore))?$/
|
|
7706
7746
|
);
|
|
7707
7747
|
if (insightMatch) {
|
|
7708
|
-
const [, kind5, id = `${kind5}_1`,
|
|
7748
|
+
const [, kind5, id = `${kind5}_1`, action25] = insightMatch;
|
|
7709
7749
|
const input = body5 ?? {};
|
|
7710
|
-
if (
|
|
7711
|
-
return { ok: false, status: 405, error: `${
|
|
7712
|
-
const archived =
|
|
7750
|
+
if (action25 && (kind5 !== "backlog" || method !== "POST"))
|
|
7751
|
+
return { ok: false, status: 405, error: `${action25} requires POST` };
|
|
7752
|
+
const archived = action25 === "archive" || method === "DELETE" || url.searchParams.get("include_archived") === "true";
|
|
7713
7753
|
const item = {
|
|
7714
7754
|
id,
|
|
7715
7755
|
...kind5 === "backlog" ? {
|
|
@@ -7735,7 +7775,7 @@ function mockResponse(method, path20, body5) {
|
|
|
7735
7775
|
return { ok: true, status: 200, data: { ok: true } };
|
|
7736
7776
|
return {
|
|
7737
7777
|
ok: true,
|
|
7738
|
-
status: method === "POST" && !
|
|
7778
|
+
status: method === "POST" && !action25 ? 201 : 200,
|
|
7739
7779
|
data: item
|
|
7740
7780
|
};
|
|
7741
7781
|
}
|
|
@@ -7749,6 +7789,38 @@ function mockResponse(method, path20, body5) {
|
|
|
7749
7789
|
}
|
|
7750
7790
|
};
|
|
7751
7791
|
}
|
|
7792
|
+
if (method === "POST" && url.pathname === "/api/v1/experiments") {
|
|
7793
|
+
const input = body5 ?? {};
|
|
7794
|
+
if (input.experiment_type === "redirect" && process.env.APEX_CLI_MOCK_REDIRECT_DISABLED === "1") {
|
|
7795
|
+
return {
|
|
7796
|
+
ok: false,
|
|
7797
|
+
status: 403,
|
|
7798
|
+
error: "Redirect tests are not enabled for this shop"
|
|
7799
|
+
};
|
|
7800
|
+
}
|
|
7801
|
+
const requestedVariations = Array.isArray(input.variations) ? input.variations : exp.variations ?? [];
|
|
7802
|
+
return {
|
|
7803
|
+
ok: true,
|
|
7804
|
+
status: 201,
|
|
7805
|
+
data: {
|
|
7806
|
+
...exp,
|
|
7807
|
+
name: String(input.name ?? exp.name),
|
|
7808
|
+
slug: typeof input.slug === "string" || input.slug === null ? input.slug : exp.slug,
|
|
7809
|
+
experiment_type: input.experiment_type === "redirect" ? "redirect" : "mutation",
|
|
7810
|
+
traffic_allocation: typeof input.traffic_allocation === "number" ? input.traffic_allocation : exp.traffic_allocation,
|
|
7811
|
+
store_url: typeof input.store_url === "string" ? input.store_url : null,
|
|
7812
|
+
targeting: input.targeting ?? null,
|
|
7813
|
+
variations: requestedVariations.map((variation, index) => ({
|
|
7814
|
+
id: `00000000-0000-4000-8000-${String(index + 1).padStart(12, "0")}`,
|
|
7815
|
+
name: String(variation.name ?? `Variation ${index + 1}`),
|
|
7816
|
+
weight: typeof variation.weight === "number" ? variation.weight : 0,
|
|
7817
|
+
is_control: variation.is_control === true,
|
|
7818
|
+
redirect_url: typeof variation.redirect_url === "string" ? variation.redirect_url : null,
|
|
7819
|
+
mutations: []
|
|
7820
|
+
}))
|
|
7821
|
+
}
|
|
7822
|
+
};
|
|
7823
|
+
}
|
|
7752
7824
|
if (method === "GET" && (url.pathname === `/api/v1/experiments/${exp.id}` || url.pathname === `/api/v1/experiments/by-slug/${exp.slug}`)) {
|
|
7753
7825
|
return { ok: true, status: 200, data: exp };
|
|
7754
7826
|
}
|
|
@@ -7822,6 +7894,7 @@ function mockResponse(method, path20, body5) {
|
|
|
7822
7894
|
}
|
|
7823
7895
|
};
|
|
7824
7896
|
}
|
|
7897
|
+
const observedPaths = files.map((file) => file.path).filter((filePath) => typeof filePath === "string");
|
|
7825
7898
|
return {
|
|
7826
7899
|
ok: true,
|
|
7827
7900
|
status: 200,
|
|
@@ -7841,7 +7914,8 @@ function mockResponse(method, path20, body5) {
|
|
|
7841
7914
|
errors: [],
|
|
7842
7915
|
warnings: [],
|
|
7843
7916
|
invalidatesQa: false,
|
|
7844
|
-
precompileRequired: false
|
|
7917
|
+
precompileRequired: false,
|
|
7918
|
+
...process.env.APEX_CLI_MOCK_FILESYSTEM_PLAN === "echo-paths" ? { observedPaths } : {}
|
|
7845
7919
|
}
|
|
7846
7920
|
};
|
|
7847
7921
|
}
|
|
@@ -8497,8 +8571,8 @@ function requestTargetingResource(creds2, method, kind5, id, body5, limit, curso
|
|
|
8497
8571
|
const path20 = `/api/v1/${kind5}${id ? `/${id.split("/").map(encodeURIComponent).join("/")}` : ""}${query ? `?${query}` : ""}`;
|
|
8498
8572
|
return request(creds2, method, path20, body5);
|
|
8499
8573
|
}
|
|
8500
|
-
function managedPath(kind5, id,
|
|
8501
|
-
return `/api/v1/${kind5}${id ? `/${encodeURIComponent(id)}` : ""}${
|
|
8574
|
+
function managedPath(kind5, id, action25) {
|
|
8575
|
+
return `/api/v1/${kind5}${id ? `/${encodeURIComponent(id)}` : ""}${action25 ? `/${action25}` : ""}`;
|
|
8502
8576
|
}
|
|
8503
8577
|
function listManagedDeliveries(creds2, kind5, limit) {
|
|
8504
8578
|
const q = limit ? `?limit=${limit}` : "";
|
|
@@ -8520,8 +8594,8 @@ function createManagedDelivery(creds2, kind5, body5) {
|
|
|
8520
8594
|
function updateManagedDelivery(creds2, kind5, id, body5) {
|
|
8521
8595
|
return request(creds2, "PATCH", managedPath(kind5, id), body5);
|
|
8522
8596
|
}
|
|
8523
|
-
function transitionManagedDelivery(creds2, kind5, id,
|
|
8524
|
-
return request(creds2, "POST", managedPath(kind5, id,
|
|
8597
|
+
function transitionManagedDelivery(creds2, kind5, id, action25) {
|
|
8598
|
+
return request(creds2, "POST", managedPath(kind5, id, action25));
|
|
8525
8599
|
}
|
|
8526
8600
|
function listConfigResources(creds2, family, opts) {
|
|
8527
8601
|
const params = new URLSearchParams();
|
|
@@ -8784,7 +8858,7 @@ function parseFormBody(body5) {
|
|
|
8784
8858
|
return new URLSearchParams(body5);
|
|
8785
8859
|
}
|
|
8786
8860
|
async function waitForBrowserLogin(baseUrl, userMode = false) {
|
|
8787
|
-
const state =
|
|
8861
|
+
const state = import_node_crypto2.default.randomBytes(24).toString("base64url");
|
|
8788
8862
|
const clientName = import_node_os4.default.hostname() || "Local machine";
|
|
8789
8863
|
return new Promise((resolve, reject) => {
|
|
8790
8864
|
let settled = false;
|
|
@@ -9013,7 +9087,7 @@ var whoamiCommand = new Command("whoami").description("Show current authenticati
|
|
|
9013
9087
|
|
|
9014
9088
|
// src/commands/experiments.ts
|
|
9015
9089
|
init_source();
|
|
9016
|
-
var
|
|
9090
|
+
var import_node_crypto3 = __toESM(require("node:crypto"));
|
|
9017
9091
|
var import_node_fs6 = __toESM(require("node:fs"));
|
|
9018
9092
|
var import_node_path4 = __toESM(require("node:path"));
|
|
9019
9093
|
function formatDate(iso) {
|
|
@@ -9084,9 +9158,25 @@ function printExperimentResults(results2) {
|
|
|
9084
9158
|
}
|
|
9085
9159
|
console.log();
|
|
9086
9160
|
}
|
|
9087
|
-
var
|
|
9161
|
+
var EXPERIMENT_FILESYSTEM_PATH_SCHEMA = {
|
|
9162
|
+
topLevel: ["experiment.json", "targeting.json", "metrics.json", "qa.json", "launch.json"],
|
|
9163
|
+
experiment: ["index.css", "index.js", "reset.js"],
|
|
9164
|
+
variation: [
|
|
9165
|
+
"variation.json",
|
|
9166
|
+
"mutations.json",
|
|
9167
|
+
"index.css",
|
|
9168
|
+
"index.js",
|
|
9169
|
+
"reset.js",
|
|
9170
|
+
"anti-flicker.json"
|
|
9171
|
+
]
|
|
9172
|
+
};
|
|
9173
|
+
var escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
9174
|
+
var pathAlternation = (paths) => paths.map(escapeRegex).join("|");
|
|
9175
|
+
var FILESYSTEM_FILE_RE = new RegExp(
|
|
9176
|
+
`^(?:${pathAlternation(EXPERIMENT_FILESYSTEM_PATH_SCHEMA.topLevel)}|experiment/(?:${pathAlternation(EXPERIMENT_FILESYSTEM_PATH_SCHEMA.experiment)})|variations/[^/]+/(?:${pathAlternation(EXPERIMENT_FILESYSTEM_PATH_SCHEMA.variation)}))$`
|
|
9177
|
+
);
|
|
9088
9178
|
function sha256Text(value) {
|
|
9089
|
-
return
|
|
9179
|
+
return import_node_crypto3.default.createHash("sha256").update(value).digest("hex");
|
|
9090
9180
|
}
|
|
9091
9181
|
function folderPath(folder) {
|
|
9092
9182
|
return import_node_path4.default.resolve(process.cwd(), folder);
|
|
@@ -9126,8 +9216,12 @@ function validateFilesystemPath(relativePath) {
|
|
|
9126
9216
|
return normalized;
|
|
9127
9217
|
}
|
|
9128
9218
|
function writeExperimentFilesystemFiles(folder, files) {
|
|
9129
|
-
|
|
9130
|
-
|
|
9219
|
+
const validatedFiles = files.map((file) => ({
|
|
9220
|
+
...file,
|
|
9221
|
+
relativePath: validateFilesystemPath(file.path)
|
|
9222
|
+
}));
|
|
9223
|
+
for (const file of validatedFiles) {
|
|
9224
|
+
const relativePath = file.relativePath;
|
|
9131
9225
|
const absPath = import_node_path4.default.join(folder, relativePath);
|
|
9132
9226
|
import_node_fs6.default.mkdirSync(import_node_path4.default.dirname(absPath), { recursive: true });
|
|
9133
9227
|
import_node_fs6.default.writeFileSync(absPath, file.content, "utf8");
|
|
@@ -9135,26 +9229,25 @@ function writeExperimentFilesystemFiles(folder, files) {
|
|
|
9135
9229
|
}
|
|
9136
9230
|
function listExperimentFilesystemFiles(folder) {
|
|
9137
9231
|
const files = [];
|
|
9138
|
-
const
|
|
9139
|
-
for (const fileName of topLevel) {
|
|
9232
|
+
for (const fileName of EXPERIMENT_FILESYSTEM_PATH_SCHEMA.topLevel) {
|
|
9140
9233
|
const absPath = import_node_path4.default.join(folder, fileName);
|
|
9141
9234
|
if (!import_node_fs6.default.existsSync(absPath)) continue;
|
|
9142
9235
|
const content = import_node_fs6.default.readFileSync(absPath, "utf8");
|
|
9143
9236
|
files.push({ path: fileName, content, sha256: sha256Text(content) });
|
|
9144
9237
|
}
|
|
9238
|
+
for (const fileName of EXPERIMENT_FILESYSTEM_PATH_SCHEMA.experiment) {
|
|
9239
|
+
const relativePath = `experiment/${fileName}`;
|
|
9240
|
+
const absPath = import_node_path4.default.join(folder, relativePath);
|
|
9241
|
+
if (!import_node_fs6.default.existsSync(absPath)) continue;
|
|
9242
|
+
const content = import_node_fs6.default.readFileSync(absPath, "utf8");
|
|
9243
|
+
files.push({ path: relativePath, content, sha256: sha256Text(content) });
|
|
9244
|
+
}
|
|
9145
9245
|
const variationsRoot = import_node_path4.default.join(folder, "variations");
|
|
9146
9246
|
if (import_node_fs6.default.existsSync(variationsRoot)) {
|
|
9147
9247
|
for (const variationDir of import_node_fs6.default.readdirSync(variationsRoot, { withFileTypes: true })) {
|
|
9148
9248
|
if (!variationDir.isDirectory()) continue;
|
|
9149
9249
|
const base = import_node_path4.default.join(variationsRoot, variationDir.name);
|
|
9150
|
-
for (const fileName of
|
|
9151
|
-
"variation.json",
|
|
9152
|
-
"mutations.json",
|
|
9153
|
-
"index.css",
|
|
9154
|
-
"index.js",
|
|
9155
|
-
"reset.js",
|
|
9156
|
-
"anti-flicker.json"
|
|
9157
|
-
]) {
|
|
9250
|
+
for (const fileName of EXPERIMENT_FILESYSTEM_PATH_SCHEMA.variation) {
|
|
9158
9251
|
const absPath = import_node_path4.default.join(base, fileName);
|
|
9159
9252
|
if (!import_node_fs6.default.existsSync(absPath)) continue;
|
|
9160
9253
|
const relativePath = validateFilesystemPath(
|
|
@@ -9365,23 +9458,134 @@ var results = new Command("results").description("Read experiment results and ti
|
|
|
9365
9458
|
fail(this, error instanceof Error ? error.message : String(error));
|
|
9366
9459
|
}
|
|
9367
9460
|
});
|
|
9368
|
-
|
|
9461
|
+
function collectCreateOption(value, previous) {
|
|
9462
|
+
return [...previous, value];
|
|
9463
|
+
}
|
|
9464
|
+
function parseHttpDestination(value) {
|
|
9465
|
+
const trimmed = value.trim();
|
|
9466
|
+
try {
|
|
9467
|
+
const url = new URL(trimmed);
|
|
9468
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") throw new Error();
|
|
9469
|
+
} catch {
|
|
9470
|
+
throw new CliError(
|
|
9471
|
+
`Invalid redirect destination "${value}": expected an HTTP(S) URL.`,
|
|
9472
|
+
EXIT_CODES.validation
|
|
9473
|
+
);
|
|
9474
|
+
}
|
|
9475
|
+
return trimmed;
|
|
9476
|
+
}
|
|
9477
|
+
function sourceStoreUrl(sourceRules) {
|
|
9478
|
+
for (const rule of sourceRules) {
|
|
9479
|
+
if (rule.includes("*")) continue;
|
|
9480
|
+
try {
|
|
9481
|
+
const url = new URL(rule);
|
|
9482
|
+
if (url.protocol === "http:" || url.protocol === "https:") {
|
|
9483
|
+
return rule;
|
|
9484
|
+
}
|
|
9485
|
+
} catch {
|
|
9486
|
+
}
|
|
9487
|
+
}
|
|
9488
|
+
return null;
|
|
9489
|
+
}
|
|
9490
|
+
var create = new Command("create").description("Create a new experiment").requiredOption("--name <name>", "Experiment name").option("--slug <slug>", "URL-safe slug for GitOps").option("--description <desc>", "Description").option("--type <type>", "Experiment type: mutation or redirect", "mutation").option(
|
|
9491
|
+
"--source-url <pattern>",
|
|
9492
|
+
"Positive source URL rule (repeatable; accepts exact URLs, paths, or wildcards)",
|
|
9493
|
+
collectCreateOption,
|
|
9494
|
+
[]
|
|
9495
|
+
).option(
|
|
9496
|
+
"--destination <url>",
|
|
9497
|
+
"Redirect treatment destination (repeatable; creates one treatment per URL)",
|
|
9498
|
+
collectCreateOption,
|
|
9499
|
+
[]
|
|
9500
|
+
).option("--traffic <pct>", "Traffic allocation (0-100)", "100").action(async function action7(opts) {
|
|
9501
|
+
if (opts.type !== "mutation" && opts.type !== "redirect") {
|
|
9502
|
+
fail(
|
|
9503
|
+
this,
|
|
9504
|
+
"--type must be mutation or redirect",
|
|
9505
|
+
EXIT_CODES.validation
|
|
9506
|
+
);
|
|
9507
|
+
}
|
|
9508
|
+
const traffic = Number(opts.traffic);
|
|
9509
|
+
if (!Number.isFinite(traffic) || traffic < 0 || traffic > 100) {
|
|
9510
|
+
fail(
|
|
9511
|
+
this,
|
|
9512
|
+
"--traffic must be a number from 0 to 100",
|
|
9513
|
+
EXIT_CODES.validation
|
|
9514
|
+
);
|
|
9515
|
+
}
|
|
9516
|
+
const sourceRules = opts.sourceUrl.map((value) => value.trim());
|
|
9517
|
+
if (sourceRules.some((value) => value.length === 0)) {
|
|
9518
|
+
fail(this, "--source-url cannot be empty", EXIT_CODES.validation);
|
|
9519
|
+
}
|
|
9520
|
+
if (opts.type !== "redirect" && opts.destination.length > 0) {
|
|
9521
|
+
fail(
|
|
9522
|
+
this,
|
|
9523
|
+
"--destination is only valid with --type redirect",
|
|
9524
|
+
EXIT_CODES.validation
|
|
9525
|
+
);
|
|
9526
|
+
}
|
|
9527
|
+
if (opts.type === "redirect" && sourceRules.length === 0) {
|
|
9528
|
+
fail(
|
|
9529
|
+
this,
|
|
9530
|
+
"Redirect experiments require at least one --source-url rule",
|
|
9531
|
+
EXIT_CODES.validation
|
|
9532
|
+
);
|
|
9533
|
+
}
|
|
9534
|
+
if (opts.type === "redirect" && opts.destination.length === 0) {
|
|
9535
|
+
fail(
|
|
9536
|
+
this,
|
|
9537
|
+
"Redirect experiments require at least one --destination URL",
|
|
9538
|
+
EXIT_CODES.validation
|
|
9539
|
+
);
|
|
9540
|
+
}
|
|
9541
|
+
const destinations = opts.destination.map(parseHttpDestination);
|
|
9542
|
+
const armWeight = destinations.length > 0 ? 1 / (destinations.length + 1) : null;
|
|
9369
9543
|
const creds2 = requireCredentials();
|
|
9370
9544
|
const ora2 = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default;
|
|
9371
|
-
const spinner = ora2("Creating experiment...").start();
|
|
9545
|
+
const spinner = process.stderr.isTTY ? ora2("Creating experiment...").start() : null;
|
|
9372
9546
|
const res = await createExperiment(creds2, {
|
|
9373
9547
|
name: opts.name,
|
|
9374
9548
|
slug: opts.slug ?? null,
|
|
9375
9549
|
description: opts.description ?? null,
|
|
9376
|
-
|
|
9550
|
+
experiment_type: opts.type,
|
|
9551
|
+
traffic_allocation: traffic / 100,
|
|
9552
|
+
...sourceRules.length > 0 ? {
|
|
9553
|
+
store_url: sourceStoreUrl(sourceRules),
|
|
9554
|
+
targeting: {
|
|
9555
|
+
url: sourceRules.map((pattern) => ({
|
|
9556
|
+
type: "simple",
|
|
9557
|
+
pattern,
|
|
9558
|
+
include: true
|
|
9559
|
+
}))
|
|
9560
|
+
}
|
|
9561
|
+
} : {},
|
|
9562
|
+
...armWeight !== null ? {
|
|
9563
|
+
variations: [
|
|
9564
|
+
{
|
|
9565
|
+
name: "Control",
|
|
9566
|
+
weight: armWeight,
|
|
9567
|
+
is_control: true
|
|
9568
|
+
},
|
|
9569
|
+
...destinations.map((redirectUrl, index) => ({
|
|
9570
|
+
name: destinations.length === 1 ? "Treatment" : `Treatment ${index + 1}`,
|
|
9571
|
+
weight: armWeight,
|
|
9572
|
+
is_control: false,
|
|
9573
|
+
redirect_url: redirectUrl
|
|
9574
|
+
}))
|
|
9575
|
+
]
|
|
9576
|
+
} : {}
|
|
9377
9577
|
});
|
|
9378
|
-
if (!res.ok) {
|
|
9379
|
-
spinner
|
|
9380
|
-
|
|
9381
|
-
|
|
9578
|
+
if (!res.ok || !res.data) {
|
|
9579
|
+
spinner?.fail("Failed to create experiment");
|
|
9580
|
+
fail(
|
|
9581
|
+
this,
|
|
9582
|
+
res.error ?? "Failed to create experiment",
|
|
9583
|
+
classifyStatus(res.status),
|
|
9584
|
+
res.errorDetails
|
|
9585
|
+
);
|
|
9382
9586
|
}
|
|
9383
|
-
spinner
|
|
9384
|
-
printExperiment(res.data);
|
|
9587
|
+
spinner?.succeed("Experiment created");
|
|
9588
|
+
output(this, { experiment: res.data }, () => printExperiment(res.data));
|
|
9385
9589
|
});
|
|
9386
9590
|
var start = new Command("start").description("Start an experiment").argument("<id-or-slug>", "Experiment ID or slug").action(async (idOrSlug) => {
|
|
9387
9591
|
const creds2 = requireCredentials();
|
|
@@ -9436,7 +9640,7 @@ var complete = new Command("complete").description("Complete/stop an experiment"
|
|
|
9436
9640
|
`Experiment "${exp.name}" is now ${statusBadge("completed")}`
|
|
9437
9641
|
);
|
|
9438
9642
|
});
|
|
9439
|
-
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
|
|
9643
|
+
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 action8(idOrSlug, opts) {
|
|
9440
9644
|
const creds2 = requireCredentials();
|
|
9441
9645
|
const ora2 = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default;
|
|
9442
9646
|
const exp = await resolveExperiment(idOrSlug);
|
|
@@ -9490,7 +9694,6 @@ var pullFilesystem = new Command("pull").description("Pull an experiment as an e
|
|
|
9490
9694
|
const res = await getExperimentFilesystem(creds2, exp.id);
|
|
9491
9695
|
if (!res.ok || !res.data) throw new Error(res.error ?? "Failed to pull experiment filesystem");
|
|
9492
9696
|
const folder = folderPath(opts.dir ?? res.data.rootName);
|
|
9493
|
-
import_node_fs6.default.mkdirSync(folder, { recursive: true });
|
|
9494
9697
|
writeExperimentFilesystemFiles(folder, res.data.files);
|
|
9495
9698
|
writeFilesystemSession(folder, {
|
|
9496
9699
|
schemaVersion: 1,
|
|
@@ -9509,7 +9712,7 @@ var pullFilesystem = new Command("pull").description("Pull an experiment as an e
|
|
|
9509
9712
|
process.exit(1);
|
|
9510
9713
|
}
|
|
9511
9714
|
});
|
|
9512
|
-
var planFilesystem = new Command("plan").description("Plan experiment filesystem changes").argument("[folder]", "Experiment filesystem folder", ".").action(async function
|
|
9715
|
+
var planFilesystem = new Command("plan").description("Plan experiment filesystem changes").argument("[folder]", "Experiment filesystem folder", ".").action(async function action9(folderArg) {
|
|
9513
9716
|
const creds2 = requireCredentials();
|
|
9514
9717
|
try {
|
|
9515
9718
|
const folder = folderPath(folderArg);
|
|
@@ -9531,7 +9734,7 @@ var planFilesystem = new Command("plan").description("Plan experiment filesystem
|
|
|
9531
9734
|
fail(this, error instanceof Error ? error.message : String(error));
|
|
9532
9735
|
}
|
|
9533
9736
|
});
|
|
9534
|
-
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
|
|
9737
|
+
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 action10(folderArg, opts) {
|
|
9535
9738
|
const creds2 = requireCredentials();
|
|
9536
9739
|
const ora2 = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default;
|
|
9537
9740
|
try {
|
|
@@ -9811,7 +10014,7 @@ function isRecord(value) {
|
|
|
9811
10014
|
}
|
|
9812
10015
|
var pushCommand = new Command("push").description(
|
|
9813
10016
|
"Push experiment definitions from config file to the API (idempotent by slug)"
|
|
9814
|
-
).option("-c, --config <path>", "Config file path").option("--dry-run", "Show what would be pushed without making changes").action(async function
|
|
10017
|
+
).option("-c, --config <path>", "Config file path").option("--dry-run", "Show what would be pushed without making changes").action(async function action11(opts) {
|
|
9815
10018
|
const creds2 = requireCredentials();
|
|
9816
10019
|
const ora2 = (await Promise.resolve().then(() => (init_ora(), ora_exports))).default;
|
|
9817
10020
|
const configPath = opts.config ?? findConfigPath();
|
|
@@ -10237,12 +10440,12 @@ harnessCommand.command("publish").argument("<folder>", "AB Test Harness experime
|
|
|
10237
10440
|
|
|
10238
10441
|
// src/commands/operator.ts
|
|
10239
10442
|
init_source();
|
|
10240
|
-
var
|
|
10443
|
+
var import_node_crypto4 = __toESM(require("node:crypto"));
|
|
10241
10444
|
var import_node_fs9 = __toESM(require("node:fs"));
|
|
10242
10445
|
var import_node_path7 = __toESM(require("node:path"));
|
|
10243
10446
|
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)$/;
|
|
10244
10447
|
function sha256Text2(value) {
|
|
10245
|
-
return
|
|
10448
|
+
return import_node_crypto4.default.createHash("sha256").update(value).digest("hex");
|
|
10246
10449
|
}
|
|
10247
10450
|
function byteLength(value) {
|
|
10248
10451
|
return Buffer.byteLength(value, "utf8");
|
|
@@ -10520,7 +10723,7 @@ operatorCommand.command("checkout").argument("<experiment-id>", "Apex experiment
|
|
|
10520
10723
|
process.exit(1);
|
|
10521
10724
|
}
|
|
10522
10725
|
});
|
|
10523
|
-
operatorCommand.command("status").argument("[folder]", "Operator workspace folder", ".").action(function
|
|
10726
|
+
operatorCommand.command("status").argument("[folder]", "Operator workspace folder", ".").action(function action12(folderArg) {
|
|
10524
10727
|
try {
|
|
10525
10728
|
const folder = folderPath2(folderArg);
|
|
10526
10729
|
const session = readSession(folder);
|
|
@@ -10901,7 +11104,7 @@ trackingCommand.command("qa").description("Print the QA checklist for a tracking
|
|
|
10901
11104
|
console.log(`${index + 1}. ${check}`);
|
|
10902
11105
|
});
|
|
10903
11106
|
});
|
|
10904
|
-
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
|
|
11107
|
+
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 action13(opts) {
|
|
10905
11108
|
const creds2 = requireCredentials();
|
|
10906
11109
|
const res = await verifyTracking(creds2, {
|
|
10907
11110
|
minutes: Number.parseInt(opts.minutes, 10),
|
|
@@ -10969,7 +11172,7 @@ function printGoal(goal) {
|
|
|
10969
11172
|
console.log(` Count once: ${goal.count_once ? "yes" : "no"}`);
|
|
10970
11173
|
console.log();
|
|
10971
11174
|
}
|
|
10972
|
-
var list2 = new Command("list").alias("ls").description("List goals").option("-n, --limit <n>", "Max results", "50").action(async function
|
|
11175
|
+
var list2 = new Command("list").alias("ls").description("List goals").option("-n, --limit <n>", "Max results", "50").action(async function action14(opts) {
|
|
10973
11176
|
const creds2 = requireCredentials();
|
|
10974
11177
|
const res = await listGoals(creds2, { limit: Number.parseInt(opts.limit, 10) });
|
|
10975
11178
|
if (!res.ok || !res.data) {
|
|
@@ -10991,7 +11194,7 @@ var list2 = new Command("list").alias("ls").description("List goals").option("-n
|
|
|
10991
11194
|
);
|
|
10992
11195
|
});
|
|
10993
11196
|
});
|
|
10994
|
-
var create2 = new Command("create").description("Create a goal").requiredOption("--name <name>", "Goal name").requiredOption("--type <type>", "click|pageview|custom|revenue").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
|
|
11197
|
+
var create2 = new Command("create").description("Create a goal").requiredOption("--name <name>", "Goal name").requiredOption("--type <type>", "click|pageview|custom|revenue").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 action15(opts) {
|
|
10995
11198
|
const creds2 = requireCredentials();
|
|
10996
11199
|
const body5 = cleanBody({
|
|
10997
11200
|
name: opts.name,
|
|
@@ -11009,7 +11212,7 @@ var create2 = new Command("create").description("Create a goal").requiredOption(
|
|
|
11009
11212
|
}
|
|
11010
11213
|
output(this, { goal: res.data }, () => printGoal(res.data));
|
|
11011
11214
|
});
|
|
11012
|
-
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("--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
|
|
11215
|
+
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("--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 action16(id, opts) {
|
|
11013
11216
|
const creds2 = requireCredentials();
|
|
11014
11217
|
const body5 = cleanBody({
|
|
11015
11218
|
name: opts.name,
|
|
@@ -11083,11 +11286,11 @@ async function load2(command) {
|
|
|
11083
11286
|
}
|
|
11084
11287
|
return result.data;
|
|
11085
11288
|
}
|
|
11086
|
-
var list3 = new Command("list").alias("ls").description("List runtime environments and mapped domains").action(async function
|
|
11289
|
+
var list3 = new Command("list").alias("ls").description("List runtime environments and mapped domains").action(async function action17() {
|
|
11087
11290
|
const { environments } = await load2(this);
|
|
11088
11291
|
output(this, { environments }, () => printEnvironments(environments.items));
|
|
11089
11292
|
});
|
|
11090
|
-
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
|
|
11293
|
+
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 action18(domain, opts) {
|
|
11091
11294
|
const normalizedDomain = normalizeEnvironmentDomain(domain);
|
|
11092
11295
|
if (!normalizedDomain) {
|
|
11093
11296
|
fail(this, "Domain must be a valid hostname containing a dot", EXIT_CODES.validation);
|
|
@@ -11130,7 +11333,7 @@ var setDomain = new Command("set-domain").description("Map a domain to a runtime
|
|
|
11130
11333
|
() => printEnvironments(result.data.environments.items)
|
|
11131
11334
|
);
|
|
11132
11335
|
});
|
|
11133
|
-
var removeDomain = new Command("remove-domain").description("Remove a domain mapping").argument("<domain>", "Domain to remove").action(async function
|
|
11336
|
+
var removeDomain = new Command("remove-domain").description("Remove a domain mapping").argument("<domain>", "Domain to remove").action(async function action19(domain) {
|
|
11134
11337
|
const normalizedDomain = normalizeEnvironmentDomain(domain);
|
|
11135
11338
|
if (!normalizedDomain) {
|
|
11136
11339
|
fail(this, "Domain must be a valid hostname containing a dot", EXIT_CODES.validation);
|
|
@@ -11198,7 +11401,7 @@ function analyticsSubcommand(name, description, defaultDays) {
|
|
|
11198
11401
|
command.option("--planning-days <n>", "Planning window in days", "180");
|
|
11199
11402
|
command.option("--limit <n>", "Maximum page rows", "50");
|
|
11200
11403
|
}
|
|
11201
|
-
return command.action(async function
|
|
11404
|
+
return command.action(async function action25(opts) {
|
|
11202
11405
|
const result = await getAnalytics(requireCredentials(), name, {
|
|
11203
11406
|
days: positiveInteger(this, opts.days, "--days"),
|
|
11204
11407
|
planningDays: positiveInteger(this, opts.planningDays, "--planning-days"),
|
|
@@ -11385,18 +11588,18 @@ function write(name) {
|
|
|
11385
11588
|
});
|
|
11386
11589
|
return c;
|
|
11387
11590
|
}
|
|
11388
|
-
function lifecycle(
|
|
11389
|
-
return new Command(
|
|
11591
|
+
function lifecycle(action25) {
|
|
11592
|
+
return new Command(action25).description(`${action25[0].toUpperCase()}${action25.slice(1)} a flag`).argument("<id>").action(async function(id) {
|
|
11390
11593
|
const r = await transitionManagedDelivery(
|
|
11391
11594
|
requireCredentials(),
|
|
11392
11595
|
kind,
|
|
11393
11596
|
id,
|
|
11394
|
-
|
|
11597
|
+
action25
|
|
11395
11598
|
);
|
|
11396
11599
|
if (!r.ok || !r.data)
|
|
11397
11600
|
fail(
|
|
11398
11601
|
this,
|
|
11399
|
-
r.error ?? `Failed to ${
|
|
11602
|
+
r.error ?? `Failed to ${action25}`,
|
|
11400
11603
|
classifyStatus(r.status),
|
|
11401
11604
|
r.errorDetails
|
|
11402
11605
|
);
|
|
@@ -11440,18 +11643,18 @@ var get3 = new Command("get").description("Get a backlog item").argument("<id>",
|
|
|
11440
11643
|
if (!r.ok || !r.data) fail(this, r.error ?? "Failed to get", classifyStatus(r.status), r.errorDetails);
|
|
11441
11644
|
output(this, { backlog_item: r.data }, () => detail2(r.data));
|
|
11442
11645
|
});
|
|
11443
|
-
function write2(
|
|
11444
|
-
const c = new Command(
|
|
11445
|
-
if (
|
|
11646
|
+
function write2(action25) {
|
|
11647
|
+
const c = new Command(action25).description(`${action25 === "create" ? "Create" : "Update"} a ${singular}`);
|
|
11648
|
+
if (action25 === "update") c.argument("<id>", `${singular} ID`);
|
|
11446
11649
|
c.argument("[json]", "Inline JSON, @file, or file path").option("--file <path>", "JSON body file");
|
|
11447
11650
|
c.action(async function(...args) {
|
|
11448
11651
|
const o = this.opts();
|
|
11449
|
-
const id =
|
|
11450
|
-
const raw = o.file ? `@${o.file}` : String(args[
|
|
11652
|
+
const id = action25 === "update" ? String(args[0]) : void 0;
|
|
11653
|
+
const raw = o.file ? `@${o.file}` : String(args[action25 === "update" ? 1 : 0] ?? "") || void 0;
|
|
11451
11654
|
const body5 = parse(this, raw);
|
|
11452
11655
|
if (!Object.keys(body5).length) fail(this, "Provide a JSON body.", EXIT_CODES.validation);
|
|
11453
|
-
const r =
|
|
11454
|
-
if (!r.ok || !r.data) fail(this, r.error ?? `Failed to ${
|
|
11656
|
+
const r = action25 === "create" ? await createInsight(requireCredentials(), kind2, body5) : await updateInsight(requireCredentials(), kind2, id, body5);
|
|
11657
|
+
if (!r.ok || !r.data) fail(this, r.error ?? `Failed to ${action25}`, classifyStatus(r.status), r.errorDetails);
|
|
11455
11658
|
output(this, { backlog_item: r.data }, () => detail2(r.data));
|
|
11456
11659
|
});
|
|
11457
11660
|
return c;
|
|
@@ -11520,18 +11723,18 @@ var get4 = new Command("get").description("Get a signal").argument("<id>", "sign
|
|
|
11520
11723
|
if (!r.ok || !r.data) fail(this, r.error ?? "Failed to get", classifyStatus(r.status), r.errorDetails);
|
|
11521
11724
|
output(this, { signal: r.data }, () => detail3(r.data));
|
|
11522
11725
|
});
|
|
11523
|
-
function write3(
|
|
11524
|
-
const c = new Command(
|
|
11525
|
-
if (
|
|
11726
|
+
function write3(action25) {
|
|
11727
|
+
const c = new Command(action25).description(`${action25 === "create" ? "Create" : "Update"} a ${singular2}`);
|
|
11728
|
+
if (action25 === "update") c.argument("<id>", `${singular2} ID`);
|
|
11526
11729
|
c.argument("[json]", "Inline JSON, @file, or file path").option("--file <path>", "JSON body file");
|
|
11527
11730
|
c.action(async function(...args) {
|
|
11528
11731
|
const o = this.opts();
|
|
11529
|
-
const id =
|
|
11530
|
-
const raw = o.file ? `@${o.file}` : String(args[
|
|
11732
|
+
const id = action25 === "update" ? String(args[0]) : void 0;
|
|
11733
|
+
const raw = o.file ? `@${o.file}` : String(args[action25 === "update" ? 1 : 0] ?? "") || void 0;
|
|
11531
11734
|
const body5 = parse2(this, raw);
|
|
11532
11735
|
if (!Object.keys(body5).length) fail(this, "Provide a JSON body.", EXIT_CODES.validation);
|
|
11533
|
-
const r =
|
|
11534
|
-
if (!r.ok || !r.data) fail(this, r.error ?? `Failed to ${
|
|
11736
|
+
const r = action25 === "create" ? await createInsight(requireCredentials(), kind3, body5) : await updateInsight(requireCredentials(), kind3, id, body5);
|
|
11737
|
+
if (!r.ok || !r.data) fail(this, r.error ?? `Failed to ${action25}`, classifyStatus(r.status), r.errorDetails);
|
|
11535
11738
|
output(this, { signal: r.data }, () => detail3(r.data));
|
|
11536
11739
|
});
|
|
11537
11740
|
return c;
|
|
@@ -11649,20 +11852,20 @@ function write4(name) {
|
|
|
11649
11852
|
});
|
|
11650
11853
|
return c;
|
|
11651
11854
|
}
|
|
11652
|
-
function lifecycle2(
|
|
11653
|
-
return new Command(
|
|
11654
|
-
`${
|
|
11855
|
+
function lifecycle2(action25) {
|
|
11856
|
+
return new Command(action25).description(
|
|
11857
|
+
`${action25[0].toUpperCase()}${action25.slice(1)} a personalization`
|
|
11655
11858
|
).argument("<id>").action(async function(id) {
|
|
11656
11859
|
const r = await transitionManagedDelivery(
|
|
11657
11860
|
requireCredentials(),
|
|
11658
11861
|
kind4,
|
|
11659
11862
|
id,
|
|
11660
|
-
|
|
11863
|
+
action25
|
|
11661
11864
|
);
|
|
11662
11865
|
if (!r.ok || !r.data)
|
|
11663
11866
|
fail(
|
|
11664
11867
|
this,
|
|
11665
|
-
r.error ?? `Failed to ${
|
|
11868
|
+
r.error ?? `Failed to ${action25}`,
|
|
11666
11869
|
classifyStatus(r.status),
|
|
11667
11870
|
r.errorDetails
|
|
11668
11871
|
);
|
|
@@ -11758,17 +11961,17 @@ function targetingResourceCommand(kind5) {
|
|
|
11758
11961
|
);
|
|
11759
11962
|
})
|
|
11760
11963
|
);
|
|
11761
|
-
for (const
|
|
11762
|
-
const command = new Command(
|
|
11763
|
-
`${
|
|
11964
|
+
for (const action25 of ["create", "update"]) {
|
|
11965
|
+
const command = new Command(action25).description(
|
|
11966
|
+
`${action25 === "create" ? "Create" : "Update"} a ${singular3}`
|
|
11764
11967
|
);
|
|
11765
|
-
if (
|
|
11968
|
+
if (action25 === "update") command.argument("<id>");
|
|
11766
11969
|
command.argument("[json]", "Inline JSON, @file, or file path").option("--file <path>", "JSON body file").action(async function(...args) {
|
|
11767
11970
|
const opts = this.opts();
|
|
11768
|
-
const id =
|
|
11769
|
-
const value = opts.file ? `@${opts.file}` : String(args[
|
|
11971
|
+
const id = action25 === "update" ? String(args[0]) : void 0;
|
|
11972
|
+
const value = opts.file ? `@${opts.file}` : String(args[action25 === "update" ? 1 : 0] ?? "") || void 0;
|
|
11770
11973
|
const requestBody = body3(this, value);
|
|
11771
|
-
if (
|
|
11974
|
+
if (action25 === "update" && typeof requestBody === "object" && requestBody !== null && !Array.isArray(requestBody) && Object.keys(requestBody).length === 0)
|
|
11772
11975
|
fail(
|
|
11773
11976
|
this,
|
|
11774
11977
|
`Updating a ${singular3} requires at least one field.`,
|
|
@@ -11776,7 +11979,7 @@ function targetingResourceCommand(kind5) {
|
|
|
11776
11979
|
);
|
|
11777
11980
|
const response = await requestTargetingResource(
|
|
11778
11981
|
requireCredentials(),
|
|
11779
|
-
|
|
11982
|
+
action25 === "create" ? "POST" : "PATCH",
|
|
11780
11983
|
kind5,
|
|
11781
11984
|
id,
|
|
11782
11985
|
requestBody
|
|
@@ -11784,7 +11987,7 @@ function targetingResourceCommand(kind5) {
|
|
|
11784
11987
|
if (!response.ok || !response.data)
|
|
11785
11988
|
fail(
|
|
11786
11989
|
this,
|
|
11787
|
-
response.error ?? `Failed to ${
|
|
11990
|
+
response.error ?? `Failed to ${action25}`,
|
|
11788
11991
|
classifyStatus(response.status),
|
|
11789
11992
|
response.errorDetails
|
|
11790
11993
|
);
|
|
@@ -11793,7 +11996,7 @@ function targetingResourceCommand(kind5) {
|
|
|
11793
11996
|
{
|
|
11794
11997
|
[kind5 === "exclusion-groups" ? "exclusion_group" : singular3]: response.data
|
|
11795
11998
|
},
|
|
11796
|
-
() => console.log(source_default.green(`${
|
|
11999
|
+
() => console.log(source_default.green(`${action25}d ${singular3}`))
|
|
11797
12000
|
);
|
|
11798
12001
|
});
|
|
11799
12002
|
root.addCommand(command);
|
|
@@ -11880,7 +12083,7 @@ var exclusionGroupsCommand = targetingResourceCommand("exclusion-groups");
|
|
|
11880
12083
|
// src/commands/brand.ts
|
|
11881
12084
|
init_source();
|
|
11882
12085
|
var NO_PROFILE_MESSAGE = "No brand profile yet \u2014 run onboarding or a site check";
|
|
11883
|
-
var brandCommand = new Command("brand").description("Print the shop brand context for external agents").action(async function
|
|
12086
|
+
var brandCommand = new Command("brand").description("Print the shop brand context for external agents").action(async function action20() {
|
|
11884
12087
|
const creds2 = requireCredentials();
|
|
11885
12088
|
const res = await getBrandContext(creds2);
|
|
11886
12089
|
if (res.status === 404) {
|
|
@@ -11913,7 +12116,7 @@ var brandCommand = new Command("brand").description("Print the shop brand contex
|
|
|
11913
12116
|
|
|
11914
12117
|
// src/commands/targeting.ts
|
|
11915
12118
|
init_source();
|
|
11916
|
-
var test = 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
|
|
12119
|
+
var test = 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 action21(url, opts) {
|
|
11917
12120
|
const creds2 = requireCredentials();
|
|
11918
12121
|
const res = await testTargeting(creds2, url, { status: opts.status });
|
|
11919
12122
|
if (!res.ok || !res.data) {
|
|
@@ -11970,7 +12173,7 @@ function printTicket(ticket, reused) {
|
|
|
11970
12173
|
console.log(` Status: ${ticket.my_status ?? ticket.mapped_status}`);
|
|
11971
12174
|
console.log();
|
|
11972
12175
|
}
|
|
11973
|
-
var create3 = 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
|
|
12176
|
+
var create3 = 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 action22(descriptionArg, opts) {
|
|
11974
12177
|
if (!SOS_TYPES.has(opts.type)) {
|
|
11975
12178
|
fail(this, "Invalid SOS type. Use full_handover, design, development, qa, config, or bug.", 3);
|
|
11976
12179
|
}
|
|
@@ -11988,7 +12191,7 @@ var create3 = new Command("create").description("Create an Apex SOS ticket").opt
|
|
|
11988
12191
|
}
|
|
11989
12192
|
output(this, res.data, () => printTicket(res.data.ticket, res.data.reused));
|
|
11990
12193
|
});
|
|
11991
|
-
var mine = new Command("mine").description("List SOS tickets created by this API key").action(async function
|
|
12194
|
+
var mine = new Command("mine").description("List SOS tickets created by this API key").action(async function action23() {
|
|
11992
12195
|
const creds2 = requireCredentials();
|
|
11993
12196
|
const res = await listMySos(creds2);
|
|
11994
12197
|
if (!res.ok || !res.data) {
|
|
@@ -12110,10 +12313,19 @@ var create4 = new Command("create").description("Create a shop").argument("[json
|
|
|
12110
12313
|
const r = await createShop(requireUserCredentials(), payload);
|
|
12111
12314
|
done(this, r, "create shop");
|
|
12112
12315
|
});
|
|
12113
|
-
var update2 = new Command("update").description("Update a shop").argument("<id>").argument("
|
|
12114
|
-
const b = body4(this, raw);
|
|
12316
|
+
var update2 = new Command("update").description("Update a shop").argument("<id>").argument("[json]", "Inline JSON, @file, or file path").option("--holdout-pct <percent>", "Global holdout percent (0-10)").option("--holdout-enabled", "Enable global holdout v1").option("--holdout-disabled", "Disable global holdout v1").option("--confirm-tier", "Confirm a commercially sensitive tier change").action(async function(id, raw, o) {
|
|
12317
|
+
const b = raw === void 0 ? {} : body4(this, raw);
|
|
12318
|
+
if (o.holdoutEnabled && o.holdoutDisabled)
|
|
12319
|
+
fail(this, "Use only one of --holdout-enabled or --holdout-disabled", EXIT_CODES.validation);
|
|
12320
|
+
if (o.holdoutPct !== void 0) {
|
|
12321
|
+
const pct = Number(o.holdoutPct);
|
|
12322
|
+
if (!Number.isFinite(pct) || pct < 0 || pct > 10)
|
|
12323
|
+
fail(this, "--holdout-pct must be between 0 and 10", EXIT_CODES.validation);
|
|
12324
|
+
b.holdout_pct = pct;
|
|
12325
|
+
}
|
|
12326
|
+
if (o.holdoutEnabled || o.holdoutDisabled) b.holdout_v1_enabled = Boolean(o.holdoutEnabled);
|
|
12115
12327
|
if (!Object.keys(b).length)
|
|
12116
|
-
fail(this, "Provide a JSON body.", EXIT_CODES.validation);
|
|
12328
|
+
fail(this, "Provide a JSON body or holdout flags.", EXIT_CODES.validation);
|
|
12117
12329
|
if (Object.hasOwn(b, "tier") && !o.confirmTier)
|
|
12118
12330
|
fail(
|
|
12119
12331
|
this,
|
|
@@ -12274,7 +12486,7 @@ function commandSchema(command) {
|
|
|
12274
12486
|
};
|
|
12275
12487
|
}
|
|
12276
12488
|
function createSchemaCommand(program3) {
|
|
12277
|
-
return new Command("schema").description("Print the current Apex CLI command and flag schema").option("--json", "Print JSON schema").action(function
|
|
12489
|
+
return new Command("schema").description("Print the current Apex CLI command and flag schema").option("--json", "Print JSON schema").action(function action25() {
|
|
12278
12490
|
const schema = commandSchema(program3);
|
|
12279
12491
|
output(this, schema, () => {
|
|
12280
12492
|
console.log(JSON.stringify(schema, null, 2));
|
|
@@ -12318,7 +12530,7 @@ function installSkill(paths, targetRoot) {
|
|
|
12318
12530
|
import_node_fs20.default.copyFileSync(paths.agents, import_node_path18.default.join(target, "AGENTS.md"));
|
|
12319
12531
|
return target;
|
|
12320
12532
|
}
|
|
12321
|
-
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
|
|
12533
|
+
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 action24(opts) {
|
|
12322
12534
|
let paths;
|
|
12323
12535
|
try {
|
|
12324
12536
|
paths = skillPaths();
|