@drip-apex/apex-cli 0.6.3 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +9 -1
- package/README.md +44 -1
- package/dist/cli.js +937 -263
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -6158,16 +6158,16 @@ __export(ora_exports, {
|
|
|
6158
6158
|
function ora(options) {
|
|
6159
6159
|
return new Ora(options);
|
|
6160
6160
|
}
|
|
6161
|
-
async function oraPromise(
|
|
6162
|
-
const actionIsFunction = typeof
|
|
6163
|
-
const actionIsPromise = typeof
|
|
6161
|
+
async function oraPromise(action74, options) {
|
|
6162
|
+
const actionIsFunction = typeof action74 === "function";
|
|
6163
|
+
const actionIsPromise = typeof action74.then === "function";
|
|
6164
6164
|
if (!actionIsFunction && !actionIsPromise) {
|
|
6165
6165
|
throw new TypeError("Parameter `action` must be a Function or a Promise");
|
|
6166
6166
|
}
|
|
6167
6167
|
const { successText, failText } = typeof options === "object" ? options : { successText: void 0, failText: void 0 };
|
|
6168
6168
|
const spinner = ora(options).start();
|
|
6169
6169
|
try {
|
|
6170
|
-
const promise = actionIsFunction ?
|
|
6170
|
+
const promise = actionIsFunction ? action74(spinner) : action74;
|
|
6171
6171
|
const result = await promise;
|
|
6172
6172
|
spinner.succeed(
|
|
6173
6173
|
successText === void 0 ? void 0 : typeof successText === "string" ? successText : successText(result)
|
|
@@ -7112,7 +7112,7 @@ var import_node_os3 = __toESM(require("node:os"));
|
|
|
7112
7112
|
// package.json
|
|
7113
7113
|
var package_default = {
|
|
7114
7114
|
name: "@drip-apex/apex-cli",
|
|
7115
|
-
version: "0.
|
|
7115
|
+
version: "0.7.0",
|
|
7116
7116
|
license: "MIT",
|
|
7117
7117
|
description: "Apex CLI for managing experiments, approved roadmap ideas, and draft workspaces.",
|
|
7118
7118
|
private: false,
|
|
@@ -7754,6 +7754,15 @@ function upsertExperimentBySlug(creds, slug, body5) {
|
|
|
7754
7754
|
function createExperiment(creds, body5) {
|
|
7755
7755
|
return request(creds, "POST", "/api/v1/experiments", body5);
|
|
7756
7756
|
}
|
|
7757
|
+
function updateExperiment(creds, id, body5) {
|
|
7758
|
+
return request(creds, "PATCH", `/api/v1/experiments/${encodeURIComponent(id)}`, body5);
|
|
7759
|
+
}
|
|
7760
|
+
function attachExperimentIdea(creds, id, body5) {
|
|
7761
|
+
return request(creds, "POST", `/api/v1/experiments/${encodeURIComponent(id)}/idea`, body5);
|
|
7762
|
+
}
|
|
7763
|
+
function detachExperimentIdea(creds, id) {
|
|
7764
|
+
return request(creds, "DELETE", `/api/v1/experiments/${encodeURIComponent(id)}/idea`);
|
|
7765
|
+
}
|
|
7757
7766
|
function pauseExperiment(creds, id) {
|
|
7758
7767
|
return request(creds, "POST", `/api/v1/experiments/${id}/pause`);
|
|
7759
7768
|
}
|
|
@@ -7781,7 +7790,13 @@ function createExperimentScreenshots(creds, id, body5) {
|
|
|
7781
7790
|
}
|
|
7782
7791
|
async function getScreenshotJob(creds, jobId, timeoutMs) {
|
|
7783
7792
|
await testScreenshotDelay?.(timeoutMs);
|
|
7784
|
-
return request(
|
|
7793
|
+
return request(
|
|
7794
|
+
creds,
|
|
7795
|
+
"GET",
|
|
7796
|
+
`/api/v1/screenshots/${encodeURIComponent(jobId)}`,
|
|
7797
|
+
void 0,
|
|
7798
|
+
timeoutMs
|
|
7799
|
+
);
|
|
7785
7800
|
}
|
|
7786
7801
|
function planExperimentFilesystem(creds, id, body5) {
|
|
7787
7802
|
return request(
|
|
@@ -8122,8 +8137,8 @@ function requestTargetingResource(creds, method, kind5, id, body5, limit, cursor
|
|
|
8122
8137
|
const path24 = `/api/v1/${kind5}${id ? `/${id.split("/").map(encodeURIComponent).join("/")}` : ""}${query ? `?${query}` : ""}`;
|
|
8123
8138
|
return request(creds, method, path24, body5);
|
|
8124
8139
|
}
|
|
8125
|
-
function managedPath(kind5, id,
|
|
8126
|
-
return `/api/v1/${kind5}${id ? `/${encodeURIComponent(id)}` : ""}${
|
|
8140
|
+
function managedPath(kind5, id, action74) {
|
|
8141
|
+
return `/api/v1/${kind5}${id ? `/${encodeURIComponent(id)}` : ""}${action74 ? `/${action74}` : ""}`;
|
|
8127
8142
|
}
|
|
8128
8143
|
function listManagedDeliveries(creds, kind5, limit) {
|
|
8129
8144
|
const q = limit ? `?limit=${limit}` : "";
|
|
@@ -8141,8 +8156,8 @@ function createManagedDelivery(creds, kind5, body5) {
|
|
|
8141
8156
|
function updateManagedDelivery(creds, kind5, id, body5) {
|
|
8142
8157
|
return request(creds, "PATCH", managedPath(kind5, id), body5);
|
|
8143
8158
|
}
|
|
8144
|
-
function transitionManagedDelivery(creds, kind5, id,
|
|
8145
|
-
return request(creds, "POST", managedPath(kind5, id,
|
|
8159
|
+
function transitionManagedDelivery(creds, kind5, id, action74) {
|
|
8160
|
+
return request(creds, "POST", managedPath(kind5, id, action74));
|
|
8146
8161
|
}
|
|
8147
8162
|
function listConfigResources(creds, family, opts) {
|
|
8148
8163
|
const params = new URLSearchParams();
|
|
@@ -8301,6 +8316,28 @@ function restoreBacklogInsight(creds, id) {
|
|
|
8301
8316
|
function deleteInsight(creds, kind5, id) {
|
|
8302
8317
|
return request(creds, "DELETE", insightPath(kind5, id));
|
|
8303
8318
|
}
|
|
8319
|
+
function listBacklogFields(creds, includeArchived) {
|
|
8320
|
+
const query = includeArchived ? "?include_archived=true" : "";
|
|
8321
|
+
return request(creds, "GET", `/api/v1/backlog/fields${query}`);
|
|
8322
|
+
}
|
|
8323
|
+
function createBacklogField(creds, body5) {
|
|
8324
|
+
return request(creds, "POST", "/api/v1/backlog/fields", body5);
|
|
8325
|
+
}
|
|
8326
|
+
function updateBacklogField(creds, id, body5) {
|
|
8327
|
+
return request(
|
|
8328
|
+
creds,
|
|
8329
|
+
"PATCH",
|
|
8330
|
+
`/api/v1/backlog/fields/${encodeURIComponent(id)}`,
|
|
8331
|
+
body5
|
|
8332
|
+
);
|
|
8333
|
+
}
|
|
8334
|
+
function archiveBacklogField(creds, id) {
|
|
8335
|
+
return request(
|
|
8336
|
+
creds,
|
|
8337
|
+
"DELETE",
|
|
8338
|
+
`/api/v1/backlog/fields/${encodeURIComponent(id)}`
|
|
8339
|
+
);
|
|
8340
|
+
}
|
|
8304
8341
|
var listShops = (c) => userRequest(c, "GET", "/api/v1/shops");
|
|
8305
8342
|
var getShop = (c, id) => userRequest(c, "GET", `/api/v1/shops/${id}`);
|
|
8306
8343
|
var createShop = (c, body5) => userRequest(c, "POST", "/api/v1/shops", body5);
|
|
@@ -8835,6 +8872,34 @@ var import_node_crypto4 = __toESM(require("node:crypto"));
|
|
|
8835
8872
|
var import_node_fs7 = __toESM(require("node:fs"));
|
|
8836
8873
|
var import_node_path5 = __toESM(require("node:path"));
|
|
8837
8874
|
|
|
8875
|
+
// src/lib/qa-vocabulary.ts
|
|
8876
|
+
var checklistStatuses = [
|
|
8877
|
+
"pending",
|
|
8878
|
+
"pass",
|
|
8879
|
+
"warning",
|
|
8880
|
+
"fail",
|
|
8881
|
+
"accepted",
|
|
8882
|
+
"skipped"
|
|
8883
|
+
];
|
|
8884
|
+
var evidenceStatuses = [
|
|
8885
|
+
"captured",
|
|
8886
|
+
"passed",
|
|
8887
|
+
"warning",
|
|
8888
|
+
"failed",
|
|
8889
|
+
"pending",
|
|
8890
|
+
"not_run"
|
|
8891
|
+
];
|
|
8892
|
+
var evidenceKinds = [
|
|
8893
|
+
"screenshot",
|
|
8894
|
+
"goal",
|
|
8895
|
+
"summary",
|
|
8896
|
+
"launch_capture",
|
|
8897
|
+
"selector_audit",
|
|
8898
|
+
"console_network",
|
|
8899
|
+
"qa_audit",
|
|
8900
|
+
"readiness"
|
|
8901
|
+
];
|
|
8902
|
+
|
|
8838
8903
|
// ../api-client/src/qa-preview-url.ts
|
|
8839
8904
|
var FORCE_ALIAS_PARAM_NAMES = ["drip_experiment_id", "drip_experiment", "drip_variation_id", "drip_variation"];
|
|
8840
8905
|
function applyQaPreviewParams(url, params) {
|
|
@@ -8872,6 +8937,92 @@ function buildQaPreviewUrl2(storeUrl, experimentId, variationId) {
|
|
|
8872
8937
|
return url;
|
|
8873
8938
|
}
|
|
8874
8939
|
|
|
8940
|
+
// src/lib/effective-page-targeting.ts
|
|
8941
|
+
var ALL_PAGES_LABEL = "All pages";
|
|
8942
|
+
var CHECKOUT_LABEL = "Checkout";
|
|
8943
|
+
var UNKNOWN_PAGE_NAME = "Saved Page";
|
|
8944
|
+
function isRecord(value) {
|
|
8945
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
8946
|
+
}
|
|
8947
|
+
function arrayOf(value) {
|
|
8948
|
+
if (Array.isArray(value)) return value;
|
|
8949
|
+
if (value === void 0 || value === null) return [];
|
|
8950
|
+
return [value];
|
|
8951
|
+
}
|
|
8952
|
+
function ruleKey(rule) {
|
|
8953
|
+
if (typeof rule === "string") return JSON.stringify(["include", "simple", rule.trim(), ""]);
|
|
8954
|
+
if (!isRecord(rule)) return "";
|
|
8955
|
+
const candidate = rule;
|
|
8956
|
+
return JSON.stringify([
|
|
8957
|
+
candidate.include === false ? "exclude" : "include",
|
|
8958
|
+
candidate.type ?? "simple",
|
|
8959
|
+
typeof candidate.pattern === "string" ? candidate.pattern : "",
|
|
8960
|
+
candidate.matchType ?? candidate.match_type ?? ""
|
|
8961
|
+
]);
|
|
8962
|
+
}
|
|
8963
|
+
function authoredUrlRules(raw) {
|
|
8964
|
+
if (Array.isArray(raw.authorUrl)) return raw.authorUrl;
|
|
8965
|
+
const url = [...arrayOf(raw.url), ...arrayOf(raw.urls), ...arrayOf(raw.rules)];
|
|
8966
|
+
if (url.length === 0) return [];
|
|
8967
|
+
const derived = /* @__PURE__ */ new Set();
|
|
8968
|
+
for (const candidate of arrayOf(raw.pageRules)) {
|
|
8969
|
+
if (!isRecord(candidate)) continue;
|
|
8970
|
+
for (const rule of [...arrayOf(candidate.includeRules), ...arrayOf(candidate.excludeRules)]) {
|
|
8971
|
+
derived.add(ruleKey(rule));
|
|
8972
|
+
}
|
|
8973
|
+
}
|
|
8974
|
+
return url.filter((rule) => !derived.has(ruleKey(rule)));
|
|
8975
|
+
}
|
|
8976
|
+
function pageEntries(raw) {
|
|
8977
|
+
const byId = /* @__PURE__ */ new Map();
|
|
8978
|
+
for (const candidate of arrayOf(raw.pageRules)) {
|
|
8979
|
+
if (!isRecord(candidate) || typeof candidate.id !== "string" || !candidate.id) continue;
|
|
8980
|
+
byId.set(candidate.id, {
|
|
8981
|
+
name: typeof candidate.name === "string" && candidate.name.trim() ? candidate.name.trim() : null,
|
|
8982
|
+
include: candidate.include !== false
|
|
8983
|
+
});
|
|
8984
|
+
}
|
|
8985
|
+
for (const candidate of arrayOf(raw.pages)) {
|
|
8986
|
+
const id = typeof candidate === "string" ? candidate : isRecord(candidate) && typeof candidate.id === "string" ? candidate.id : "";
|
|
8987
|
+
if (!id) continue;
|
|
8988
|
+
byId.set(id, {
|
|
8989
|
+
name: byId.get(id)?.name ?? null,
|
|
8990
|
+
include: isRecord(candidate) && candidate.include === false ? false : true
|
|
8991
|
+
});
|
|
8992
|
+
}
|
|
8993
|
+
return [...byId.values()];
|
|
8994
|
+
}
|
|
8995
|
+
function urlRuleClause(count) {
|
|
8996
|
+
return count === 1 ? "1 URL rule" : `${count} URL rules`;
|
|
8997
|
+
}
|
|
8998
|
+
function effectivePageTargetingLabel(targeting) {
|
|
8999
|
+
const raw = isRecord(targeting) ? targeting : {};
|
|
9000
|
+
if (raw.surface === "checkout" || isRecord(raw.checkoutBlock) && raw.checkoutBlock.enabled === true) {
|
|
9001
|
+
return CHECKOUT_LABEL;
|
|
9002
|
+
}
|
|
9003
|
+
const pages = pageEntries(raw);
|
|
9004
|
+
const rules = authoredUrlRules(raw);
|
|
9005
|
+
const pagesLabel = pages.map((page) => {
|
|
9006
|
+
const readable = page.name ?? UNKNOWN_PAGE_NAME;
|
|
9007
|
+
return page.include ? readable : `Exclude ${readable}`;
|
|
9008
|
+
}).join(", ");
|
|
9009
|
+
if (pages.length && rules.length) return `${pagesLabel} and ${urlRuleClause(rules.length)}`;
|
|
9010
|
+
if (pages.length) return pagesLabel;
|
|
9011
|
+
if (rules.length) return `${urlRuleClause(rules.length)} (no saved Page)`;
|
|
9012
|
+
return ALL_PAGES_LABEL;
|
|
9013
|
+
}
|
|
9014
|
+
|
|
9015
|
+
// ../api-client/src/idea-reference.ts
|
|
9016
|
+
var IDEA_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
9017
|
+
var IDEA_KEY = /^[A-Za-z0-9]{2,8}-\d+$/;
|
|
9018
|
+
function classifyIdeaReference(reference) {
|
|
9019
|
+
const trimmed = reference.trim();
|
|
9020
|
+
if (IDEA_UUID.test(trimmed)) return { kind: "uuid", value: trimmed };
|
|
9021
|
+
if (IDEA_KEY.test(trimmed))
|
|
9022
|
+
return { kind: "key", value: trimmed.toUpperCase() };
|
|
9023
|
+
return { kind: "invalid", value: trimmed };
|
|
9024
|
+
}
|
|
9025
|
+
|
|
8875
9026
|
// src/lib/asset-upload.ts
|
|
8876
9027
|
var import_node_crypto3 = __toESM(require("node:crypto"));
|
|
8877
9028
|
var import_node_fs6 = __toESM(require("node:fs"));
|
|
@@ -9072,8 +9223,17 @@ function printExperiment(exp) {
|
|
|
9072
9223
|
console.log(` ID: ${source_default.dim(exp.id)}`);
|
|
9073
9224
|
if (exp.slug) console.log(` Slug: ${source_default.cyan(exp.slug)}`);
|
|
9074
9225
|
console.log(` Status: ${statusBadge(exp.status)}`);
|
|
9226
|
+
console.log(` Classification: ${exp.counting_type}`);
|
|
9075
9227
|
console.log(` Traffic: ${(exp.traffic_allocation * 100).toFixed(0)}%`);
|
|
9228
|
+
if (exp.targeting !== void 0) {
|
|
9229
|
+
console.log(` Targeting: ${effectivePageTargetingLabel(exp.targeting)}`);
|
|
9230
|
+
}
|
|
9076
9231
|
console.log(` Created: ${formatDate(exp.created_at)}`);
|
|
9232
|
+
if (exp.linked_idea) {
|
|
9233
|
+
const label = exp.linked_idea.key ? `${exp.linked_idea.key}: ` : "";
|
|
9234
|
+
const stage = exp.linked_idea.stage ? ` (${exp.linked_idea.stage})` : "";
|
|
9235
|
+
console.log(` Idea: ${label}${exp.linked_idea.title}${stage}`);
|
|
9236
|
+
}
|
|
9077
9237
|
if (exp.variations && exp.variations.length > 0) {
|
|
9078
9238
|
console.log(` Variations:`);
|
|
9079
9239
|
for (const v of exp.variations) {
|
|
@@ -9122,6 +9282,21 @@ function printExperimentResults(results2, currency) {
|
|
|
9122
9282
|
console.log(` Conversions: ${String(totals.primaryConversions ?? 0)}`);
|
|
9123
9283
|
console.log(` Revenue: ${formatRevenue(totals.revenue, currency)}`);
|
|
9124
9284
|
console.log(` Outcome: ${statisticsStateLabel(presentedOutcome)}${outcomeProvenance}`);
|
|
9285
|
+
const resultsWeekdays = results2.results_weekdays;
|
|
9286
|
+
if (resultsWeekdays && Array.isArray(resultsWeekdays.days)) {
|
|
9287
|
+
const labels = {
|
|
9288
|
+
monday: "Mon",
|
|
9289
|
+
tuesday: "Tue",
|
|
9290
|
+
wednesday: "Wed",
|
|
9291
|
+
thursday: "Thu",
|
|
9292
|
+
friday: "Fri",
|
|
9293
|
+
saturday: "Sat",
|
|
9294
|
+
sunday: "Sun"
|
|
9295
|
+
};
|
|
9296
|
+
console.log(
|
|
9297
|
+
` Judged on: ${resultsWeekdays.days.map((day) => labels[String(day)] ?? day).join(", ")} (${String(resultsWeekdays.timezone)})`
|
|
9298
|
+
);
|
|
9299
|
+
}
|
|
9125
9300
|
console.log(` Statistics: ${statisticsStateLabel(statisticsState)}`);
|
|
9126
9301
|
if (decision.treatmentName) {
|
|
9127
9302
|
console.log(` Treatment: ${String(decision.treatmentName)}`);
|
|
@@ -9335,6 +9510,7 @@ function printFilesystemPlan(plan) {
|
|
|
9335
9510
|
}
|
|
9336
9511
|
console.log();
|
|
9337
9512
|
console.log(`QA invalidated: ${plan.invalidatesQa ? source_default.yellow("yes") : "no"}`);
|
|
9513
|
+
console.log(`Acknowledgment: ${plan.invalidatesQa ? "--accept-qa-invalidation" : "none"}`);
|
|
9338
9514
|
console.log(`Precompile: ${plan.precompileRequired ? "yes" : "no"}`);
|
|
9339
9515
|
}
|
|
9340
9516
|
function blockingIssueSummary(issues) {
|
|
@@ -9430,8 +9606,29 @@ function parseEvidenceInput(value) {
|
|
|
9430
9606
|
}
|
|
9431
9607
|
return parsed;
|
|
9432
9608
|
}
|
|
9433
|
-
function
|
|
9434
|
-
return
|
|
9609
|
+
function qaChoice(flags, description, choices) {
|
|
9610
|
+
return new Option(flags, description).choices([...choices]).argParser((value) => value);
|
|
9611
|
+
}
|
|
9612
|
+
function validateQaChoice(command, option, value, allowed2) {
|
|
9613
|
+
if (value !== void 0 && !allowed2.includes(value)) {
|
|
9614
|
+
fail(command, `Invalid ${option}: ${value}`, EXIT_CODES.validation, {
|
|
9615
|
+
option,
|
|
9616
|
+
allowedValues: [...allowed2]
|
|
9617
|
+
});
|
|
9618
|
+
}
|
|
9619
|
+
}
|
|
9620
|
+
function qaSummary(run3, changed) {
|
|
9621
|
+
return {
|
|
9622
|
+
run: {
|
|
9623
|
+
id: run3.id,
|
|
9624
|
+
status: run3.status,
|
|
9625
|
+
summary: run3.summary,
|
|
9626
|
+
approvedAt: run3.approvedAt,
|
|
9627
|
+
updatedAt: run3.updatedAt
|
|
9628
|
+
},
|
|
9629
|
+
changed,
|
|
9630
|
+
validationErrors: []
|
|
9631
|
+
};
|
|
9435
9632
|
}
|
|
9436
9633
|
function tryReadFilesystemSession(folder) {
|
|
9437
9634
|
try {
|
|
@@ -9563,7 +9760,40 @@ var get = new Command("get").description("Get experiment details by ID or slug")
|
|
|
9563
9760
|
}
|
|
9564
9761
|
output(this, { experiment: exp }, () => printExperiment(exp));
|
|
9565
9762
|
});
|
|
9566
|
-
var
|
|
9763
|
+
var attachIdea = new Command("attach-idea").description("Attach an experiment to an idea").argument("<experiment>", "Experiment ID or slug").requiredOption("--idea <key>", "Idea UUID or Apex backlog key").option("--follow-up", "Attach as a follow-up test").action(async function action5(experimentRef, opts) {
|
|
9764
|
+
const experiment = await resolveExperiment(experimentRef);
|
|
9765
|
+
if (!experiment) fail(this, `Experiment "${experimentRef}" not found.`, EXIT_CODES.validation);
|
|
9766
|
+
const classified = classifyIdeaReference(opts.idea);
|
|
9767
|
+
if (classified.kind === "invalid") {
|
|
9768
|
+
fail(this, "--idea must be a UUID or Apex backlog key", EXIT_CODES.validation);
|
|
9769
|
+
}
|
|
9770
|
+
const res = await attachExperimentIdea(requireCredentials(), experiment.id, {
|
|
9771
|
+
...classified.kind === "uuid" ? { backlog_item_id: classified.value } : { idea_key: classified.value },
|
|
9772
|
+
...opts.followUp ? { as_follow_up: true } : {}
|
|
9773
|
+
});
|
|
9774
|
+
if (!res.ok)
|
|
9775
|
+
fail(
|
|
9776
|
+
this,
|
|
9777
|
+
res.error ?? "Failed to attach idea",
|
|
9778
|
+
classifyStatus(res.status),
|
|
9779
|
+
res.errorDetails
|
|
9780
|
+
);
|
|
9781
|
+
output(this, res.data, () => printSuccess("Idea attached."));
|
|
9782
|
+
});
|
|
9783
|
+
var detachIdea = new Command("detach-idea").description("Detach an experiment from its idea").argument("<experiment>", "Experiment ID or slug").action(async function action6(experimentRef) {
|
|
9784
|
+
const experiment = await resolveExperiment(experimentRef);
|
|
9785
|
+
if (!experiment) fail(this, `Experiment "${experimentRef}" not found.`, EXIT_CODES.validation);
|
|
9786
|
+
const res = await detachExperimentIdea(requireCredentials(), experiment.id);
|
|
9787
|
+
if (!res.ok)
|
|
9788
|
+
fail(
|
|
9789
|
+
this,
|
|
9790
|
+
res.error ?? "Failed to detach idea",
|
|
9791
|
+
classifyStatus(res.status),
|
|
9792
|
+
res.errorDetails
|
|
9793
|
+
);
|
|
9794
|
+
output(this, res.data, () => printSuccess("Idea detached."));
|
|
9795
|
+
});
|
|
9796
|
+
var results = new Command("results").description("Read experiment results and timeseries by ID or slug").argument("<id-or-slug>", "Experiment ID or slug").option("--currency <code>", "Currency code for revenue formatting").action(async function action7(idOrSlug, opts) {
|
|
9567
9797
|
const creds = requireCredentials();
|
|
9568
9798
|
const spinner = await createSpinner("Fetching experiment results...");
|
|
9569
9799
|
try {
|
|
@@ -9594,6 +9824,62 @@ var results = new Command("results").description("Read experiment results and ti
|
|
|
9594
9824
|
fail(this, error instanceof Error ? error.message : String(error));
|
|
9595
9825
|
}
|
|
9596
9826
|
});
|
|
9827
|
+
var DAY_NAMES = {
|
|
9828
|
+
mon: "monday",
|
|
9829
|
+
monday: "monday",
|
|
9830
|
+
tue: "tuesday",
|
|
9831
|
+
tuesday: "tuesday",
|
|
9832
|
+
wed: "wednesday",
|
|
9833
|
+
wednesday: "wednesday",
|
|
9834
|
+
thu: "thursday",
|
|
9835
|
+
thursday: "thursday",
|
|
9836
|
+
fri: "friday",
|
|
9837
|
+
friday: "friday",
|
|
9838
|
+
sat: "saturday",
|
|
9839
|
+
saturday: "saturday",
|
|
9840
|
+
sun: "sunday",
|
|
9841
|
+
sunday: "sunday"
|
|
9842
|
+
};
|
|
9843
|
+
var countingTypes = ["standard", "setup_test", "aa_test", "non_program"];
|
|
9844
|
+
var update = new Command("update").description("Update an experiment").addOption(
|
|
9845
|
+
new Option("--counting-type <type>", "Draft experiment classification").choices(countingTypes)
|
|
9846
|
+
).argument("<id-or-slug>", "Experiment ID or slug").option("--results-weekdays <days>", "Comma-separated weekdays").option("--results-timezone <timezone>", "IANA timezone for the results window").option("--clear-results-weekdays", "Clear the results weekday window").action(async function action8(reference, opts) {
|
|
9847
|
+
if (opts.clearResultsWeekdays && (opts.resultsWeekdays || opts.resultsTimezone)) {
|
|
9848
|
+
fail(
|
|
9849
|
+
this,
|
|
9850
|
+
"--clear-results-weekdays cannot be combined with window options",
|
|
9851
|
+
EXIT_CODES.validation
|
|
9852
|
+
);
|
|
9853
|
+
}
|
|
9854
|
+
const hasWindowOption = opts.resultsWeekdays !== void 0 || opts.resultsTimezone !== void 0;
|
|
9855
|
+
if (!opts.clearResultsWeekdays && (!opts.countingType && !hasWindowOption || hasWindowOption && (!opts.resultsWeekdays || !opts.resultsTimezone))) {
|
|
9856
|
+
fail(
|
|
9857
|
+
this,
|
|
9858
|
+
"--results-weekdays and --results-timezone are required together",
|
|
9859
|
+
EXIT_CODES.validation
|
|
9860
|
+
);
|
|
9861
|
+
}
|
|
9862
|
+
const days = opts.resultsWeekdays?.split(",").map((day) => DAY_NAMES[day.trim().toLowerCase()]);
|
|
9863
|
+
if (days?.some((day) => !day)) fail(this, "Invalid weekday token", EXIT_CODES.validation);
|
|
9864
|
+
const exp = await resolveExperiment(reference);
|
|
9865
|
+
if (!exp) fail(this, `Experiment "${reference}" not found.`, EXIT_CODES.validation);
|
|
9866
|
+
const results_weekdays = opts.clearResultsWeekdays ? null : { days, timezone: opts.resultsTimezone };
|
|
9867
|
+
const hasWindow = opts.clearResultsWeekdays || opts.resultsWeekdays !== void 0;
|
|
9868
|
+
const res = await updateExperiment(requireCredentials(), exp.id, {
|
|
9869
|
+
...hasWindow ? { results_weekdays } : {},
|
|
9870
|
+
...opts.countingType !== void 0 ? { counting_type: opts.countingType } : {}
|
|
9871
|
+
});
|
|
9872
|
+
if (!res.ok) fail(this, res.error ?? "Failed to update experiment", classifyStatus(res.status));
|
|
9873
|
+
const stored = res.data?.results_weekdays ?? null;
|
|
9874
|
+
output(this, res.data, () => {
|
|
9875
|
+
if (opts.countingType !== void 0)
|
|
9876
|
+
console.log(`Classification: ${res.data.counting_type}`);
|
|
9877
|
+
if (hasWindow)
|
|
9878
|
+
console.log(
|
|
9879
|
+
stored?.days?.length ? `Judged on: ${stored.days.map((day) => day.slice(0, 3).replace(/^./, (c) => c.toUpperCase())).join(", ")} (${stored.timezone})` : "Results weekday window cleared: judged on all days."
|
|
9880
|
+
);
|
|
9881
|
+
});
|
|
9882
|
+
});
|
|
9597
9883
|
function collectCreateOption(value, previous) {
|
|
9598
9884
|
return [...previous, value];
|
|
9599
9885
|
}
|
|
@@ -9623,7 +9909,9 @@ function sourceStoreUrl(sourceRules) {
|
|
|
9623
9909
|
}
|
|
9624
9910
|
return null;
|
|
9625
9911
|
}
|
|
9626
|
-
var create = new Command("create").description("Create a new experiment").
|
|
9912
|
+
var create = new Command("create").description("Create a new experiment").addOption(
|
|
9913
|
+
new Option("--counting-type <type>", "Experiment classification").choices(countingTypes).default("standard")
|
|
9914
|
+
).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(
|
|
9627
9915
|
"--source-url <pattern>",
|
|
9628
9916
|
"Positive source URL rule (repeatable; accepts exact URLs, paths, or wildcards)",
|
|
9629
9917
|
collectCreateOption,
|
|
@@ -9633,7 +9921,7 @@ var create = new Command("create").description("Create a new experiment").requir
|
|
|
9633
9921
|
"Redirect treatment destination (repeatable; creates one treatment per URL)",
|
|
9634
9922
|
collectCreateOption,
|
|
9635
9923
|
[]
|
|
9636
|
-
).option("--traffic <pct>", "Traffic allocation (0-100)", "100").option("--
|
|
9924
|
+
).option("--traffic <pct>", "Traffic allocation (0-100)", "100").option("--idea <id>", "Idea UUID or Apex backlog key").option("--backlog-item <id>", "Alias for --idea").action(async function action9(opts) {
|
|
9637
9925
|
if (opts.type !== "mutation" && opts.type !== "redirect") {
|
|
9638
9926
|
fail(this, "--type must be mutation or redirect", EXIT_CODES.validation);
|
|
9639
9927
|
}
|
|
@@ -9666,50 +9954,23 @@ var create = new Command("create").description("Create a new experiment").requir
|
|
|
9666
9954
|
const armWeight = destinations.length > 0 ? 1 / (destinations.length + 1) : null;
|
|
9667
9955
|
const creds = requireCredentials();
|
|
9668
9956
|
let backlogItemId;
|
|
9669
|
-
|
|
9670
|
-
|
|
9671
|
-
|
|
9672
|
-
|
|
9673
|
-
|
|
9674
|
-
|
|
9675
|
-
|
|
9676
|
-
|
|
9677
|
-
|
|
9678
|
-
|
|
9679
|
-
|
|
9680
|
-
|
|
9681
|
-
|
|
9682
|
-
|
|
9683
|
-
|
|
9684
|
-
page.error ?? `Unable to resolve backlog item "${ref}"`,
|
|
9685
|
-
classifyStatus(page.status),
|
|
9686
|
-
page.errorDetails
|
|
9687
|
-
);
|
|
9688
|
-
}
|
|
9689
|
-
const match2 = page.data.data.find((item) => {
|
|
9690
|
-
const metadata = item.metadata && typeof item.metadata === "object" && !Array.isArray(item.metadata) ? item.metadata : {};
|
|
9691
|
-
return typeof metadata.backlog_key === "string" && metadata.backlog_key.toUpperCase() === ref.toUpperCase();
|
|
9692
|
-
});
|
|
9693
|
-
resolvedId = match2?.id;
|
|
9694
|
-
cursor = page.data.pagination.has_more ? page.data.pagination.next_cursor ?? void 0 : void 0;
|
|
9695
|
-
} while (!resolvedId && cursor);
|
|
9696
|
-
const backlog = resolvedId ? { ok: true, status: 200, data: { id: resolvedId } } : await getInsight(creds, "backlog", ref);
|
|
9697
|
-
if (!backlog.ok || !backlog.data?.id) {
|
|
9698
|
-
fail(
|
|
9699
|
-
this,
|
|
9700
|
-
backlog.error ?? `Backlog item "${ref}" not found`,
|
|
9701
|
-
classifyStatus(backlog.status),
|
|
9702
|
-
backlog.errorDetails
|
|
9703
|
-
);
|
|
9704
|
-
}
|
|
9705
|
-
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(
|
|
9706
|
-
backlog.data.id
|
|
9707
|
-
)) {
|
|
9708
|
-
fail(this, `Backlog item "${ref}" not found`, EXIT_CODES.validation);
|
|
9709
|
-
}
|
|
9710
|
-
backlogItemId = backlog.data.id;
|
|
9957
|
+
let ideaKey;
|
|
9958
|
+
if (opts.idea !== void 0 && opts.backlogItem !== void 0 && opts.idea.trim().toUpperCase() !== opts.backlogItem.trim().toUpperCase()) {
|
|
9959
|
+
fail(
|
|
9960
|
+
this,
|
|
9961
|
+
"--idea and --backlog-item name the same idea; pass only one",
|
|
9962
|
+
EXIT_CODES.validation
|
|
9963
|
+
);
|
|
9964
|
+
}
|
|
9965
|
+
const ideaReference = opts.idea ?? opts.backlogItem;
|
|
9966
|
+
if (ideaReference !== void 0) {
|
|
9967
|
+
const classified = classifyIdeaReference(ideaReference);
|
|
9968
|
+
if (classified.kind === "uuid") {
|
|
9969
|
+
backlogItemId = classified.value;
|
|
9970
|
+
} else if (classified.kind === "key") {
|
|
9971
|
+
ideaKey = classified.value;
|
|
9711
9972
|
} else {
|
|
9712
|
-
fail(this, "--
|
|
9973
|
+
fail(this, "--idea must be a UUID or Apex backlog key", EXIT_CODES.validation);
|
|
9713
9974
|
}
|
|
9714
9975
|
}
|
|
9715
9976
|
const spinner = await createSpinner("Creating experiment...");
|
|
@@ -9718,8 +9979,10 @@ var create = new Command("create").description("Create a new experiment").requir
|
|
|
9718
9979
|
slug: opts.slug ?? null,
|
|
9719
9980
|
description: opts.description ?? null,
|
|
9720
9981
|
delivery_type: opts.type,
|
|
9982
|
+
counting_type: opts.countingType,
|
|
9721
9983
|
traffic_allocation: traffic / 100,
|
|
9722
9984
|
...backlogItemId ? { backlog_item_id: backlogItemId } : {},
|
|
9985
|
+
...ideaKey ? { idea_key: ideaKey } : {},
|
|
9723
9986
|
...sourceRules.length > 0 ? {
|
|
9724
9987
|
store_url: sourceStoreUrl(sourceRules),
|
|
9725
9988
|
targeting: {
|
|
@@ -9761,11 +10024,16 @@ var create = new Command("create").description("Create a new experiment").requir
|
|
|
9761
10024
|
);
|
|
9762
10025
|
output(
|
|
9763
10026
|
this,
|
|
9764
|
-
{
|
|
10027
|
+
{
|
|
10028
|
+
outcome,
|
|
10029
|
+
backlog_item_id: backlogItemId ?? null,
|
|
10030
|
+
idea_key: ideaKey ?? null,
|
|
10031
|
+
experiment: res.data
|
|
10032
|
+
},
|
|
9765
10033
|
() => printExperiment(res.data)
|
|
9766
10034
|
);
|
|
9767
10035
|
});
|
|
9768
|
-
var pause = new Command("pause").description("Pause a running experiment").argument("<id-or-slug>", "Experiment ID or slug").action(async function
|
|
10036
|
+
var pause = new Command("pause").description("Pause a running experiment").argument("<id-or-slug>", "Experiment ID or slug").action(async function action10(idOrSlug) {
|
|
9769
10037
|
const creds = requireCredentials();
|
|
9770
10038
|
const spinner = await createSpinner("Pausing experiment...");
|
|
9771
10039
|
const exp = await resolveExperiment(idOrSlug);
|
|
@@ -9787,7 +10055,7 @@ var pause = new Command("pause").description("Pause a running experiment").argum
|
|
|
9787
10055
|
spinner.succeed(`Experiment "${exp.name}" is now ${statusBadge("paused")}`);
|
|
9788
10056
|
});
|
|
9789
10057
|
});
|
|
9790
|
-
var complete = new Command("complete").description("Complete/stop an experiment").argument("<id-or-slug>", "Experiment ID or slug").action(async function
|
|
10058
|
+
var complete = new Command("complete").description("Complete/stop an experiment").argument("<id-or-slug>", "Experiment ID or slug").action(async function action11(idOrSlug) {
|
|
9791
10059
|
const creds = requireCredentials();
|
|
9792
10060
|
const spinner = await createSpinner("Completing experiment...");
|
|
9793
10061
|
const exp = await resolveExperiment(idOrSlug);
|
|
@@ -9809,7 +10077,7 @@ var complete = new Command("complete").description("Complete/stop an experiment"
|
|
|
9809
10077
|
spinner.succeed(`Experiment "${exp.name}" is now ${statusBadge("completed")}`);
|
|
9810
10078
|
});
|
|
9811
10079
|
});
|
|
9812
|
-
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
|
|
10080
|
+
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 action12(idOrSlug, opts) {
|
|
9813
10081
|
const creds = requireCredentials();
|
|
9814
10082
|
const exp = await resolveExperiment(idOrSlug);
|
|
9815
10083
|
if (!exp) {
|
|
@@ -9854,7 +10122,7 @@ var del = new Command("delete").alias("rm").description("Delete an experiment").
|
|
|
9854
10122
|
() => spinner.succeed(`Experiment "${exp.name}" deleted.`)
|
|
9855
10123
|
);
|
|
9856
10124
|
});
|
|
9857
|
-
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").action(async function
|
|
10125
|
+
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").action(async function action13(idOrSlug, opts) {
|
|
9858
10126
|
const creds = requireCredentials();
|
|
9859
10127
|
const spinner = await createSpinner("Pulling experiment filesystem...");
|
|
9860
10128
|
try {
|
|
@@ -9895,7 +10163,7 @@ var pullFilesystem = new Command("pull").description("Pull an experiment as an e
|
|
|
9895
10163
|
throw error;
|
|
9896
10164
|
}
|
|
9897
10165
|
});
|
|
9898
|
-
var planFilesystem = new Command("plan").description("Plan experiment filesystem changes").argument("[folder]", "Experiment filesystem folder", ".").action(async function
|
|
10166
|
+
var planFilesystem = new Command("plan").description("Plan experiment filesystem changes").argument("[folder]", "Experiment filesystem folder", ".").action(async function action14(folderArg) {
|
|
9899
10167
|
const creds = requireCredentials();
|
|
9900
10168
|
try {
|
|
9901
10169
|
const folder = folderPath(folderArg);
|
|
@@ -9920,7 +10188,12 @@ var planFilesystem = new Command("plan").description("Plan experiment filesystem
|
|
|
9920
10188
|
await refreshServerOwnedFilesIfNeeded(creds, folder, session.experimentId);
|
|
9921
10189
|
output(
|
|
9922
10190
|
this,
|
|
9923
|
-
{
|
|
10191
|
+
{
|
|
10192
|
+
folder,
|
|
10193
|
+
experimentId: session.experimentId,
|
|
10194
|
+
plan: res.data,
|
|
10195
|
+
requiredAcknowledgment: res.data.invalidatesQa ? "accept-qa-invalidation" : null
|
|
10196
|
+
},
|
|
9924
10197
|
() => printFilesystemPlan(res.data)
|
|
9925
10198
|
);
|
|
9926
10199
|
} catch (error) {
|
|
@@ -9928,11 +10201,13 @@ var planFilesystem = new Command("plan").description("Plan experiment filesystem
|
|
|
9928
10201
|
fail(this, error instanceof Error ? error.message : String(error));
|
|
9929
10202
|
}
|
|
9930
10203
|
});
|
|
9931
|
-
var applyFilesystem = new Command("apply").description("Apply experiment filesystem changes to Apex").argument("[folder]", "Experiment filesystem folder", ".").option("-f, --force", "
|
|
10204
|
+
var applyFilesystem = new Command("apply").description("Apply experiment filesystem changes to Apex").argument("[folder]", "Experiment filesystem folder", ".").option("--accept-qa-invalidation", "Acknowledge that applying changes invalidates QA approval").addOption(new Option("-f, --force", "Deprecated alias for --accept-qa-invalidation").hideHelp()).option("--no-asset-upload", "Keep inline and local image references unchanged").action(async function action15(folderArg, opts) {
|
|
9932
10205
|
const creds = requireCredentials();
|
|
9933
10206
|
try {
|
|
9934
10207
|
const folder = folderPath(folderArg);
|
|
9935
10208
|
const session = readFilesystemSession(folder);
|
|
10209
|
+
if (opts.force)
|
|
10210
|
+
process.stderr.write("--force is deprecated; use --accept-qa-invalidation instead.\n");
|
|
9936
10211
|
const files = await rewriteExperimentFilesystemAssets({
|
|
9937
10212
|
files: listExperimentFilesystemFiles(folder, session),
|
|
9938
10213
|
folder,
|
|
@@ -9960,7 +10235,8 @@ var applyFilesystem = new Command("apply").description("Apply experiment filesys
|
|
|
9960
10235
|
}
|
|
9961
10236
|
fail(this, blockingIssueSummary(planRes.data.blockingIssues), EXIT_CODES.validation, {
|
|
9962
10237
|
errors: planRes.data.errors,
|
|
9963
|
-
plan: planRes.data
|
|
10238
|
+
plan: planRes.data,
|
|
10239
|
+
requiredAcknowledgment: planRes.data.invalidatesQa ? "accept-qa-invalidation" : null
|
|
9964
10240
|
});
|
|
9965
10241
|
}
|
|
9966
10242
|
if (planRes.data.changes.length === 0) {
|
|
@@ -9971,19 +10247,20 @@ var applyFilesystem = new Command("apply").description("Apply experiment filesys
|
|
|
9971
10247
|
experimentId: session.experimentId,
|
|
9972
10248
|
applied: false,
|
|
9973
10249
|
plan: planRes.data,
|
|
10250
|
+
requiredAcknowledgment: planRes.data.invalidatesQa ? "accept-qa-invalidation" : null,
|
|
9974
10251
|
message: "No changes."
|
|
9975
10252
|
},
|
|
9976
10253
|
() => printFilesystemPlan(planRes.data)
|
|
9977
10254
|
);
|
|
9978
10255
|
return;
|
|
9979
10256
|
}
|
|
9980
|
-
if (planRes.data.invalidatesQa && !opts.force) {
|
|
10257
|
+
if (planRes.data.invalidatesQa && !opts.force && !opts.acceptQaInvalidation) {
|
|
9981
10258
|
const ok = await confirmApply("This apply invalidates QA approval. Continue?");
|
|
9982
10259
|
if (ok === null) {
|
|
9983
10260
|
confirmationRequired(
|
|
9984
10261
|
this,
|
|
9985
10262
|
"Applying these changes invalidates QA approval and requires confirmation.",
|
|
9986
|
-
`apex experiments apply ${folderArg} --
|
|
10263
|
+
`apex experiments apply ${folderArg} --accept-qa-invalidation`
|
|
9987
10264
|
);
|
|
9988
10265
|
}
|
|
9989
10266
|
if (!ok) {
|
|
@@ -10011,11 +10288,32 @@ var applyFilesystem = new Command("apply").description("Apply experiment filesys
|
|
|
10011
10288
|
experimentId: session.experimentId,
|
|
10012
10289
|
applied: true,
|
|
10013
10290
|
plan: planRes.data,
|
|
10291
|
+
requiredAcknowledgment: planRes.data.invalidatesQa ? "accept-qa-invalidation" : null,
|
|
10014
10292
|
result: res.data,
|
|
10293
|
+
receipt: res.data?.receipt ?? null,
|
|
10015
10294
|
previewUrls
|
|
10016
10295
|
},
|
|
10017
10296
|
() => {
|
|
10018
10297
|
printFilesystemPlan(planRes.data);
|
|
10298
|
+
const receipt = res.data?.receipt;
|
|
10299
|
+
if (receipt) {
|
|
10300
|
+
console.log("\nReceipt");
|
|
10301
|
+
console.log(` Status: ${receipt.status}`);
|
|
10302
|
+
console.log(` Classification: ${receipt.delivery_type} / ${receipt.counting_type}`);
|
|
10303
|
+
console.log(` Traffic: ${(receipt.traffic_allocation * 100).toFixed(0)}%`);
|
|
10304
|
+
console.log(
|
|
10305
|
+
` Schedule: ${receipt.schedule.scheduled_start_at ?? "none"} \u2192 ${receipt.schedule.scheduled_end_at ?? "none"}`
|
|
10306
|
+
);
|
|
10307
|
+
console.log(` Goals: ${receipt.goal_count}`);
|
|
10308
|
+
console.log(
|
|
10309
|
+
` QA: ${receipt.qa ? `${receipt.qa.status} (${receipt.qa.run_id}), approved: ${receipt.qa.approved_at ?? "none"}` : "none"}`
|
|
10310
|
+
);
|
|
10311
|
+
for (const variation of receipt.variations) {
|
|
10312
|
+
console.log(
|
|
10313
|
+
` ${variation.name} (${variation.slug}): ${Object.entries(variation.digests).map(([key, value]) => `${key}=${value ?? "null"}`).join(" ")}`
|
|
10314
|
+
);
|
|
10315
|
+
}
|
|
10316
|
+
}
|
|
10019
10317
|
printQaPreviewUrls(session.experimentId, previewResult);
|
|
10020
10318
|
}
|
|
10021
10319
|
);
|
|
@@ -10024,7 +10322,7 @@ var applyFilesystem = new Command("apply").description("Apply experiment filesys
|
|
|
10024
10322
|
fail(this, error instanceof Error ? error.message : String(error));
|
|
10025
10323
|
}
|
|
10026
10324
|
});
|
|
10027
|
-
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
|
|
10325
|
+
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 action16(target) {
|
|
10028
10326
|
const creds = requireCredentials();
|
|
10029
10327
|
const spinner = await createSpinner("Fetching QA run...");
|
|
10030
10328
|
try {
|
|
@@ -10048,15 +10346,13 @@ var qaStatus = new Command("status").description("Show or create the latest expe
|
|
|
10048
10346
|
throw error;
|
|
10049
10347
|
}
|
|
10050
10348
|
});
|
|
10051
|
-
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").
|
|
10349
|
+
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").addOption(
|
|
10350
|
+
qaChoice("--status <status>", "Checklist status", checklistStatuses).makeOptionMandatory()
|
|
10351
|
+
).option("--notes <text>", "Checklist notes").option("--evidence <json-or-path>", "Evidence JSON object or @path/to/evidence.json").option("--summary", "Print a compact run and changed values").option("--json", "Print raw QA run JSON").action(async function action17(target, opts) {
|
|
10352
|
+
validateQaChoice(this, "--status", opts.status, checklistStatuses);
|
|
10052
10353
|
const creds = requireCredentials();
|
|
10053
10354
|
const spinner = await createSpinner("Updating QA checklist...");
|
|
10054
10355
|
try {
|
|
10055
|
-
if (!isQaCheckStatus(opts.status)) {
|
|
10056
|
-
throw new Error(
|
|
10057
|
-
"Invalid QA status. Use pending, pass, warning, fail, accepted, or skipped."
|
|
10058
|
-
);
|
|
10059
|
-
}
|
|
10060
10356
|
const evidence = parseEvidenceInput(opts.evidence);
|
|
10061
10357
|
const resolved = await resolveExperimentForFilesystemOrId(target);
|
|
10062
10358
|
const current = await getExperimentQaRun(creds, resolved.experimentId);
|
|
@@ -10082,16 +10378,19 @@ var qaCheck = new Command("check").description("Update one QA checklist item wit
|
|
|
10082
10378
|
resolved.folder,
|
|
10083
10379
|
resolved.experimentId
|
|
10084
10380
|
);
|
|
10085
|
-
|
|
10381
|
+
const data = opts.summary ? qaSummary(run3, run3.checklist.find((item) => item.id === opts.item) ?? null) : { run: run3 };
|
|
10382
|
+
output(this, data, () => {
|
|
10086
10383
|
spinner.succeed("Updated QA checklist");
|
|
10087
|
-
|
|
10384
|
+
if (opts.summary) process.stdout.write(`${JSON.stringify(data, null, 2)}
|
|
10385
|
+
`);
|
|
10386
|
+
else printQaRun(run3);
|
|
10088
10387
|
});
|
|
10089
10388
|
} catch (error) {
|
|
10090
10389
|
spinner.fail("Failed to update QA checklist");
|
|
10091
10390
|
throw error;
|
|
10092
10391
|
}
|
|
10093
10392
|
});
|
|
10094
|
-
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(async function
|
|
10393
|
+
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("--summary", "Print a compact run and changed values").option("--json", "Print raw QA run JSON").action(async function action18(target, opts) {
|
|
10095
10394
|
const creds = requireCredentials();
|
|
10096
10395
|
const spinner = await createSpinner("Updating QA notes...");
|
|
10097
10396
|
try {
|
|
@@ -10103,9 +10402,10 @@ var qaNotes = new Command("notes").description("Update QA run notes or acceptanc
|
|
|
10103
10402
|
if (!current.ok || !current.data) {
|
|
10104
10403
|
throw new Error(current.error ?? "Failed to fetch QA run");
|
|
10105
10404
|
}
|
|
10106
|
-
const body5 =
|
|
10405
|
+
const body5 = {
|
|
10107
10406
|
runId: current.data.run.id,
|
|
10108
|
-
|
|
10407
|
+
...opts.notes !== void 0 ? { runNotes: opts.notes } : {},
|
|
10408
|
+
...opts.acceptance !== void 0 ? { acceptanceCriteriaSummary: opts.acceptance } : {}
|
|
10109
10409
|
};
|
|
10110
10410
|
const res = await updateExperimentQaRun(creds, resolved.experimentId, body5);
|
|
10111
10411
|
if (!res.ok || !res.data)
|
|
@@ -10120,20 +10420,28 @@ var qaNotes = new Command("notes").description("Update QA run notes or acceptanc
|
|
|
10120
10420
|
resolved.folder,
|
|
10121
10421
|
resolved.experimentId
|
|
10122
10422
|
);
|
|
10123
|
-
|
|
10423
|
+
const data = opts.summary ? qaSummary(run3, {
|
|
10424
|
+
notes: run3.notes,
|
|
10425
|
+
acceptanceCriteriaSummary: run3.acceptanceCriteriaSummary
|
|
10426
|
+
}) : { run: run3 };
|
|
10427
|
+
output(this, data, () => {
|
|
10124
10428
|
spinner.succeed("Updated QA notes");
|
|
10125
|
-
|
|
10429
|
+
if (opts.summary) process.stdout.write(`${JSON.stringify(data, null, 2)}
|
|
10430
|
+
`);
|
|
10431
|
+
else printQaRun(run3);
|
|
10126
10432
|
});
|
|
10127
10433
|
} catch (error) {
|
|
10128
10434
|
spinner.fail("Failed to update QA notes");
|
|
10129
10435
|
throw error;
|
|
10130
10436
|
}
|
|
10131
10437
|
});
|
|
10132
|
-
var qaApprove = new Command("approve").description("
|
|
10438
|
+
var qaApprove = new Command("approve").description("Approve QA and move the experiment to ready_to_launch; qa reopen returns it to QA").argument("[folder-or-id]", "Experiment filesystem folder, ID, or slug", ".").action(async function action19(target) {
|
|
10133
10439
|
const creds = requireCredentials();
|
|
10134
10440
|
const spinner = await createSpinner("Approving QA...");
|
|
10135
10441
|
try {
|
|
10136
10442
|
const resolved = await resolveExperimentForFilesystemOrId(target);
|
|
10443
|
+
const before = await resolveExperiment(resolved.experimentId);
|
|
10444
|
+
if (!before) fail(this, "Experiment not found.", EXIT_CODES.validation);
|
|
10137
10445
|
const res = await approveExperimentQa(creds, resolved.experimentId);
|
|
10138
10446
|
if (!res.ok)
|
|
10139
10447
|
throw new CliError(
|
|
@@ -10146,17 +10454,31 @@ var qaApprove = new Command("approve").description("Mark QA done after the check
|
|
|
10146
10454
|
resolved.folder,
|
|
10147
10455
|
resolved.experimentId
|
|
10148
10456
|
);
|
|
10457
|
+
if (!res.data?.status) fail(this, "QA approval response is missing experiment status.");
|
|
10458
|
+
const transition = {
|
|
10459
|
+
from: before.status,
|
|
10460
|
+
to: res.data.status,
|
|
10461
|
+
reopen: `apex experiments qa reopen ${resolved.experimentId}`
|
|
10462
|
+
};
|
|
10149
10463
|
output(
|
|
10150
10464
|
this,
|
|
10151
|
-
{
|
|
10152
|
-
|
|
10465
|
+
{
|
|
10466
|
+
experimentId: resolved.experimentId,
|
|
10467
|
+
qaStatus: "done",
|
|
10468
|
+
experiment_status_before: before.status,
|
|
10469
|
+
experiment_status_after: res.data.status,
|
|
10470
|
+
transition
|
|
10471
|
+
},
|
|
10472
|
+
() => spinner.succeed(
|
|
10473
|
+
`QA approved: ${transition.from} \u2192 ${transition.to}; reopen: ${transition.reopen}`
|
|
10474
|
+
)
|
|
10153
10475
|
);
|
|
10154
10476
|
} catch (error) {
|
|
10155
10477
|
spinner.fail("Failed to approve QA");
|
|
10156
10478
|
throw error;
|
|
10157
10479
|
}
|
|
10158
10480
|
});
|
|
10159
|
-
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
|
|
10481
|
+
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 action20(target) {
|
|
10160
10482
|
const creds = requireCredentials();
|
|
10161
10483
|
const spinner = await createSpinner("Reopening QA...");
|
|
10162
10484
|
try {
|
|
@@ -10214,7 +10536,9 @@ function resolveEvidenceVariation(experiment, ref) {
|
|
|
10214
10536
|
}
|
|
10215
10537
|
throw new Error(`Unknown --variation value: ${ref}`);
|
|
10216
10538
|
}
|
|
10217
|
-
var qaEvidence = new Command("evidence").description("Attach screenshot jobs or upload typed QA evidence").argument("[folder-or-id]", "Experiment filesystem folder, ID, or slug", ".").option("--job <jobId>", "Attach a terminal screenshot job").option("--file <path>", "Upload an evidence image; repeat for more files", collectFile, []).option("--label <text>", "Evidence label").option("--device <device>", "desktop, mobile, tablet, or a raw device value").option("--variation <ref>", "Variation ID, slug, or name").
|
|
10539
|
+
var qaEvidence = new Command("evidence").description("Attach screenshot jobs or upload typed QA evidence").argument("[folder-or-id]", "Experiment filesystem folder, ID, or slug", ".").option("--job <jobId>", "Attach a terminal screenshot job").option("--file <path>", "Upload an evidence image; repeat for more files", collectFile, []).option("--label <text>", "Evidence label").option("--device <device>", "desktop, mobile, tablet, or a raw device value").option("--variation <ref>", "Variation ID, slug, or name").addOption(qaChoice("--status <status>", "Evidence status", evidenceStatuses)).addOption(qaChoice("--kind <kind>", "Evidence kind", evidenceKinds).default("screenshot")).option("--records <json-or-@path>", "Typed evidence record array").option("--summary", "Print a compact run and newly added evidence").option("--json", "Print the QA run in the standard JSON envelope").action(async function action21(target, opts) {
|
|
10540
|
+
validateQaChoice(this, "--status", opts.status, evidenceStatuses);
|
|
10541
|
+
validateQaChoice(this, "--kind", opts.kind, evidenceKinds);
|
|
10218
10542
|
const modes = Number(Boolean(opts.job)) + Number(opts.file.length > 0) + Number(Boolean(opts.records));
|
|
10219
10543
|
if (modes !== 1) {
|
|
10220
10544
|
fail(this, "Provide exactly one of --job, --file, or --records.", EXIT_CODES.validation);
|
|
@@ -10223,6 +10547,17 @@ var qaEvidence = new Command("evidence").description("Attach screenshot jobs or
|
|
|
10223
10547
|
const spinner = await createSpinner("Writing QA evidence...");
|
|
10224
10548
|
try {
|
|
10225
10549
|
const resolved = await resolveExperimentForFilesystemOrId(target);
|
|
10550
|
+
const previousIds = /* @__PURE__ */ new Set();
|
|
10551
|
+
if (opts.summary) {
|
|
10552
|
+
const current = await getExperimentQaRun(creds, resolved.experimentId);
|
|
10553
|
+
if (!current.ok || !current.data)
|
|
10554
|
+
throw new CliError(
|
|
10555
|
+
current.error ?? "Failed to fetch QA run",
|
|
10556
|
+
classifyStatus(current.status),
|
|
10557
|
+
current.errorDetails
|
|
10558
|
+
);
|
|
10559
|
+
for (const record2 of current.data.run.evidenceRecords) previousIds.add(record2.id);
|
|
10560
|
+
}
|
|
10226
10561
|
let response;
|
|
10227
10562
|
if (opts.job) {
|
|
10228
10563
|
response = await attachExperimentQaEvidence(creds, resolved.experimentId, {
|
|
@@ -10263,7 +10598,16 @@ var qaEvidence = new Command("evidence").description("Attach screenshot jobs or
|
|
|
10263
10598
|
);
|
|
10264
10599
|
}
|
|
10265
10600
|
spinner.stop();
|
|
10266
|
-
|
|
10601
|
+
const run3 = response.data.run;
|
|
10602
|
+
const data = opts.summary ? qaSummary(
|
|
10603
|
+
run3,
|
|
10604
|
+
run3.evidenceRecords.filter((record2) => !previousIds.has(record2.id))
|
|
10605
|
+
) : { run: run3 };
|
|
10606
|
+
output(this, data, () => {
|
|
10607
|
+
if (opts.summary) process.stdout.write(`${JSON.stringify(data, null, 2)}
|
|
10608
|
+
`);
|
|
10609
|
+
else printQaRun(run3);
|
|
10610
|
+
});
|
|
10267
10611
|
} catch (error) {
|
|
10268
10612
|
spinner.fail("Failed to write QA evidence");
|
|
10269
10613
|
throw error;
|
|
@@ -10279,102 +10623,255 @@ var SCREENSHOT_DEVICES = [
|
|
|
10279
10623
|
];
|
|
10280
10624
|
var safeFilenamePart = (value) => value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "screenshot";
|
|
10281
10625
|
var commaList = (value) => value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
10626
|
+
function shellQuote(value) {
|
|
10627
|
+
if (value.length > 0 && /^[A-Za-z0-9_@%+=:,./-]+$/.test(value)) return value;
|
|
10628
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
10629
|
+
}
|
|
10630
|
+
function isTransientResumeStatus(status2) {
|
|
10631
|
+
return status2 === 429 || status2 !== void 0 && status2 >= 500;
|
|
10632
|
+
}
|
|
10633
|
+
function screenshotTimeoutMessage(job, experimentRef, out, seconds, flags = {}) {
|
|
10634
|
+
const progress = job.progress ? ` Last progress: ${job.progress.captured}/${job.progress.total} captured.` : "";
|
|
10635
|
+
const carried = [
|
|
10636
|
+
flags.attachQa === false ? " --no-attach-qa" : "",
|
|
10637
|
+
flags.labelPairs === false ? " --no-label-pairs" : "",
|
|
10638
|
+
flags.json ? " --json" : ""
|
|
10639
|
+
].join("");
|
|
10640
|
+
const resume = `apex experiments screenshots ${shellQuote(experimentRef)} --job ${shellQuote(job.job_id)} --out ${shellQuote(out)}${carried}`;
|
|
10641
|
+
return `Screenshot job ${job.job_id} timed out after ${seconds}s.${progress} Resume with: ${resume}`;
|
|
10642
|
+
}
|
|
10282
10643
|
var screenshots = new Command("screenshots").description(
|
|
10283
10644
|
"Force-assign every capture, including control, to its variation server-side for deterministic screenshots"
|
|
10284
10645
|
).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(
|
|
10285
10646
|
"--variations <list>",
|
|
10286
10647
|
"Comma-separated treatment IDs, slugs, or names; control is always included and force-assigned"
|
|
10287
|
-
).option("--out <dir>", "Download directory", ".").option("--json", "Print the completed raw job payload with signed URLs").option("--no-attach-qa", "Do not attach the completed job to the QA run").option(
|
|
10648
|
+
).option("--out <dir>", "Download directory", ".").option("--job <id>", "Resume an existing screenshot job instead of creating one").option("--json", "Print the completed raw job payload with signed URLs").option("--no-attach-qa", "Do not attach the completed job to the QA run").option("--no-label-pairs", "Keep legacy screenshot filenames without before/after labels").option(
|
|
10288
10649
|
"--timeout <seconds>",
|
|
10289
10650
|
"Maximum wait for completion; finished results are always downloaded",
|
|
10290
10651
|
"300"
|
|
10291
|
-
).action(async function
|
|
10652
|
+
).action(async function action22(experimentRef, opts) {
|
|
10292
10653
|
if (opts.url)
|
|
10293
10654
|
fail(this, "--url is not supported by the current screenshot v1 API.", EXIT_CODES.validation);
|
|
10294
10655
|
const timeoutSeconds = Number(opts.timeout);
|
|
10656
|
+
const explicitJson = process.argv.includes("--json");
|
|
10295
10657
|
if (!Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0)
|
|
10296
10658
|
fail(this, "--timeout must be a positive number of seconds.", EXIT_CODES.validation);
|
|
10297
10659
|
const experiment = await resolveExperiment(experimentRef);
|
|
10298
10660
|
if (!experiment?.variations?.length)
|
|
10299
10661
|
fail(this, "Experiment has no variations to capture.", EXIT_CODES.validation);
|
|
10300
|
-
const
|
|
10301
|
-
|
|
10302
|
-
|
|
10303
|
-
|
|
10304
|
-
|
|
10305
|
-
|
|
10306
|
-
|
|
10307
|
-
|
|
10308
|
-
|
|
10309
|
-
);
|
|
10310
|
-
|
|
10311
|
-
|
|
10312
|
-
|
|
10313
|
-
|
|
10314
|
-
|
|
10315
|
-
|
|
10316
|
-
|
|
10317
|
-
|
|
10318
|
-
|
|
10319
|
-
|
|
10320
|
-
|
|
10321
|
-
|
|
10322
|
-
|
|
10323
|
-
|
|
10324
|
-
|
|
10662
|
+
const creds = requireCredentials();
|
|
10663
|
+
let devices;
|
|
10664
|
+
let variationIds;
|
|
10665
|
+
let job;
|
|
10666
|
+
let deadlineStart = null;
|
|
10667
|
+
if (opts.job) {
|
|
10668
|
+
const ignoredFlags = [
|
|
10669
|
+
opts.devices ? "--devices" : null,
|
|
10670
|
+
opts.variations ? "--variations" : null
|
|
10671
|
+
].filter((flag) => flag !== null);
|
|
10672
|
+
if (ignoredFlags.length)
|
|
10673
|
+
console.error(
|
|
10674
|
+
source_default.yellow(
|
|
10675
|
+
`Warning: ${ignoredFlags.join(" and ")} ${ignoredFlags.length > 1 ? "are" : "is"} ignored with --job; screenshot job ${opts.job} keeps its stored devices and variations.`
|
|
10676
|
+
)
|
|
10677
|
+
);
|
|
10678
|
+
deadlineStart = screenshotNow();
|
|
10679
|
+
const resumeDeadline = deadlineStart + timeoutSeconds * 1e3;
|
|
10680
|
+
let existing;
|
|
10681
|
+
let resumeDelayMs = cliTestSetting("API") === "1" ? 1 : 1e3;
|
|
10682
|
+
for (let attempt = 1; ; attempt += 1) {
|
|
10683
|
+
try {
|
|
10684
|
+
existing = await getScreenshotJob(
|
|
10685
|
+
creds,
|
|
10686
|
+
opts.job,
|
|
10687
|
+
Math.max(1, resumeDeadline - screenshotNow())
|
|
10688
|
+
);
|
|
10689
|
+
} catch (error) {
|
|
10690
|
+
if (attempt > 3 || screenshotNow() >= resumeDeadline) throw error;
|
|
10691
|
+
await new Promise(
|
|
10692
|
+
(resolve2) => setTimeout(
|
|
10693
|
+
resolve2,
|
|
10694
|
+
Math.min(resumeDelayMs, Math.max(0, resumeDeadline - screenshotNow()))
|
|
10695
|
+
)
|
|
10696
|
+
);
|
|
10697
|
+
resumeDelayMs = Math.min(Math.round(resumeDelayMs * 1.6), 1e4);
|
|
10698
|
+
continue;
|
|
10699
|
+
}
|
|
10700
|
+
if (!existing.ok && isTransientResumeStatus(existing.status) && attempt <= 3 && screenshotNow() < resumeDeadline) {
|
|
10701
|
+
await new Promise(
|
|
10702
|
+
(resolve2) => setTimeout(
|
|
10703
|
+
resolve2,
|
|
10704
|
+
Math.min(resumeDelayMs, Math.max(0, resumeDeadline - screenshotNow()))
|
|
10705
|
+
)
|
|
10706
|
+
);
|
|
10707
|
+
resumeDelayMs = Math.min(Math.round(resumeDelayMs * 1.6), 1e4);
|
|
10708
|
+
continue;
|
|
10709
|
+
}
|
|
10710
|
+
break;
|
|
10711
|
+
}
|
|
10712
|
+
if (!existing || !existing.ok || !existing.data)
|
|
10713
|
+
fail(
|
|
10714
|
+
this,
|
|
10715
|
+
existing?.error ?? "Failed to resume screenshot job",
|
|
10716
|
+
classifyStatus(existing?.status),
|
|
10717
|
+
existing?.errorDetails
|
|
10718
|
+
);
|
|
10719
|
+
job = existing.data;
|
|
10720
|
+
if (!job.experiment_id)
|
|
10721
|
+
fail(
|
|
10722
|
+
this,
|
|
10723
|
+
`Screenshot job ${job.job_id} names no experiment; refusing to resume it under ${experimentRef} (${experiment.id}).`,
|
|
10724
|
+
EXIT_CODES.validation
|
|
10725
|
+
);
|
|
10726
|
+
if (job.experiment_id !== experiment.id)
|
|
10727
|
+
fail(
|
|
10728
|
+
this,
|
|
10729
|
+
`Screenshot job ${job.job_id} belongs to experiment ${job.experiment_id}, not ${experimentRef} (${experiment.id}).`,
|
|
10730
|
+
EXIT_CODES.validation
|
|
10731
|
+
);
|
|
10732
|
+
const storedDevices = job.devices ?? [];
|
|
10733
|
+
for (const device of storedDevices.filter((device2) => !SCREENSHOT_DEVICES.includes(device2)))
|
|
10734
|
+
console.error(
|
|
10735
|
+
source_default.yellow(
|
|
10736
|
+
`Warning: screenshot job ${job.job_id} names device ${device}, which this CLI does not know; skipping it.`
|
|
10737
|
+
)
|
|
10738
|
+
);
|
|
10739
|
+
devices = storedDevices.filter((device) => SCREENSHOT_DEVICES.includes(device));
|
|
10740
|
+
if (!devices.length)
|
|
10741
|
+
fail(
|
|
10742
|
+
this,
|
|
10743
|
+
storedDevices.length ? `Screenshot job ${job.job_id} names no device this CLI knows (${storedDevices.join(", ")}).` : `Screenshot job ${job.job_id} names no devices; it cannot be resumed.`,
|
|
10744
|
+
EXIT_CODES.validation
|
|
10745
|
+
);
|
|
10746
|
+
if (Array.isArray(job.results))
|
|
10747
|
+
fail(
|
|
10748
|
+
this,
|
|
10749
|
+
`Screenshot job ${job.job_id} stores its results in a legacy shape this CLI cannot download; create a new job instead of resuming it.`,
|
|
10750
|
+
EXIT_CODES.validation
|
|
10751
|
+
);
|
|
10752
|
+
const knownVariationIds = new Set(experiment.variations.map((variation) => variation.id));
|
|
10753
|
+
const storedVariationIds = job.variation_ids ?? [];
|
|
10754
|
+
for (const id of storedVariationIds.filter((id2) => !knownVariationIds.has(id2)))
|
|
10755
|
+
console.error(
|
|
10756
|
+
source_default.yellow(
|
|
10757
|
+
`Warning: screenshot job ${job.job_id} names variation ${id}, which is not part of this experiment; skipping it.`
|
|
10758
|
+
)
|
|
10325
10759
|
);
|
|
10760
|
+
variationIds = storedVariationIds.filter((id) => knownVariationIds.has(id));
|
|
10761
|
+
if (!variationIds.length)
|
|
10326
10762
|
fail(
|
|
10327
10763
|
this,
|
|
10328
|
-
`
|
|
10764
|
+
`Screenshot job ${job.job_id} names no variation that belongs to experiment ${experimentRef}.`,
|
|
10329
10765
|
EXIT_CODES.validation
|
|
10330
10766
|
);
|
|
10767
|
+
} else {
|
|
10768
|
+
devices = opts.devices ? commaList(opts.devices) : SCREENSHOT_DEVICES;
|
|
10769
|
+
if (!devices.length)
|
|
10770
|
+
fail(this, "--devices must contain at least one device.", EXIT_CODES.validation);
|
|
10771
|
+
const unknownDevices = devices.filter((device) => !SCREENSHOT_DEVICES.includes(device));
|
|
10772
|
+
if (unknownDevices.length)
|
|
10773
|
+
fail(
|
|
10774
|
+
this,
|
|
10775
|
+
`Unknown screenshot device(s): ${unknownDevices.join(", ")}`,
|
|
10776
|
+
EXIT_CODES.validation
|
|
10777
|
+
);
|
|
10778
|
+
const requested = opts.variations ? commaList(opts.variations) : [];
|
|
10779
|
+
const control = experiment.variations.find((variation) => variation.is_control);
|
|
10780
|
+
if (!control) fail(this, "Experiment has no control variation.", EXIT_CODES.validation);
|
|
10781
|
+
const matchedById = /* @__PURE__ */ new Map();
|
|
10782
|
+
const unmatched = [];
|
|
10783
|
+
for (const ref of requested) {
|
|
10784
|
+
const exactMatch = experiment.variations.find(
|
|
10785
|
+
(candidate) => ref === candidate.id || ref === candidate.slug
|
|
10786
|
+
);
|
|
10787
|
+
const nameMatches = exactMatch ? [] : experiment.variations.filter(
|
|
10788
|
+
(candidate) => ref.toLowerCase() === candidate.name.toLowerCase()
|
|
10789
|
+
);
|
|
10790
|
+
if (nameMatches.length > 1) {
|
|
10791
|
+
const matches = nameMatches.map(
|
|
10792
|
+
(candidate) => `${candidate.name} (ID: ${candidate.id}${candidate.slug ? `, slug: ${candidate.slug}` : ""})`
|
|
10793
|
+
);
|
|
10794
|
+
fail(
|
|
10795
|
+
this,
|
|
10796
|
+
`Ambiguous --variations value "${ref}" matches: ${matches.join("; ")}. Use a variation ID or slug.`,
|
|
10797
|
+
EXIT_CODES.validation
|
|
10798
|
+
);
|
|
10799
|
+
}
|
|
10800
|
+
const variation = exactMatch ?? nameMatches[0];
|
|
10801
|
+
if (variation) matchedById.set(variation.id, variation);
|
|
10802
|
+
else unmatched.push(ref);
|
|
10331
10803
|
}
|
|
10332
|
-
|
|
10333
|
-
|
|
10334
|
-
|
|
10335
|
-
|
|
10336
|
-
|
|
10337
|
-
|
|
10338
|
-
|
|
10339
|
-
|
|
10340
|
-
|
|
10341
|
-
|
|
10342
|
-
|
|
10343
|
-
|
|
10344
|
-
|
|
10345
|
-
|
|
10346
|
-
|
|
10347
|
-
|
|
10348
|
-
|
|
10349
|
-
|
|
10350
|
-
created.errorDetails
|
|
10351
|
-
);
|
|
10352
|
-
let job = created.data;
|
|
10353
|
-
const deadline = screenshotNow() + timeoutSeconds * 1e3;
|
|
10804
|
+
if (unmatched.length)
|
|
10805
|
+
fail(this, `Unknown --variations value(s): ${unmatched.join(", ")}`, EXIT_CODES.validation);
|
|
10806
|
+
const selected2 = requested.length ? [...matchedById.values()] : experiment.variations;
|
|
10807
|
+
variationIds = [.../* @__PURE__ */ new Set([control.id, ...selected2.map((variation) => variation.id)])];
|
|
10808
|
+
const created = await createExperimentScreenshots(creds, experiment.id, {
|
|
10809
|
+
devices,
|
|
10810
|
+
variation_ids: variationIds
|
|
10811
|
+
});
|
|
10812
|
+
if (!created.ok || !created.data)
|
|
10813
|
+
fail(
|
|
10814
|
+
this,
|
|
10815
|
+
created.error ?? "Failed to create screenshot job",
|
|
10816
|
+
classifyStatus(created.status),
|
|
10817
|
+
created.errorDetails
|
|
10818
|
+
);
|
|
10819
|
+
job = created.data;
|
|
10820
|
+
}
|
|
10821
|
+
const deadline = (deadlineStart ?? screenshotNow()) + timeoutSeconds * 1e3;
|
|
10354
10822
|
let delayMs = cliTestSetting("API") === "1" ? 1 : 1e3;
|
|
10355
10823
|
let consecutiveTransientFailures = 0;
|
|
10356
10824
|
while (job.status === "queued" || job.status === "processing") {
|
|
10357
10825
|
if (screenshotNow() >= deadline)
|
|
10358
|
-
fail(
|
|
10826
|
+
fail(
|
|
10827
|
+
this,
|
|
10828
|
+
screenshotTimeoutMessage(job, experimentRef, opts.out, timeoutSeconds, {
|
|
10829
|
+
labelPairs: opts.labelPairs,
|
|
10830
|
+
json: explicitJson,
|
|
10831
|
+
attachQa: opts.attachQa
|
|
10832
|
+
})
|
|
10833
|
+
);
|
|
10359
10834
|
await new Promise(
|
|
10360
10835
|
(resolve2) => setTimeout(resolve2, Math.min(delayMs, Math.max(0, deadline - screenshotNow())))
|
|
10361
10836
|
);
|
|
10362
10837
|
if (screenshotNow() >= deadline)
|
|
10363
|
-
fail(
|
|
10838
|
+
fail(
|
|
10839
|
+
this,
|
|
10840
|
+
screenshotTimeoutMessage(job, experimentRef, opts.out, timeoutSeconds, {
|
|
10841
|
+
labelPairs: opts.labelPairs,
|
|
10842
|
+
json: explicitJson,
|
|
10843
|
+
attachQa: opts.attachQa
|
|
10844
|
+
})
|
|
10845
|
+
);
|
|
10364
10846
|
let polled;
|
|
10365
10847
|
try {
|
|
10366
10848
|
polled = await getScreenshotJob(creds, job.job_id, Math.max(1, deadline - screenshotNow()));
|
|
10367
10849
|
} catch (error) {
|
|
10368
10850
|
if (screenshotNow() >= deadline)
|
|
10369
|
-
fail(
|
|
10851
|
+
fail(
|
|
10852
|
+
this,
|
|
10853
|
+
screenshotTimeoutMessage(job, experimentRef, opts.out, timeoutSeconds, {
|
|
10854
|
+
labelPairs: opts.labelPairs,
|
|
10855
|
+
json: explicitJson,
|
|
10856
|
+
attachQa: opts.attachQa
|
|
10857
|
+
})
|
|
10858
|
+
);
|
|
10370
10859
|
consecutiveTransientFailures += 1;
|
|
10371
10860
|
if (consecutiveTransientFailures > 3) throw error;
|
|
10372
10861
|
delayMs = Math.min(Math.round(delayMs * 1.6), 1e4);
|
|
10373
10862
|
continue;
|
|
10374
10863
|
}
|
|
10375
10864
|
const polledToTerminalState = polled.ok && polled.data && polled.data.status !== "queued" && polled.data.status !== "processing";
|
|
10865
|
+
if (polled.ok && polled.data) job = polled.data;
|
|
10376
10866
|
if (!polledToTerminalState && screenshotNow() >= deadline) {
|
|
10377
|
-
fail(
|
|
10867
|
+
fail(
|
|
10868
|
+
this,
|
|
10869
|
+
screenshotTimeoutMessage(job, experimentRef, opts.out, timeoutSeconds, {
|
|
10870
|
+
labelPairs: opts.labelPairs,
|
|
10871
|
+
json: explicitJson,
|
|
10872
|
+
attachQa: opts.attachQa
|
|
10873
|
+
})
|
|
10874
|
+
);
|
|
10378
10875
|
}
|
|
10379
10876
|
if (!polled.ok || !polled.data) {
|
|
10380
10877
|
const transient = polled.status === 429 || polled.status >= 500;
|
|
@@ -10390,7 +10887,6 @@ var screenshots = new Command("screenshots").description(
|
|
|
10390
10887
|
continue;
|
|
10391
10888
|
}
|
|
10392
10889
|
consecutiveTransientFailures = 0;
|
|
10393
|
-
job = polled.data;
|
|
10394
10890
|
delayMs = Math.min(Math.round(delayMs * 1.6), 1e4);
|
|
10395
10891
|
}
|
|
10396
10892
|
if (opts.attachQa !== false) {
|
|
@@ -10421,16 +10917,32 @@ var screenshots = new Command("screenshots").description(
|
|
|
10421
10917
|
const rows = [];
|
|
10422
10918
|
const errors = [];
|
|
10423
10919
|
const usedFilenames = /* @__PURE__ */ new Set();
|
|
10920
|
+
const hasControlVariation = experiment.variations.some((item) => item.is_control);
|
|
10424
10921
|
const downloads = [];
|
|
10922
|
+
if (Array.isArray(job.results))
|
|
10923
|
+
fail(
|
|
10924
|
+
this,
|
|
10925
|
+
`Screenshot job ${job.job_id} stores its results in a legacy shape this CLI cannot download; create a new job instead of resuming it.`,
|
|
10926
|
+
EXIT_CODES.validation
|
|
10927
|
+
);
|
|
10425
10928
|
for (const variationId of variationIds) {
|
|
10426
10929
|
const variation = experiment.variations.find((item) => item.id === variationId);
|
|
10930
|
+
if (!variation) {
|
|
10931
|
+
console.error(
|
|
10932
|
+
source_default.yellow(
|
|
10933
|
+
`Warning: variation ${variationId} is not part of this experiment; skipping it.`
|
|
10934
|
+
)
|
|
10935
|
+
);
|
|
10936
|
+
continue;
|
|
10937
|
+
}
|
|
10427
10938
|
for (const device of devices) {
|
|
10428
10939
|
const url = job.results?.[variationId]?.[device];
|
|
10429
10940
|
if (!url) {
|
|
10430
10941
|
downloads.push({ variation, device, url, filename: "" });
|
|
10431
10942
|
continue;
|
|
10432
10943
|
}
|
|
10433
|
-
const
|
|
10944
|
+
const pairPrefix = opts.labelPairs === false || !hasControlVariation ? "" : variation.is_control ? "before-" : "after-";
|
|
10945
|
+
const baseFilename = `${pairPrefix}${safeFilenamePart(experiment.slug ?? experiment.name)}-${safeFilenamePart(variation.slug ?? variation.name)}-${safeFilenamePart(device)}`;
|
|
10434
10946
|
let filename = `${baseFilename}.png`;
|
|
10435
10947
|
if (usedFilenames.has(filename)) {
|
|
10436
10948
|
const suffix = variation.id.replace(/[^a-z0-9]/gi, "").slice(-8).toLowerCase();
|
|
@@ -10487,7 +10999,7 @@ var screenshots = new Command("screenshots").description(
|
|
|
10487
10999
|
printTable(["VARIATION", "DEVICE", "FILE"], rows, [24, 20, 48]);
|
|
10488
11000
|
});
|
|
10489
11001
|
});
|
|
10490
|
-
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(
|
|
11002
|
+
var experimentsCommand = new Command("experiments").alias("exp").description("Manage experiments").addCommand(list).addCommand(get).addCommand(results).addCommand(update).addCommand(create).addCommand(attachIdea).addCommand(detachIdea).addCommand(pause).addCommand(complete).addCommand(del).addCommand(pullFilesystem).addCommand(planFilesystem).addCommand(applyFilesystem).addCommand(screenshots).addCommand(qa).addHelpText(
|
|
10491
11003
|
"after",
|
|
10492
11004
|
`
|
|
10493
11005
|
Examples:
|
|
@@ -10581,10 +11093,10 @@ async function importConfigModule(absPath) {
|
|
|
10581
11093
|
}
|
|
10582
11094
|
throw new Error(`Unsupported config extension "${ext}". Use .drip.config.ts or .drip.config.js.`);
|
|
10583
11095
|
}
|
|
10584
|
-
function
|
|
11096
|
+
function isRecord2(value) {
|
|
10585
11097
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
10586
11098
|
}
|
|
10587
|
-
var pushCommand = new Command("push").description("Push experiment definitions from config file to the API (idempotent by slug)").option("-c, --config <path>", "Config file path").option("--dry-run", "Show what would be pushed without making changes").option("--no-asset-upload", "Keep inline and local image references unchanged").action(async function
|
|
11099
|
+
var pushCommand = new Command("push").description("Push experiment definitions from config file to the API (idempotent by slug)").option("-c, --config <path>", "Config file path").option("--dry-run", "Show what would be pushed without making changes").option("--no-asset-upload", "Keep inline and local image references unchanged").action(async function action23(opts) {
|
|
10588
11100
|
const creds = requireCredentials();
|
|
10589
11101
|
const configPath = opts.config ?? findConfigPath();
|
|
10590
11102
|
if (!configPath) {
|
|
@@ -10599,12 +11111,12 @@ var pushCommand = new Command("push").description("Push experiment definitions f
|
|
|
10599
11111
|
let experiments;
|
|
10600
11112
|
try {
|
|
10601
11113
|
const mod = await importConfigModule(absoluteConfigPath);
|
|
10602
|
-
const exported =
|
|
11114
|
+
const exported = isRecord2(mod) && "default" in mod ? mod.default : mod;
|
|
10603
11115
|
if (Array.isArray(exported)) {
|
|
10604
11116
|
experiments = exported;
|
|
10605
|
-
} else if (
|
|
11117
|
+
} else if (isRecord2(exported) && Array.isArray(exported.experiments)) {
|
|
10606
11118
|
experiments = exported.experiments;
|
|
10607
|
-
} else if (
|
|
11119
|
+
} else if (isRecord2(exported) && typeof exported.slug === "string" && Array.isArray(exported.variations)) {
|
|
10608
11120
|
experiments = [exported];
|
|
10609
11121
|
} else {
|
|
10610
11122
|
spinner?.fail("Invalid config format");
|
|
@@ -11028,7 +11540,7 @@ async function createSession(folder) {
|
|
|
11028
11540
|
var harnessCommand = new Command("harness").description(
|
|
11029
11541
|
"Develop AB Test Harness projects through Apex live QA sessions"
|
|
11030
11542
|
);
|
|
11031
|
-
harnessCommand.command("init").argument("<folder>", "AB Test Harness experiment folder").option("--force", "Overwrite existing apex.harness.json").action(function
|
|
11543
|
+
harnessCommand.command("init").argument("<folder>", "AB Test Harness experiment folder").option("--force", "Overwrite existing apex.harness.json").action(function action24(folderArg, opts) {
|
|
11032
11544
|
const folder = resolveFolder(folderArg);
|
|
11033
11545
|
const targetPath = apexConfigPath(folder);
|
|
11034
11546
|
if (import_node_fs9.default.existsSync(targetPath) && !opts.force) {
|
|
@@ -11048,7 +11560,7 @@ harnessCommand.command("init").argument("<folder>", "AB Test Harness experiment
|
|
|
11048
11560
|
() => printSuccess(`Created ${targetPath}`)
|
|
11049
11561
|
);
|
|
11050
11562
|
});
|
|
11051
|
-
harnessCommand.command("dev").argument("<folder>", "AB Test Harness experiment folder").option("--no-open", "Print preview URL instead of opening it").action(async function
|
|
11563
|
+
harnessCommand.command("dev").argument("<folder>", "AB Test Harness experiment folder").option("--no-open", "Print preview URL instead of opening it").action(async function action25(folderArg, opts) {
|
|
11052
11564
|
const folder = resolveFolder(folderArg);
|
|
11053
11565
|
const created = await createSession(folder);
|
|
11054
11566
|
await runAbHarness(["build", folder], folder);
|
|
@@ -11115,7 +11627,7 @@ harnessCommand.command("dev").argument("<folder>", "AB Test Harness experiment f
|
|
|
11115
11627
|
process.exitCode = 0;
|
|
11116
11628
|
});
|
|
11117
11629
|
});
|
|
11118
|
-
harnessCommand.command("publish").argument("<folder>", "AB Test Harness experiment folder").action(async function
|
|
11630
|
+
harnessCommand.command("publish").argument("<folder>", "AB Test Harness experiment folder").action(async function action26(folderArg) {
|
|
11119
11631
|
const folder = resolveFolder(folderArg);
|
|
11120
11632
|
const created = await createSession(folder);
|
|
11121
11633
|
await runAbHarness(["verify", folder], folder);
|
|
@@ -11490,7 +12002,7 @@ Examples:
|
|
|
11490
12002
|
apex workspace publish ./workspace
|
|
11491
12003
|
`
|
|
11492
12004
|
);
|
|
11493
|
-
operatorCommand.command("checkout").argument("<experiment-id>", "Apex experiment ID").requiredOption("--variation <id-or-slug>", "Variation ID, slug, or name").option("--out <path>", "Target folder").action(async function
|
|
12005
|
+
operatorCommand.command("checkout").argument("<experiment-id>", "Apex experiment ID").requiredOption("--variation <id-or-slug>", "Variation ID, slug, or name").option("--out <path>", "Target folder").action(async function action27(experimentId, opts) {
|
|
11494
12006
|
try {
|
|
11495
12007
|
const creds = requireCredentials();
|
|
11496
12008
|
const expRes = await getExperiment(creds, experimentId);
|
|
@@ -11540,7 +12052,7 @@ operatorCommand.command("checkout").argument("<experiment-id>", "Apex experiment
|
|
|
11540
12052
|
throw error;
|
|
11541
12053
|
}
|
|
11542
12054
|
});
|
|
11543
|
-
operatorCommand.command("status").argument("[folder]", "Operator workspace folder", ".").action(function
|
|
12055
|
+
operatorCommand.command("status").argument("[folder]", "Operator workspace folder", ".").action(function action28(folderArg) {
|
|
11544
12056
|
try {
|
|
11545
12057
|
const folder = folderPath2(folderArg);
|
|
11546
12058
|
const session = readSession(folder);
|
|
@@ -11572,7 +12084,7 @@ operatorCommand.command("status").argument("[folder]", "Operator workspace folde
|
|
|
11572
12084
|
fail(this, error instanceof Error ? error.message : String(error));
|
|
11573
12085
|
}
|
|
11574
12086
|
});
|
|
11575
|
-
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
|
|
12087
|
+
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 action29(folderArg, opts) {
|
|
11576
12088
|
try {
|
|
11577
12089
|
const folder = folderPath2(folderArg);
|
|
11578
12090
|
const session = readSession(folder);
|
|
@@ -11651,7 +12163,7 @@ operatorCommand.command("dev").argument("[folder]", "Operator workspace folder",
|
|
|
11651
12163
|
throw error;
|
|
11652
12164
|
}
|
|
11653
12165
|
});
|
|
11654
|
-
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
|
|
12166
|
+
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 action30(folderArg, opts) {
|
|
11655
12167
|
try {
|
|
11656
12168
|
const result = await runPublish(folderArg, opts);
|
|
11657
12169
|
const build = result.build;
|
|
@@ -11695,7 +12207,7 @@ function briefLine(item) {
|
|
|
11695
12207
|
].filter(Boolean);
|
|
11696
12208
|
return parts.join("\n");
|
|
11697
12209
|
}
|
|
11698
|
-
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
|
|
12210
|
+
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 action31(opts) {
|
|
11699
12211
|
const creds = requireCredentials();
|
|
11700
12212
|
const limit = parseInt(opts.limit, 10);
|
|
11701
12213
|
const hasExperiment = opts.unbuilt ? false : opts.built ? true : void 0;
|
|
@@ -11749,7 +12261,7 @@ More results: --cursor ${res.data.pagination.next_cursor}`));
|
|
|
11749
12261
|
}
|
|
11750
12262
|
);
|
|
11751
12263
|
});
|
|
11752
|
-
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
|
|
12264
|
+
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 action32(id, opts) {
|
|
11753
12265
|
if (opts.output !== void 0) {
|
|
11754
12266
|
process.stderr.write(
|
|
11755
12267
|
`Warning: --output is deprecated for apex roadmap handoff; use --out instead. --output will be removed in v${LEGACY_OUTPUT_REMOVAL_VERSION}.
|
|
@@ -11968,7 +12480,7 @@ trackingCommand.command("qa").description("Print the QA checklist for a tracking
|
|
|
11968
12480
|
console.log(`${index + 1}. ${check}`);
|
|
11969
12481
|
});
|
|
11970
12482
|
});
|
|
11971
|
-
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
|
|
12483
|
+
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 action33(opts) {
|
|
11972
12484
|
const creds = requireCredentials();
|
|
11973
12485
|
const res = await verifyTracking(creds, {
|
|
11974
12486
|
minutes: Number.parseInt(opts.minutes, 10),
|
|
@@ -12225,7 +12737,7 @@ function printGoal(goal) {
|
|
|
12225
12737
|
console.log(` Count once: ${goal.count_once ? "yes" : "no"}`);
|
|
12226
12738
|
console.log();
|
|
12227
12739
|
}
|
|
12228
|
-
var list2 = new Command("list").alias("ls").description("List goals").option("-n, --limit <n>", "Max results", "50").action(async function
|
|
12740
|
+
var list2 = new Command("list").alias("ls").description("List goals").option("-n, --limit <n>", "Max results", "50").action(async function action34(opts) {
|
|
12229
12741
|
const creds = requireCredentials();
|
|
12230
12742
|
const res = await listGoals(creds, { limit: Number.parseInt(opts.limit, 10) });
|
|
12231
12743
|
if (!res.ok || !res.data) {
|
|
@@ -12249,7 +12761,7 @@ var list2 = new Command("list").alias("ls").description("List goals").option("-n
|
|
|
12249
12761
|
);
|
|
12250
12762
|
});
|
|
12251
12763
|
});
|
|
12252
|
-
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
|
|
12764
|
+
var create2 = new Command("create").description("Create a goal").requiredOption("--name <name>", "Goal name").requiredOption("--type <type>", "click|pageview|custom|revenue").option("--measurement <measurement>", "conversion|value", "conversion").option("--value-property <property>", "Numeric event property for value goals").option("--value-unit <unit>", "currency|number|duration_seconds|percent").option("--selector <selector>", "CSS selector for click goals").option("--text-match <text>", "Text match for click goals").option("--url-pattern <pattern>", "URL pattern for pageview goals").option("--event-name <name>", "Event name for custom goals").option("--count-once", "Count only once per visitor").option("--capture <json-or-path>", "Capture JSON array or @path").action(async function action35(opts) {
|
|
12253
12765
|
const creds = requireCredentials();
|
|
12254
12766
|
const body5 = cleanBody({
|
|
12255
12767
|
name: opts.name,
|
|
@@ -12275,10 +12787,10 @@ var create2 = new Command("create").description("Create a goal").requiredOption(
|
|
|
12275
12787
|
}
|
|
12276
12788
|
output(this, { goal: res.data }, () => printGoal(res.data));
|
|
12277
12789
|
});
|
|
12278
|
-
var
|
|
12790
|
+
var update2 = new Command("update").description("Update a goal").argument("<id>", "Goal ID").option("--name <name>", "Goal name").option("--type <type>", "click|pageview|custom|revenue").option("--measurement <measurement>", "conversion|value").option(
|
|
12279
12791
|
"--value-property <property>",
|
|
12280
12792
|
"Numeric event property for value goals; pass null to clear"
|
|
12281
|
-
).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
|
|
12793
|
+
).option("--value-unit <unit>", "currency|number|duration_seconds|percent; pass null to clear").option("--selector <selector>", "CSS selector for click goals").option("--text-match <text>", "Text match for click goals").option("--url-pattern <pattern>", "URL pattern for pageview goals").option("--event-name <name>", "Event name for custom goals").option("--count-once", "Count only once per visitor").option("--no-count-once", "Do not count only once per visitor").option("--capture <json-or-path>", "Capture JSON array or @path").action(async function action36(id, opts) {
|
|
12282
12794
|
const creds = requireCredentials();
|
|
12283
12795
|
const body5 = cleanBody({
|
|
12284
12796
|
name: opts.name,
|
|
@@ -12310,7 +12822,7 @@ var update = new Command("update").description("Update a goal").argument("<id>",
|
|
|
12310
12822
|
var test = new Command("test").description("Dry run a goal URL pattern against traffic").option(
|
|
12311
12823
|
"--pattern <regex>",
|
|
12312
12824
|
"URL regex pattern to test (matched against the full URL, as the SDK matches pageview goals)"
|
|
12313
|
-
).option("--goal <id>", "Existing goal ID whose URL pattern should be tested").option("--days <n>", "Traffic lookback days", "14").action(async function
|
|
12825
|
+
).option("--goal <id>", "Existing goal ID whose URL pattern should be tested").option("--days <n>", "Traffic lookback days", "14").action(async function action37(opts) {
|
|
12314
12826
|
if (!opts.pattern && !opts.goal)
|
|
12315
12827
|
fail(this, "Provide --pattern or --goal.", EXIT_CODES.validation);
|
|
12316
12828
|
if (opts.pattern && opts.goal)
|
|
@@ -12392,7 +12904,7 @@ var test = new Command("test").description("Dry run a goal URL pattern against t
|
|
|
12392
12904
|
}
|
|
12393
12905
|
);
|
|
12394
12906
|
});
|
|
12395
|
-
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
|
|
12907
|
+
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 action38(opts) {
|
|
12396
12908
|
const creds = requireCredentials();
|
|
12397
12909
|
const res = await suggestGoals(creds);
|
|
12398
12910
|
if (!res.ok || !res.data)
|
|
@@ -12440,7 +12952,7 @@ var suggest = new Command("suggest").description("Suggest goals and funnels from
|
|
|
12440
12952
|
);
|
|
12441
12953
|
}
|
|
12442
12954
|
});
|
|
12443
|
-
var goalsCommand = new Command("goals").description("Manage conversion and revenue goals").addCommand(list2).addCommand(create2).addCommand(
|
|
12955
|
+
var goalsCommand = new Command("goals").description("Manage conversion and revenue goals").addCommand(list2).addCommand(create2).addCommand(update2).addCommand(test).addCommand(suggest).addHelpText(
|
|
12444
12956
|
"after",
|
|
12445
12957
|
`
|
|
12446
12958
|
Examples:
|
|
@@ -12482,7 +12994,7 @@ function printFunnel(funnel) {
|
|
|
12482
12994
|
console.log(` ${step.order}. ${step.label} \u2014 ${step.url_pattern}`);
|
|
12483
12995
|
console.log();
|
|
12484
12996
|
}
|
|
12485
|
-
var list3 = new Command("list").alias("ls").description("List funnels").action(async function
|
|
12997
|
+
var list3 = new Command("list").alias("ls").description("List funnels").action(async function action39() {
|
|
12486
12998
|
const res = await listFunnels(requireCredentials());
|
|
12487
12999
|
if (!res.ok || !res.data)
|
|
12488
13000
|
fail(
|
|
@@ -12501,7 +13013,7 @@ var list3 = new Command("list").alias("ls").description("List funnels").action(a
|
|
|
12501
13013
|
)
|
|
12502
13014
|
);
|
|
12503
13015
|
});
|
|
12504
|
-
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
|
|
13016
|
+
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 action40(opts) {
|
|
12505
13017
|
const res = await createFunnel(requireCredentials(), {
|
|
12506
13018
|
name: opts.name,
|
|
12507
13019
|
steps: readSteps(this, opts.stepsFile)
|
|
@@ -12515,7 +13027,7 @@ var create3 = new Command("create").description("Create a funnel").requiredOptio
|
|
|
12515
13027
|
);
|
|
12516
13028
|
output(this, { funnel: res.data }, () => printFunnel(res.data));
|
|
12517
13029
|
});
|
|
12518
|
-
var
|
|
13030
|
+
var update3 = 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 action41(id, opts) {
|
|
12519
13031
|
const res = await updateFunnel(requireCredentials(), id, {
|
|
12520
13032
|
steps: readSteps(this, opts.stepsFile)
|
|
12521
13033
|
});
|
|
@@ -12528,7 +13040,7 @@ var update2 = new Command("update").description("Replace a funnel's steps").argu
|
|
|
12528
13040
|
);
|
|
12529
13041
|
output(this, { funnel: res.data }, () => printFunnel(res.data));
|
|
12530
13042
|
});
|
|
12531
|
-
var del2 = new Command("delete").description("Delete a funnel").argument("<id>", "Funnel ID").action(async function
|
|
13043
|
+
var del2 = new Command("delete").description("Delete a funnel").argument("<id>", "Funnel ID").action(async function action42(id) {
|
|
12532
13044
|
const res = await deleteFunnel(requireCredentials(), id);
|
|
12533
13045
|
if (!res.ok)
|
|
12534
13046
|
fail(
|
|
@@ -12539,7 +13051,7 @@ var del2 = new Command("delete").description("Delete a funnel").argument("<id>",
|
|
|
12539
13051
|
);
|
|
12540
13052
|
output(this, { deleted: true, id }, () => console.log(`Deleted funnel ${id}`));
|
|
12541
13053
|
});
|
|
12542
|
-
var run = new Command("run").description("Run funnel analysis").argument("<id>", "Funnel ID").option("--days <n>", "Analysis window", "30").action(async function
|
|
13054
|
+
var run = new Command("run").description("Run funnel analysis").argument("<id>", "Funnel ID").option("--days <n>", "Analysis window", "30").action(async function action43(id, opts) {
|
|
12543
13055
|
if (!/^[1-9]\d*$/.test(opts.days))
|
|
12544
13056
|
fail(this, "--days must be a positive integer", EXIT_CODES.validation);
|
|
12545
13057
|
const days = Number.parseInt(opts.days, 10);
|
|
@@ -12563,11 +13075,11 @@ var run = new Command("run").description("Run funnel analysis").argument("<id>",
|
|
|
12563
13075
|
)
|
|
12564
13076
|
);
|
|
12565
13077
|
});
|
|
12566
|
-
var validate2 = new Command("validate").description("Validate funnel steps without writing").requiredOption("--steps-file <file>", "JSON array of { label, pattern }").action(function
|
|
13078
|
+
var validate2 = new Command("validate").description("Validate funnel steps without writing").requiredOption("--steps-file <file>", "JSON array of { label, pattern }").action(function action44(opts) {
|
|
12567
13079
|
const steps = readSteps(this, opts.stepsFile);
|
|
12568
13080
|
output(this, { valid: true, steps }, () => console.log(`Valid funnel steps: ${steps.length}`));
|
|
12569
13081
|
});
|
|
12570
|
-
var funnelsCommand = new Command("funnels").description("Manage analytics funnels").addCommand(list3).addCommand(create3).addCommand(
|
|
13082
|
+
var funnelsCommand = new Command("funnels").description("Manage analytics funnels").addCommand(list3).addCommand(create3).addCommand(update3).addCommand(del2).addCommand(run).addCommand(validate2);
|
|
12571
13083
|
|
|
12572
13084
|
// src/commands/environments.ts
|
|
12573
13085
|
init_source();
|
|
@@ -12624,11 +13136,11 @@ async function load2(command) {
|
|
|
12624
13136
|
}
|
|
12625
13137
|
return result.data;
|
|
12626
13138
|
}
|
|
12627
|
-
var list4 = new Command("list").alias("ls").description("List runtime environments and mapped domains").action(async function
|
|
13139
|
+
var list4 = new Command("list").alias("ls").description("List runtime environments and mapped domains").action(async function action45() {
|
|
12628
13140
|
const { environments } = await load2(this);
|
|
12629
13141
|
output(this, { environments }, () => printEnvironments(environments.items));
|
|
12630
13142
|
});
|
|
12631
|
-
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
|
|
13143
|
+
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 action46(domain, opts) {
|
|
12632
13144
|
const normalizedDomain = normalizeEnvironmentDomain(domain);
|
|
12633
13145
|
if (!normalizedDomain) {
|
|
12634
13146
|
fail(this, "Domain must be a valid hostname containing a dot", EXIT_CODES.validation);
|
|
@@ -12675,7 +13187,7 @@ var setDomain = new Command("set-domain").description("Map a domain to a runtime
|
|
|
12675
13187
|
() => printEnvironments(result.data.environments.items)
|
|
12676
13188
|
);
|
|
12677
13189
|
});
|
|
12678
|
-
var removeDomain = new Command("remove-domain").description("Remove a domain mapping").argument("<domain>", "Domain to remove").action(async function
|
|
13190
|
+
var removeDomain = new Command("remove-domain").description("Remove a domain mapping").argument("<domain>", "Domain to remove").action(async function action47(domain) {
|
|
12679
13191
|
const normalizedDomain = normalizeEnvironmentDomain(domain);
|
|
12680
13192
|
if (!normalizedDomain) {
|
|
12681
13193
|
fail(this, "Domain must be a valid hostname containing a dot", EXIT_CODES.validation);
|
|
@@ -12739,11 +13251,11 @@ function printConsent(data) {
|
|
|
12739
13251
|
`Split: ${data.prerequisites.showVariationsRule && data.prerequisites.startTrackingRule ? "canonical" : "off"}`
|
|
12740
13252
|
);
|
|
12741
13253
|
}
|
|
12742
|
-
var get2 = new Command("get").description("Show consent settings").action(async function
|
|
13254
|
+
var get2 = new Command("get").description("Show consent settings").action(async function action48() {
|
|
12743
13255
|
const data = await load3(this);
|
|
12744
13256
|
output(this, data, () => printConsent(data));
|
|
12745
13257
|
});
|
|
12746
|
-
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
|
|
13258
|
+
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 action49(opts) {
|
|
12747
13259
|
const consentMode = {
|
|
12748
13260
|
enabled: true,
|
|
12749
13261
|
provider: opts.provider,
|
|
@@ -12764,7 +13276,7 @@ var set = new Command("set").description("Enable and configure consent gating").
|
|
|
12764
13276
|
warn(this, result.data.precompile);
|
|
12765
13277
|
output(this, result.data, () => printConsent(result.data));
|
|
12766
13278
|
});
|
|
12767
|
-
var off = new Command("off").description("Disable consent gating").action(async function
|
|
13279
|
+
var off = new Command("off").description("Disable consent gating").action(async function action50() {
|
|
12768
13280
|
const result = await updateConsent(requireCredentials(), { consentMode: { enabled: false } });
|
|
12769
13281
|
if (!result.ok || !result.data)
|
|
12770
13282
|
fail(
|
|
@@ -13078,8 +13590,8 @@ function score2(path24, patterns) {
|
|
|
13078
13590
|
0
|
|
13079
13591
|
);
|
|
13080
13592
|
}
|
|
13081
|
-
function best(
|
|
13082
|
-
const ranked =
|
|
13593
|
+
function best(fields3, patterns, numeric2 = false) {
|
|
13594
|
+
const ranked = fields3.filter(
|
|
13083
13595
|
(field) => !numeric2 || field.numericType === true || field.values.some((value) => typeof value === "number") || field.magnitude !== void 0
|
|
13084
13596
|
).map((field) => ({ field, score: score2(field.path.toLowerCase(), patterns) })).filter((item) => item.score > 0).sort((a, b) => b.score - a.score || (b.field.magnitude ?? 0) - (a.field.magnitude ?? 0));
|
|
13085
13597
|
if (!ranked.length) return [];
|
|
@@ -13104,15 +13616,15 @@ function proposeTriggers(report) {
|
|
|
13104
13616
|
continue;
|
|
13105
13617
|
const descriptors = candidate.fields ?? candidate.paths;
|
|
13106
13618
|
const payload = candidate.properties ?? candidate.payload ?? candidate;
|
|
13107
|
-
const
|
|
13619
|
+
const fields3 = descriptors !== void 0 ? fieldsFrom(descriptors, "", true) : fieldsFrom(payload, "", isDescriptorArray(payload));
|
|
13108
13620
|
const revenues = best(
|
|
13109
|
-
|
|
13621
|
+
fields3,
|
|
13110
13622
|
[/revenue/, /total.*amount|amount.*total/, /grand.*total|total/, /value|amount/],
|
|
13111
13623
|
true
|
|
13112
13624
|
);
|
|
13113
|
-
const orderIds = best(
|
|
13114
|
-
const currencies = best(
|
|
13115
|
-
const items = best(
|
|
13625
|
+
const orderIds = best(fields3, [/order.*id|id.*order/, /transaction.*id/, /(^|\.)id$/]);
|
|
13626
|
+
const currencies = best(fields3, [/currency/, /(^|\.)cur(rency)?$/]);
|
|
13627
|
+
const items = best(fields3, [/(^|\.)((line_?)?items|products)$/]);
|
|
13116
13628
|
if (!revenues.length || !orderIds.length || !currencies.length) continue;
|
|
13117
13629
|
for (const revenue of revenues)
|
|
13118
13630
|
for (const orderId of orderIds)
|
|
@@ -13662,11 +14174,11 @@ async function patchWithRetry(command, build) {
|
|
|
13662
14174
|
warn2(command, result.data.precompile);
|
|
13663
14175
|
return result.data;
|
|
13664
14176
|
}
|
|
13665
|
-
var list5 = new Command("list").alias("ls").description("List install domains").action(async function
|
|
14177
|
+
var list5 = new Command("list").alias("ls").description("List install domains").action(async function action51() {
|
|
13666
14178
|
const data = await load4(this);
|
|
13667
14179
|
output(this, data, () => printDomains(data));
|
|
13668
14180
|
});
|
|
13669
|
-
var add = new Command("add").description("Add install domains").argument("<domain...>").action(async function
|
|
14181
|
+
var add = new Command("add").description("Add install domains").argument("<domain...>").action(async function action52(domain) {
|
|
13670
14182
|
const added = domain.map(normalizeDomain);
|
|
13671
14183
|
const data = await patchWithRetry(
|
|
13672
14184
|
this,
|
|
@@ -13674,7 +14186,7 @@ var add = new Command("add").description("Add install domains").argument("<domai
|
|
|
13674
14186
|
);
|
|
13675
14187
|
output(this, data, () => printDomains(data));
|
|
13676
14188
|
});
|
|
13677
|
-
var remove = new Command("remove").description("Remove install domains").argument("<domain...>").action(async function
|
|
14189
|
+
var remove = new Command("remove").description("Remove install domains").argument("<domain...>").action(async function action53(domain) {
|
|
13678
14190
|
const removeSet = new Set(domain.map(normalizeDomain));
|
|
13679
14191
|
const data = await patchWithRetry(
|
|
13680
14192
|
this,
|
|
@@ -13682,7 +14194,7 @@ var remove = new Command("remove").description("Remove install domains").argumen
|
|
|
13682
14194
|
);
|
|
13683
14195
|
output(this, data, () => printDomains(data));
|
|
13684
14196
|
});
|
|
13685
|
-
var set3 = new Command("set").description("Replace install domains").argument("<domain...>").action(async function
|
|
14197
|
+
var set3 = new Command("set").description("Replace install domains").argument("<domain...>").action(async function action54(domain) {
|
|
13686
14198
|
const replacement = domain.map(normalizeDomain);
|
|
13687
14199
|
const data = await patchWithRetry(this, () => replacement);
|
|
13688
14200
|
output(this, data, () => printDomains(data));
|
|
@@ -13774,14 +14286,14 @@ async function patchWithRetry2(command, build) {
|
|
|
13774
14286
|
);
|
|
13775
14287
|
return result.data;
|
|
13776
14288
|
}
|
|
13777
|
-
var get4 = new Command("get").description("Show cross-domain handover settings").action(async function
|
|
14289
|
+
var get4 = new Command("get").description("Show cross-domain handover settings").action(async function action55() {
|
|
13778
14290
|
const data = await load5(this);
|
|
13779
14291
|
output(this, data, () => printSettings(data.externalTracking));
|
|
13780
14292
|
});
|
|
13781
14293
|
var set4 = new Command("set").description("Enable cross-domain identity handover").requiredOption("--domain <domain...>", "Bound handover domain(s)").option("--param <param>", "Handover query parameter").option(
|
|
13782
14294
|
"--signed",
|
|
13783
14295
|
"Require signed handover tokens (the default; unsigning is a platform-support action)"
|
|
13784
|
-
).action(async function
|
|
14296
|
+
).action(async function action56(opts) {
|
|
13785
14297
|
const domains = opts.domain.map(normalizeDomain2);
|
|
13786
14298
|
if (domains.some((domain) => !domain))
|
|
13787
14299
|
fail(
|
|
@@ -13801,14 +14313,14 @@ var set4 = new Command("set").description("Enable cross-domain identity handover
|
|
|
13801
14313
|
}));
|
|
13802
14314
|
output(this, data, () => printSettings(data.externalTracking));
|
|
13803
14315
|
});
|
|
13804
|
-
var off2 = new Command("off").description("Disable cross-domain handover").action(async function
|
|
14316
|
+
var off2 = new Command("off").description("Disable cross-domain handover").action(async function action57() {
|
|
13805
14317
|
const data = await patchWithRetry2(this, (current) => ({
|
|
13806
14318
|
externalTracking: { domains: [], signed: true },
|
|
13807
14319
|
ifRevision: current.revision
|
|
13808
14320
|
}));
|
|
13809
14321
|
output(this, data, () => printSettings(data.externalTracking));
|
|
13810
14322
|
});
|
|
13811
|
-
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
|
|
14323
|
+
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 action58(opts) {
|
|
13812
14324
|
const config = await load5(this);
|
|
13813
14325
|
const target = normalizeDomain2(opts.toDomain);
|
|
13814
14326
|
if (!target) fail(this, "--to-domain must be a valid hostname", EXIT_CODES.validation);
|
|
@@ -14851,7 +15363,7 @@ var doctorCommand = new Command("doctor").description("Check shop configuration
|
|
|
14851
15363
|
).option("--check <id>", "Run one check (for example purchase_capture)").option(
|
|
14852
15364
|
"--worker <url>",
|
|
14853
15365
|
`Apex Worker base URL; defaults to DRIP_WORKER_URL or ${PRODUCTION_WORKER_BASE_URL}`
|
|
14854
|
-
).action(async function
|
|
15366
|
+
).action(async function action59(options) {
|
|
14855
15367
|
const data = await runDoctor(options);
|
|
14856
15368
|
if (options.check && data.checks.length === 0)
|
|
14857
15369
|
fail(this, `Unknown doctor check: ${options.check}`, EXIT_CODES.validation);
|
|
@@ -14873,7 +15385,7 @@ function printDoctorReport(checks) {
|
|
|
14873
15385
|
var consentRateCommand = new Command("consent-rate").description("Estimate analytics consent acceptance from visitor series").option(
|
|
14874
15386
|
"--since <date>",
|
|
14875
15387
|
"Consent gating start date (YYYY-MM-DD); splits the baseline and gated windows"
|
|
14876
|
-
).action(async function
|
|
15388
|
+
).action(async function action60(opts) {
|
|
14877
15389
|
if (opts.since !== void 0 && parseDay(opts.since) === null)
|
|
14878
15390
|
fail(this, "--since must be a calendar date in YYYY-MM-DD form", EXIT_CODES.validation);
|
|
14879
15391
|
const result = await getVisitorSeries(requireCredentials(), { days: VISITOR_SERIES_DAYS });
|
|
@@ -14933,7 +15445,7 @@ function analyticsSubcommand(name, description, defaultDays) {
|
|
|
14933
15445
|
command.option("--planning-days <n>", "Planning window in days", "180");
|
|
14934
15446
|
command.option("--limit <n>", "Maximum page rows", "50");
|
|
14935
15447
|
}
|
|
14936
|
-
return command.action(async function
|
|
15448
|
+
return command.action(async function action74(opts) {
|
|
14937
15449
|
const result = await getAnalytics(requireCredentials(), name, {
|
|
14938
15450
|
days: positiveInteger(this, opts.days, "--days"),
|
|
14939
15451
|
planningDays: positiveInteger(this, opts.planningDays, "--planning-days"),
|
|
@@ -14953,7 +15465,7 @@ function urlsCommand() {
|
|
|
14953
15465
|
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(
|
|
14954
15466
|
"--match <regex>",
|
|
14955
15467
|
"Client-side regex filter; exhausts inventory pages before filtering"
|
|
14956
|
-
).option("--limit <n>", "Maximum rows to print", "50").action(async function
|
|
15468
|
+
).option("--limit <n>", "Maximum rows to print", "50").action(async function action74(opts) {
|
|
14957
15469
|
const creds = requireCredentials();
|
|
14958
15470
|
const days = positiveInteger(this, opts.days, "--days") ?? 14;
|
|
14959
15471
|
const displayLimit = Math.min(200, positiveInteger(this, opts.limit, "--limit") ?? 50);
|
|
@@ -15147,10 +15659,10 @@ function decodeBody(raw, header, limits, hostname, inputTruncated = false) {
|
|
|
15147
15659
|
const chunks = [];
|
|
15148
15660
|
let bytes = 0;
|
|
15149
15661
|
let settled = false;
|
|
15150
|
-
const settle = (
|
|
15662
|
+
const settle = (action74) => {
|
|
15151
15663
|
if (settled) return;
|
|
15152
15664
|
settled = true;
|
|
15153
|
-
|
|
15665
|
+
action74();
|
|
15154
15666
|
};
|
|
15155
15667
|
stream.on("data", (chunk) => {
|
|
15156
15668
|
if (settled) return;
|
|
@@ -15221,11 +15733,11 @@ function pinnedFetch(url, addresses, limits) {
|
|
|
15221
15733
|
else callback(null, addresses[0], (0, import_node_net.isIP)(addresses[0]));
|
|
15222
15734
|
};
|
|
15223
15735
|
let settled = false;
|
|
15224
|
-
const settle = (
|
|
15736
|
+
const settle = (action74) => {
|
|
15225
15737
|
if (settled) return;
|
|
15226
15738
|
settled = true;
|
|
15227
15739
|
clearTimeout(deadline);
|
|
15228
|
-
|
|
15740
|
+
action74();
|
|
15229
15741
|
};
|
|
15230
15742
|
const req = send3(
|
|
15231
15743
|
url,
|
|
@@ -15560,7 +16072,7 @@ async function checkServedConfig(configUrl, workerBaseUrl, workerBaseSource) {
|
|
|
15560
16072
|
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(
|
|
15561
16073
|
"--worker-url <url>",
|
|
15562
16074
|
"Apex Worker base URL that serves /config (defaults to the URL in the served snippet, then DRIP_WORKER_URL, then the production worker)"
|
|
15563
|
-
).option("--environment <key>", "Environment key to request from the worker", "production").action(async function
|
|
16075
|
+
).option("--environment <key>", "Environment key to request from the worker", "production").action(async function action61(opts) {
|
|
15564
16076
|
const creds = requireCredentials();
|
|
15565
16077
|
const shopId = opts.shopId?.trim() || process.env.DRIP_SHOP_ID?.trim() || null;
|
|
15566
16078
|
if (!shopId)
|
|
@@ -15860,11 +16372,11 @@ function write(name) {
|
|
|
15860
16372
|
});
|
|
15861
16373
|
return c;
|
|
15862
16374
|
}
|
|
15863
|
-
function lifecycle(
|
|
15864
|
-
return new Command(
|
|
15865
|
-
const r = await transitionManagedDelivery(requireCredentials(), kind, id,
|
|
16375
|
+
function lifecycle(action74) {
|
|
16376
|
+
return new Command(action74).description(`${action74[0].toUpperCase()}${action74.slice(1)} a flag`).argument("<id>").action(async function(id) {
|
|
16377
|
+
const r = await transitionManagedDelivery(requireCredentials(), kind, id, action74);
|
|
15866
16378
|
if (!r.ok || !r.data)
|
|
15867
|
-
fail(this, r.error ?? `Failed to ${
|
|
16379
|
+
fail(this, r.error ?? `Failed to ${action74}`, classifyStatus(r.status), r.errorDetails);
|
|
15868
16380
|
output(this, { flag: r.data }, () => detail(r.data));
|
|
15869
16381
|
});
|
|
15870
16382
|
}
|
|
@@ -15902,13 +16414,74 @@ function parse(command, value) {
|
|
|
15902
16414
|
);
|
|
15903
16415
|
}
|
|
15904
16416
|
}
|
|
15905
|
-
function
|
|
16417
|
+
function collect2(value, previous) {
|
|
16418
|
+
return [...previous, value];
|
|
16419
|
+
}
|
|
16420
|
+
function customFieldsRecord(value) {
|
|
16421
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
16422
|
+
}
|
|
16423
|
+
function typedFieldValue(definition, raw) {
|
|
16424
|
+
if (raw === "null") return null;
|
|
16425
|
+
if (raw.trim() === "") return null;
|
|
16426
|
+
switch (definition?.type) {
|
|
16427
|
+
case "number": {
|
|
16428
|
+
const parsed = Number(raw.trim());
|
|
16429
|
+
return Number.isFinite(parsed) ? parsed : raw;
|
|
16430
|
+
}
|
|
16431
|
+
case "boolean": {
|
|
16432
|
+
const trimmed = raw.trim().toLowerCase();
|
|
16433
|
+
if (["true", "yes", "y", "1"].includes(trimmed)) return true;
|
|
16434
|
+
if (["false", "no", "n", "0"].includes(trimmed)) return false;
|
|
16435
|
+
return raw;
|
|
16436
|
+
}
|
|
16437
|
+
default:
|
|
16438
|
+
return raw;
|
|
16439
|
+
}
|
|
16440
|
+
}
|
|
16441
|
+
async function customFieldsFromPairs(command, pairs) {
|
|
16442
|
+
if (pairs.length === 0) return void 0;
|
|
16443
|
+
const parsedPairs = pairs.map((pair) => {
|
|
16444
|
+
const separator = pair.indexOf("=");
|
|
16445
|
+
if (separator <= 0)
|
|
16446
|
+
fail(command, `Invalid --field "${pair}": use key=value.`, EXIT_CODES.validation);
|
|
16447
|
+
return { key: pair.slice(0, separator), raw: pair.slice(separator + 1) };
|
|
16448
|
+
});
|
|
16449
|
+
const fields3 = await listBacklogFields(requireCredentials());
|
|
16450
|
+
if (!fields3.ok || !fields3.data) {
|
|
16451
|
+
process.stderr.write(
|
|
16452
|
+
`Could not read the shop's field definitions (${fields3.error ?? `HTTP ${fields3.status}`}). Sending every --field value as text, so a number or yes/no field will answer 422. Add the backlog:read scope to this key to type them.
|
|
16453
|
+
`
|
|
16454
|
+
);
|
|
16455
|
+
}
|
|
16456
|
+
const byKey = new Map(
|
|
16457
|
+
(fields3.ok && fields3.data ? fields3.data.data : []).map((field) => [field.key, field])
|
|
16458
|
+
);
|
|
16459
|
+
const values = {};
|
|
16460
|
+
for (const { key, raw } of parsedPairs) values[key] = typedFieldValue(byKey.get(key), raw);
|
|
16461
|
+
return values;
|
|
16462
|
+
}
|
|
16463
|
+
function detail2(x, labels) {
|
|
15906
16464
|
console.log();
|
|
15907
16465
|
console.log(source_default.bold(String(x.title ?? x.label ?? x.key ?? x.id)));
|
|
15908
16466
|
console.log(` ID: ${source_default.dim(x.id)}`);
|
|
15909
16467
|
if (x.status) console.log(` State: ${x.status}`);
|
|
16468
|
+
const custom = customFieldsRecord(x.custom_fields);
|
|
16469
|
+
if (Object.keys(custom).length > 0) {
|
|
16470
|
+
console.log();
|
|
16471
|
+
for (const [key, value] of Object.entries(custom)) {
|
|
16472
|
+
console.log(` ${labels.get(key) ?? key}: ${value === null ? "-" : String(value)}`);
|
|
16473
|
+
}
|
|
16474
|
+
}
|
|
15910
16475
|
console.log();
|
|
15911
16476
|
}
|
|
16477
|
+
async function fieldLabels(command, item) {
|
|
16478
|
+
if (shouldUseJson(command)) return /* @__PURE__ */ new Map();
|
|
16479
|
+
if (Object.keys(customFieldsRecord(item.custom_fields)).length === 0) return /* @__PURE__ */ new Map();
|
|
16480
|
+
const fields3 = await listBacklogFields(requireCredentials());
|
|
16481
|
+
return new Map(
|
|
16482
|
+
(fields3.ok && fields3.data ? fields3.data.data : []).map((field) => [field.key, field.label])
|
|
16483
|
+
);
|
|
16484
|
+
}
|
|
15912
16485
|
var list7 = new Command("list").alias("ls").description("List backlog").option("-n, --limit <n>", "Max results", "50").option("--cursor <cursor>", "Continue from cursor").option("--include-archived", "Include archived backlog items").action(async function(o) {
|
|
15913
16486
|
const r = await listInsights(
|
|
15914
16487
|
requireCredentials(),
|
|
@@ -15937,27 +16510,35 @@ var get6 = new Command("get").description("Get a backlog item").argument("<id>",
|
|
|
15937
16510
|
const r = await getInsight(requireCredentials(), kind2, id);
|
|
15938
16511
|
if (!r.ok || !r.data)
|
|
15939
16512
|
fail(this, r.error ?? "Failed to get", classifyStatus(r.status), r.errorDetails);
|
|
15940
|
-
|
|
16513
|
+
const labels = await fieldLabels(this, r.data);
|
|
16514
|
+
output(this, { backlog_item: r.data }, () => detail2(r.data, labels));
|
|
15941
16515
|
});
|
|
15942
|
-
function write2(
|
|
15943
|
-
const c = new Command(
|
|
15944
|
-
`${
|
|
16516
|
+
function write2(action74) {
|
|
16517
|
+
const c = new Command(action74).description(
|
|
16518
|
+
`${action74 === "create" ? "Create" : "Update"} a ${singular}`
|
|
15945
16519
|
);
|
|
15946
|
-
if (
|
|
15947
|
-
c.argument("[json]", "Inline JSON, @file, or file path").option(
|
|
15948
|
-
"--
|
|
15949
|
-
"
|
|
16520
|
+
if (action74 === "update") c.argument("<id>", `${singular} ID`);
|
|
16521
|
+
c.argument("[json]", "Inline JSON, @file, or file path").option("--file <path>", "JSON body file").option(
|
|
16522
|
+
"--field <key=value>",
|
|
16523
|
+
"Set one of the shop's own fields; repeatable. Use key=null to clear it.",
|
|
16524
|
+
collect2,
|
|
16525
|
+
[]
|
|
15950
16526
|
);
|
|
15951
16527
|
c.action(async function(...args) {
|
|
15952
16528
|
const o = this.opts();
|
|
15953
|
-
const id =
|
|
15954
|
-
const raw = o.file ? `@${o.file}` : String(args[
|
|
16529
|
+
const id = action74 === "update" ? String(args[0]) : void 0;
|
|
16530
|
+
const raw = o.file ? `@${o.file}` : String(args[action74 === "update" ? 1 : 0] ?? "") || void 0;
|
|
15955
16531
|
const body5 = parse(this, raw);
|
|
16532
|
+
const customFields = await customFieldsFromPairs(this, o.field);
|
|
16533
|
+
if (customFields) {
|
|
16534
|
+
body5.custom_fields = { ...customFieldsRecord(body5.custom_fields), ...customFields };
|
|
16535
|
+
}
|
|
15956
16536
|
if (!Object.keys(body5).length) fail(this, "Provide a JSON body.", EXIT_CODES.validation);
|
|
15957
|
-
const r =
|
|
16537
|
+
const r = action74 === "create" ? await createInsight(requireCredentials(), kind2, body5) : await updateInsight(requireCredentials(), kind2, id, body5);
|
|
15958
16538
|
if (!r.ok || !r.data)
|
|
15959
|
-
fail(this, r.error ?? `Failed to ${
|
|
15960
|
-
|
|
16539
|
+
fail(this, r.error ?? `Failed to ${action74}`, classifyStatus(r.status), r.errorDetails);
|
|
16540
|
+
const labels = await fieldLabels(this, r.data);
|
|
16541
|
+
output(this, { backlog_item: r.data }, () => detail2(r.data, labels));
|
|
15961
16542
|
});
|
|
15962
16543
|
return c;
|
|
15963
16544
|
}
|
|
@@ -16002,7 +16583,99 @@ var remove2 = new Command("delete").description("Delete a backlog item").argumen
|
|
|
16002
16583
|
fail(this, r.error ?? "Failed to delete", classifyStatus(r.status), r.errorDetails);
|
|
16003
16584
|
output(this, { deleted: true, id }, () => console.log(`Deleted ${id}`));
|
|
16004
16585
|
});
|
|
16005
|
-
|
|
16586
|
+
function fieldDetail(field) {
|
|
16587
|
+
console.log();
|
|
16588
|
+
console.log(source_default.bold(field.label));
|
|
16589
|
+
console.log(` ID: ${source_default.dim(field.id)}`);
|
|
16590
|
+
console.log(` Key: ${field.key}`);
|
|
16591
|
+
console.log(` Type: ${field.type}`);
|
|
16592
|
+
if (field.options.length > 0) console.log(` Choices: ${field.options.join(", ")}`);
|
|
16593
|
+
console.log(` Required: ${field.required ? "yes" : "no"}`);
|
|
16594
|
+
console.log(` Visible to clients: ${field.client_visible ? "yes" : "no"}`);
|
|
16595
|
+
if (field.archived_at) console.log(` Archived: ${field.archived_at}`);
|
|
16596
|
+
console.log();
|
|
16597
|
+
}
|
|
16598
|
+
function boolOption(command, flag, value) {
|
|
16599
|
+
if (value === void 0) return void 0;
|
|
16600
|
+
const trimmed = value.trim().toLowerCase();
|
|
16601
|
+
if (["true", "yes", "y", "1"].includes(trimmed)) return true;
|
|
16602
|
+
if (["false", "no", "n", "0"].includes(trimmed)) return false;
|
|
16603
|
+
fail(command, `Invalid ${flag} "${value}": use true or false.`, EXIT_CODES.validation);
|
|
16604
|
+
}
|
|
16605
|
+
var fieldsList = new Command("list").alias("ls").description("List the shop's idea fields").option("--include-archived", "Include archived fields").action(async function(o) {
|
|
16606
|
+
const r = await listBacklogFields(requireCredentials(), o.includeArchived);
|
|
16607
|
+
if (!r.ok || !r.data)
|
|
16608
|
+
fail(this, r.error ?? "Failed to list fields", classifyStatus(r.status), r.errorDetails);
|
|
16609
|
+
output(
|
|
16610
|
+
this,
|
|
16611
|
+
{ fields: r.data.data },
|
|
16612
|
+
() => printTable(
|
|
16613
|
+
["KEY", "LABEL", "TYPE", "REQUIRED", "CLIENT"],
|
|
16614
|
+
r.data.data.map((field) => [
|
|
16615
|
+
field.key,
|
|
16616
|
+
field.label,
|
|
16617
|
+
field.type,
|
|
16618
|
+
field.required ? "yes" : "no",
|
|
16619
|
+
field.client_visible ? "yes" : "no"
|
|
16620
|
+
]),
|
|
16621
|
+
[24, 28, 14, 10, 8]
|
|
16622
|
+
)
|
|
16623
|
+
);
|
|
16624
|
+
});
|
|
16625
|
+
var fieldsCreate = new Command("create").description("Add an idea field").requiredOption("--label <text>", "What the field is called").requiredOption(
|
|
16626
|
+
"--type <type>",
|
|
16627
|
+
"short_text, long_text, number, boolean, single_choice, date, url or person"
|
|
16628
|
+
).option("--key <key>", "Field key; derived from the label when omitted").option(
|
|
16629
|
+
"--option <value>",
|
|
16630
|
+
"A choice for a single_choice field; repeatable",
|
|
16631
|
+
collect2,
|
|
16632
|
+
[]
|
|
16633
|
+
).option("--required <true|false>", "Every idea must carry a value").option("--client-visible <true|false>", "Share the value with the client").action(async function(o) {
|
|
16634
|
+
const required = boolOption(this, "--required", o.required);
|
|
16635
|
+
const clientVisible = boolOption(this, "--client-visible", o.clientVisible);
|
|
16636
|
+
const r = await createBacklogField(requireCredentials(), {
|
|
16637
|
+
label: o.label,
|
|
16638
|
+
type: o.type,
|
|
16639
|
+
...o.key ? { key: o.key } : {},
|
|
16640
|
+
...o.option.length > 0 ? { options: o.option } : {},
|
|
16641
|
+
...required === void 0 ? {} : { required },
|
|
16642
|
+
...clientVisible === void 0 ? {} : { client_visible: clientVisible }
|
|
16643
|
+
});
|
|
16644
|
+
if (!r.ok || !r.data)
|
|
16645
|
+
fail(this, r.error ?? "Failed to create field", classifyStatus(r.status), r.errorDetails);
|
|
16646
|
+
output(this, { field: r.data }, () => fieldDetail(r.data));
|
|
16647
|
+
});
|
|
16648
|
+
var fieldsUpdate = new Command("update").description("Update an idea field").argument("<id>", "field ID").option("--label <text>", "What the field is called").option(
|
|
16649
|
+
"--option <value>",
|
|
16650
|
+
"Replace the choices of a single_choice field; repeatable",
|
|
16651
|
+
collect2,
|
|
16652
|
+
[]
|
|
16653
|
+
).option("--required <true|false>", "Every idea must carry a value").option("--client-visible <true|false>", "Share the value with the client").option("--archived <true|false>", "Archive or restore the field").action(async function(id, o) {
|
|
16654
|
+
const required = boolOption(this, "--required", o.required);
|
|
16655
|
+
const clientVisible = boolOption(this, "--client-visible", o.clientVisible);
|
|
16656
|
+
const archived = boolOption(this, "--archived", o.archived);
|
|
16657
|
+
const body5 = {
|
|
16658
|
+
...o.label ? { label: o.label } : {},
|
|
16659
|
+
...o.option.length > 0 ? { options: o.option } : {},
|
|
16660
|
+
...required === void 0 ? {} : { required },
|
|
16661
|
+
...clientVisible === void 0 ? {} : { client_visible: clientVisible },
|
|
16662
|
+
...archived === void 0 ? {} : { archived }
|
|
16663
|
+
};
|
|
16664
|
+
if (!Object.keys(body5).length)
|
|
16665
|
+
fail(this, "Provide at least one field option to change.", EXIT_CODES.validation);
|
|
16666
|
+
const r = await updateBacklogField(requireCredentials(), id, body5);
|
|
16667
|
+
if (!r.ok || !r.data)
|
|
16668
|
+
fail(this, r.error ?? "Failed to update field", classifyStatus(r.status), r.errorDetails);
|
|
16669
|
+
output(this, { field: r.data }, () => fieldDetail(r.data));
|
|
16670
|
+
});
|
|
16671
|
+
var fieldsArchive = new Command("archive").description("Archive an idea field; values already on ideas are kept").argument("<id>", "field ID").action(async function(id) {
|
|
16672
|
+
const r = await archiveBacklogField(requireCredentials(), id);
|
|
16673
|
+
if (!r.ok || !r.data)
|
|
16674
|
+
fail(this, r.error ?? "Failed to archive field", classifyStatus(r.status), r.errorDetails);
|
|
16675
|
+
output(this, { field: r.data }, () => fieldDetail(r.data));
|
|
16676
|
+
});
|
|
16677
|
+
var fields2 = new Command("fields").description("Manage the shop's own idea fields").addCommand(fieldsList).addCommand(fieldsCreate).addCommand(fieldsUpdate).addCommand(fieldsArchive);
|
|
16678
|
+
var backlogCommand = new Command("backlog").description("Manage backlog").addCommand(list7).addCommand(get6).addCommand(write2("create")).addCommand(write2("update")).addCommand(archive).addCommand(restore).addCommand(remove2).addCommand(fields2);
|
|
16006
16679
|
|
|
16007
16680
|
// src/commands/signals.ts
|
|
16008
16681
|
var import_node_fs19 = __toESM(require("node:fs"));
|
|
@@ -16064,24 +16737,24 @@ var get7 = new Command("get").description("Get a signal").argument("<id>", "sign
|
|
|
16064
16737
|
fail(this, r.error ?? "Failed to get", classifyStatus(r.status), r.errorDetails);
|
|
16065
16738
|
output(this, { signal: r.data }, () => detail3(r.data));
|
|
16066
16739
|
});
|
|
16067
|
-
function write3(
|
|
16068
|
-
const c = new Command(
|
|
16069
|
-
`${
|
|
16740
|
+
function write3(action74) {
|
|
16741
|
+
const c = new Command(action74).description(
|
|
16742
|
+
`${action74 === "create" ? "Create" : "Update"} a ${singular2}`
|
|
16070
16743
|
);
|
|
16071
|
-
if (
|
|
16744
|
+
if (action74 === "update") c.argument("<id>", `${singular2} ID`);
|
|
16072
16745
|
c.argument("[json]", "Inline JSON, @file, or file path").option(
|
|
16073
16746
|
"--file <path>",
|
|
16074
16747
|
"JSON body file"
|
|
16075
16748
|
);
|
|
16076
16749
|
c.action(async function(...args) {
|
|
16077
16750
|
const o = this.opts();
|
|
16078
|
-
const id =
|
|
16079
|
-
const raw = o.file ? `@${o.file}` : String(args[
|
|
16751
|
+
const id = action74 === "update" ? String(args[0]) : void 0;
|
|
16752
|
+
const raw = o.file ? `@${o.file}` : String(args[action74 === "update" ? 1 : 0] ?? "") || void 0;
|
|
16080
16753
|
const body5 = parse2(this, raw);
|
|
16081
16754
|
if (!Object.keys(body5).length) fail(this, "Provide a JSON body.", EXIT_CODES.validation);
|
|
16082
|
-
const r =
|
|
16755
|
+
const r = action74 === "create" ? await createInsight(requireCredentials(), kind3, body5) : await updateInsight(requireCredentials(), kind3, id, body5);
|
|
16083
16756
|
if (!r.ok || !r.data)
|
|
16084
|
-
fail(this, r.error ?? `Failed to ${
|
|
16757
|
+
fail(this, r.error ?? `Failed to ${action74}`, classifyStatus(r.status), r.errorDetails);
|
|
16085
16758
|
output(this, { signal: r.data }, () => detail3(r.data));
|
|
16086
16759
|
});
|
|
16087
16760
|
return c;
|
|
@@ -16173,11 +16846,11 @@ function write4(name) {
|
|
|
16173
16846
|
});
|
|
16174
16847
|
return c;
|
|
16175
16848
|
}
|
|
16176
|
-
function lifecycle2(
|
|
16177
|
-
return new Command(
|
|
16178
|
-
const r = await transitionManagedDelivery(requireCredentials(), kind4, id,
|
|
16849
|
+
function lifecycle2(action74) {
|
|
16850
|
+
return new Command(action74).description(`${action74[0].toUpperCase()}${action74.slice(1)} a personalization`).argument("<id>").action(async function(id) {
|
|
16851
|
+
const r = await transitionManagedDelivery(requireCredentials(), kind4, id, action74);
|
|
16179
16852
|
if (!r.ok || !r.data)
|
|
16180
|
-
fail(this, r.error ?? `Failed to ${
|
|
16853
|
+
fail(this, r.error ?? `Failed to ${action74}`, classifyStatus(r.status), r.errorDetails);
|
|
16181
16854
|
output(this, { personalization: r.data }, () => detail4(r.data));
|
|
16182
16855
|
});
|
|
16183
16856
|
}
|
|
@@ -16259,21 +16932,21 @@ function targetingResourceCommand(kind5) {
|
|
|
16259
16932
|
);
|
|
16260
16933
|
})
|
|
16261
16934
|
);
|
|
16262
|
-
for (const
|
|
16263
|
-
const command = new Command(
|
|
16264
|
-
`${
|
|
16935
|
+
for (const action74 of ["create", "update"]) {
|
|
16936
|
+
const command = new Command(action74).description(
|
|
16937
|
+
`${action74 === "create" ? "Create" : "Update"} a ${singular3}`
|
|
16265
16938
|
);
|
|
16266
|
-
if (
|
|
16939
|
+
if (action74 === "update") command.argument("<id>");
|
|
16267
16940
|
command.argument("[json]", "Inline JSON, @file, or file path").option("--file <path>", "JSON body file").action(async function(...args) {
|
|
16268
16941
|
const opts = this.opts();
|
|
16269
|
-
const id =
|
|
16270
|
-
const value = opts.file ? `@${opts.file}` : String(args[
|
|
16942
|
+
const id = action74 === "update" ? String(args[0]) : void 0;
|
|
16943
|
+
const value = opts.file ? `@${opts.file}` : String(args[action74 === "update" ? 1 : 0] ?? "") || void 0;
|
|
16271
16944
|
const requestBody = body3(this, value);
|
|
16272
|
-
if (
|
|
16945
|
+
if (action74 === "update" && typeof requestBody === "object" && requestBody !== null && !Array.isArray(requestBody) && Object.keys(requestBody).length === 0)
|
|
16273
16946
|
fail(this, `Updating a ${singular3} requires at least one field.`, EXIT_CODES.validation);
|
|
16274
16947
|
const response = await requestTargetingResource(
|
|
16275
16948
|
requireCredentials(),
|
|
16276
|
-
|
|
16949
|
+
action74 === "create" ? "POST" : "PATCH",
|
|
16277
16950
|
kind5,
|
|
16278
16951
|
id,
|
|
16279
16952
|
requestBody
|
|
@@ -16281,7 +16954,7 @@ function targetingResourceCommand(kind5) {
|
|
|
16281
16954
|
if (!response.ok || !response.data)
|
|
16282
16955
|
fail(
|
|
16283
16956
|
this,
|
|
16284
|
-
response.error ?? `Failed to ${
|
|
16957
|
+
response.error ?? `Failed to ${action74}`,
|
|
16285
16958
|
classifyStatus(response.status),
|
|
16286
16959
|
response.errorDetails
|
|
16287
16960
|
);
|
|
@@ -16290,7 +16963,7 @@ function targetingResourceCommand(kind5) {
|
|
|
16290
16963
|
{
|
|
16291
16964
|
[kind5 === "exclusion-groups" ? "exclusion_group" : singular3]: response.data
|
|
16292
16965
|
},
|
|
16293
|
-
() => console.log(source_default.green(`${
|
|
16966
|
+
() => console.log(source_default.green(`${action74}d ${singular3}`))
|
|
16294
16967
|
);
|
|
16295
16968
|
});
|
|
16296
16969
|
root.addCommand(command);
|
|
@@ -16364,7 +17037,7 @@ var exclusionGroupsCommand = targetingResourceCommand("exclusion-groups");
|
|
|
16364
17037
|
// src/commands/brand.ts
|
|
16365
17038
|
init_source();
|
|
16366
17039
|
var NO_PROFILE_MESSAGE = "No brand profile yet \u2014 run onboarding or a site check";
|
|
16367
|
-
var brandCommand = new Command("brand").description("Print the shop brand context for external agents").action(async function
|
|
17040
|
+
var brandCommand = new Command("brand").description("Print the shop brand context for external agents").action(async function action62() {
|
|
16368
17041
|
const creds = requireCredentials();
|
|
16369
17042
|
const res = await getBrandContext(creds);
|
|
16370
17043
|
if (res.status === 404) {
|
|
@@ -16397,7 +17070,7 @@ var brandCommand = new Command("brand").description("Print the shop brand contex
|
|
|
16397
17070
|
|
|
16398
17071
|
// src/commands/targeting.ts
|
|
16399
17072
|
init_source();
|
|
16400
|
-
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
|
|
17073
|
+
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 action63(url, opts) {
|
|
16401
17074
|
const creds = requireCredentials();
|
|
16402
17075
|
const res = await testTargeting(creds, url, { status: opts.status });
|
|
16403
17076
|
if (!res.ok || !res.data) {
|
|
@@ -16482,7 +17155,7 @@ var create4 = new Command("create").description("Create an Apex SOS ticket").opt
|
|
|
16482
17155
|
"--type <type>",
|
|
16483
17156
|
"full_handover|design|development|qa|config|bug|help (internal types require a DRIP account)",
|
|
16484
17157
|
"qa"
|
|
16485
|
-
).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
|
|
17158
|
+
).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 action64(descriptionArg, opts) {
|
|
16486
17159
|
if (!SOS_TYPES.has(opts.type)) {
|
|
16487
17160
|
fail(
|
|
16488
17161
|
this,
|
|
@@ -16509,7 +17182,7 @@ var create4 = new Command("create").description("Create an Apex SOS ticket").opt
|
|
|
16509
17182
|
}
|
|
16510
17183
|
output(this, res.data, () => printCreatedTicket(res.data));
|
|
16511
17184
|
});
|
|
16512
|
-
var mine = new Command("mine").description("List SOS tickets created by this API key").action(async function
|
|
17185
|
+
var mine = new Command("mine").description("List SOS tickets created by this API key").action(async function action65() {
|
|
16513
17186
|
const creds = requireCredentials();
|
|
16514
17187
|
const res = await listMySos(creds);
|
|
16515
17188
|
if (!res.ok || !res.data) {
|
|
@@ -16658,7 +17331,7 @@ var create5 = new Command("create").description("Create a shop").argument("[json
|
|
|
16658
17331
|
const r = await createShop(requireUserCredentials(), payload);
|
|
16659
17332
|
done(this, r, "create shop");
|
|
16660
17333
|
});
|
|
16661
|
-
var
|
|
17334
|
+
var update4 = new Command("update").description("Update a shop").argument("<id>").argument("[json]", "Inline JSON, @file, or file path").option("--holdout-pct <percent>", "Platform-operator global holdout percent (0-10)").option("--holdout-enabled", "Enable the platform-operator global holdout v1 setting").option("--holdout-disabled", "Disable the platform-operator global holdout v1 setting").option("--confirm-tier", "Confirm a commercially sensitive tier change").action(async function(id, raw, o) {
|
|
16662
17335
|
const b = raw === void 0 ? {} : body4(this, raw);
|
|
16663
17336
|
if (o.holdoutEnabled && o.holdoutDisabled)
|
|
16664
17337
|
fail(this, "Use only one of --holdout-enabled or --holdout-disabled", EXIT_CODES.validation);
|
|
@@ -16791,7 +17464,7 @@ shopware.addCommand(
|
|
|
16791
17464
|
);
|
|
16792
17465
|
})
|
|
16793
17466
|
);
|
|
16794
|
-
var shopsCommand = new Command("shops").description("Manage organization shops").addCommand(list12).addCommand(get9).addCommand(create5).addCommand(
|
|
17467
|
+
var shopsCommand = new Command("shops").description("Manage organization shops").addCommand(list12).addCommand(get9).addCommand(create5).addCommand(update4).addCommand(doctorCommand).addCommand(
|
|
16795
17468
|
settings("auto-stop", "Read or update auto-stop defaults", { on: "enable", off: "disable" })
|
|
16796
17469
|
).addCommand(settings("testing-defaults", "Read or update testing defaults")).addCommand(settings("guardian", "Read or update guardian defaults", { on: "on", off: "off" })).addCommand(profile).addCommand(shopware);
|
|
16797
17470
|
|
|
@@ -16799,6 +17472,7 @@ var shopsCommand = new Command("shops").description("Manage organization shops")
|
|
|
16799
17472
|
function optionSchema(option) {
|
|
16800
17473
|
return {
|
|
16801
17474
|
flags: option.flags,
|
|
17475
|
+
...option.argChoices ? { choices: option.argChoices } : {},
|
|
16802
17476
|
description: option.description,
|
|
16803
17477
|
required: option.required,
|
|
16804
17478
|
optional: option.optional,
|
|
@@ -16824,7 +17498,7 @@ function commandSchema(command) {
|
|
|
16824
17498
|
};
|
|
16825
17499
|
}
|
|
16826
17500
|
function createSchemaCommand(program3) {
|
|
16827
|
-
return new Command("schema").description("Print the current Apex CLI command and flag schema").option("--json", "Print JSON schema").action(function
|
|
17501
|
+
return new Command("schema").description("Print the current Apex CLI command and flag schema").option("--json", "Print JSON schema").action(function action74() {
|
|
16828
17502
|
const schema = commandSchema(program3);
|
|
16829
17503
|
output(this, schema, () => {
|
|
16830
17504
|
process.stdout.write(`${JSON.stringify(schema, null, 2)}
|
|
@@ -17239,7 +17913,7 @@ ${event.event || "(unnamed event)"} [${event.dlName}] \u2014 ${event.count} obse
|
|
|
17239
17913
|
return `${lines.join("\n")}
|
|
17240
17914
|
`;
|
|
17241
17915
|
}
|
|
17242
|
-
var discoverCommand = new Command("discover").description("Read back the tracking schema observed for the current shop").option("--minutes <n>", "Observation window in minutes").option("--gtm <url-or-id>", "Mine a public page or GTM container locally").option("--gtm-url <url>", "Fetch an explicit GTM container URL, including first-party proxies").option("--create-dimension <path>", "Create a custom dimension from an observed dataLayer path").option("--event <name>", "Select the observed event when a path appears on multiple events").option("--key <signal-key>", "Override the dimension signal key").option("--label <text>", "Override the dimension label").action(async function
|
|
17916
|
+
var discoverCommand = new Command("discover").description("Read back the tracking schema observed for the current shop").option("--minutes <n>", "Observation window in minutes").option("--gtm <url-or-id>", "Mine a public page or GTM container locally").option("--gtm-url <url>", "Fetch an explicit GTM container URL, including first-party proxies").option("--create-dimension <path>", "Create a custom dimension from an observed dataLayer path").option("--event <name>", "Select the observed event when a path appears on multiple events").option("--key <signal-key>", "Override the dimension signal key").option("--label <text>", "Override the dimension label").action(async function action66(opts) {
|
|
17243
17917
|
if (opts.gtm && opts.gtmUrl)
|
|
17244
17918
|
fail(this, "Use only one of --gtm or --gtm-url", EXIT_CODES.validation);
|
|
17245
17919
|
if (opts.createDimension && (opts.gtm || opts.gtmUrl)) {
|
|
@@ -17442,7 +18116,7 @@ function installSkill(paths, targetRoot) {
|
|
|
17442
18116
|
import_node_fs24.default.copyFileSync(paths.agents, import_node_path22.default.join(target, "AGENTS.md"));
|
|
17443
18117
|
return target;
|
|
17444
18118
|
}
|
|
17445
|
-
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
|
|
18119
|
+
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 action67(opts) {
|
|
17446
18120
|
let paths;
|
|
17447
18121
|
try {
|
|
17448
18122
|
paths = skillPaths();
|
|
@@ -18066,7 +18740,7 @@ function selectedEvents(opts) {
|
|
|
18066
18740
|
}
|
|
18067
18741
|
var list14 = new Command("list").alias("ls").description(
|
|
18068
18742
|
"List webhooks. JSON includes availableEvents; secrets are never returned after create."
|
|
18069
|
-
).action(async function
|
|
18743
|
+
).action(async function action68() {
|
|
18070
18744
|
const data = await loadWebhookIndex(this);
|
|
18071
18745
|
output(this, data, () => printWebhooks(data.data));
|
|
18072
18746
|
});
|
|
@@ -18075,7 +18749,7 @@ var create7 = new Command("create").description(`Create a webhook. Prints the si
|
|
|
18075
18749
|
"Webhook event to subscribe to; repeat for multiple events",
|
|
18076
18750
|
(value, previous = []) => [...previous, value],
|
|
18077
18751
|
[]
|
|
18078
|
-
).action(async function
|
|
18752
|
+
).action(async function action69(opts) {
|
|
18079
18753
|
const events = selectedEvents(opts);
|
|
18080
18754
|
if (events.length === 0) fail(this, "At least one --event is required", EXIT_CODES.validation);
|
|
18081
18755
|
const index = await loadWebhookIndex(this);
|
|
@@ -18107,7 +18781,7 @@ var create7 = new Command("create").description(`Create a webhook. Prints the si
|
|
|
18107
18781
|
}
|
|
18108
18782
|
);
|
|
18109
18783
|
});
|
|
18110
|
-
var remove4 = new Command("delete").alias("rm").description("Delete a webhook").argument("<id>", "Webhook id").action(async function
|
|
18784
|
+
var remove4 = new Command("delete").alias("rm").description("Delete a webhook").argument("<id>", "Webhook id").action(async function action70(id) {
|
|
18111
18785
|
requireShopSecretKey(this);
|
|
18112
18786
|
const result = handleResult(
|
|
18113
18787
|
this,
|
|
@@ -18116,7 +18790,7 @@ var remove4 = new Command("delete").alias("rm").description("Delete a webhook").
|
|
|
18116
18790
|
);
|
|
18117
18791
|
output(this, result, () => console.log(source_default.green(`Deleted webhook ${id}`)));
|
|
18118
18792
|
});
|
|
18119
|
-
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
|
|
18793
|
+
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 action71(id, opts) {
|
|
18120
18794
|
const limit = Number.parseInt(opts.limit, 10);
|
|
18121
18795
|
if (!Number.isInteger(limit) || limit < 1)
|
|
18122
18796
|
fail(this, "--limit must be a positive integer", EXIT_CODES.validation);
|
|
@@ -18128,12 +18802,12 @@ var deliveries = new Command("deliveries").description("List webhook deliveries
|
|
|
18128
18802
|
);
|
|
18129
18803
|
output(this, result, () => printDeliveries(result.data, result.webhook));
|
|
18130
18804
|
});
|
|
18131
|
-
var
|
|
18805
|
+
var update5 = new Command("update").description("Update a webhook URL, events, or active state").argument("<id>", "Webhook id").option("--url <url>", "HTTPS callback URL").option(
|
|
18132
18806
|
"--event <event>",
|
|
18133
18807
|
"Replacement event; repeat for multiple events",
|
|
18134
18808
|
(value, previous = []) => [...previous, value],
|
|
18135
18809
|
[]
|
|
18136
|
-
).option("--active", "Enable the webhook").option("--inactive", "Disable the webhook").action(async function
|
|
18810
|
+
).option("--active", "Enable the webhook").option("--inactive", "Disable the webhook").action(async function action72(id, opts) {
|
|
18137
18811
|
if (opts.active && opts.inactive)
|
|
18138
18812
|
fail(this, "Use only one of --active or --inactive", EXIT_CODES.validation);
|
|
18139
18813
|
const events = selectedEvents(opts);
|
|
@@ -18166,7 +18840,7 @@ var update4 = new Command("update").description("Update a webhook URL, events, o
|
|
|
18166
18840
|
);
|
|
18167
18841
|
output(this, { webhook: result }, () => printWebhooks([result]));
|
|
18168
18842
|
});
|
|
18169
|
-
var test3 = new Command("test").description("Send a signed test event to a webhook").argument("<id>", "Webhook id").action(async function
|
|
18843
|
+
var test3 = new Command("test").description("Send a signed test event to a webhook").argument("<id>", "Webhook id").action(async function action73(id) {
|
|
18170
18844
|
requireShopSecretKey(this);
|
|
18171
18845
|
const result = handleResult(
|
|
18172
18846
|
this,
|
|
@@ -18181,7 +18855,7 @@ var test3 = new Command("test").description("Send a signed test event to a webho
|
|
|
18181
18855
|
)
|
|
18182
18856
|
);
|
|
18183
18857
|
});
|
|
18184
|
-
var webhooksCommand = new Command("webhooks").description("Manage shop webhooks").addCommand(list14).addCommand(create7).addCommand(
|
|
18858
|
+
var webhooksCommand = new Command("webhooks").description("Manage shop webhooks").addCommand(list14).addCommand(create7).addCommand(update5).addCommand(remove4).addCommand(deliveries).addCommand(test3);
|
|
18185
18859
|
|
|
18186
18860
|
// src/commands/simulate.ts
|
|
18187
18861
|
init_source();
|