@drip-apex/apex-cli 0.6.2 → 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 +37 -1
- package/README.md +76 -1
- package/dist/cli.js +1231 -346
- package/package.json +1 -1
- package/skills/apex-cli/SKILL.md +28 -0
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)
|
|
@@ -6988,14 +6988,14 @@ var init_open = __esm({
|
|
|
6988
6988
|
}
|
|
6989
6989
|
const subprocess = import_node_child_process5.default.spawn(command, cliArguments, childProcessOptions);
|
|
6990
6990
|
if (options.wait) {
|
|
6991
|
-
return new Promise((
|
|
6991
|
+
return new Promise((resolve2, reject) => {
|
|
6992
6992
|
subprocess.once("error", reject);
|
|
6993
6993
|
subprocess.once("close", (exitCode) => {
|
|
6994
6994
|
if (!options.allowNonzeroExitCode && exitCode > 0) {
|
|
6995
6995
|
reject(new Error(`Exited with code ${exitCode}`));
|
|
6996
6996
|
return;
|
|
6997
6997
|
}
|
|
6998
|
-
|
|
6998
|
+
resolve2(subprocess);
|
|
6999
6999
|
});
|
|
7000
7000
|
});
|
|
7001
7001
|
}
|
|
@@ -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);
|
|
@@ -8369,9 +8406,9 @@ function resolveSelection(tree, input) {
|
|
|
8369
8406
|
function ask(question) {
|
|
8370
8407
|
const rl = import_node_readline.default.createInterface({ input: process.stdin, output: process.stderr });
|
|
8371
8408
|
return new Promise(
|
|
8372
|
-
(
|
|
8409
|
+
(resolve2) => rl.question(question, (answer) => {
|
|
8373
8410
|
rl.close();
|
|
8374
|
-
|
|
8411
|
+
resolve2(answer.trim());
|
|
8375
8412
|
})
|
|
8376
8413
|
);
|
|
8377
8414
|
}
|
|
@@ -8449,7 +8486,7 @@ function prompt(question, hide = false) {
|
|
|
8449
8486
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
8450
8487
|
throw new Error("Interactive input is unavailable in non-TTY mode.");
|
|
8451
8488
|
}
|
|
8452
|
-
return new Promise((
|
|
8489
|
+
return new Promise((resolve2) => {
|
|
8453
8490
|
const rl = import_node_readline2.default.createInterface({
|
|
8454
8491
|
input: process.stdin,
|
|
8455
8492
|
output: process.stderr
|
|
@@ -8469,13 +8506,13 @@ function prompt(question, hide = false) {
|
|
|
8469
8506
|
stdout.write = originalWrite;
|
|
8470
8507
|
console.error();
|
|
8471
8508
|
rl.close();
|
|
8472
|
-
|
|
8509
|
+
resolve2(answer.trim());
|
|
8473
8510
|
});
|
|
8474
8511
|
masking = true;
|
|
8475
8512
|
} else {
|
|
8476
8513
|
rl.question(question, (answer) => {
|
|
8477
8514
|
rl.close();
|
|
8478
|
-
|
|
8515
|
+
resolve2(answer.trim());
|
|
8479
8516
|
});
|
|
8480
8517
|
}
|
|
8481
8518
|
});
|
|
@@ -8501,7 +8538,7 @@ function parseFormBody(body5) {
|
|
|
8501
8538
|
async function waitForBrowserLogin(baseUrl, userMode = false) {
|
|
8502
8539
|
const state = import_node_crypto2.default.randomBytes(24).toString("base64url");
|
|
8503
8540
|
const clientName = import_node_os5.default.hostname() || "Local machine";
|
|
8504
|
-
return new Promise((
|
|
8541
|
+
return new Promise((resolve2, reject) => {
|
|
8505
8542
|
let settled = false;
|
|
8506
8543
|
let timeout = null;
|
|
8507
8544
|
const finish = (err, apiKey) => {
|
|
@@ -8513,7 +8550,7 @@ async function waitForBrowserLogin(baseUrl, userMode = false) {
|
|
|
8513
8550
|
} catch {
|
|
8514
8551
|
}
|
|
8515
8552
|
if (err) reject(err);
|
|
8516
|
-
else
|
|
8553
|
+
else resolve2(apiKey ?? "");
|
|
8517
8554
|
};
|
|
8518
8555
|
const server = import_node_http.default.createServer((req, res) => {
|
|
8519
8556
|
if (req.method === "OPTIONS") {
|
|
@@ -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) {
|
|
@@ -9414,8 +9590,8 @@ async function confirmApply(message) {
|
|
|
9414
9590
|
}
|
|
9415
9591
|
const readline3 = await import("node:readline");
|
|
9416
9592
|
const rl = readline3.createInterface({ input: process.stdin, output: process.stderr });
|
|
9417
|
-
const answer = await new Promise((
|
|
9418
|
-
rl.question(source_default.yellow(`${message} [y/N] `),
|
|
9593
|
+
const answer = await new Promise((resolve2) => {
|
|
9594
|
+
rl.question(source_default.yellow(`${message} [y/N] `), resolve2);
|
|
9419
9595
|
});
|
|
9420
9596
|
rl.close();
|
|
9421
9597
|
return answer.toLowerCase() === "y";
|
|
@@ -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) {
|
|
@@ -9828,8 +10096,8 @@ var del = new Command("delete").alias("rm").description("Delete an experiment").
|
|
|
9828
10096
|
input: process.stdin,
|
|
9829
10097
|
output: process.stderr
|
|
9830
10098
|
});
|
|
9831
|
-
const answer = await new Promise((
|
|
9832
|
-
rl.question(source_default.yellow(`Delete experiment "${exp.name}" (${exp.id})? [y/N] `),
|
|
10099
|
+
const answer = await new Promise((resolve2) => {
|
|
10100
|
+
rl.question(source_default.yellow(`Delete experiment "${exp.name}" (${exp.id})? [y/N] `), resolve2);
|
|
9833
10101
|
});
|
|
9834
10102
|
rl.close();
|
|
9835
10103
|
if (answer.toLowerCase() !== "y") {
|
|
@@ -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
|
-
for (const ref of requested) {
|
|
10316
|
-
const exactMatch = experiment.variations.find(
|
|
10317
|
-
(candidate) => ref === candidate.id || ref === candidate.slug
|
|
10318
|
-
);
|
|
10319
|
-
const nameMatches = exactMatch ? [] : experiment.variations.filter(
|
|
10320
|
-
(candidate) => ref.toLowerCase() === candidate.name.toLowerCase()
|
|
10321
|
-
);
|
|
10322
|
-
if (nameMatches.length > 1) {
|
|
10323
|
-
const matches = nameMatches.map(
|
|
10324
|
-
(candidate) => `${candidate.name} (ID: ${candidate.id}${candidate.slug ? `, slug: ${candidate.slug}` : ""})`
|
|
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
|
+
)
|
|
10325
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
|
+
)
|
|
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");
|
|
@@ -10938,11 +11450,11 @@ function cleanupResetJs(experiment) {
|
|
|
10938
11450
|
}
|
|
10939
11451
|
function runAbHarness(args, cwd) {
|
|
10940
11452
|
const bin = process.env.AB_HARNESS_BIN || "ab-harness";
|
|
10941
|
-
return new Promise((
|
|
11453
|
+
return new Promise((resolve2, reject) => {
|
|
10942
11454
|
const child = (0, import_node_child_process6.spawn)(bin, args, { cwd, stdio: "inherit" });
|
|
10943
11455
|
child.on("error", reject);
|
|
10944
11456
|
child.on("exit", (code) => {
|
|
10945
|
-
if (code === 0)
|
|
11457
|
+
if (code === 0) resolve2();
|
|
10946
11458
|
else reject(new Error(`${bin} ${args.join(" ")} exited with ${code}`));
|
|
10947
11459
|
});
|
|
10948
11460
|
});
|
|
@@ -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)
|
|
@@ -12416,7 +12928,7 @@ var suggest = new Command("suggest").description("Suggest goals and funnels from
|
|
|
12416
12928
|
const readline3 = await import("node:readline");
|
|
12417
12929
|
const rl = readline3.createInterface({ input: process.stdin, output: process.stderr });
|
|
12418
12930
|
const answer = await new Promise(
|
|
12419
|
-
(
|
|
12931
|
+
(resolve2) => rl.question(source_default.yellow("Create this goal? [y/N] "), resolve2)
|
|
12420
12932
|
);
|
|
12421
12933
|
rl.close();
|
|
12422
12934
|
if (answer.toLowerCase() !== "y") continue;
|
|
@@ -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(
|
|
@@ -12778,9 +13290,219 @@ var off = new Command("off").description("Disable consent gating").action(async
|
|
|
12778
13290
|
});
|
|
12779
13291
|
var consentCommand = new Command("consent").description("Manage consent gating").addCommand(get2).addCommand(set).addCommand(off);
|
|
12780
13292
|
|
|
13293
|
+
// src/commands/project-code.ts
|
|
13294
|
+
var import_node_fs15 = require("node:fs");
|
|
13295
|
+
var import_node_path13 = require("node:path");
|
|
13296
|
+
|
|
13297
|
+
// src/commands/runtime-settings-shop.ts
|
|
13298
|
+
var uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
13299
|
+
async function identifyRuntimeSettingsShop(command, requested) {
|
|
13300
|
+
let credentials = requireCredentials();
|
|
13301
|
+
if (credentials.credentialKind === "user") {
|
|
13302
|
+
let shopId = requested;
|
|
13303
|
+
if (!uuidPattern.test(requested)) {
|
|
13304
|
+
const context = await getAccountContext({
|
|
13305
|
+
...credentials,
|
|
13306
|
+
resolvedContext: { ...credentials.resolvedContext, shopId: void 0 }
|
|
13307
|
+
});
|
|
13308
|
+
if (!context.ok || !context.data)
|
|
13309
|
+
fail(command, context.error ?? "Shop discovery failed", classifyStatus(context.status));
|
|
13310
|
+
const match2 = context.data.data.orgs.flatMap((org) => org.shops).find((shop) => shop.domain.toLowerCase() === requested.toLowerCase());
|
|
13311
|
+
if (!match2)
|
|
13312
|
+
fail(command, "--shop domain is not available to this user", EXIT_CODES.validation);
|
|
13313
|
+
shopId = match2.id;
|
|
13314
|
+
}
|
|
13315
|
+
credentials = { ...credentials, resolvedContext: { ...credentials.resolvedContext, shopId } };
|
|
13316
|
+
}
|
|
13317
|
+
const response = await getInstallDomains(credentials);
|
|
13318
|
+
if (!response.ok || !response.data)
|
|
13319
|
+
fail(command, response.error ?? "Shop identity unavailable", classifyStatus(response.status));
|
|
13320
|
+
const { shop_id, domain } = response.data;
|
|
13321
|
+
if (credentials.credentialKind === "user" && shop_id.toLowerCase() !== credentials.resolvedContext?.shopId?.toLowerCase()) {
|
|
13322
|
+
fail(
|
|
13323
|
+
command,
|
|
13324
|
+
"Shop identity does not match the requested user-key context",
|
|
13325
|
+
EXIT_CODES.validation
|
|
13326
|
+
);
|
|
13327
|
+
}
|
|
13328
|
+
if (requested.toLowerCase() !== shop_id.toLowerCase() && requested.toLowerCase() !== domain.toLowerCase()) {
|
|
13329
|
+
fail(command, "--shop does not match the authenticated shop", EXIT_CODES.validation);
|
|
13330
|
+
}
|
|
13331
|
+
setEnvelopeContext({ shop_id, credential: credentials.credentialKind ?? "shop" });
|
|
13332
|
+
return { shop_id, domain, credentials };
|
|
13333
|
+
}
|
|
13334
|
+
|
|
13335
|
+
// src/commands/project-code.ts
|
|
13336
|
+
var fields = ["enabled", "css", "helperJs", "js"];
|
|
13337
|
+
function readPatch(command, file) {
|
|
13338
|
+
let input;
|
|
13339
|
+
try {
|
|
13340
|
+
input = JSON.parse((0, import_node_fs15.readFileSync)(file, "utf8"));
|
|
13341
|
+
} catch (error) {
|
|
13342
|
+
fail(command, `Cannot read project code JSON: ${String(error)}`, EXIT_CODES.validation);
|
|
13343
|
+
}
|
|
13344
|
+
if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).length === 0) {
|
|
13345
|
+
fail(command, "Project code must be a nonempty partial object", EXIT_CODES.validation);
|
|
13346
|
+
}
|
|
13347
|
+
for (const [key, value] of Object.entries(input)) {
|
|
13348
|
+
if (!fields.includes(key))
|
|
13349
|
+
fail(command, `Unknown project code field: ${key}`, EXIT_CODES.validation);
|
|
13350
|
+
if (key === "enabled" ? typeof value !== "boolean" : value !== null && (typeof value !== "string" || value.length > 2e4)) {
|
|
13351
|
+
fail(
|
|
13352
|
+
command,
|
|
13353
|
+
`${key} must be ${key === "enabled" ? "a boolean" : "null or a string of at most 20,000 characters"}`,
|
|
13354
|
+
EXIT_CODES.validation
|
|
13355
|
+
);
|
|
13356
|
+
}
|
|
13357
|
+
}
|
|
13358
|
+
return input;
|
|
13359
|
+
}
|
|
13360
|
+
async function readCurrent(command, shop) {
|
|
13361
|
+
const response = await getRuntimeSettings(shop.credentials);
|
|
13362
|
+
if (!response.ok || !response.data)
|
|
13363
|
+
fail(
|
|
13364
|
+
command,
|
|
13365
|
+
response.error ?? "Runtime settings read failed",
|
|
13366
|
+
classifyStatus(response.status)
|
|
13367
|
+
);
|
|
13368
|
+
const data = response.data;
|
|
13369
|
+
if (data.shop_id !== shop.shop_id)
|
|
13370
|
+
fail(command, "Runtime settings returned a different shop", EXIT_CODES.validation);
|
|
13371
|
+
const projectCode = data.runtime_settings?.projectCode;
|
|
13372
|
+
if (!projectCode || typeof projectCode.enabled !== "boolean" || [projectCode.css, projectCode.helperJs, projectCode.js].some(
|
|
13373
|
+
(code) => typeof code !== "string"
|
|
13374
|
+
) || !(data.version === null || typeof data.version === "string" && data.version.length > 0)) {
|
|
13375
|
+
fail(
|
|
13376
|
+
command,
|
|
13377
|
+
"Runtime settings must include complete project code and an opaque version",
|
|
13378
|
+
EXIT_CODES.general
|
|
13379
|
+
);
|
|
13380
|
+
}
|
|
13381
|
+
return { projectCode, version: data.version };
|
|
13382
|
+
}
|
|
13383
|
+
function effectiveDiff(before, patch) {
|
|
13384
|
+
const after = { ...before };
|
|
13385
|
+
if (patch.enabled !== void 0) after.enabled = patch.enabled;
|
|
13386
|
+
for (const key of ["css", "helperJs", "js"]) {
|
|
13387
|
+
if (patch[key] !== void 0) after[key] = patch[key]?.trim() || "";
|
|
13388
|
+
}
|
|
13389
|
+
const changedFields = fields.filter((field) => before[field] !== after[field]);
|
|
13390
|
+
const changes = Object.fromEntries(
|
|
13391
|
+
changedFields.map((field) => [field, { before: before[field], after: after[field] }])
|
|
13392
|
+
);
|
|
13393
|
+
return { changedFields, changes, before, after };
|
|
13394
|
+
}
|
|
13395
|
+
var projectCodeCommand = new Command("project-code").description(
|
|
13396
|
+
"Inspect, preview, and back up shop project code"
|
|
13397
|
+
);
|
|
13398
|
+
projectCodeCommand.addCommand(
|
|
13399
|
+
new Command("get").description("Read complete project code and its opaque version").requiredOption("--shop <domain-or-id>", "Shop domain or UUID").action(async (options, command) => {
|
|
13400
|
+
const { credentials: _credentials, ...identity } = await identifyRuntimeSettingsShop(
|
|
13401
|
+
command,
|
|
13402
|
+
options.shop
|
|
13403
|
+
);
|
|
13404
|
+
const current = await readCurrent(command, { ...identity, credentials: _credentials });
|
|
13405
|
+
output(
|
|
13406
|
+
command,
|
|
13407
|
+
{ ...identity, ...current },
|
|
13408
|
+
() => process.stdout.write(`${JSON.stringify({ ...identity, ...current }, null, 2)}
|
|
13409
|
+
`)
|
|
13410
|
+
);
|
|
13411
|
+
})
|
|
13412
|
+
);
|
|
13413
|
+
for (const mode of ["diff", "set"]) {
|
|
13414
|
+
const command = new Command(mode).description(
|
|
13415
|
+
mode === "diff" ? "Preview an effective partial project code change without writing" : "Back up prior code and apply a version-checked partial change"
|
|
13416
|
+
).requiredOption("--file <path>", "Partial projectCode JSON file").requiredOption("--shop <domain-or-id>", "Shop domain or UUID");
|
|
13417
|
+
if (mode === "set")
|
|
13418
|
+
command.requiredOption(
|
|
13419
|
+
"--backup <path>",
|
|
13420
|
+
"New backup file; an existing file is never replaced"
|
|
13421
|
+
);
|
|
13422
|
+
command.action(async (options, actionCommand) => {
|
|
13423
|
+
const patch = readPatch(actionCommand, options.file);
|
|
13424
|
+
const shop = await identifyRuntimeSettingsShop(actionCommand, options.shop);
|
|
13425
|
+
const current = await readCurrent(actionCommand, shop);
|
|
13426
|
+
const { credentials: _credentials, ...identity } = shop;
|
|
13427
|
+
const diff = effectiveDiff(current.projectCode, patch);
|
|
13428
|
+
if (mode === "diff" || diff.changedFields.length === 0) {
|
|
13429
|
+
const result2 = {
|
|
13430
|
+
...identity,
|
|
13431
|
+
status: diff.changedFields.length ? "changed" : "unchanged",
|
|
13432
|
+
version: current.version,
|
|
13433
|
+
diff
|
|
13434
|
+
};
|
|
13435
|
+
output(
|
|
13436
|
+
actionCommand,
|
|
13437
|
+
result2,
|
|
13438
|
+
() => process.stdout.write(`${JSON.stringify(result2, null, 2)}
|
|
13439
|
+
`)
|
|
13440
|
+
);
|
|
13441
|
+
return;
|
|
13442
|
+
}
|
|
13443
|
+
const backupPath = (0, import_node_path13.resolve)(options.backup);
|
|
13444
|
+
try {
|
|
13445
|
+
(0, import_node_fs15.writeFileSync)(
|
|
13446
|
+
backupPath,
|
|
13447
|
+
`${JSON.stringify({ ...identity, ...current, capturedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)}
|
|
13448
|
+
`,
|
|
13449
|
+
{ flag: "wx", mode: 384 }
|
|
13450
|
+
);
|
|
13451
|
+
} catch (error) {
|
|
13452
|
+
fail(
|
|
13453
|
+
actionCommand,
|
|
13454
|
+
`Backup failed; project code was not sent: ${String(error)}`,
|
|
13455
|
+
EXIT_CODES.general
|
|
13456
|
+
);
|
|
13457
|
+
}
|
|
13458
|
+
const response = await updateRuntimeSettings(shop.credentials, {
|
|
13459
|
+
projectCode: patch,
|
|
13460
|
+
expectedVersion: current.version
|
|
13461
|
+
});
|
|
13462
|
+
if (!response.ok || !response.data) {
|
|
13463
|
+
fail(
|
|
13464
|
+
actionCommand,
|
|
13465
|
+
response.error ?? "Project code update failed",
|
|
13466
|
+
classifyStatus(response.status),
|
|
13467
|
+
{ ...response.errorDetails, backupPath }
|
|
13468
|
+
);
|
|
13469
|
+
}
|
|
13470
|
+
const data = response.data;
|
|
13471
|
+
const warnings = [...data.warnings ?? []];
|
|
13472
|
+
if (data.precompile?.status === "warning" || data.precompile?.status === "error") {
|
|
13473
|
+
warnings.push(
|
|
13474
|
+
`Project code saved; publication ${data.precompile.status}: ${data.precompile.summary}`
|
|
13475
|
+
);
|
|
13476
|
+
}
|
|
13477
|
+
if (data.shop_id !== identity.shop_id)
|
|
13478
|
+
warnings.push(
|
|
13479
|
+
"The write response has a different shop identity; inspect the saved state before another write."
|
|
13480
|
+
);
|
|
13481
|
+
for (const warning of warnings) process.stderr.write(`WARNING: ${warning}
|
|
13482
|
+
`);
|
|
13483
|
+
const result = {
|
|
13484
|
+
...identity,
|
|
13485
|
+
status: "saved",
|
|
13486
|
+
diff,
|
|
13487
|
+
backupPath,
|
|
13488
|
+
version: data.version,
|
|
13489
|
+
writtenVersion: data.writtenVersion,
|
|
13490
|
+
precompile: data.precompile,
|
|
13491
|
+
warnings
|
|
13492
|
+
};
|
|
13493
|
+
output(
|
|
13494
|
+
actionCommand,
|
|
13495
|
+
result,
|
|
13496
|
+
() => process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
13497
|
+
`)
|
|
13498
|
+
);
|
|
13499
|
+
});
|
|
13500
|
+
projectCodeCommand.addCommand(command);
|
|
13501
|
+
}
|
|
13502
|
+
|
|
12781
13503
|
// src/commands/triggers.ts
|
|
12782
|
-
var
|
|
12783
|
-
var
|
|
13504
|
+
var import_node_fs16 = __toESM(require("node:fs"));
|
|
13505
|
+
var import_node_path14 = __toESM(require("node:path"));
|
|
12784
13506
|
|
|
12785
13507
|
// src/trigger-proposal.ts
|
|
12786
13508
|
function runtimePath(path24) {
|
|
@@ -12868,8 +13590,8 @@ function score2(path24, patterns) {
|
|
|
12868
13590
|
0
|
|
12869
13591
|
);
|
|
12870
13592
|
}
|
|
12871
|
-
function best(
|
|
12872
|
-
const ranked =
|
|
13593
|
+
function best(fields3, patterns, numeric2 = false) {
|
|
13594
|
+
const ranked = fields3.filter(
|
|
12873
13595
|
(field) => !numeric2 || field.numericType === true || field.values.some((value) => typeof value === "number") || field.magnitude !== void 0
|
|
12874
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));
|
|
12875
13597
|
if (!ranked.length) return [];
|
|
@@ -12894,15 +13616,15 @@ function proposeTriggers(report) {
|
|
|
12894
13616
|
continue;
|
|
12895
13617
|
const descriptors = candidate.fields ?? candidate.paths;
|
|
12896
13618
|
const payload = candidate.properties ?? candidate.payload ?? candidate;
|
|
12897
|
-
const
|
|
13619
|
+
const fields3 = descriptors !== void 0 ? fieldsFrom(descriptors, "", true) : fieldsFrom(payload, "", isDescriptorArray(payload));
|
|
12898
13620
|
const revenues = best(
|
|
12899
|
-
|
|
13621
|
+
fields3,
|
|
12900
13622
|
[/revenue/, /total.*amount|amount.*total/, /grand.*total|total/, /value|amount/],
|
|
12901
13623
|
true
|
|
12902
13624
|
);
|
|
12903
|
-
const orderIds = best(
|
|
12904
|
-
const currencies = best(
|
|
12905
|
-
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)$/]);
|
|
12906
13628
|
if (!revenues.length || !orderIds.length || !currencies.length) continue;
|
|
12907
13629
|
for (const revenue of revenues)
|
|
12908
13630
|
for (const orderId of orderIds)
|
|
@@ -13197,10 +13919,10 @@ function writeWarnings(result) {
|
|
|
13197
13919
|
`);
|
|
13198
13920
|
}
|
|
13199
13921
|
}
|
|
13200
|
-
var
|
|
13922
|
+
var uuidPattern2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
13201
13923
|
async function identify(command, requestedShop) {
|
|
13202
13924
|
let credentials = requireCredentials();
|
|
13203
|
-
if (credentials.credentialKind === "user" && !
|
|
13925
|
+
if (credentials.credentialKind === "user" && !uuidPattern2.test(requestedShop)) {
|
|
13204
13926
|
const discoveryCredentials = {
|
|
13205
13927
|
...credentials,
|
|
13206
13928
|
resolvedContext: { ...credentials.resolvedContext, shopId: void 0 }
|
|
@@ -13273,7 +13995,7 @@ var get3 = new Command("get").description("Get data layer triggers").requiredOpt
|
|
|
13273
13995
|
var set2 = new Command("set").requiredOption("--file <json>", "JSON trigger array").requiredOption("--shop <domain-or-id>", "Shop domain or Apex shop ID").action(async function(opts) {
|
|
13274
13996
|
let triggers;
|
|
13275
13997
|
try {
|
|
13276
|
-
triggers = strictTriggers(JSON.parse(
|
|
13998
|
+
triggers = strictTriggers(JSON.parse(import_node_fs16.default.readFileSync(import_node_path14.default.resolve(opts.file), "utf8")));
|
|
13277
13999
|
} catch (error) {
|
|
13278
14000
|
fail(
|
|
13279
14001
|
this,
|
|
@@ -13300,7 +14022,7 @@ var propose = new Command("propose").requiredOption("--from-file <u2-report.json
|
|
|
13300
14022
|
let proposals;
|
|
13301
14023
|
let report;
|
|
13302
14024
|
try {
|
|
13303
|
-
report = JSON.parse(
|
|
14025
|
+
report = JSON.parse(import_node_fs16.default.readFileSync(import_node_path14.default.resolve(opts.fromFile), "utf8"));
|
|
13304
14026
|
proposals = proposeTriggers(report);
|
|
13305
14027
|
} catch (error) {
|
|
13306
14028
|
fail(this, error instanceof Error ? error.message : String(error), 2);
|
|
@@ -13452,11 +14174,11 @@ async function patchWithRetry(command, build) {
|
|
|
13452
14174
|
warn2(command, result.data.precompile);
|
|
13453
14175
|
return result.data;
|
|
13454
14176
|
}
|
|
13455
|
-
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() {
|
|
13456
14178
|
const data = await load4(this);
|
|
13457
14179
|
output(this, data, () => printDomains(data));
|
|
13458
14180
|
});
|
|
13459
|
-
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) {
|
|
13460
14182
|
const added = domain.map(normalizeDomain);
|
|
13461
14183
|
const data = await patchWithRetry(
|
|
13462
14184
|
this,
|
|
@@ -13464,7 +14186,7 @@ var add = new Command("add").description("Add install domains").argument("<domai
|
|
|
13464
14186
|
);
|
|
13465
14187
|
output(this, data, () => printDomains(data));
|
|
13466
14188
|
});
|
|
13467
|
-
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) {
|
|
13468
14190
|
const removeSet = new Set(domain.map(normalizeDomain));
|
|
13469
14191
|
const data = await patchWithRetry(
|
|
13470
14192
|
this,
|
|
@@ -13472,7 +14194,7 @@ var remove = new Command("remove").description("Remove install domains").argumen
|
|
|
13472
14194
|
);
|
|
13473
14195
|
output(this, data, () => printDomains(data));
|
|
13474
14196
|
});
|
|
13475
|
-
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) {
|
|
13476
14198
|
const replacement = domain.map(normalizeDomain);
|
|
13477
14199
|
const data = await patchWithRetry(this, () => replacement);
|
|
13478
14200
|
output(this, data, () => printDomains(data));
|
|
@@ -13564,14 +14286,14 @@ async function patchWithRetry2(command, build) {
|
|
|
13564
14286
|
);
|
|
13565
14287
|
return result.data;
|
|
13566
14288
|
}
|
|
13567
|
-
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() {
|
|
13568
14290
|
const data = await load5(this);
|
|
13569
14291
|
output(this, data, () => printSettings(data.externalTracking));
|
|
13570
14292
|
});
|
|
13571
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(
|
|
13572
14294
|
"--signed",
|
|
13573
14295
|
"Require signed handover tokens (the default; unsigning is a platform-support action)"
|
|
13574
|
-
).action(async function
|
|
14296
|
+
).action(async function action56(opts) {
|
|
13575
14297
|
const domains = opts.domain.map(normalizeDomain2);
|
|
13576
14298
|
if (domains.some((domain) => !domain))
|
|
13577
14299
|
fail(
|
|
@@ -13591,14 +14313,14 @@ var set4 = new Command("set").description("Enable cross-domain identity handover
|
|
|
13591
14313
|
}));
|
|
13592
14314
|
output(this, data, () => printSettings(data.externalTracking));
|
|
13593
14315
|
});
|
|
13594
|
-
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() {
|
|
13595
14317
|
const data = await patchWithRetry2(this, (current) => ({
|
|
13596
14318
|
externalTracking: { domains: [], signed: true },
|
|
13597
14319
|
ifRevision: current.revision
|
|
13598
14320
|
}));
|
|
13599
14321
|
output(this, data, () => printSettings(data.externalTracking));
|
|
13600
14322
|
});
|
|
13601
|
-
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) {
|
|
13602
14324
|
const config = await load5(this);
|
|
13603
14325
|
const target = normalizeDomain2(opts.toDomain);
|
|
13604
14326
|
if (!target) fail(this, "--to-domain must be a valid hostname", EXIT_CODES.validation);
|
|
@@ -14641,7 +15363,7 @@ var doctorCommand = new Command("doctor").description("Check shop configuration
|
|
|
14641
15363
|
).option("--check <id>", "Run one check (for example purchase_capture)").option(
|
|
14642
15364
|
"--worker <url>",
|
|
14643
15365
|
`Apex Worker base URL; defaults to DRIP_WORKER_URL or ${PRODUCTION_WORKER_BASE_URL}`
|
|
14644
|
-
).action(async function
|
|
15366
|
+
).action(async function action59(options) {
|
|
14645
15367
|
const data = await runDoctor(options);
|
|
14646
15368
|
if (options.check && data.checks.length === 0)
|
|
14647
15369
|
fail(this, `Unknown doctor check: ${options.check}`, EXIT_CODES.validation);
|
|
@@ -14663,7 +15385,7 @@ function printDoctorReport(checks) {
|
|
|
14663
15385
|
var consentRateCommand = new Command("consent-rate").description("Estimate analytics consent acceptance from visitor series").option(
|
|
14664
15386
|
"--since <date>",
|
|
14665
15387
|
"Consent gating start date (YYYY-MM-DD); splits the baseline and gated windows"
|
|
14666
|
-
).action(async function
|
|
15388
|
+
).action(async function action60(opts) {
|
|
14667
15389
|
if (opts.since !== void 0 && parseDay(opts.since) === null)
|
|
14668
15390
|
fail(this, "--since must be a calendar date in YYYY-MM-DD form", EXIT_CODES.validation);
|
|
14669
15391
|
const result = await getVisitorSeries(requireCredentials(), { days: VISITOR_SERIES_DAYS });
|
|
@@ -14723,7 +15445,7 @@ function analyticsSubcommand(name, description, defaultDays) {
|
|
|
14723
15445
|
command.option("--planning-days <n>", "Planning window in days", "180");
|
|
14724
15446
|
command.option("--limit <n>", "Maximum page rows", "50");
|
|
14725
15447
|
}
|
|
14726
|
-
return command.action(async function
|
|
15448
|
+
return command.action(async function action74(opts) {
|
|
14727
15449
|
const result = await getAnalytics(requireCredentials(), name, {
|
|
14728
15450
|
days: positiveInteger(this, opts.days, "--days"),
|
|
14729
15451
|
planningDays: positiveInteger(this, opts.planningDays, "--planning-days"),
|
|
@@ -14743,7 +15465,7 @@ function urlsCommand() {
|
|
|
14743
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(
|
|
14744
15466
|
"--match <regex>",
|
|
14745
15467
|
"Client-side regex filter; exhausts inventory pages before filtering"
|
|
14746
|
-
).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) {
|
|
14747
15469
|
const creds = requireCredentials();
|
|
14748
15470
|
const days = positiveInteger(this, opts.days, "--days") ?? 14;
|
|
14749
15471
|
const displayLimit = Math.min(200, positiveInteger(this, opts.limit, "--limit") ?? 50);
|
|
@@ -14932,15 +15654,15 @@ function decodeBody(raw, header, limits, hostname, inputTruncated = false) {
|
|
|
14932
15654
|
`Response from ${hostname} uses unsupported content encoding "${encoding}"`
|
|
14933
15655
|
)
|
|
14934
15656
|
);
|
|
14935
|
-
return new Promise((
|
|
15657
|
+
return new Promise((resolve2, reject) => {
|
|
14936
15658
|
const stream = decoderFor(encoding);
|
|
14937
15659
|
const chunks = [];
|
|
14938
15660
|
let bytes = 0;
|
|
14939
15661
|
let settled = false;
|
|
14940
|
-
const settle = (
|
|
15662
|
+
const settle = (action74) => {
|
|
14941
15663
|
if (settled) return;
|
|
14942
15664
|
settled = true;
|
|
14943
|
-
|
|
15665
|
+
action74();
|
|
14944
15666
|
};
|
|
14945
15667
|
stream.on("data", (chunk) => {
|
|
14946
15668
|
if (settled) return;
|
|
@@ -14952,7 +15674,7 @@ function decodeBody(raw, header, limits, hostname, inputTruncated = false) {
|
|
|
14952
15674
|
}
|
|
14953
15675
|
chunks.push(chunk.subarray(0, limits.maxBytes - bytes));
|
|
14954
15676
|
stream.destroy();
|
|
14955
|
-
settle(() =>
|
|
15677
|
+
settle(() => resolve2({ body: Buffer.concat(chunks), truncated: true }));
|
|
14956
15678
|
return;
|
|
14957
15679
|
}
|
|
14958
15680
|
chunks.push(chunk);
|
|
@@ -14962,7 +15684,7 @@ function decodeBody(raw, header, limits, hostname, inputTruncated = false) {
|
|
|
14962
15684
|
"error",
|
|
14963
15685
|
(error) => settle(() => {
|
|
14964
15686
|
if (limits.onExceed === "truncate" && inputTruncated && chunks.length > 0)
|
|
14965
|
-
|
|
15687
|
+
resolve2({ body: Buffer.concat(chunks), truncated: true });
|
|
14966
15688
|
else
|
|
14967
15689
|
reject(
|
|
14968
15690
|
new BodyDecodeError(
|
|
@@ -14973,7 +15695,7 @@ function decodeBody(raw, header, limits, hostname, inputTruncated = false) {
|
|
|
14973
15695
|
);
|
|
14974
15696
|
stream.on(
|
|
14975
15697
|
"end",
|
|
14976
|
-
() => settle(() =>
|
|
15698
|
+
() => settle(() => resolve2({ body: Buffer.concat(chunks), truncated: false }))
|
|
14977
15699
|
);
|
|
14978
15700
|
stream.end(raw);
|
|
14979
15701
|
});
|
|
@@ -15000,7 +15722,7 @@ function responseFromIncoming(res, body5, decoded) {
|
|
|
15000
15722
|
});
|
|
15001
15723
|
}
|
|
15002
15724
|
function pinnedFetch(url, addresses, limits) {
|
|
15003
|
-
return new Promise((
|
|
15725
|
+
return new Promise((resolve2, reject) => {
|
|
15004
15726
|
const send3 = url.protocol === "https:" ? import_node_https.request : import_node_http2.request;
|
|
15005
15727
|
const pinnedLookup = (_hostname, options, callback) => {
|
|
15006
15728
|
if (options?.all)
|
|
@@ -15011,11 +15733,11 @@ function pinnedFetch(url, addresses, limits) {
|
|
|
15011
15733
|
else callback(null, addresses[0], (0, import_node_net.isIP)(addresses[0]));
|
|
15012
15734
|
};
|
|
15013
15735
|
let settled = false;
|
|
15014
|
-
const settle = (
|
|
15736
|
+
const settle = (action74) => {
|
|
15015
15737
|
if (settled) return;
|
|
15016
15738
|
settled = true;
|
|
15017
15739
|
clearTimeout(deadline);
|
|
15018
|
-
|
|
15740
|
+
action74();
|
|
15019
15741
|
};
|
|
15020
15742
|
const req = send3(
|
|
15021
15743
|
url,
|
|
@@ -15047,7 +15769,7 @@ function pinnedFetch(url, addresses, limits) {
|
|
|
15047
15769
|
chunks.push(chunk.subarray(0, limits.maxBytes - bytes));
|
|
15048
15770
|
req.destroy();
|
|
15049
15771
|
settle(
|
|
15050
|
-
() => finalizeIncoming(url, res, Buffer.concat(chunks), true, limits).then(
|
|
15772
|
+
() => finalizeIncoming(url, res, Buffer.concat(chunks), true, limits).then(resolve2, reject)
|
|
15051
15773
|
);
|
|
15052
15774
|
return;
|
|
15053
15775
|
}
|
|
@@ -15062,7 +15784,7 @@ function pinnedFetch(url, addresses, limits) {
|
|
|
15062
15784
|
reject(new Error(`No HTTP status from ${url.hostname}`));
|
|
15063
15785
|
return;
|
|
15064
15786
|
}
|
|
15065
|
-
finalizeIncoming(url, res, Buffer.concat(chunks), false, limits).then(
|
|
15787
|
+
finalizeIncoming(url, res, Buffer.concat(chunks), false, limits).then(resolve2, reject);
|
|
15066
15788
|
})
|
|
15067
15789
|
);
|
|
15068
15790
|
}
|
|
@@ -15350,7 +16072,7 @@ async function checkServedConfig(configUrl, workerBaseUrl, workerBaseSource) {
|
|
|
15350
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(
|
|
15351
16073
|
"--worker-url <url>",
|
|
15352
16074
|
"Apex Worker base URL that serves /config (defaults to the URL in the served snippet, then DRIP_WORKER_URL, then the production worker)"
|
|
15353
|
-
).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) {
|
|
15354
16076
|
const creds = requireCredentials();
|
|
15355
16077
|
const shopId = opts.shopId?.trim() || process.env.DRIP_SHOP_ID?.trim() || null;
|
|
15356
16078
|
if (!shopId)
|
|
@@ -15494,8 +16216,8 @@ var installCommand = new Command("install").description("Check Apex storefront i
|
|
|
15494
16216
|
|
|
15495
16217
|
// src/commands/flags.ts
|
|
15496
16218
|
init_source();
|
|
15497
|
-
var
|
|
15498
|
-
var
|
|
16219
|
+
var import_node_fs17 = __toESM(require("node:fs"));
|
|
16220
|
+
var import_node_path15 = __toESM(require("node:path"));
|
|
15499
16221
|
|
|
15500
16222
|
// src/edge-status.ts
|
|
15501
16223
|
function getEdgeDeliveryStatus(deliveryType, expectedWorstCaseSeconds = 360) {
|
|
@@ -15537,7 +16259,7 @@ function body(command, value, opts) {
|
|
|
15537
16259
|
let parsed = {};
|
|
15538
16260
|
if (value) {
|
|
15539
16261
|
const source = value.startsWith("@") ? value.slice(1) : value;
|
|
15540
|
-
const text = value.startsWith("@") ||
|
|
16262
|
+
const text = value.startsWith("@") || import_node_fs17.default.existsSync(source) ? import_node_fs17.default.readFileSync(import_node_path15.default.resolve(process.cwd(), source), "utf8") : value;
|
|
15541
16263
|
try {
|
|
15542
16264
|
parsed = JSON.parse(text);
|
|
15543
16265
|
} catch (e) {
|
|
@@ -15650,11 +16372,11 @@ function write(name) {
|
|
|
15650
16372
|
});
|
|
15651
16373
|
return c;
|
|
15652
16374
|
}
|
|
15653
|
-
function lifecycle(
|
|
15654
|
-
return new Command(
|
|
15655
|
-
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);
|
|
15656
16378
|
if (!r.ok || !r.data)
|
|
15657
|
-
fail(this, r.error ?? `Failed to ${
|
|
16379
|
+
fail(this, r.error ?? `Failed to ${action74}`, classifyStatus(r.status), r.errorDetails);
|
|
15658
16380
|
output(this, { flag: r.data }, () => detail(r.data));
|
|
15659
16381
|
});
|
|
15660
16382
|
}
|
|
@@ -15670,8 +16392,8 @@ Examples:
|
|
|
15670
16392
|
);
|
|
15671
16393
|
|
|
15672
16394
|
// src/commands/backlog.ts
|
|
15673
|
-
var
|
|
15674
|
-
var
|
|
16395
|
+
var import_node_fs18 = __toESM(require("node:fs"));
|
|
16396
|
+
var import_node_path16 = __toESM(require("node:path"));
|
|
15675
16397
|
init_source();
|
|
15676
16398
|
var kind2 = "backlog";
|
|
15677
16399
|
var singular = "backlog item";
|
|
@@ -15679,7 +16401,7 @@ function parse(command, value) {
|
|
|
15679
16401
|
if (!value) return {};
|
|
15680
16402
|
try {
|
|
15681
16403
|
const source = value.startsWith("@") ? value.slice(1) : value;
|
|
15682
|
-
const text = value.startsWith("@") ||
|
|
16404
|
+
const text = value.startsWith("@") || import_node_fs18.default.existsSync(source) ? import_node_fs18.default.readFileSync(import_node_path16.default.resolve(process.cwd(), source), "utf8") : value;
|
|
15683
16405
|
const body5 = JSON.parse(text);
|
|
15684
16406
|
if (!body5 || typeof body5 !== "object" || Array.isArray(body5))
|
|
15685
16407
|
throw new Error("body must be an object");
|
|
@@ -15692,13 +16414,74 @@ function parse(command, value) {
|
|
|
15692
16414
|
);
|
|
15693
16415
|
}
|
|
15694
16416
|
}
|
|
15695
|
-
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) {
|
|
15696
16464
|
console.log();
|
|
15697
16465
|
console.log(source_default.bold(String(x.title ?? x.label ?? x.key ?? x.id)));
|
|
15698
16466
|
console.log(` ID: ${source_default.dim(x.id)}`);
|
|
15699
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
|
+
}
|
|
15700
16475
|
console.log();
|
|
15701
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
|
+
}
|
|
15702
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) {
|
|
15703
16486
|
const r = await listInsights(
|
|
15704
16487
|
requireCredentials(),
|
|
@@ -15727,27 +16510,35 @@ var get6 = new Command("get").description("Get a backlog item").argument("<id>",
|
|
|
15727
16510
|
const r = await getInsight(requireCredentials(), kind2, id);
|
|
15728
16511
|
if (!r.ok || !r.data)
|
|
15729
16512
|
fail(this, r.error ?? "Failed to get", classifyStatus(r.status), r.errorDetails);
|
|
15730
|
-
|
|
16513
|
+
const labels = await fieldLabels(this, r.data);
|
|
16514
|
+
output(this, { backlog_item: r.data }, () => detail2(r.data, labels));
|
|
15731
16515
|
});
|
|
15732
|
-
function write2(
|
|
15733
|
-
const c = new Command(
|
|
15734
|
-
`${
|
|
16516
|
+
function write2(action74) {
|
|
16517
|
+
const c = new Command(action74).description(
|
|
16518
|
+
`${action74 === "create" ? "Create" : "Update"} a ${singular}`
|
|
15735
16519
|
);
|
|
15736
|
-
if (
|
|
15737
|
-
c.argument("[json]", "Inline JSON, @file, or file path").option(
|
|
15738
|
-
"--
|
|
15739
|
-
"
|
|
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
|
+
[]
|
|
15740
16526
|
);
|
|
15741
16527
|
c.action(async function(...args) {
|
|
15742
16528
|
const o = this.opts();
|
|
15743
|
-
const id =
|
|
15744
|
-
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;
|
|
15745
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
|
+
}
|
|
15746
16536
|
if (!Object.keys(body5).length) fail(this, "Provide a JSON body.", EXIT_CODES.validation);
|
|
15747
|
-
const r =
|
|
16537
|
+
const r = action74 === "create" ? await createInsight(requireCredentials(), kind2, body5) : await updateInsight(requireCredentials(), kind2, id, body5);
|
|
15748
16538
|
if (!r.ok || !r.data)
|
|
15749
|
-
fail(this, r.error ?? `Failed to ${
|
|
15750
|
-
|
|
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));
|
|
15751
16542
|
});
|
|
15752
16543
|
return c;
|
|
15753
16544
|
}
|
|
@@ -15779,7 +16570,7 @@ var remove2 = new Command("delete").description("Delete a backlog item").argumen
|
|
|
15779
16570
|
const readline3 = await import("node:readline");
|
|
15780
16571
|
const rl = readline3.createInterface({ input: process.stdin, output: process.stderr });
|
|
15781
16572
|
const answer = await new Promise(
|
|
15782
|
-
(
|
|
16573
|
+
(resolve2) => rl.question(source_default.yellow(`Delete backlog item ${id}? [y/N] `), resolve2)
|
|
15783
16574
|
);
|
|
15784
16575
|
rl.close();
|
|
15785
16576
|
if (answer.toLowerCase() !== "y") {
|
|
@@ -15792,11 +16583,103 @@ var remove2 = new Command("delete").description("Delete a backlog item").argumen
|
|
|
15792
16583
|
fail(this, r.error ?? "Failed to delete", classifyStatus(r.status), r.errorDetails);
|
|
15793
16584
|
output(this, { deleted: true, id }, () => console.log(`Deleted ${id}`));
|
|
15794
16585
|
});
|
|
15795
|
-
|
|
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);
|
|
15796
16679
|
|
|
15797
16680
|
// src/commands/signals.ts
|
|
15798
|
-
var
|
|
15799
|
-
var
|
|
16681
|
+
var import_node_fs19 = __toESM(require("node:fs"));
|
|
16682
|
+
var import_node_path17 = __toESM(require("node:path"));
|
|
15800
16683
|
init_source();
|
|
15801
16684
|
var kind3 = "signals";
|
|
15802
16685
|
var singular2 = "signal";
|
|
@@ -15804,7 +16687,7 @@ function parse2(command, value) {
|
|
|
15804
16687
|
if (!value) return {};
|
|
15805
16688
|
try {
|
|
15806
16689
|
const source = value.startsWith("@") ? value.slice(1) : value;
|
|
15807
|
-
const text = value.startsWith("@") ||
|
|
16690
|
+
const text = value.startsWith("@") || import_node_fs19.default.existsSync(source) ? import_node_fs19.default.readFileSync(import_node_path17.default.resolve(process.cwd(), source), "utf8") : value;
|
|
15808
16691
|
const body5 = JSON.parse(text);
|
|
15809
16692
|
if (!body5 || typeof body5 !== "object" || Array.isArray(body5))
|
|
15810
16693
|
throw new Error("body must be an object");
|
|
@@ -15854,24 +16737,24 @@ var get7 = new Command("get").description("Get a signal").argument("<id>", "sign
|
|
|
15854
16737
|
fail(this, r.error ?? "Failed to get", classifyStatus(r.status), r.errorDetails);
|
|
15855
16738
|
output(this, { signal: r.data }, () => detail3(r.data));
|
|
15856
16739
|
});
|
|
15857
|
-
function write3(
|
|
15858
|
-
const c = new Command(
|
|
15859
|
-
`${
|
|
16740
|
+
function write3(action74) {
|
|
16741
|
+
const c = new Command(action74).description(
|
|
16742
|
+
`${action74 === "create" ? "Create" : "Update"} a ${singular2}`
|
|
15860
16743
|
);
|
|
15861
|
-
if (
|
|
16744
|
+
if (action74 === "update") c.argument("<id>", `${singular2} ID`);
|
|
15862
16745
|
c.argument("[json]", "Inline JSON, @file, or file path").option(
|
|
15863
16746
|
"--file <path>",
|
|
15864
16747
|
"JSON body file"
|
|
15865
16748
|
);
|
|
15866
16749
|
c.action(async function(...args) {
|
|
15867
16750
|
const o = this.opts();
|
|
15868
|
-
const id =
|
|
15869
|
-
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;
|
|
15870
16753
|
const body5 = parse2(this, raw);
|
|
15871
16754
|
if (!Object.keys(body5).length) fail(this, "Provide a JSON body.", EXIT_CODES.validation);
|
|
15872
|
-
const r =
|
|
16755
|
+
const r = action74 === "create" ? await createInsight(requireCredentials(), kind3, body5) : await updateInsight(requireCredentials(), kind3, id, body5);
|
|
15873
16756
|
if (!r.ok || !r.data)
|
|
15874
|
-
fail(this, r.error ?? `Failed to ${
|
|
16757
|
+
fail(this, r.error ?? `Failed to ${action74}`, classifyStatus(r.status), r.errorDetails);
|
|
15875
16758
|
output(this, { signal: r.data }, () => detail3(r.data));
|
|
15876
16759
|
});
|
|
15877
16760
|
return c;
|
|
@@ -15886,14 +16769,14 @@ var signalsCommand = new Command("signals").description("Manage signals").addCom
|
|
|
15886
16769
|
|
|
15887
16770
|
// src/commands/personalizations.ts
|
|
15888
16771
|
init_source();
|
|
15889
|
-
var
|
|
15890
|
-
var
|
|
16772
|
+
var import_node_fs20 = __toESM(require("node:fs"));
|
|
16773
|
+
var import_node_path18 = __toESM(require("node:path"));
|
|
15891
16774
|
var kind4 = "personalizations";
|
|
15892
16775
|
function body2(command, value, opts) {
|
|
15893
16776
|
let parsed = {};
|
|
15894
16777
|
if (value) {
|
|
15895
16778
|
const source = value.startsWith("@") ? value.slice(1) : value;
|
|
15896
|
-
const text = value.startsWith("@") ||
|
|
16779
|
+
const text = value.startsWith("@") || import_node_fs20.default.existsSync(source) ? import_node_fs20.default.readFileSync(import_node_path18.default.resolve(process.cwd(), source), "utf8") : value;
|
|
15897
16780
|
try {
|
|
15898
16781
|
parsed = JSON.parse(text);
|
|
15899
16782
|
} catch (e) {
|
|
@@ -15963,11 +16846,11 @@ function write4(name) {
|
|
|
15963
16846
|
});
|
|
15964
16847
|
return c;
|
|
15965
16848
|
}
|
|
15966
|
-
function lifecycle2(
|
|
15967
|
-
return new Command(
|
|
15968
|
-
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);
|
|
15969
16852
|
if (!r.ok || !r.data)
|
|
15970
|
-
fail(this, r.error ?? `Failed to ${
|
|
16853
|
+
fail(this, r.error ?? `Failed to ${action74}`, classifyStatus(r.status), r.errorDetails);
|
|
15971
16854
|
output(this, { personalization: r.data }, () => detail4(r.data));
|
|
15972
16855
|
});
|
|
15973
16856
|
}
|
|
@@ -15975,13 +16858,13 @@ var personalizationsCommand = new Command("personalizations").description("Manag
|
|
|
15975
16858
|
|
|
15976
16859
|
// src/commands/targeting-resources.ts
|
|
15977
16860
|
init_source();
|
|
15978
|
-
var
|
|
15979
|
-
var
|
|
16861
|
+
var import_node_fs21 = __toESM(require("node:fs"));
|
|
16862
|
+
var import_node_path19 = __toESM(require("node:path"));
|
|
15980
16863
|
function body3(command, value) {
|
|
15981
16864
|
if (!value) return {};
|
|
15982
16865
|
const source = value.startsWith("@") ? value.slice(1) : value;
|
|
15983
16866
|
try {
|
|
15984
|
-
const text = value.startsWith("@") ||
|
|
16867
|
+
const text = value.startsWith("@") || import_node_fs21.default.existsSync(source) ? import_node_fs21.default.readFileSync(import_node_path19.default.resolve(process.cwd(), source), "utf8") : value;
|
|
15985
16868
|
return JSON.parse(text);
|
|
15986
16869
|
} catch (error) {
|
|
15987
16870
|
fail(
|
|
@@ -16049,21 +16932,21 @@ function targetingResourceCommand(kind5) {
|
|
|
16049
16932
|
);
|
|
16050
16933
|
})
|
|
16051
16934
|
);
|
|
16052
|
-
for (const
|
|
16053
|
-
const command = new Command(
|
|
16054
|
-
`${
|
|
16935
|
+
for (const action74 of ["create", "update"]) {
|
|
16936
|
+
const command = new Command(action74).description(
|
|
16937
|
+
`${action74 === "create" ? "Create" : "Update"} a ${singular3}`
|
|
16055
16938
|
);
|
|
16056
|
-
if (
|
|
16939
|
+
if (action74 === "update") command.argument("<id>");
|
|
16057
16940
|
command.argument("[json]", "Inline JSON, @file, or file path").option("--file <path>", "JSON body file").action(async function(...args) {
|
|
16058
16941
|
const opts = this.opts();
|
|
16059
|
-
const id =
|
|
16060
|
-
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;
|
|
16061
16944
|
const requestBody = body3(this, value);
|
|
16062
|
-
if (
|
|
16945
|
+
if (action74 === "update" && typeof requestBody === "object" && requestBody !== null && !Array.isArray(requestBody) && Object.keys(requestBody).length === 0)
|
|
16063
16946
|
fail(this, `Updating a ${singular3} requires at least one field.`, EXIT_CODES.validation);
|
|
16064
16947
|
const response = await requestTargetingResource(
|
|
16065
16948
|
requireCredentials(),
|
|
16066
|
-
|
|
16949
|
+
action74 === "create" ? "POST" : "PATCH",
|
|
16067
16950
|
kind5,
|
|
16068
16951
|
id,
|
|
16069
16952
|
requestBody
|
|
@@ -16071,7 +16954,7 @@ function targetingResourceCommand(kind5) {
|
|
|
16071
16954
|
if (!response.ok || !response.data)
|
|
16072
16955
|
fail(
|
|
16073
16956
|
this,
|
|
16074
|
-
response.error ?? `Failed to ${
|
|
16957
|
+
response.error ?? `Failed to ${action74}`,
|
|
16075
16958
|
classifyStatus(response.status),
|
|
16076
16959
|
response.errorDetails
|
|
16077
16960
|
);
|
|
@@ -16080,7 +16963,7 @@ function targetingResourceCommand(kind5) {
|
|
|
16080
16963
|
{
|
|
16081
16964
|
[kind5 === "exclusion-groups" ? "exclusion_group" : singular3]: response.data
|
|
16082
16965
|
},
|
|
16083
|
-
() => console.log(source_default.green(`${
|
|
16966
|
+
() => console.log(source_default.green(`${action74}d ${singular3}`))
|
|
16084
16967
|
);
|
|
16085
16968
|
});
|
|
16086
16969
|
root.addCommand(command);
|
|
@@ -16101,8 +16984,8 @@ function targetingResourceCommand(kind5) {
|
|
|
16101
16984
|
input: process.stdin,
|
|
16102
16985
|
output: process.stderr
|
|
16103
16986
|
});
|
|
16104
|
-
const answer = await new Promise((
|
|
16105
|
-
rl.question(source_default.yellow(`Delete exclusion group "${id}"? [y/N] `),
|
|
16987
|
+
const answer = await new Promise((resolve2) => {
|
|
16988
|
+
rl.question(source_default.yellow(`Delete exclusion group "${id}"? [y/N] `), resolve2);
|
|
16106
16989
|
});
|
|
16107
16990
|
rl.close();
|
|
16108
16991
|
if (answer.toLowerCase() !== "y") {
|
|
@@ -16154,7 +17037,7 @@ var exclusionGroupsCommand = targetingResourceCommand("exclusion-groups");
|
|
|
16154
17037
|
// src/commands/brand.ts
|
|
16155
17038
|
init_source();
|
|
16156
17039
|
var NO_PROFILE_MESSAGE = "No brand profile yet \u2014 run onboarding or a site check";
|
|
16157
|
-
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() {
|
|
16158
17041
|
const creds = requireCredentials();
|
|
16159
17042
|
const res = await getBrandContext(creds);
|
|
16160
17043
|
if (res.status === 404) {
|
|
@@ -16187,7 +17070,7 @@ var brandCommand = new Command("brand").description("Print the shop brand contex
|
|
|
16187
17070
|
|
|
16188
17071
|
// src/commands/targeting.ts
|
|
16189
17072
|
init_source();
|
|
16190
|
-
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) {
|
|
16191
17074
|
const creds = requireCredentials();
|
|
16192
17075
|
const res = await testTargeting(creds, url, { status: opts.status });
|
|
16193
17076
|
if (!res.ok || !res.data) {
|
|
@@ -16232,8 +17115,8 @@ var targetingCommand = new Command("targeting").description("Test experiment tar
|
|
|
16232
17115
|
|
|
16233
17116
|
// src/commands/sos.ts
|
|
16234
17117
|
init_source();
|
|
16235
|
-
var
|
|
16236
|
-
var
|
|
17118
|
+
var import_node_fs22 = __toESM(require("node:fs"));
|
|
17119
|
+
var import_node_path20 = __toESM(require("node:path"));
|
|
16237
17120
|
var SOS_TYPES = /* @__PURE__ */ new Set([
|
|
16238
17121
|
"full_handover",
|
|
16239
17122
|
"design",
|
|
@@ -16246,7 +17129,7 @@ var SOS_TYPES = /* @__PURE__ */ new Set([
|
|
|
16246
17129
|
function readText(value) {
|
|
16247
17130
|
if (!value) return void 0;
|
|
16248
17131
|
if (!value.startsWith("@")) return value;
|
|
16249
|
-
return
|
|
17132
|
+
return import_node_fs22.default.readFileSync(import_node_path20.default.resolve(process.cwd(), value.slice(1)), "utf8");
|
|
16250
17133
|
}
|
|
16251
17134
|
function printTicket(ticket, reused) {
|
|
16252
17135
|
console.log();
|
|
@@ -16272,7 +17155,7 @@ var create4 = new Command("create").description("Create an Apex SOS ticket").opt
|
|
|
16272
17155
|
"--type <type>",
|
|
16273
17156
|
"full_handover|design|development|qa|config|bug|help (internal types require a DRIP account)",
|
|
16274
17157
|
"qa"
|
|
16275
|
-
).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) {
|
|
16276
17159
|
if (!SOS_TYPES.has(opts.type)) {
|
|
16277
17160
|
fail(
|
|
16278
17161
|
this,
|
|
@@ -16299,7 +17182,7 @@ var create4 = new Command("create").description("Create an Apex SOS ticket").opt
|
|
|
16299
17182
|
}
|
|
16300
17183
|
output(this, res.data, () => printCreatedTicket(res.data));
|
|
16301
17184
|
});
|
|
16302
|
-
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() {
|
|
16303
17186
|
const creds = requireCredentials();
|
|
16304
17187
|
const res = await listMySos(creds);
|
|
16305
17188
|
if (!res.ok || !res.data) {
|
|
@@ -16380,13 +17263,13 @@ var markRead = new Command("mark-read").option("--channel <key>", "Mark one chan
|
|
|
16380
17263
|
var inboxCommand = new Command("inbox").description("Read and send inbox messages").addCommand(list11).addCommand(send2).addCommand(markRead);
|
|
16381
17264
|
|
|
16382
17265
|
// src/commands/shops.ts
|
|
16383
|
-
var
|
|
16384
|
-
var
|
|
17266
|
+
var import_node_fs23 = __toESM(require("node:fs"));
|
|
17267
|
+
var import_node_path21 = __toESM(require("node:path"));
|
|
16385
17268
|
function body4(command, raw) {
|
|
16386
17269
|
if (!raw) return {};
|
|
16387
17270
|
try {
|
|
16388
17271
|
const source = raw.startsWith("@") ? raw.slice(1) : raw;
|
|
16389
|
-
const text = raw.startsWith("@") ||
|
|
17272
|
+
const text = raw.startsWith("@") || import_node_fs23.default.existsSync(source) ? import_node_fs23.default.readFileSync(import_node_path21.default.resolve(source), "utf8") : raw;
|
|
16390
17273
|
const value = JSON.parse(text);
|
|
16391
17274
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
16392
17275
|
throw new Error("body must be an object");
|
|
@@ -16448,7 +17331,7 @@ var create5 = new Command("create").description("Create a shop").argument("[json
|
|
|
16448
17331
|
const r = await createShop(requireUserCredentials(), payload);
|
|
16449
17332
|
done(this, r, "create shop");
|
|
16450
17333
|
});
|
|
16451
|
-
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) {
|
|
16452
17335
|
const b = raw === void 0 ? {} : body4(this, raw);
|
|
16453
17336
|
if (o.holdoutEnabled && o.holdoutDisabled)
|
|
16454
17337
|
fail(this, "Use only one of --holdout-enabled or --holdout-disabled", EXIT_CODES.validation);
|
|
@@ -16581,7 +17464,7 @@ shopware.addCommand(
|
|
|
16581
17464
|
);
|
|
16582
17465
|
})
|
|
16583
17466
|
);
|
|
16584
|
-
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(
|
|
16585
17468
|
settings("auto-stop", "Read or update auto-stop defaults", { on: "enable", off: "disable" })
|
|
16586
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);
|
|
16587
17470
|
|
|
@@ -16589,6 +17472,7 @@ var shopsCommand = new Command("shops").description("Manage organization shops")
|
|
|
16589
17472
|
function optionSchema(option) {
|
|
16590
17473
|
return {
|
|
16591
17474
|
flags: option.flags,
|
|
17475
|
+
...option.argChoices ? { choices: option.argChoices } : {},
|
|
16592
17476
|
description: option.description,
|
|
16593
17477
|
required: option.required,
|
|
16594
17478
|
optional: option.optional,
|
|
@@ -16614,7 +17498,7 @@ function commandSchema(command) {
|
|
|
16614
17498
|
};
|
|
16615
17499
|
}
|
|
16616
17500
|
function createSchemaCommand(program3) {
|
|
16617
|
-
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() {
|
|
16618
17502
|
const schema = commandSchema(program3);
|
|
16619
17503
|
output(this, schema, () => {
|
|
16620
17504
|
process.stdout.write(`${JSON.stringify(schema, null, 2)}
|
|
@@ -17029,7 +17913,7 @@ ${event.event || "(unnamed event)"} [${event.dlName}] \u2014 ${event.count} obse
|
|
|
17029
17913
|
return `${lines.join("\n")}
|
|
17030
17914
|
`;
|
|
17031
17915
|
}
|
|
17032
|
-
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) {
|
|
17033
17917
|
if (opts.gtm && opts.gtmUrl)
|
|
17034
17918
|
fail(this, "Use only one of --gtm or --gtm-url", EXIT_CODES.validation);
|
|
17035
17919
|
if (opts.createDimension && (opts.gtm || opts.gtmUrl)) {
|
|
@@ -17199,16 +18083,16 @@ var discoverCommand = new Command("discover").description("Read back the trackin
|
|
|
17199
18083
|
});
|
|
17200
18084
|
|
|
17201
18085
|
// src/commands/skills.ts
|
|
17202
|
-
var
|
|
17203
|
-
var
|
|
18086
|
+
var import_node_fs24 = __toESM(require("node:fs"));
|
|
18087
|
+
var import_node_path22 = __toESM(require("node:path"));
|
|
17204
18088
|
function packageRoot() {
|
|
17205
18089
|
const candidates = [
|
|
17206
|
-
|
|
17207
|
-
|
|
17208
|
-
|
|
18090
|
+
import_node_path22.default.resolve(__dirname, ".."),
|
|
18091
|
+
import_node_path22.default.resolve(import_node_path22.default.dirname(process.argv[1] ?? process.cwd()), ".."),
|
|
18092
|
+
import_node_path22.default.resolve(process.cwd(), "packages/cli")
|
|
17209
18093
|
];
|
|
17210
18094
|
for (const candidate of candidates) {
|
|
17211
|
-
if (
|
|
18095
|
+
if (import_node_fs24.default.existsSync(import_node_path22.default.join(candidate, "AGENTS.md"))) return candidate;
|
|
17212
18096
|
}
|
|
17213
18097
|
throw new Error(
|
|
17214
18098
|
`Apex CLI package files not found. Looked for AGENTS.md in: ${candidates.join(", ")}`
|
|
@@ -17217,22 +18101,22 @@ function packageRoot() {
|
|
|
17217
18101
|
function skillPaths() {
|
|
17218
18102
|
const root = packageRoot();
|
|
17219
18103
|
return {
|
|
17220
|
-
agents:
|
|
17221
|
-
skill:
|
|
18104
|
+
agents: import_node_path22.default.join(root, "AGENTS.md"),
|
|
18105
|
+
skill: import_node_path22.default.join(root, "skills", "apex-cli", "SKILL.md")
|
|
17222
18106
|
};
|
|
17223
18107
|
}
|
|
17224
18108
|
function installSkill(paths, targetRoot) {
|
|
17225
|
-
const missing = [paths.skill, paths.agents].filter((filePath) => !
|
|
18109
|
+
const missing = [paths.skill, paths.agents].filter((filePath) => !import_node_fs24.default.existsSync(filePath));
|
|
17226
18110
|
if (missing.length > 0) {
|
|
17227
18111
|
throw new Error(`Apex CLI package files not found. Missing: ${missing.join(", ")}`);
|
|
17228
18112
|
}
|
|
17229
|
-
const target =
|
|
17230
|
-
|
|
17231
|
-
|
|
17232
|
-
|
|
18113
|
+
const target = import_node_path22.default.resolve(targetRoot, "apex-cli");
|
|
18114
|
+
import_node_fs24.default.mkdirSync(target, { recursive: true });
|
|
18115
|
+
import_node_fs24.default.copyFileSync(paths.skill, import_node_path22.default.join(target, "SKILL.md"));
|
|
18116
|
+
import_node_fs24.default.copyFileSync(paths.agents, import_node_path22.default.join(target, "AGENTS.md"));
|
|
17233
18117
|
return target;
|
|
17234
18118
|
}
|
|
17235
|
-
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) {
|
|
17236
18120
|
let paths;
|
|
17237
18121
|
try {
|
|
17238
18122
|
paths = skillPaths();
|
|
@@ -17329,8 +18213,8 @@ async function confirmArchive(command, shopId, force) {
|
|
|
17329
18213
|
}
|
|
17330
18214
|
const readline3 = await import("node:readline");
|
|
17331
18215
|
const rl = readline3.createInterface({ input: process.stdin, output: process.stderr });
|
|
17332
|
-
const answer = await new Promise((
|
|
17333
|
-
rl.question("Archive this shop? Apex delivery will be disabled. [y/N] ",
|
|
18216
|
+
const answer = await new Promise((resolve2) => {
|
|
18217
|
+
rl.question("Archive this shop? Apex delivery will be disabled. [y/N] ", resolve2);
|
|
17334
18218
|
});
|
|
17335
18219
|
rl.close();
|
|
17336
18220
|
if (answer.toLowerCase() !== "y") {
|
|
@@ -17384,7 +18268,7 @@ async function confirm(c, force, label, retry) {
|
|
|
17384
18268
|
const readline3 = await import("node:readline");
|
|
17385
18269
|
const rl = readline3.createInterface({ input: process.stdin, output: process.stderr });
|
|
17386
18270
|
const answer = await new Promise(
|
|
17387
|
-
(
|
|
18271
|
+
(resolve2) => rl.question(source_default.yellow(`${label}? [y/N] `), resolve2)
|
|
17388
18272
|
);
|
|
17389
18273
|
rl.close();
|
|
17390
18274
|
if (answer.toLowerCase() !== "y") {
|
|
@@ -17856,7 +18740,7 @@ function selectedEvents(opts) {
|
|
|
17856
18740
|
}
|
|
17857
18741
|
var list14 = new Command("list").alias("ls").description(
|
|
17858
18742
|
"List webhooks. JSON includes availableEvents; secrets are never returned after create."
|
|
17859
|
-
).action(async function
|
|
18743
|
+
).action(async function action68() {
|
|
17860
18744
|
const data = await loadWebhookIndex(this);
|
|
17861
18745
|
output(this, data, () => printWebhooks(data.data));
|
|
17862
18746
|
});
|
|
@@ -17865,7 +18749,7 @@ var create7 = new Command("create").description(`Create a webhook. Prints the si
|
|
|
17865
18749
|
"Webhook event to subscribe to; repeat for multiple events",
|
|
17866
18750
|
(value, previous = []) => [...previous, value],
|
|
17867
18751
|
[]
|
|
17868
|
-
).action(async function
|
|
18752
|
+
).action(async function action69(opts) {
|
|
17869
18753
|
const events = selectedEvents(opts);
|
|
17870
18754
|
if (events.length === 0) fail(this, "At least one --event is required", EXIT_CODES.validation);
|
|
17871
18755
|
const index = await loadWebhookIndex(this);
|
|
@@ -17897,7 +18781,7 @@ var create7 = new Command("create").description(`Create a webhook. Prints the si
|
|
|
17897
18781
|
}
|
|
17898
18782
|
);
|
|
17899
18783
|
});
|
|
17900
|
-
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) {
|
|
17901
18785
|
requireShopSecretKey(this);
|
|
17902
18786
|
const result = handleResult(
|
|
17903
18787
|
this,
|
|
@@ -17906,7 +18790,7 @@ var remove4 = new Command("delete").alias("rm").description("Delete a webhook").
|
|
|
17906
18790
|
);
|
|
17907
18791
|
output(this, result, () => console.log(source_default.green(`Deleted webhook ${id}`)));
|
|
17908
18792
|
});
|
|
17909
|
-
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) {
|
|
17910
18794
|
const limit = Number.parseInt(opts.limit, 10);
|
|
17911
18795
|
if (!Number.isInteger(limit) || limit < 1)
|
|
17912
18796
|
fail(this, "--limit must be a positive integer", EXIT_CODES.validation);
|
|
@@ -17918,12 +18802,12 @@ var deliveries = new Command("deliveries").description("List webhook deliveries
|
|
|
17918
18802
|
);
|
|
17919
18803
|
output(this, result, () => printDeliveries(result.data, result.webhook));
|
|
17920
18804
|
});
|
|
17921
|
-
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(
|
|
17922
18806
|
"--event <event>",
|
|
17923
18807
|
"Replacement event; repeat for multiple events",
|
|
17924
18808
|
(value, previous = []) => [...previous, value],
|
|
17925
18809
|
[]
|
|
17926
|
-
).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) {
|
|
17927
18811
|
if (opts.active && opts.inactive)
|
|
17928
18812
|
fail(this, "Use only one of --active or --inactive", EXIT_CODES.validation);
|
|
17929
18813
|
const events = selectedEvents(opts);
|
|
@@ -17956,7 +18840,7 @@ var update4 = new Command("update").description("Update a webhook URL, events, o
|
|
|
17956
18840
|
);
|
|
17957
18841
|
output(this, { webhook: result }, () => printWebhooks([result]));
|
|
17958
18842
|
});
|
|
17959
|
-
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) {
|
|
17960
18844
|
requireShopSecretKey(this);
|
|
17961
18845
|
const result = handleResult(
|
|
17962
18846
|
this,
|
|
@@ -17971,14 +18855,14 @@ var test3 = new Command("test").description("Send a signed test event to a webho
|
|
|
17971
18855
|
)
|
|
17972
18856
|
);
|
|
17973
18857
|
});
|
|
17974
|
-
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);
|
|
17975
18859
|
|
|
17976
18860
|
// src/commands/simulate.ts
|
|
17977
18861
|
init_source();
|
|
17978
|
-
var
|
|
17979
|
-
var
|
|
18862
|
+
var import_node_fs25 = __toESM(require("node:fs"));
|
|
18863
|
+
var import_node_path23 = __toESM(require("node:path"));
|
|
17980
18864
|
function readJson2(file) {
|
|
17981
|
-
const text = file === "-" ?
|
|
18865
|
+
const text = file === "-" ? import_node_fs25.default.readFileSync(0, "utf8") : import_node_fs25.default.readFileSync(import_node_path23.default.resolve(file), "utf8");
|
|
17982
18866
|
try {
|
|
17983
18867
|
return JSON.parse(text);
|
|
17984
18868
|
} catch (error) {
|
|
@@ -18100,8 +18984,8 @@ var simulateCommand = new Command("simulate").description("Simulate data-layer t
|
|
|
18100
18984
|
});
|
|
18101
18985
|
|
|
18102
18986
|
// src/commands/config-resources.ts
|
|
18103
|
-
var
|
|
18104
|
-
var
|
|
18987
|
+
var import_node_fs26 = __toESM(require("node:fs"));
|
|
18988
|
+
var import_node_path24 = __toESM(require("node:path"));
|
|
18105
18989
|
function show(resource) {
|
|
18106
18990
|
process.stdout.write(`${JSON.stringify(resource, null, 2)}
|
|
18107
18991
|
`);
|
|
@@ -18127,8 +19011,8 @@ async function confirmAssetDelete(id) {
|
|
|
18127
19011
|
input: process.stdin,
|
|
18128
19012
|
output: process.stderr
|
|
18129
19013
|
});
|
|
18130
|
-
const answer = await new Promise((
|
|
18131
|
-
rl.question(`Delete asset "${id}"? This cannot be undone. [y/N] `,
|
|
19014
|
+
const answer = await new Promise((resolve2) => {
|
|
19015
|
+
rl.question(`Delete asset "${id}"? This cannot be undone. [y/N] `, resolve2);
|
|
18132
19016
|
});
|
|
18133
19017
|
rl.close();
|
|
18134
19018
|
return answer.trim().toLowerCase() === "y";
|
|
@@ -18174,12 +19058,12 @@ function readFamily(family, label) {
|
|
|
18174
19058
|
}
|
|
18175
19059
|
var assetReads = readFamily("assets", "assets");
|
|
18176
19060
|
var upload = new Command("upload").description("Upload an asset").argument("<file>").action(async function(file) {
|
|
18177
|
-
const absolute =
|
|
18178
|
-
if (!
|
|
18179
|
-
const fileStat =
|
|
19061
|
+
const absolute = import_node_path24.default.resolve(process.cwd(), file);
|
|
19062
|
+
if (!import_node_fs26.default.existsSync(absolute)) fail(this, `File not found: ${file}`, 3);
|
|
19063
|
+
const fileStat = import_node_fs26.default.statSync(absolute);
|
|
18180
19064
|
if (!fileStat.isFile()) fail(this, `File not found: ${file}`, 3);
|
|
18181
19065
|
if (fileStat.size > MAX_ASSET_SIZE_BYTES) fail(this, `${file} is larger than 10 MB`, 3);
|
|
18182
|
-
const mimeType = ASSET_MIME_TYPES[
|
|
19066
|
+
const mimeType = ASSET_MIME_TYPES[import_node_path24.default.extname(absolute).toLowerCase()];
|
|
18183
19067
|
if (!mimeType)
|
|
18184
19068
|
fail(
|
|
18185
19069
|
this,
|
|
@@ -18188,8 +19072,8 @@ var upload = new Command("upload").description("Upload an asset").argument("<fil
|
|
|
18188
19072
|
);
|
|
18189
19073
|
const r = await uploadAssets(requireCredentials(), [
|
|
18190
19074
|
{
|
|
18191
|
-
name:
|
|
18192
|
-
bytes:
|
|
19075
|
+
name: import_node_path24.default.basename(absolute),
|
|
19076
|
+
bytes: import_node_fs26.default.readFileSync(absolute),
|
|
18193
19077
|
type: mimeType
|
|
18194
19078
|
}
|
|
18195
19079
|
]);
|
|
@@ -18279,6 +19163,7 @@ program2.addCommand(funnelsCommand);
|
|
|
18279
19163
|
program2.addCommand(environmentsCommand);
|
|
18280
19164
|
program2.addCommand(consentCommand);
|
|
18281
19165
|
program2.addCommand(triggersCommand);
|
|
19166
|
+
program2.addCommand(projectCodeCommand);
|
|
18282
19167
|
program2.addCommand(domainsCommand);
|
|
18283
19168
|
program2.addCommand(handoverCommand);
|
|
18284
19169
|
program2.addCommand(analyticsCommand);
|