@drip-apex/apex-cli 0.15.1 → 0.15.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/cli.js +1375 -76
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -7325,7 +7325,7 @@ async function checkForUpdate(current) {
|
|
|
7325
7325
|
// package.json
|
|
7326
7326
|
var package_default = {
|
|
7327
7327
|
name: "@drip-apex/apex-cli",
|
|
7328
|
-
version: "0.15.
|
|
7328
|
+
version: "0.15.3",
|
|
7329
7329
|
license: "MIT",
|
|
7330
7330
|
description: "Apex CLI for managing experiments, approved roadmap ideas, and draft workspaces.",
|
|
7331
7331
|
private: false,
|
|
@@ -7589,7 +7589,8 @@ function classifyApiError(status4, details) {
|
|
|
7589
7589
|
const code = details?.code;
|
|
7590
7590
|
if (code === "shop_context_required" || code === "org_context_required")
|
|
7591
7591
|
return EXIT_CODES.validation;
|
|
7592
|
-
if (code === "shop_access_denied" || code === "org_access_denied"
|
|
7592
|
+
if (code === "shop_access_denied" || code === "org_access_denied" || code === "member_suspended")
|
|
7593
|
+
return EXIT_CODES.auth;
|
|
7593
7594
|
return classifyStatus(status4);
|
|
7594
7595
|
}
|
|
7595
7596
|
function confirmationRequired(command, message, retry) {
|
|
@@ -7686,7 +7687,7 @@ function errorReason(error) {
|
|
|
7686
7687
|
const body5 = error.api?.body;
|
|
7687
7688
|
const code = body5?.code ?? body5?.error?.code ?? (error.api ? details?.code : void 0);
|
|
7688
7689
|
if (typeof code === "string" && code) return code;
|
|
7689
|
-
if (["shop_context_required", "local_file_error", "validation_failed"].includes(
|
|
7690
|
+
if (["shop_context_required", "local_file_error", "validation_failed", "member_suspended"].includes(
|
|
7690
7691
|
String(details?.code)
|
|
7691
7692
|
))
|
|
7692
7693
|
return String(details?.code);
|
|
@@ -7704,6 +7705,13 @@ function errorReason(error) {
|
|
|
7704
7705
|
function errorNextSteps(error) {
|
|
7705
7706
|
const reason2 = errorReason(error);
|
|
7706
7707
|
const steps = [];
|
|
7708
|
+
if (reason2 === "member_suspended")
|
|
7709
|
+
return [
|
|
7710
|
+
{
|
|
7711
|
+
command: "contact organization admin",
|
|
7712
|
+
why: "Ask an organization owner or admin to reinstate your membership."
|
|
7713
|
+
}
|
|
7714
|
+
];
|
|
7707
7715
|
if (reason2 === "auth_required" || error.api?.status === 401) {
|
|
7708
7716
|
steps.push({ command: "apex login", why: "Authenticate with a valid API key." });
|
|
7709
7717
|
if (!error.api)
|
|
@@ -8438,6 +8446,7 @@ function listExperiments(creds, opts) {
|
|
|
8438
8446
|
if (opts?.includeArchived) params.set("archived", "1");
|
|
8439
8447
|
if (opts?.view) params.set("view", opts.view);
|
|
8440
8448
|
if (opts?.fields !== void 0) params.set("fields", opts.fields);
|
|
8449
|
+
if (opts?.url !== void 0) params.set("url", opts.url);
|
|
8441
8450
|
const qs = params.toString();
|
|
8442
8451
|
return request(creds, "GET", `/api/v1/experiments${qs ? `?${qs}` : ""}`);
|
|
8443
8452
|
}
|
|
@@ -8485,6 +8494,8 @@ function getExperimentFilesystem(creds, id) {
|
|
|
8485
8494
|
function getExperimentResults(creds, id, opts) {
|
|
8486
8495
|
const params = new URLSearchParams();
|
|
8487
8496
|
if (opts?.currency) params.set("currency", opts.currency);
|
|
8497
|
+
for (const key of ["segment", "exclude_segment"])
|
|
8498
|
+
if (opts?.[key] !== void 0) params.set(key, opts[key]);
|
|
8488
8499
|
const qs = params.toString();
|
|
8489
8500
|
return request(
|
|
8490
8501
|
creds,
|
|
@@ -8530,6 +8541,7 @@ function applyExperimentFilesystem(creds, id, body5) {
|
|
|
8530
8541
|
`/api/v1/experiments/${id}/filesystem/apply`,
|
|
8531
8542
|
{
|
|
8532
8543
|
files: body5.files,
|
|
8544
|
+
...body5.accept_qa_invalidation !== void 0 ? { accept_qa_invalidation: body5.accept_qa_invalidation } : {},
|
|
8533
8545
|
...body5.confirmTargetingReplace === true ? { confirmTargetingReplace: true } : {}
|
|
8534
8546
|
}
|
|
8535
8547
|
);
|
|
@@ -8537,6 +8549,17 @@ function applyExperimentFilesystem(creds, id, body5) {
|
|
|
8537
8549
|
function getExperimentQaRun(creds, id) {
|
|
8538
8550
|
return request(creds, "GET", `/api/v1/experiments/${id}/qa/run`);
|
|
8539
8551
|
}
|
|
8552
|
+
function getExperimentQaEvents(creds, id, options = {}) {
|
|
8553
|
+
const params = new URLSearchParams();
|
|
8554
|
+
if (options.session) params.set("qa_session", options.session);
|
|
8555
|
+
if (options.minutes !== void 0) params.set("minutes", String(options.minutes));
|
|
8556
|
+
if (options.type) params.set("type", options.type);
|
|
8557
|
+
return request(
|
|
8558
|
+
creds,
|
|
8559
|
+
"GET",
|
|
8560
|
+
`/api/v1/experiments/${id}/qa/events${params.size ? `?${params}` : ""}`
|
|
8561
|
+
);
|
|
8562
|
+
}
|
|
8540
8563
|
function updateExperimentQaRun(creds, id, body5) {
|
|
8541
8564
|
return request(creds, "PATCH", `/api/v1/experiments/${id}/qa/run`, body5);
|
|
8542
8565
|
}
|
|
@@ -8746,7 +8769,7 @@ function testTargeting(creds, url, opts) {
|
|
|
8746
8769
|
if (opts?.status) params.set("status", opts.status);
|
|
8747
8770
|
return request(creds, "GET", `/api/v1/targeting/test?${params.toString()}`);
|
|
8748
8771
|
}
|
|
8749
|
-
function createSos(creds, body5) {
|
|
8772
|
+
function createSos(creds, body5, options = {}) {
|
|
8750
8773
|
const idempotency_key = body5.idempotency_key ?? (0, import_node_crypto2.createHash)("sha256").update(
|
|
8751
8774
|
[
|
|
8752
8775
|
body5.type,
|
|
@@ -8754,11 +8777,29 @@ function createSos(creds, body5) {
|
|
|
8754
8777
|
body5.description,
|
|
8755
8778
|
body5.sub_class ?? "",
|
|
8756
8779
|
body5.loom_url ?? "",
|
|
8780
|
+
...options.attachmentDigests ?? [],
|
|
8757
8781
|
String(Math.floor(Date.now() / 36e5))
|
|
8758
8782
|
].join("\0")
|
|
8759
8783
|
).digest("hex");
|
|
8760
8784
|
return request(creds, "POST", "/api/v1/sos", { ...body5, idempotency_key });
|
|
8761
8785
|
}
|
|
8786
|
+
function createSosUploadGrants(creds, files) {
|
|
8787
|
+
return request(
|
|
8788
|
+
creds,
|
|
8789
|
+
"POST",
|
|
8790
|
+
"/api/v1/sos/attachment-uploads",
|
|
8791
|
+
{ files }
|
|
8792
|
+
);
|
|
8793
|
+
}
|
|
8794
|
+
async function uploadSosAttachment(signedUrl, contentType, bytes) {
|
|
8795
|
+
const response = await fetch(signedUrl, {
|
|
8796
|
+
method: "PUT",
|
|
8797
|
+
headers: { "Content-Type": contentType },
|
|
8798
|
+
body: bytes,
|
|
8799
|
+
signal: AbortSignal.timeout(12e4)
|
|
8800
|
+
});
|
|
8801
|
+
if (!response.ok) throw new Error(`Attachment upload failed (${response.status})`);
|
|
8802
|
+
}
|
|
8762
8803
|
function listMySos(creds) {
|
|
8763
8804
|
return request(creds, "GET", "/api/v1/sos/mine");
|
|
8764
8805
|
}
|
|
@@ -8869,6 +8910,15 @@ function createAdminCustomer(creds, body5) {
|
|
|
8869
8910
|
body5
|
|
8870
8911
|
);
|
|
8871
8912
|
}
|
|
8913
|
+
function setAdminCustomerCategory(creds, orgId, body5) {
|
|
8914
|
+
return request(userCredentials(creds), "PATCH", `/api/v1/admin/customers/${orgId}`, body5);
|
|
8915
|
+
}
|
|
8916
|
+
function setAdminCustomerManagedBy(creds, orgId, body5) {
|
|
8917
|
+
return request(userCredentials(creds), "PATCH", `/api/v1/admin/customers/${orgId}`, body5);
|
|
8918
|
+
}
|
|
8919
|
+
function setAdminCustomerWorkspaceType(creds, orgId, body5) {
|
|
8920
|
+
return request(userCredentials(creds), "PATCH", `/api/v1/admin/customers/${orgId}`, body5);
|
|
8921
|
+
}
|
|
8872
8922
|
function getAdminSettings(creds) {
|
|
8873
8923
|
return request(userCredentials(creds), "GET", "/api/v1/admin/settings");
|
|
8874
8924
|
}
|
|
@@ -9023,6 +9073,17 @@ function revokeOrgInvite(creds, id) {
|
|
|
9023
9073
|
function setOrgMember(creds, id, body5) {
|
|
9024
9074
|
return request(userCreds(creds), "PATCH", `/api/v1/org/members/${encodeURIComponent(id)}`, body5);
|
|
9025
9075
|
}
|
|
9076
|
+
function suspendOrgMember(creds, id, reason2) {
|
|
9077
|
+
return request(userCreds(creds), "PATCH", `/api/v1/org/members/${encodeURIComponent(id)}`, {
|
|
9078
|
+
suspended: true,
|
|
9079
|
+
...reason2 ? { reason: reason2 } : {}
|
|
9080
|
+
});
|
|
9081
|
+
}
|
|
9082
|
+
function reinstateOrgMember(creds, id) {
|
|
9083
|
+
return request(userCreds(creds), "PATCH", `/api/v1/org/members/${encodeURIComponent(id)}`, {
|
|
9084
|
+
suspended: false
|
|
9085
|
+
});
|
|
9086
|
+
}
|
|
9026
9087
|
function removeOrgMember(creds, id) {
|
|
9027
9088
|
return request(userCreds(creds), "DELETE", `/api/v1/org/members/${encodeURIComponent(id)}`);
|
|
9028
9089
|
}
|
|
@@ -9166,6 +9227,14 @@ function getOrderJourney(creds, opts) {
|
|
|
9166
9227
|
45e3
|
|
9167
9228
|
);
|
|
9168
9229
|
}
|
|
9230
|
+
function moveAdminCustomerStores(creds, orgId, body5) {
|
|
9231
|
+
return request(
|
|
9232
|
+
userCredentials(creds),
|
|
9233
|
+
"POST",
|
|
9234
|
+
`/api/v1/admin/customers/${orgId}/move-stores`,
|
|
9235
|
+
body5
|
|
9236
|
+
);
|
|
9237
|
+
}
|
|
9169
9238
|
|
|
9170
9239
|
// src/context.ts
|
|
9171
9240
|
var import_node_readline = __toESM(require("node:readline"));
|
|
@@ -9377,10 +9446,14 @@ async function waitForBrowserLogin(baseUrl, userMode = false) {
|
|
|
9377
9446
|
const returnedState = params.get("state");
|
|
9378
9447
|
const apiKey = params.get("apiKey");
|
|
9379
9448
|
const error = params.get("error");
|
|
9449
|
+
const code = params.get("code");
|
|
9380
9450
|
if (error) {
|
|
9381
9451
|
res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
|
|
9382
9452
|
res.end("<h1>Apex CLI login failed</h1><p>You can close this window.</p>");
|
|
9383
|
-
|
|
9453
|
+
const details = code ? { code } : void 0;
|
|
9454
|
+
finish(
|
|
9455
|
+
details ? new CliError(error, classifyApiError(400, details), details) : new Error(error)
|
|
9456
|
+
);
|
|
9384
9457
|
return;
|
|
9385
9458
|
}
|
|
9386
9459
|
if (returnedState !== state) {
|
|
@@ -9464,6 +9537,7 @@ var loginCommand = new Command("login").description("Authenticate with Apex").op
|
|
|
9464
9537
|
try {
|
|
9465
9538
|
apiKey = await waitForBrowserLogin(baseUrl, true);
|
|
9466
9539
|
} catch (err) {
|
|
9540
|
+
if (err instanceof CliError) throw err;
|
|
9467
9541
|
fail(this, err instanceof Error ? err.message : "Browser login failed.");
|
|
9468
9542
|
}
|
|
9469
9543
|
}
|
|
@@ -9755,13 +9829,18 @@ function buildQaPreviewUrl(experiment2, options = {}) {
|
|
|
9755
9829
|
}
|
|
9756
9830
|
|
|
9757
9831
|
// src/lib/qa-preview-url.ts
|
|
9758
|
-
function buildQaPreviewUrl2(storeUrl, experimentId, variationId) {
|
|
9759
|
-
const url = buildQaPreviewUrl(
|
|
9760
|
-
|
|
9761
|
-
|
|
9762
|
-
|
|
9763
|
-
|
|
9832
|
+
function buildQaPreviewUrl2(storeUrl, experimentId, variationId, qaSessionId) {
|
|
9833
|
+
const url = buildQaPreviewUrl(
|
|
9834
|
+
{
|
|
9835
|
+
id: experimentId,
|
|
9836
|
+
store_url: storeUrl,
|
|
9837
|
+
variations: [{ id: variationId, is_control: false }]
|
|
9838
|
+
},
|
|
9839
|
+
{ qaSessionId }
|
|
9840
|
+
);
|
|
9764
9841
|
if (!url) throw new TypeError("Invalid QA preview URL");
|
|
9842
|
+
if (qaSessionId && url.searchParams.get("drip_qa_session") !== qaSessionId)
|
|
9843
|
+
url.searchParams.set("drip_qa_session", qaSessionId);
|
|
9765
9844
|
return url;
|
|
9766
9845
|
}
|
|
9767
9846
|
|
|
@@ -10159,6 +10238,755 @@ function createAssetReferenceRewriter(options) {
|
|
|
10159
10238
|
return { rewrite, rewriteUnknown };
|
|
10160
10239
|
}
|
|
10161
10240
|
|
|
10241
|
+
// src/generated/experiment-filesystem.schema.json
|
|
10242
|
+
var experiment_filesystem_schema_default = {
|
|
10243
|
+
$id: "https://api.drip-apex.com/schemas/experiment-filesystem-v1.json",
|
|
10244
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
10245
|
+
files: {
|
|
10246
|
+
"experiment.json": {
|
|
10247
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
10248
|
+
additionalProperties: false,
|
|
10249
|
+
properties: {
|
|
10250
|
+
autoStopOverride: {
|
|
10251
|
+
anyOf: [
|
|
10252
|
+
{},
|
|
10253
|
+
{
|
|
10254
|
+
type: "null"
|
|
10255
|
+
}
|
|
10256
|
+
]
|
|
10257
|
+
},
|
|
10258
|
+
countingType: {
|
|
10259
|
+
anyOf: [
|
|
10260
|
+
{
|
|
10261
|
+
enum: ["standard", "setup_test", "aa_test", "non_program"],
|
|
10262
|
+
type: "string"
|
|
10263
|
+
},
|
|
10264
|
+
{
|
|
10265
|
+
type: "null"
|
|
10266
|
+
}
|
|
10267
|
+
]
|
|
10268
|
+
},
|
|
10269
|
+
description: {
|
|
10270
|
+
anyOf: [
|
|
10271
|
+
{
|
|
10272
|
+
type: "string"
|
|
10273
|
+
},
|
|
10274
|
+
{
|
|
10275
|
+
type: "null"
|
|
10276
|
+
}
|
|
10277
|
+
]
|
|
10278
|
+
},
|
|
10279
|
+
environmentKey: {
|
|
10280
|
+
type: "string"
|
|
10281
|
+
},
|
|
10282
|
+
experimentType: {
|
|
10283
|
+
enum: ["mutation", "redirect"],
|
|
10284
|
+
type: "string"
|
|
10285
|
+
},
|
|
10286
|
+
id: {
|
|
10287
|
+
anyOf: [
|
|
10288
|
+
{
|
|
10289
|
+
type: "string"
|
|
10290
|
+
},
|
|
10291
|
+
{
|
|
10292
|
+
type: "null"
|
|
10293
|
+
}
|
|
10294
|
+
]
|
|
10295
|
+
},
|
|
10296
|
+
name: {
|
|
10297
|
+
type: "string"
|
|
10298
|
+
},
|
|
10299
|
+
schemaVersion: {
|
|
10300
|
+
const: 1,
|
|
10301
|
+
type: "number"
|
|
10302
|
+
},
|
|
10303
|
+
sequentialTestingEnabled: {
|
|
10304
|
+
type: "boolean"
|
|
10305
|
+
},
|
|
10306
|
+
significanceLevel: {
|
|
10307
|
+
type: "number"
|
|
10308
|
+
},
|
|
10309
|
+
slug: {
|
|
10310
|
+
anyOf: [
|
|
10311
|
+
{
|
|
10312
|
+
type: "string"
|
|
10313
|
+
},
|
|
10314
|
+
{
|
|
10315
|
+
type: "null"
|
|
10316
|
+
}
|
|
10317
|
+
]
|
|
10318
|
+
},
|
|
10319
|
+
statisticalPower: {
|
|
10320
|
+
type: "number"
|
|
10321
|
+
},
|
|
10322
|
+
storeUrl: {
|
|
10323
|
+
anyOf: [
|
|
10324
|
+
{
|
|
10325
|
+
type: "string"
|
|
10326
|
+
},
|
|
10327
|
+
{
|
|
10328
|
+
type: "null"
|
|
10329
|
+
}
|
|
10330
|
+
]
|
|
10331
|
+
},
|
|
10332
|
+
testKey: {
|
|
10333
|
+
anyOf: [
|
|
10334
|
+
{
|
|
10335
|
+
type: "string"
|
|
10336
|
+
},
|
|
10337
|
+
{
|
|
10338
|
+
type: "null"
|
|
10339
|
+
}
|
|
10340
|
+
]
|
|
10341
|
+
},
|
|
10342
|
+
trafficAllocation: {
|
|
10343
|
+
maximum: 1,
|
|
10344
|
+
minimum: 0,
|
|
10345
|
+
type: "number"
|
|
10346
|
+
}
|
|
10347
|
+
},
|
|
10348
|
+
required: ["schemaVersion"],
|
|
10349
|
+
type: "object"
|
|
10350
|
+
},
|
|
10351
|
+
"experiment/index.css": {
|
|
10352
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
10353
|
+
contentMediaType: "text/css",
|
|
10354
|
+
type: "string"
|
|
10355
|
+
},
|
|
10356
|
+
"experiment/index.js": {
|
|
10357
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
10358
|
+
contentMediaType: "text/javascript",
|
|
10359
|
+
type: "string"
|
|
10360
|
+
},
|
|
10361
|
+
"experiment/reset.js": {
|
|
10362
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
10363
|
+
contentMediaType: "text/javascript",
|
|
10364
|
+
type: "string"
|
|
10365
|
+
},
|
|
10366
|
+
"launch.json": {
|
|
10367
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
10368
|
+
additionalProperties: false,
|
|
10369
|
+
properties: {
|
|
10370
|
+
endedAt: {
|
|
10371
|
+
anyOf: [
|
|
10372
|
+
{
|
|
10373
|
+
type: "string"
|
|
10374
|
+
},
|
|
10375
|
+
{
|
|
10376
|
+
type: "null"
|
|
10377
|
+
}
|
|
10378
|
+
]
|
|
10379
|
+
},
|
|
10380
|
+
launchApproval: {
|
|
10381
|
+
anyOf: [
|
|
10382
|
+
{
|
|
10383
|
+
additionalProperties: false,
|
|
10384
|
+
properties: {
|
|
10385
|
+
decidedAt: {
|
|
10386
|
+
anyOf: [
|
|
10387
|
+
{
|
|
10388
|
+
type: "string"
|
|
10389
|
+
},
|
|
10390
|
+
{
|
|
10391
|
+
type: "null"
|
|
10392
|
+
}
|
|
10393
|
+
]
|
|
10394
|
+
},
|
|
10395
|
+
note: {
|
|
10396
|
+
anyOf: [
|
|
10397
|
+
{
|
|
10398
|
+
type: "string"
|
|
10399
|
+
},
|
|
10400
|
+
{
|
|
10401
|
+
type: "null"
|
|
10402
|
+
}
|
|
10403
|
+
]
|
|
10404
|
+
},
|
|
10405
|
+
status: {
|
|
10406
|
+
type: "string"
|
|
10407
|
+
}
|
|
10408
|
+
},
|
|
10409
|
+
required: ["status", "decidedAt", "note"],
|
|
10410
|
+
type: "object"
|
|
10411
|
+
},
|
|
10412
|
+
{
|
|
10413
|
+
type: "null"
|
|
10414
|
+
}
|
|
10415
|
+
]
|
|
10416
|
+
},
|
|
10417
|
+
launchGate: {
|
|
10418
|
+
additionalProperties: false,
|
|
10419
|
+
properties: {
|
|
10420
|
+
qaApproved: {
|
|
10421
|
+
type: "boolean"
|
|
10422
|
+
},
|
|
10423
|
+
treatmentArtifactsApproved: {
|
|
10424
|
+
type: "boolean"
|
|
10425
|
+
}
|
|
10426
|
+
},
|
|
10427
|
+
required: ["qaApproved", "treatmentArtifactsApproved"],
|
|
10428
|
+
type: "object"
|
|
10429
|
+
},
|
|
10430
|
+
readonly: {
|
|
10431
|
+
const: true,
|
|
10432
|
+
type: "boolean"
|
|
10433
|
+
},
|
|
10434
|
+
scheduledEndAt: {
|
|
10435
|
+
anyOf: [
|
|
10436
|
+
{
|
|
10437
|
+
type: "string"
|
|
10438
|
+
},
|
|
10439
|
+
{
|
|
10440
|
+
type: "null"
|
|
10441
|
+
}
|
|
10442
|
+
]
|
|
10443
|
+
},
|
|
10444
|
+
scheduledStartAt: {
|
|
10445
|
+
anyOf: [
|
|
10446
|
+
{
|
|
10447
|
+
type: "string"
|
|
10448
|
+
},
|
|
10449
|
+
{
|
|
10450
|
+
type: "null"
|
|
10451
|
+
}
|
|
10452
|
+
]
|
|
10453
|
+
},
|
|
10454
|
+
schemaVersion: {
|
|
10455
|
+
const: 1,
|
|
10456
|
+
type: "number"
|
|
10457
|
+
},
|
|
10458
|
+
startedAt: {
|
|
10459
|
+
anyOf: [
|
|
10460
|
+
{
|
|
10461
|
+
type: "string"
|
|
10462
|
+
},
|
|
10463
|
+
{
|
|
10464
|
+
type: "null"
|
|
10465
|
+
}
|
|
10466
|
+
]
|
|
10467
|
+
},
|
|
10468
|
+
status: {
|
|
10469
|
+
type: "string"
|
|
10470
|
+
}
|
|
10471
|
+
},
|
|
10472
|
+
readOnly: true,
|
|
10473
|
+
required: [
|
|
10474
|
+
"schemaVersion",
|
|
10475
|
+
"readonly",
|
|
10476
|
+
"status",
|
|
10477
|
+
"scheduledStartAt",
|
|
10478
|
+
"scheduledEndAt",
|
|
10479
|
+
"startedAt",
|
|
10480
|
+
"endedAt",
|
|
10481
|
+
"launchApproval",
|
|
10482
|
+
"launchGate"
|
|
10483
|
+
],
|
|
10484
|
+
type: "object"
|
|
10485
|
+
},
|
|
10486
|
+
"metrics.json": {
|
|
10487
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
10488
|
+
additionalProperties: false,
|
|
10489
|
+
properties: {
|
|
10490
|
+
goals: {
|
|
10491
|
+
items: {
|
|
10492
|
+
additionalProperties: false,
|
|
10493
|
+
properties: {
|
|
10494
|
+
capture: {
|
|
10495
|
+
anyOf: [
|
|
10496
|
+
{},
|
|
10497
|
+
{
|
|
10498
|
+
type: "null"
|
|
10499
|
+
}
|
|
10500
|
+
]
|
|
10501
|
+
},
|
|
10502
|
+
countOnce: {
|
|
10503
|
+
type: "boolean"
|
|
10504
|
+
},
|
|
10505
|
+
eventName: {
|
|
10506
|
+
anyOf: [
|
|
10507
|
+
{
|
|
10508
|
+
type: "string"
|
|
10509
|
+
},
|
|
10510
|
+
{
|
|
10511
|
+
type: "null"
|
|
10512
|
+
}
|
|
10513
|
+
]
|
|
10514
|
+
},
|
|
10515
|
+
filter: {
|
|
10516
|
+
anyOf: [
|
|
10517
|
+
{},
|
|
10518
|
+
{
|
|
10519
|
+
type: "null"
|
|
10520
|
+
}
|
|
10521
|
+
]
|
|
10522
|
+
},
|
|
10523
|
+
id: {
|
|
10524
|
+
anyOf: [
|
|
10525
|
+
{
|
|
10526
|
+
type: "string"
|
|
10527
|
+
},
|
|
10528
|
+
{
|
|
10529
|
+
type: "null"
|
|
10530
|
+
}
|
|
10531
|
+
]
|
|
10532
|
+
},
|
|
10533
|
+
isPrimary: {
|
|
10534
|
+
type: "boolean"
|
|
10535
|
+
},
|
|
10536
|
+
measurement: {},
|
|
10537
|
+
name: {
|
|
10538
|
+
type: "string"
|
|
10539
|
+
},
|
|
10540
|
+
pageId: {
|
|
10541
|
+
anyOf: [
|
|
10542
|
+
{
|
|
10543
|
+
type: "string"
|
|
10544
|
+
},
|
|
10545
|
+
{
|
|
10546
|
+
type: "null"
|
|
10547
|
+
}
|
|
10548
|
+
]
|
|
10549
|
+
},
|
|
10550
|
+
selector: {
|
|
10551
|
+
anyOf: [
|
|
10552
|
+
{
|
|
10553
|
+
type: "string"
|
|
10554
|
+
},
|
|
10555
|
+
{
|
|
10556
|
+
type: "null"
|
|
10557
|
+
}
|
|
10558
|
+
]
|
|
10559
|
+
},
|
|
10560
|
+
textMatch: {
|
|
10561
|
+
anyOf: [
|
|
10562
|
+
{
|
|
10563
|
+
type: "string"
|
|
10564
|
+
},
|
|
10565
|
+
{
|
|
10566
|
+
type: "null"
|
|
10567
|
+
}
|
|
10568
|
+
]
|
|
10569
|
+
},
|
|
10570
|
+
type: {
|
|
10571
|
+
enum: ["click", "pageview", "custom", "revenue"],
|
|
10572
|
+
type: "string"
|
|
10573
|
+
},
|
|
10574
|
+
urlPattern: {
|
|
10575
|
+
anyOf: [
|
|
10576
|
+
{
|
|
10577
|
+
type: "string"
|
|
10578
|
+
},
|
|
10579
|
+
{
|
|
10580
|
+
type: "null"
|
|
10581
|
+
}
|
|
10582
|
+
]
|
|
10583
|
+
},
|
|
10584
|
+
valueProperty: {
|
|
10585
|
+
anyOf: [
|
|
10586
|
+
{
|
|
10587
|
+
type: "string"
|
|
10588
|
+
},
|
|
10589
|
+
{
|
|
10590
|
+
type: "null"
|
|
10591
|
+
}
|
|
10592
|
+
]
|
|
10593
|
+
},
|
|
10594
|
+
valueUnit: {
|
|
10595
|
+
anyOf: [
|
|
10596
|
+
{
|
|
10597
|
+
type: "string"
|
|
10598
|
+
},
|
|
10599
|
+
{
|
|
10600
|
+
type: "null"
|
|
10601
|
+
}
|
|
10602
|
+
]
|
|
10603
|
+
}
|
|
10604
|
+
},
|
|
10605
|
+
required: ["name", "type"],
|
|
10606
|
+
type: "object"
|
|
10607
|
+
},
|
|
10608
|
+
type: "array"
|
|
10609
|
+
},
|
|
10610
|
+
primaryGoalId: {
|
|
10611
|
+
anyOf: [
|
|
10612
|
+
{
|
|
10613
|
+
type: "string"
|
|
10614
|
+
},
|
|
10615
|
+
{
|
|
10616
|
+
type: "null"
|
|
10617
|
+
}
|
|
10618
|
+
]
|
|
10619
|
+
},
|
|
10620
|
+
schemaVersion: {
|
|
10621
|
+
const: 1,
|
|
10622
|
+
type: "number"
|
|
10623
|
+
}
|
|
10624
|
+
},
|
|
10625
|
+
required: ["schemaVersion", "primaryGoalId", "goals"],
|
|
10626
|
+
type: "object"
|
|
10627
|
+
},
|
|
10628
|
+
"qa.json": {
|
|
10629
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
10630
|
+
additionalProperties: false,
|
|
10631
|
+
properties: {
|
|
10632
|
+
latestRun: {
|
|
10633
|
+
anyOf: [
|
|
10634
|
+
{
|
|
10635
|
+
additionalProperties: false,
|
|
10636
|
+
properties: {
|
|
10637
|
+
approvedAt: {
|
|
10638
|
+
anyOf: [
|
|
10639
|
+
{
|
|
10640
|
+
type: "string"
|
|
10641
|
+
},
|
|
10642
|
+
{
|
|
10643
|
+
type: "null"
|
|
10644
|
+
}
|
|
10645
|
+
]
|
|
10646
|
+
},
|
|
10647
|
+
completedAt: {
|
|
10648
|
+
anyOf: [
|
|
10649
|
+
{
|
|
10650
|
+
type: "string"
|
|
10651
|
+
},
|
|
10652
|
+
{
|
|
10653
|
+
type: "null"
|
|
10654
|
+
}
|
|
10655
|
+
]
|
|
10656
|
+
},
|
|
10657
|
+
id: {
|
|
10658
|
+
type: "string"
|
|
10659
|
+
},
|
|
10660
|
+
source: {
|
|
10661
|
+
type: "string"
|
|
10662
|
+
},
|
|
10663
|
+
status: {
|
|
10664
|
+
type: "string"
|
|
10665
|
+
},
|
|
10666
|
+
summary: {
|
|
10667
|
+
anyOf: [
|
|
10668
|
+
{},
|
|
10669
|
+
{
|
|
10670
|
+
type: "null"
|
|
10671
|
+
}
|
|
10672
|
+
]
|
|
10673
|
+
}
|
|
10674
|
+
},
|
|
10675
|
+
required: ["id", "status", "source", "approvedAt", "completedAt", "summary"],
|
|
10676
|
+
type: "object"
|
|
10677
|
+
},
|
|
10678
|
+
{
|
|
10679
|
+
type: "null"
|
|
10680
|
+
}
|
|
10681
|
+
]
|
|
10682
|
+
},
|
|
10683
|
+
qaApprovedAt: {
|
|
10684
|
+
anyOf: [
|
|
10685
|
+
{
|
|
10686
|
+
type: "string"
|
|
10687
|
+
},
|
|
10688
|
+
{
|
|
10689
|
+
type: "null"
|
|
10690
|
+
}
|
|
10691
|
+
]
|
|
10692
|
+
},
|
|
10693
|
+
qaApprovedBy: {
|
|
10694
|
+
anyOf: [
|
|
10695
|
+
{
|
|
10696
|
+
type: "string"
|
|
10697
|
+
},
|
|
10698
|
+
{
|
|
10699
|
+
type: "null"
|
|
10700
|
+
}
|
|
10701
|
+
]
|
|
10702
|
+
},
|
|
10703
|
+
readonly: {
|
|
10704
|
+
const: true,
|
|
10705
|
+
type: "boolean"
|
|
10706
|
+
},
|
|
10707
|
+
schemaVersion: {
|
|
10708
|
+
const: 1,
|
|
10709
|
+
type: "number"
|
|
10710
|
+
}
|
|
10711
|
+
},
|
|
10712
|
+
readOnly: true,
|
|
10713
|
+
required: ["schemaVersion", "readonly", "qaApprovedAt", "qaApprovedBy", "latestRun"],
|
|
10714
|
+
type: "object"
|
|
10715
|
+
},
|
|
10716
|
+
"targeting.json": {
|
|
10717
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
10718
|
+
additionalProperties: {},
|
|
10719
|
+
properties: {
|
|
10720
|
+
authorUrl: {
|
|
10721
|
+
items: {
|
|
10722
|
+
additionalProperties: false,
|
|
10723
|
+
properties: {
|
|
10724
|
+
include: {
|
|
10725
|
+
type: "boolean"
|
|
10726
|
+
},
|
|
10727
|
+
match_type: {
|
|
10728
|
+
enum: ["exact", "contains", "wildcard", "regex"],
|
|
10729
|
+
type: "string"
|
|
10730
|
+
},
|
|
10731
|
+
matchType: {
|
|
10732
|
+
enum: ["exact", "contains", "wildcard", "regex"],
|
|
10733
|
+
type: "string"
|
|
10734
|
+
},
|
|
10735
|
+
pattern: {
|
|
10736
|
+
minLength: 1,
|
|
10737
|
+
type: "string"
|
|
10738
|
+
},
|
|
10739
|
+
type: {
|
|
10740
|
+
enum: ["regex", "simple"],
|
|
10741
|
+
type: "string"
|
|
10742
|
+
}
|
|
10743
|
+
},
|
|
10744
|
+
required: ["type", "pattern"],
|
|
10745
|
+
type: "object"
|
|
10746
|
+
},
|
|
10747
|
+
type: "array"
|
|
10748
|
+
},
|
|
10749
|
+
custom: {},
|
|
10750
|
+
device: {},
|
|
10751
|
+
deviceType: {
|
|
10752
|
+
type: "string"
|
|
10753
|
+
},
|
|
10754
|
+
lifecycle: {},
|
|
10755
|
+
pageRules: {
|
|
10756
|
+
items: {},
|
|
10757
|
+
type: "array"
|
|
10758
|
+
},
|
|
10759
|
+
pages: {
|
|
10760
|
+
items: {},
|
|
10761
|
+
type: "array"
|
|
10762
|
+
},
|
|
10763
|
+
segments: {
|
|
10764
|
+
items: {},
|
|
10765
|
+
type: "array"
|
|
10766
|
+
},
|
|
10767
|
+
url: {
|
|
10768
|
+
items: {
|
|
10769
|
+
additionalProperties: false,
|
|
10770
|
+
properties: {
|
|
10771
|
+
include: {
|
|
10772
|
+
type: "boolean"
|
|
10773
|
+
},
|
|
10774
|
+
match_type: {
|
|
10775
|
+
enum: ["exact", "contains", "wildcard", "regex"],
|
|
10776
|
+
type: "string"
|
|
10777
|
+
},
|
|
10778
|
+
matchType: {
|
|
10779
|
+
enum: ["exact", "contains", "wildcard", "regex"],
|
|
10780
|
+
type: "string"
|
|
10781
|
+
},
|
|
10782
|
+
pattern: {
|
|
10783
|
+
minLength: 1,
|
|
10784
|
+
type: "string"
|
|
10785
|
+
},
|
|
10786
|
+
type: {
|
|
10787
|
+
enum: ["regex", "simple"],
|
|
10788
|
+
type: "string"
|
|
10789
|
+
}
|
|
10790
|
+
},
|
|
10791
|
+
required: ["type", "pattern"],
|
|
10792
|
+
type: "object"
|
|
10793
|
+
},
|
|
10794
|
+
type: "array"
|
|
10795
|
+
}
|
|
10796
|
+
},
|
|
10797
|
+
type: "object"
|
|
10798
|
+
},
|
|
10799
|
+
"variations/*/anti-flicker.json": {
|
|
10800
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
10801
|
+
items: {
|
|
10802
|
+
type: "string"
|
|
10803
|
+
},
|
|
10804
|
+
type: "array"
|
|
10805
|
+
},
|
|
10806
|
+
"variations/*/index.css": {
|
|
10807
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
10808
|
+
contentMediaType: "text/css",
|
|
10809
|
+
type: "string"
|
|
10810
|
+
},
|
|
10811
|
+
"variations/*/index.js": {
|
|
10812
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
10813
|
+
contentMediaType: "text/javascript",
|
|
10814
|
+
type: "string"
|
|
10815
|
+
},
|
|
10816
|
+
"variations/*/mutations.json": {
|
|
10817
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
10818
|
+
items: {
|
|
10819
|
+
additionalProperties: {},
|
|
10820
|
+
anyOf: [
|
|
10821
|
+
{
|
|
10822
|
+
required: ["selector"]
|
|
10823
|
+
},
|
|
10824
|
+
{
|
|
10825
|
+
properties: {
|
|
10826
|
+
action: {
|
|
10827
|
+
enum: [
|
|
10828
|
+
"priceTest",
|
|
10829
|
+
"shippingTest",
|
|
10830
|
+
"offerTest",
|
|
10831
|
+
"postPurchaseOffer",
|
|
10832
|
+
"checkoutBlock"
|
|
10833
|
+
]
|
|
10834
|
+
}
|
|
10835
|
+
},
|
|
10836
|
+
required: ["action"]
|
|
10837
|
+
}
|
|
10838
|
+
],
|
|
10839
|
+
properties: {
|
|
10840
|
+
action: {
|
|
10841
|
+
type: "string"
|
|
10842
|
+
},
|
|
10843
|
+
selector: {
|
|
10844
|
+
minLength: 1,
|
|
10845
|
+
type: "string"
|
|
10846
|
+
},
|
|
10847
|
+
type: {
|
|
10848
|
+
type: "string"
|
|
10849
|
+
}
|
|
10850
|
+
},
|
|
10851
|
+
type: "object"
|
|
10852
|
+
},
|
|
10853
|
+
type: "array"
|
|
10854
|
+
},
|
|
10855
|
+
"variations/*/reset.js": {
|
|
10856
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
10857
|
+
contentMediaType: "text/javascript",
|
|
10858
|
+
type: "string"
|
|
10859
|
+
},
|
|
10860
|
+
"variations/*/url-blocks.json": {
|
|
10861
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
10862
|
+
items: {},
|
|
10863
|
+
type: "array"
|
|
10864
|
+
},
|
|
10865
|
+
"variations/*/variation.json": {
|
|
10866
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
10867
|
+
additionalProperties: false,
|
|
10868
|
+
properties: {
|
|
10869
|
+
artifactReview: {
|
|
10870
|
+
additionalProperties: false,
|
|
10871
|
+
properties: {
|
|
10872
|
+
hash: {
|
|
10873
|
+
anyOf: [
|
|
10874
|
+
{
|
|
10875
|
+
type: "string"
|
|
10876
|
+
},
|
|
10877
|
+
{
|
|
10878
|
+
type: "null"
|
|
10879
|
+
}
|
|
10880
|
+
]
|
|
10881
|
+
},
|
|
10882
|
+
reviewedAt: {
|
|
10883
|
+
anyOf: [
|
|
10884
|
+
{
|
|
10885
|
+
type: "string"
|
|
10886
|
+
},
|
|
10887
|
+
{
|
|
10888
|
+
type: "null"
|
|
10889
|
+
}
|
|
10890
|
+
]
|
|
10891
|
+
},
|
|
10892
|
+
reviewedBy: {
|
|
10893
|
+
anyOf: [
|
|
10894
|
+
{
|
|
10895
|
+
type: "string"
|
|
10896
|
+
},
|
|
10897
|
+
{
|
|
10898
|
+
type: "null"
|
|
10899
|
+
}
|
|
10900
|
+
]
|
|
10901
|
+
},
|
|
10902
|
+
source: {
|
|
10903
|
+
anyOf: [
|
|
10904
|
+
{
|
|
10905
|
+
type: "string"
|
|
10906
|
+
},
|
|
10907
|
+
{
|
|
10908
|
+
type: "null"
|
|
10909
|
+
}
|
|
10910
|
+
]
|
|
10911
|
+
},
|
|
10912
|
+
status: {
|
|
10913
|
+
type: "string"
|
|
10914
|
+
}
|
|
10915
|
+
},
|
|
10916
|
+
required: ["status"],
|
|
10917
|
+
type: "object"
|
|
10918
|
+
},
|
|
10919
|
+
codeDeliveryMode: {
|
|
10920
|
+
enum: ["inline", "external"],
|
|
10921
|
+
type: "string"
|
|
10922
|
+
},
|
|
10923
|
+
cssArtifactId: {
|
|
10924
|
+
anyOf: [
|
|
10925
|
+
{
|
|
10926
|
+
type: "string"
|
|
10927
|
+
},
|
|
10928
|
+
{
|
|
10929
|
+
type: "null"
|
|
10930
|
+
}
|
|
10931
|
+
]
|
|
10932
|
+
},
|
|
10933
|
+
id: {
|
|
10934
|
+
anyOf: [
|
|
10935
|
+
{
|
|
10936
|
+
type: "string"
|
|
10937
|
+
},
|
|
10938
|
+
{
|
|
10939
|
+
type: "null"
|
|
10940
|
+
}
|
|
10941
|
+
]
|
|
10942
|
+
},
|
|
10943
|
+
isControl: {
|
|
10944
|
+
type: "boolean"
|
|
10945
|
+
},
|
|
10946
|
+
jsArtifactId: {
|
|
10947
|
+
anyOf: [
|
|
10948
|
+
{
|
|
10949
|
+
type: "string"
|
|
10950
|
+
},
|
|
10951
|
+
{
|
|
10952
|
+
type: "null"
|
|
10953
|
+
}
|
|
10954
|
+
]
|
|
10955
|
+
},
|
|
10956
|
+
name: {
|
|
10957
|
+
type: "string"
|
|
10958
|
+
},
|
|
10959
|
+
redirectUrl: {
|
|
10960
|
+
anyOf: [
|
|
10961
|
+
{
|
|
10962
|
+
type: "string"
|
|
10963
|
+
},
|
|
10964
|
+
{
|
|
10965
|
+
type: "null"
|
|
10966
|
+
}
|
|
10967
|
+
]
|
|
10968
|
+
},
|
|
10969
|
+
schemaVersion: {
|
|
10970
|
+
const: 1,
|
|
10971
|
+
type: "number"
|
|
10972
|
+
},
|
|
10973
|
+
slug: {
|
|
10974
|
+
type: "string"
|
|
10975
|
+
},
|
|
10976
|
+
weight: {
|
|
10977
|
+
maximum: 1,
|
|
10978
|
+
minimum: 0,
|
|
10979
|
+
type: "number"
|
|
10980
|
+
}
|
|
10981
|
+
},
|
|
10982
|
+
required: ["schemaVersion"],
|
|
10983
|
+
type: "object"
|
|
10984
|
+
}
|
|
10985
|
+
},
|
|
10986
|
+
schemaVersion: 1,
|
|
10987
|
+
source: "experiment-filesystem"
|
|
10988
|
+
};
|
|
10989
|
+
|
|
10162
10990
|
// src/commands/experiments.ts
|
|
10163
10991
|
function formatDate(iso) {
|
|
10164
10992
|
return new Date(iso).toLocaleDateString("en-US", {
|
|
@@ -10222,6 +11050,13 @@ function formatRevenue(value, currency) {
|
|
|
10222
11050
|
return String(value);
|
|
10223
11051
|
}
|
|
10224
11052
|
}
|
|
11053
|
+
function audienceLine(segmentApplied) {
|
|
11054
|
+
if (!segmentApplied || typeof segmentApplied !== "object") return null;
|
|
11055
|
+
const { requested, applied, reason: reason2 } = segmentApplied;
|
|
11056
|
+
if (!requested) return null;
|
|
11057
|
+
const label = `${requested.mode === "exclude" ? "excluding " : ""}${String(requested.dim)}=${String(requested.value)}`;
|
|
11058
|
+
return applied === true ? label : `all visitors (${label} not applied: ${typeof reason2 === "string" ? reason2 : "unknown reason"})`;
|
|
11059
|
+
}
|
|
10225
11060
|
function printExperimentResults(results2, currency) {
|
|
10226
11061
|
const summary = summarizeResults(results2);
|
|
10227
11062
|
const { experiment: experiment2, totals, decision, verdictWithheld, statisticsState, presentedOutcome } = summary;
|
|
@@ -10229,6 +11064,8 @@ function printExperimentResults(results2, currency) {
|
|
|
10229
11064
|
console.log();
|
|
10230
11065
|
console.log(source_default.bold(String(experiment2.name ?? experiment2.id ?? "Experiment results")));
|
|
10231
11066
|
console.log(` Status: ${statusBadge(String(experiment2.status ?? "unknown"))}`);
|
|
11067
|
+
const audience = audienceLine(results2.segment_applied);
|
|
11068
|
+
if (audience) console.log(` Audience: ${audience}`);
|
|
10232
11069
|
console.log(` Visitors: ${String(totals.visitors ?? 0)}`);
|
|
10233
11070
|
console.log(` Conversions: ${String(totals.primaryConversions ?? 0)}`);
|
|
10234
11071
|
console.log(` Revenue: ${formatRevenue(totals.revenue, currency)}`);
|
|
@@ -10292,18 +11129,11 @@ function printExperimentResults(results2, currency) {
|
|
|
10292
11129
|
}
|
|
10293
11130
|
console.log();
|
|
10294
11131
|
}
|
|
11132
|
+
var filesystemPaths = Object.keys(experiment_filesystem_schema_default.files);
|
|
10295
11133
|
var EXPERIMENT_FILESYSTEM_PATH_SCHEMA = {
|
|
10296
|
-
topLevel:
|
|
10297
|
-
experiment:
|
|
10298
|
-
variation:
|
|
10299
|
-
"variation.json",
|
|
10300
|
-
"mutations.json",
|
|
10301
|
-
"url-blocks.json",
|
|
10302
|
-
"index.css",
|
|
10303
|
-
"index.js",
|
|
10304
|
-
"reset.js",
|
|
10305
|
-
"anti-flicker.json"
|
|
10306
|
-
]
|
|
11134
|
+
topLevel: filesystemPaths.filter((entry) => !entry.includes("/")),
|
|
11135
|
+
experiment: filesystemPaths.filter((entry) => entry.startsWith("experiment/")).map((entry) => entry.slice("experiment/".length)),
|
|
11136
|
+
variation: filesystemPaths.filter((entry) => entry.startsWith("variations/*/")).map((entry) => entry.slice("variations/*/".length))
|
|
10307
11137
|
};
|
|
10308
11138
|
var SERVER_OWNED_EXPERIMENT_FILES = /* @__PURE__ */ new Set(["qa.json", "launch.json"]);
|
|
10309
11139
|
var escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -10507,14 +11337,31 @@ function printFilesystemPlan(plan2) {
|
|
|
10507
11337
|
}
|
|
10508
11338
|
console.log();
|
|
10509
11339
|
console.log(`QA invalidated: ${plan2.invalidatesQa ? source_default.yellow("yes") : "no"}`);
|
|
10510
|
-
console.log(
|
|
11340
|
+
console.log(
|
|
11341
|
+
`QA approval: ${!plan2.qaApproval ? "unknown (older server)" : plan2.qaApproval.present ? `present${plan2.qaApproval.approvedAt ? ` (approved ${plan2.qaApproval.approvedAt})` : ""}` : "none"}`
|
|
11342
|
+
);
|
|
11343
|
+
console.log(`Acknowledgment: ${requiredQaAcknowledgment(plan2) ?? "none"}`);
|
|
10511
11344
|
console.log(`Precompile: ${plan2.precompileRequired ? "yes" : "no"}`);
|
|
10512
11345
|
}
|
|
11346
|
+
function requiredQaAcknowledgment(plan2) {
|
|
11347
|
+
return "requiredAcknowledgment" in plan2 ? plan2.requiredAcknowledgment ?? null : plan2.invalidatesQa ? "accept-qa-invalidation" : null;
|
|
11348
|
+
}
|
|
10513
11349
|
function blockingIssueSummary(issues) {
|
|
10514
11350
|
if (!issues?.length) return "Experiment filesystem plan has errors.";
|
|
10515
11351
|
const remaining = issues.length - 1;
|
|
10516
11352
|
return `Experiment filesystem plan has errors. ${issues[0].message}${remaining ? ` and ${remaining} more` : ""}`;
|
|
10517
11353
|
}
|
|
11354
|
+
function qaApprovalRefusal(approvedAt, folderArg) {
|
|
11355
|
+
return {
|
|
11356
|
+
message: `Applying these changes revokes the QA approval from ${approvedAt ?? "an unspecified time"}; pass --accept-qa-invalidation to confirm.`,
|
|
11357
|
+
details: {
|
|
11358
|
+
reason: "qa_approval_present",
|
|
11359
|
+
qa_approved_at: approvedAt,
|
|
11360
|
+
required_acknowledgment: "accept-qa-invalidation",
|
|
11361
|
+
retry: `apex experiments apply ${folderArg} --accept-qa-invalidation`
|
|
11362
|
+
}
|
|
11363
|
+
};
|
|
11364
|
+
}
|
|
10518
11365
|
async function getQaPreviewUrls(creds, experimentId) {
|
|
10519
11366
|
const fallbackDashboardUrl = buildDashboardUrl(
|
|
10520
11367
|
creds.baseUrl,
|
|
@@ -10738,20 +11585,31 @@ var list = new Command("list").alias("ls").description(
|
|
|
10738
11585
|
"List experiments in summary view by default; use --full for details or --fields for selected columns (archived experiments are hidden by default)"
|
|
10739
11586
|
).option(
|
|
10740
11587
|
"-s, --status <status>",
|
|
10741
|
-
"Filter by status (draft|awaiting_client_approval|changes_requested|client_approved|qa|ready_to_launch|running|paused|completed)"
|
|
11588
|
+
"Filter by status (draft|awaiting_client_approval|changes_requested|client_approved|qa|ready_to_launch|running|paused|completed) (applied server-side)"
|
|
11589
|
+
).option(
|
|
11590
|
+
"--url <absolute-url>",
|
|
11591
|
+
"Filter by effective storefront URL targeting (applied server-side)"
|
|
10742
11592
|
).option("-n, --limit <n>", "Max results", "20").option("--include-archived", "Include archived experiments").option("--full", "Return full experiment details instead of the default summary").option(
|
|
10743
11593
|
"--fields <list>",
|
|
10744
11594
|
"Comma-separated fields to return and table columns in order (JSON always keeps id)"
|
|
10745
11595
|
).action(async function action3(opts) {
|
|
10746
11596
|
const creds = requireCredentials();
|
|
10747
11597
|
const spinner = await createSpinner("Fetching experiments...");
|
|
10748
|
-
const
|
|
11598
|
+
const listDefault = () => listExperiments(creds, {
|
|
10749
11599
|
status: opts.status,
|
|
10750
11600
|
limit: parseInt(opts.limit, 10),
|
|
10751
11601
|
includeArchived: opts.includeArchived,
|
|
10752
11602
|
view: opts.full ? "full" : "summary",
|
|
10753
11603
|
fields: opts.fields
|
|
10754
11604
|
});
|
|
11605
|
+
const res = opts.url !== void 0 ? await listExperiments(creds, {
|
|
11606
|
+
status: opts.status,
|
|
11607
|
+
limit: parseInt(opts.limit, 10),
|
|
11608
|
+
includeArchived: opts.includeArchived,
|
|
11609
|
+
view: opts.full ? "full" : "summary",
|
|
11610
|
+
fields: opts.fields,
|
|
11611
|
+
url: opts.url
|
|
11612
|
+
}) : await listDefault();
|
|
10755
11613
|
if (!res.ok) {
|
|
10756
11614
|
spinner?.fail("Failed to fetch experiments");
|
|
10757
11615
|
fail(this, res.error, classifyStatus(res.status), res.errorDetails);
|
|
@@ -10889,7 +11747,13 @@ ${JSON.stringify(data.order, null, 2)}`);
|
|
|
10889
11747
|
console.log(`
|
|
10890
11748
|
Definitions: ${data.definitions_url}`);
|
|
10891
11749
|
}
|
|
10892
|
-
var results = new Command("results").description("Read experiment results and timeseries by ID or slug").argument("<id-or-slug>", EXPERIMENT_REFERENCE_DESCRIPTION).option("--currency <code>", "Currency code for revenue formatting").option("--explain", "Explain raw-event exclusions and residuals").option("--goal <id>", "Goal ID or event key for --explain").option("--order <key>", "Order label trail for --explain").option("--samples", "Show sample identifiers with --explain").action(async function action7(idOrSlug, opts) {
|
|
11750
|
+
var results = new Command("results").description("Read experiment results and timeseries by ID or slug").argument("<id-or-slug>", EXPERIMENT_REFERENCE_DESCRIPTION).option("--currency <code>", "Currency code for revenue formatting").option("--segment <dim:value>", "Include one audience segment").option("--exclude-segment <dim:value>", "Exclude one audience segment (include wins)").option("--explain", "Explain raw-event exclusions and residuals").option("--goal <id>", "Goal ID or event key for --explain").option("--order <key>", "Order label trail for --explain").option("--samples", "Show sample identifiers with --explain").action(async function action7(idOrSlug, opts) {
|
|
11751
|
+
if (opts.explain && (opts.segment !== void 0 || opts.excludeSegment !== void 0))
|
|
11752
|
+
fail(
|
|
11753
|
+
this,
|
|
11754
|
+
"--segment and --exclude-segment are only available for results, without --explain",
|
|
11755
|
+
EXIT_CODES.validation
|
|
11756
|
+
);
|
|
10893
11757
|
if (!opts.explain && (opts.goal || opts.order || opts.samples))
|
|
10894
11758
|
fail(this, "--goal, --order and --samples require --explain", EXIT_CODES.validation);
|
|
10895
11759
|
const creds = requireCredentials();
|
|
@@ -10900,7 +11764,11 @@ var results = new Command("results").description("Read experiment results and ti
|
|
|
10900
11764
|
spinner?.fail(`Experiment "${idOrSlug}" not found`);
|
|
10901
11765
|
fail(this, `Experiment "${idOrSlug}" not found.`, EXIT_CODES.validation);
|
|
10902
11766
|
}
|
|
10903
|
-
const res = opts.explain ? await getExperimentResultsExplain(creds, exp.id, opts) : await getExperimentResults(creds, exp.id, {
|
|
11767
|
+
const res = opts.explain ? await getExperimentResultsExplain(creds, exp.id, opts) : await getExperimentResults(creds, exp.id, {
|
|
11768
|
+
currency: opts.currency,
|
|
11769
|
+
segment: opts.segment,
|
|
11770
|
+
exclude_segment: opts.excludeSegment
|
|
11771
|
+
});
|
|
10904
11772
|
if (!res.ok || !res.data) {
|
|
10905
11773
|
spinner?.fail("Failed to fetch experiment results");
|
|
10906
11774
|
fail(
|
|
@@ -11174,7 +12042,7 @@ var create = new Command("create").description(
|
|
|
11174
12042
|
[]
|
|
11175
12043
|
).option(
|
|
11176
12044
|
"--goal <goal-id|new:fields>",
|
|
11177
|
-
"Goal id or new:type=...,selector=...,name=... (repeatable)",
|
|
12045
|
+
"Goal id or new:type=...,selector=...,name=... (repeatable; omitted uses the shop default main goal)",
|
|
11178
12046
|
collectCreateOption,
|
|
11179
12047
|
[]
|
|
11180
12048
|
).option(
|
|
@@ -11593,6 +12461,7 @@ var pullFilesystem = new Command("pull").description("Pull an experiment as an e
|
|
|
11593
12461
|
output(
|
|
11594
12462
|
this,
|
|
11595
12463
|
{
|
|
12464
|
+
schemaVersion: 1,
|
|
11596
12465
|
folder,
|
|
11597
12466
|
experimentId: exp.id,
|
|
11598
12467
|
files: res.data.files.map((file) => import_node_path6.default.join(folder, file.path))
|
|
@@ -11890,7 +12759,7 @@ var planFilesystem = new Command("plan").description("Plan experiment filesystem
|
|
|
11890
12759
|
folder,
|
|
11891
12760
|
experimentId: session.experimentId,
|
|
11892
12761
|
plan: res.data,
|
|
11893
|
-
requiredAcknowledgment: res.data
|
|
12762
|
+
requiredAcknowledgment: requiredQaAcknowledgment(res.data)
|
|
11894
12763
|
},
|
|
11895
12764
|
() => printFilesystemPlan(res.data)
|
|
11896
12765
|
);
|
|
@@ -11902,7 +12771,10 @@ var planFilesystem = new Command("plan").description("Plan experiment filesystem
|
|
|
11902
12771
|
var applyFilesystem = new Command("apply").option(
|
|
11903
12772
|
"--confirm-targeting-replace",
|
|
11904
12773
|
"Confirm that attaching a Page may replace URL targeting authored through the API"
|
|
11905
|
-
).option("--sync", "Refresh local authored files with the stored server bytes").description("Apply experiment filesystem changes to Apex").argument("[folder]", "Experiment filesystem folder", ".").option(
|
|
12774
|
+
).option("--sync", "Refresh local authored files with the stored server bytes").description("Apply experiment filesystem changes to Apex").argument("[folder]", "Experiment filesystem folder", ".").option(
|
|
12775
|
+
"--accept-qa-invalidation",
|
|
12776
|
+
"Confirm an apply that revokes an existing QA approval (required only when the experiment holds one)"
|
|
12777
|
+
).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) {
|
|
11906
12778
|
const creds = requireCredentials();
|
|
11907
12779
|
try {
|
|
11908
12780
|
const folder = folderPath(folderArg);
|
|
@@ -11949,7 +12821,7 @@ var applyFilesystem = new Command("apply").option(
|
|
|
11949
12821
|
why: "Retry with hosted asset upload."
|
|
11950
12822
|
}
|
|
11951
12823
|
] : [],
|
|
11952
|
-
requiredAcknowledgment: planRes.data
|
|
12824
|
+
requiredAcknowledgment: requiredQaAcknowledgment(planRes.data)
|
|
11953
12825
|
});
|
|
11954
12826
|
}
|
|
11955
12827
|
if (planRes.data.changes.length === 0) {
|
|
@@ -11966,7 +12838,7 @@ var applyFilesystem = new Command("apply").option(
|
|
|
11966
12838
|
synced: synced2,
|
|
11967
12839
|
dashboardUrl: dashboardUrl2,
|
|
11968
12840
|
plan: planRes.data,
|
|
11969
|
-
requiredAcknowledgment: planRes.data
|
|
12841
|
+
requiredAcknowledgment: requiredQaAcknowledgment(planRes.data),
|
|
11970
12842
|
message: "No changes."
|
|
11971
12843
|
},
|
|
11972
12844
|
() => {
|
|
@@ -11978,23 +12850,23 @@ var applyFilesystem = new Command("apply").option(
|
|
|
11978
12850
|
);
|
|
11979
12851
|
return;
|
|
11980
12852
|
}
|
|
11981
|
-
|
|
11982
|
-
|
|
12853
|
+
let acceptQaInvalidation = Boolean(opts.force || opts.acceptQaInvalidation);
|
|
12854
|
+
if (requiredQaAcknowledgment(planRes.data) && !acceptQaInvalidation) {
|
|
12855
|
+
const refusal = qaApprovalRefusal(planRes.data.qaApproval?.approvedAt ?? null, folderArg);
|
|
12856
|
+
const ok = await confirmApply(`${refusal.message} Continue?`);
|
|
11983
12857
|
if (ok === null) {
|
|
11984
|
-
confirmationRequired
|
|
11985
|
-
this,
|
|
11986
|
-
"Applying these changes invalidates QA approval and requires confirmation.",
|
|
11987
|
-
`apex experiments apply ${folderArg} --accept-qa-invalidation`
|
|
11988
|
-
);
|
|
12858
|
+
fail(this, refusal.message, EXIT_CODES.confirmationRequired, refusal.details);
|
|
11989
12859
|
}
|
|
11990
12860
|
if (!ok) {
|
|
11991
12861
|
output(this, { aborted: true }, () => console.log("Aborted."));
|
|
11992
12862
|
return;
|
|
11993
12863
|
}
|
|
12864
|
+
acceptQaInvalidation = true;
|
|
11994
12865
|
}
|
|
11995
12866
|
const spinner = process.stderr.isTTY ? await createSpinner("Applying experiment filesystem...") : null;
|
|
11996
12867
|
let res = await applyExperimentFilesystem(creds, session.experimentId, {
|
|
11997
12868
|
files,
|
|
12869
|
+
accept_qa_invalidation: acceptQaInvalidation,
|
|
11998
12870
|
...opts.confirmTargetingReplace ? { confirmTargetingReplace: true } : {}
|
|
11999
12871
|
});
|
|
12000
12872
|
if (!res.ok && res.status === 409 && res.errorDetails?.code === "confirm_targeting_replace_required" && !opts.confirmTargetingReplace) {
|
|
@@ -12015,9 +12887,19 @@ var applyFilesystem = new Command("apply").option(
|
|
|
12015
12887
|
}
|
|
12016
12888
|
res = await applyExperimentFilesystem(creds, session.experimentId, {
|
|
12017
12889
|
files,
|
|
12890
|
+
accept_qa_invalidation: acceptQaInvalidation,
|
|
12018
12891
|
confirmTargetingReplace: true
|
|
12019
12892
|
});
|
|
12020
12893
|
}
|
|
12894
|
+
if (!res.ok && res.status === 409 && res.errorDetails?.code === "qa_acknowledgment_required") {
|
|
12895
|
+
spinner?.stop();
|
|
12896
|
+
const approvedAt = res.errorDetails.qa_approved_at;
|
|
12897
|
+
const refusal = qaApprovalRefusal(
|
|
12898
|
+
typeof approvedAt === "string" ? approvedAt : null,
|
|
12899
|
+
folderArg
|
|
12900
|
+
);
|
|
12901
|
+
fail(this, refusal.message, EXIT_CODES.confirmationRequired, refusal.details);
|
|
12902
|
+
}
|
|
12021
12903
|
if (!res.ok || !res.data?.applied) {
|
|
12022
12904
|
spinner?.fail("Failed to apply experiment filesystem");
|
|
12023
12905
|
fail(this, res.error ?? "Apply failed", classifyStatus(res.status), {
|
|
@@ -12056,7 +12938,7 @@ var applyFilesystem = new Command("apply").option(
|
|
|
12056
12938
|
experimentId: session.experimentId,
|
|
12057
12939
|
applied: true,
|
|
12058
12940
|
plan: planRes.data,
|
|
12059
|
-
requiredAcknowledgment: planRes.data
|
|
12941
|
+
requiredAcknowledgment: requiredQaAcknowledgment(planRes.data),
|
|
12060
12942
|
result: res.data,
|
|
12061
12943
|
synced,
|
|
12062
12944
|
dashboardUrl,
|
|
@@ -12408,7 +13290,55 @@ var qaEvidence = new Command("evidence").description("Attach screenshot jobs or
|
|
|
12408
13290
|
throw error;
|
|
12409
13291
|
}
|
|
12410
13292
|
});
|
|
12411
|
-
var qa = new Command("qa").description("Manage experiment QA runs from the CLI").addCommand(qaStatus).addCommand(
|
|
13293
|
+
var qa = new Command("qa").description("Manage experiment QA runs from the CLI").addCommand(qaStatus).addCommand(
|
|
13294
|
+
new Command("events").description("List QA-tagged events for an experiment session").argument("<id-or-slug>", EXPERIMENT_REFERENCE_DESCRIPTION).option("--session <id>", "QA session id (defaults to the latest QA run)").option("--minutes <n>", "Lookback window in minutes", "60").option("--type <type>", "Event type: all, goal, or custom", "all").action(async function(target, opts) {
|
|
13295
|
+
const minutes = Number(opts.minutes);
|
|
13296
|
+
if (!Number.isInteger(minutes) || minutes < 5 || minutes > 1440)
|
|
13297
|
+
fail(this, "--minutes must be an integer between 5 and 1440", EXIT_CODES.validation);
|
|
13298
|
+
if (!["all", "goal", "custom"].includes(opts.type))
|
|
13299
|
+
fail(this, "--type must be all, goal, or custom", EXIT_CODES.validation);
|
|
13300
|
+
const creds = requireCredentials();
|
|
13301
|
+
const experiment2 = { id: (await resolveExperimentForFilesystemOrId(target)).experimentId };
|
|
13302
|
+
let session = opts.session;
|
|
13303
|
+
if (!session) {
|
|
13304
|
+
const status4 = await getExperimentQaRun(creds, experiment2.id);
|
|
13305
|
+
if (!status4.ok || !status4.data)
|
|
13306
|
+
throw new CliError(
|
|
13307
|
+
status4.error ?? "Failed to fetch QA run",
|
|
13308
|
+
classifyStatus(status4.status),
|
|
13309
|
+
status4.errorDetails
|
|
13310
|
+
);
|
|
13311
|
+
session = status4.data.run.id;
|
|
13312
|
+
}
|
|
13313
|
+
const response = await getExperimentQaEvents(creds, experiment2.id, {
|
|
13314
|
+
session,
|
|
13315
|
+
minutes,
|
|
13316
|
+
type: opts.type
|
|
13317
|
+
});
|
|
13318
|
+
if (!response.ok || !response.data)
|
|
13319
|
+
throw new CliError(
|
|
13320
|
+
response.error ?? "Failed to fetch QA events",
|
|
13321
|
+
classifyStatus(response.status),
|
|
13322
|
+
response.errorDetails
|
|
13323
|
+
);
|
|
13324
|
+
output(
|
|
13325
|
+
this,
|
|
13326
|
+
response.data,
|
|
13327
|
+
() => printTable(
|
|
13328
|
+
["TYPE", "EVENT", "VARIATION", "SESSION", "COUNT", "LAST SEEN", "LAST URL"],
|
|
13329
|
+
response.data.data.map((row) => [
|
|
13330
|
+
row.event_type === "goal" ? "goal" : "custom",
|
|
13331
|
+
row.event_name || row.goal_id,
|
|
13332
|
+
row.variation_id,
|
|
13333
|
+
row.qa_session_id,
|
|
13334
|
+
String(row.events),
|
|
13335
|
+
row.last_event_at ?? "\u2014",
|
|
13336
|
+
row.last_url ?? "\u2014"
|
|
13337
|
+
])
|
|
13338
|
+
)
|
|
13339
|
+
);
|
|
13340
|
+
})
|
|
13341
|
+
).addCommand(qaCheck).addCommand(qaNotes).addCommand(qaApprove).addCommand(qaReopen).addCommand(qaEvidence);
|
|
12412
13342
|
var SCREENSHOT_DEVICES = [
|
|
12413
13343
|
"desktop_chrome",
|
|
12414
13344
|
"iphone_15_pro",
|
|
@@ -13057,6 +13987,25 @@ var pushCommand = new Command("push").description("Push experiment definitions f
|
|
|
13057
13987
|
|
|
13058
13988
|
// src/commands/preview.ts
|
|
13059
13989
|
init_source();
|
|
13990
|
+
async function signedPreviewFromReadiness(creds, experimentId, variationId, targetUrl) {
|
|
13991
|
+
try {
|
|
13992
|
+
const client = createHttpClient(creds, {
|
|
13993
|
+
onNetworkError: (info) => {
|
|
13994
|
+
throw new Error(info.cause);
|
|
13995
|
+
}
|
|
13996
|
+
});
|
|
13997
|
+
const query = new URLSearchParams({ variation_id: variationId, target_url: targetUrl });
|
|
13998
|
+
const response = await client.request(
|
|
13999
|
+
"GET",
|
|
14000
|
+
`/api/v1/experiments/${encodeURIComponent(experimentId)}/readiness?${query}`,
|
|
14001
|
+
void 0,
|
|
14002
|
+
5e3
|
|
14003
|
+
);
|
|
14004
|
+
return response.ok ? response.data?.data : void 0;
|
|
14005
|
+
} catch {
|
|
14006
|
+
return void 0;
|
|
14007
|
+
}
|
|
14008
|
+
}
|
|
13060
14009
|
var previewCommand = new Command("preview").description(
|
|
13061
14010
|
"Open QA preview URL for an experiment variation; proxy previews return a QA run receipt"
|
|
13062
14011
|
).argument("<id-or-slug>", "Experiment ID or slug").argument("[variation-id]", "Variation ID (defaults to first non-control)").option("--store-url <url>", "Override experiment store_url (target website URL)").option("--proxy", "Create an artifact-injecting proxy preview").option("--ttl <hours>", "Proxy preview lifetime in hours (1-168)").option("--no-open", "Print URL instead of opening browser").action(
|
|
@@ -13168,28 +14117,51 @@ var previewCommand = new Command("preview").description(
|
|
|
13168
14117
|
variation = exp.variations.find((v) => !v.is_control) ?? exp.variations[0];
|
|
13169
14118
|
}
|
|
13170
14119
|
spinner.stop();
|
|
13171
|
-
const storeUrl = opts?.storeUrl ?? exp.preview_target?.url ?? exp.store_url;
|
|
14120
|
+
const storeUrl = opts?.storeUrl ?? (!variation.is_control && variation.redirect_url?.trim() ? variation.redirect_url : null) ?? exp.preview_target?.url ?? exp.store_url;
|
|
13172
14121
|
if (!storeUrl) {
|
|
13173
14122
|
fail(
|
|
13174
14123
|
previewCommand,
|
|
13175
14124
|
'Experiment is missing "store_url". Set it on the experiment or pass --store-url <url>.'
|
|
13176
14125
|
);
|
|
13177
14126
|
}
|
|
14127
|
+
const snapshot = await getExperimentFilesystem(creds, exp.id).catch(() => null);
|
|
14128
|
+
let qaRun;
|
|
14129
|
+
if (snapshot?.ok && snapshot.data) {
|
|
14130
|
+
const qaFile = snapshot.data.files.find((file) => file.path === "qa.json");
|
|
14131
|
+
if (qaFile) {
|
|
14132
|
+
try {
|
|
14133
|
+
const run3 = JSON.parse(qaFile.content)?.latestRun;
|
|
14134
|
+
if (run3 && typeof run3.id === "string" && run3.id.trim()) qaRun = run3;
|
|
14135
|
+
} catch {
|
|
14136
|
+
}
|
|
14137
|
+
}
|
|
14138
|
+
}
|
|
13178
14139
|
let previewUrl;
|
|
13179
14140
|
try {
|
|
13180
|
-
previewUrl = buildQaPreviewUrl2(storeUrl, exp.id, variation.id);
|
|
14141
|
+
previewUrl = buildQaPreviewUrl2(storeUrl, exp.id, variation.id, qaRun?.id);
|
|
13181
14142
|
} catch {
|
|
13182
14143
|
fail(
|
|
13183
14144
|
previewCommand,
|
|
13184
14145
|
'Invalid store URL. Pass an absolute URL like "https://example.com/products/item".'
|
|
13185
14146
|
);
|
|
13186
14147
|
}
|
|
14148
|
+
const signed = await signedPreviewFromReadiness(creds, exp.id, variation.id, storeUrl);
|
|
14149
|
+
const selectedPreviewUrl = signed?.signed_preview_url ?? previewUrl.toString();
|
|
14150
|
+
if (signed?.signed_preview_url) {
|
|
14151
|
+
const signedSessionId = signed.signed_preview_qa_session_id;
|
|
14152
|
+
qaRun = signedSessionId ? qaRun?.id === signedSessionId ? qaRun : { id: signedSessionId } : void 0;
|
|
14153
|
+
}
|
|
14154
|
+
const warning = signed?.signed_preview_url ? null : "Test images may not load on this unsigned link. Open Apex \u2192 QA for a signed link.";
|
|
14155
|
+
if (warning) console.error(warning);
|
|
13187
14156
|
output(
|
|
13188
14157
|
previewCommand,
|
|
13189
14158
|
{
|
|
13190
14159
|
experimentId: exp.id,
|
|
13191
14160
|
variationId: variation.id,
|
|
13192
|
-
previewUrl:
|
|
14161
|
+
previewUrl: selectedPreviewUrl,
|
|
14162
|
+
expiresAt: signed?.signed_preview_expires_at ?? null,
|
|
14163
|
+
...warning ? { warning } : {},
|
|
14164
|
+
...qaRun ? { qa_run: qaRun, qa_session_id: qaRun.id } : {},
|
|
13193
14165
|
...!opts?.storeUrl && exp.preview_target ? { previewTarget: exp.preview_target } : {}
|
|
13194
14166
|
},
|
|
13195
14167
|
() => {
|
|
@@ -13205,14 +14177,16 @@ var previewCommand = new Command("preview").description(
|
|
|
13205
14177
|
}
|
|
13206
14178
|
}
|
|
13207
14179
|
console.log();
|
|
13208
|
-
console.log(source_default.cyan(
|
|
14180
|
+
console.log(source_default.cyan(selectedPreviewUrl));
|
|
14181
|
+
if (signed?.signed_preview_expires_at)
|
|
14182
|
+
console.log(`Valid until ${signed.signed_preview_expires_at}.`);
|
|
13209
14183
|
console.log();
|
|
13210
14184
|
}
|
|
13211
14185
|
);
|
|
13212
14186
|
if (opts?.open !== false && !shouldUseJson(previewCommand)) {
|
|
13213
14187
|
try {
|
|
13214
14188
|
const open3 = (await Promise.resolve().then(() => (init_open(), open_exports))).default;
|
|
13215
|
-
await open3(
|
|
14189
|
+
await open3(selectedPreviewUrl);
|
|
13216
14190
|
console.log(source_default.dim("Opened in browser."));
|
|
13217
14191
|
} catch {
|
|
13218
14192
|
console.log(source_default.dim("Copy the URL above to preview."));
|
|
@@ -14199,9 +15173,7 @@ function commonCookieDomain(hostnames) {
|
|
|
14199
15173
|
const suffix = suffixes[0];
|
|
14200
15174
|
return suffix && suffixes.every((candidate) => candidate === suffix) ? `.${suffix}` : null;
|
|
14201
15175
|
}
|
|
14202
|
-
|
|
14203
|
-
return import_node_path11.default.resolve(process.cwd(), ".apex", "tracking-manifest.json");
|
|
14204
|
-
}
|
|
15176
|
+
var DEFAULT_MANIFEST_PATH = ".apex/tracking-manifest.json";
|
|
14205
15177
|
function readManifest(filePath) {
|
|
14206
15178
|
if (!import_node_fs13.default.existsSync(filePath)) throw new Error(`Manifest not found: ${filePath}`);
|
|
14207
15179
|
const parsed = JSON.parse(import_node_fs13.default.readFileSync(filePath, "utf8"));
|
|
@@ -14277,7 +15249,7 @@ function buildManifest(urlArgs, opts) {
|
|
|
14277
15249
|
};
|
|
14278
15250
|
}
|
|
14279
15251
|
function manifestPath(input) {
|
|
14280
|
-
return import_node_path11.default.resolve(process.cwd(), input ??
|
|
15252
|
+
return import_node_path11.default.resolve(process.cwd(), input ?? DEFAULT_MANIFEST_PATH);
|
|
14281
15253
|
}
|
|
14282
15254
|
function printManifestPlan(manifest) {
|
|
14283
15255
|
console.log(source_default.bold("Apex tracking ingestion plan"));
|
|
@@ -14305,7 +15277,11 @@ function printManifestPlan(manifest) {
|
|
|
14305
15277
|
var trackingCommand = new Command("tracking").description(
|
|
14306
15278
|
"Create Apex-first tracking ingestion manifests and QA handoffs"
|
|
14307
15279
|
);
|
|
14308
|
-
trackingCommand.command("inspect").description("Create a local tracking manifest from funnel URLs").argument("<urls...>", "Landing, storefront, checkout, or confirmation URLs").option("--shop-id <id>", "Apex shop ID for snippet generation").option("--endpoint <url>", "Apex Worker endpoint", "https://events.drip-apex.com").option(
|
|
15280
|
+
trackingCommand.command("inspect").description("Create a local tracking manifest from funnel URLs").argument("<urls...>", "Landing, storefront, checkout, or confirmation URLs").option("--shop-id <id>", "Apex shop ID for snippet generation").option("--endpoint <url>", "Apex Worker endpoint", "https://events.drip-apex.com").option(
|
|
15281
|
+
"--out <path>",
|
|
15282
|
+
"Manifest output path, relative to the current directory",
|
|
15283
|
+
DEFAULT_MANIFEST_PATH
|
|
15284
|
+
).action(function(urls, opts) {
|
|
14309
15285
|
const manifest = buildManifest(urls, opts);
|
|
14310
15286
|
const outPath = manifestPath(opts.out);
|
|
14311
15287
|
writeManifest(outPath, manifest);
|
|
@@ -14321,12 +15297,20 @@ trackingCommand.command("inspect").description("Create a local tracking manifest
|
|
|
14321
15297
|
}
|
|
14322
15298
|
});
|
|
14323
15299
|
});
|
|
14324
|
-
trackingCommand.command("plan").description("Print the implementation plan from a tracking manifest").argument(
|
|
15300
|
+
trackingCommand.command("plan").description("Print the implementation plan from a tracking manifest").argument(
|
|
15301
|
+
"[manifest]",
|
|
15302
|
+
"Tracking manifest path, relative to the current directory",
|
|
15303
|
+
DEFAULT_MANIFEST_PATH
|
|
15304
|
+
).action(function(input) {
|
|
14325
15305
|
const filePath = manifestPath(input);
|
|
14326
15306
|
const manifest = readManifest(filePath);
|
|
14327
15307
|
output(this, { path: filePath, manifest }, () => printManifestPlan(manifest));
|
|
14328
15308
|
});
|
|
14329
|
-
trackingCommand.command("qa").description("Print the QA checklist for a tracking manifest").argument(
|
|
15309
|
+
trackingCommand.command("qa").description("Print the QA checklist for a tracking manifest").argument(
|
|
15310
|
+
"[manifest]",
|
|
15311
|
+
"Tracking manifest path, relative to the current directory",
|
|
15312
|
+
DEFAULT_MANIFEST_PATH
|
|
15313
|
+
).action(function(input) {
|
|
14330
15314
|
const filePath = manifestPath(input);
|
|
14331
15315
|
const manifest = readManifest(filePath);
|
|
14332
15316
|
output(this, { path: filePath, checks: manifest.qa.checks }, () => {
|
|
@@ -18540,16 +19524,21 @@ async function checkServedConfig(configUrl, workerBaseUrl, workerBaseSource) {
|
|
|
18540
19524
|
message: "Worker served a recognized config document for this shop."
|
|
18541
19525
|
};
|
|
18542
19526
|
}
|
|
18543
|
-
var installCommand = new Command("install").description("Check Apex storefront installation").command("check").description(
|
|
19527
|
+
var installCommand = new Command("install").description("Check Apex storefront installation").command("check").description(
|
|
19528
|
+
"Verify snippet, served config, and recent tracking using --shop-id, the selected shop, or DRIP_SHOP_ID"
|
|
19529
|
+
).option("--url <url>", "Check one explicit bound storefront URL").option(
|
|
19530
|
+
"--shop-id <id>",
|
|
19531
|
+
"Apex shop ID to verify (falls back to --shop/apex use, then DRIP_SHOP_ID)"
|
|
19532
|
+
).option(
|
|
18544
19533
|
"--worker-url <url>",
|
|
18545
19534
|
"Apex Worker base URL that serves /config (defaults to the URL in the served snippet, then DRIP_WORKER_URL, then the production worker)"
|
|
18546
19535
|
).option("--environment <key>", "Environment key to request from the worker", "production").action(async function action65(opts) {
|
|
18547
19536
|
const creds = requireCredentials();
|
|
18548
|
-
const shopId = opts.shopId?.trim() || process.env.DRIP_SHOP_ID?.trim() || null;
|
|
19537
|
+
const shopId = opts.shopId?.trim() || resolveContext(creds).shopId?.trim() || process.env.DRIP_SHOP_ID?.trim() || null;
|
|
18549
19538
|
if (!shopId)
|
|
18550
19539
|
fail(
|
|
18551
19540
|
this,
|
|
18552
|
-
"Shop ID is required: pass --shop-id <id> or set DRIP_SHOP_ID",
|
|
19541
|
+
"Shop ID is required: pass --shop-id <id>, select a shop with --shop <id> or apex use, or set DRIP_SHOP_ID",
|
|
18553
19542
|
EXIT_CODES.validation,
|
|
18554
19543
|
{ field: "shop-id" }
|
|
18555
19544
|
);
|
|
@@ -19589,6 +20578,7 @@ var test2 = new Command("test").description("Evaluate experiment URL targeting f
|
|
|
19589
20578
|
var targetingCommand = new Command("targeting").description("Test experiment targeting").addCommand(test2);
|
|
19590
20579
|
|
|
19591
20580
|
// src/commands/sos.ts
|
|
20581
|
+
var import_node_crypto7 = require("node:crypto");
|
|
19592
20582
|
init_source();
|
|
19593
20583
|
var import_node_fs24 = __toESM(require("node:fs"));
|
|
19594
20584
|
var import_node_path21 = __toESM(require("node:path"));
|
|
@@ -19630,6 +20620,11 @@ var create4 = new Command("create").description("Create an Apex SOS ticket").opt
|
|
|
19630
20620
|
"--type <type>",
|
|
19631
20621
|
"full_handover|design|development|qa|config|bug|help (internal types require a DRIP account)",
|
|
19632
20622
|
"qa"
|
|
20623
|
+
).option(
|
|
20624
|
+
"--attach <file>",
|
|
20625
|
+
"Attach an image or video (repeatable, maximum 5)",
|
|
20626
|
+
(file, files) => [...files, file],
|
|
20627
|
+
[]
|
|
19633
20628
|
).option("--loom <url>", "Loom URL with reproduction or QA context").option("--experiment <id>", "Experiment ID").option("--backlog-item <id>", "Backlog item ID (internal SOS only)").option("--sub-class <text>", "SOS sub-classification").option("--description <text-or-@file>", "Description text or @path").argument("[description]", "Description text").action(async function action68(descriptionArg, opts) {
|
|
19634
20629
|
if (!SOS_TYPES.has(opts.type)) {
|
|
19635
20630
|
fail(
|
|
@@ -19641,16 +20636,76 @@ var create4 = new Command("create").description("Create an Apex SOS ticket").opt
|
|
|
19641
20636
|
if (opts.experiment && opts.backlogItem) {
|
|
19642
20637
|
fail(this, "Provide only one of --experiment or --backlog-item", 3);
|
|
19643
20638
|
}
|
|
20639
|
+
const mediaTypes = {
|
|
20640
|
+
".png": "image/png",
|
|
20641
|
+
".jpg": "image/jpeg",
|
|
20642
|
+
".jpeg": "image/jpeg",
|
|
20643
|
+
".webp": "image/webp",
|
|
20644
|
+
".gif": "image/gif",
|
|
20645
|
+
".mp4": "video/mp4",
|
|
20646
|
+
".webm": "video/webm",
|
|
20647
|
+
".mov": "video/quicktime"
|
|
20648
|
+
};
|
|
20649
|
+
if (opts.attach.length > 5) fail(this, "Attach no more than 5 files", 3);
|
|
20650
|
+
const files = opts.attach.map((file) => {
|
|
20651
|
+
const filename = import_node_path21.default.basename(file);
|
|
20652
|
+
const content_type = mediaTypes[import_node_path21.default.extname(file).toLowerCase()];
|
|
20653
|
+
if (!content_type) fail(this, "Unsupported attachment type", 3);
|
|
20654
|
+
let size;
|
|
20655
|
+
try {
|
|
20656
|
+
size = import_node_fs24.default.statSync(file).size;
|
|
20657
|
+
} catch {
|
|
20658
|
+
fail(this, `Attachment not found: ${file}`, 3);
|
|
20659
|
+
}
|
|
20660
|
+
const cap = (content_type.startsWith("video/") ? 50 : 10) * 1024 * 1024;
|
|
20661
|
+
if (size <= 0 || size > cap) fail(this, "Attachment exceeds its size limit or is empty", 3);
|
|
20662
|
+
let bytes;
|
|
20663
|
+
try {
|
|
20664
|
+
bytes = import_node_fs24.default.readFileSync(file);
|
|
20665
|
+
} catch {
|
|
20666
|
+
fail(this, `Attachment not found: ${file}`, 3);
|
|
20667
|
+
}
|
|
20668
|
+
return { filename, content_type, size, bytes };
|
|
20669
|
+
});
|
|
19644
20670
|
const creds = requireCredentials();
|
|
20671
|
+
const attachmentPaths = [];
|
|
20672
|
+
const attachmentDigests = [];
|
|
20673
|
+
if (files.length) {
|
|
20674
|
+
const grants = await createSosUploadGrants(
|
|
20675
|
+
creds,
|
|
20676
|
+
files.map(({ filename, content_type, size }) => ({ filename, content_type, size }))
|
|
20677
|
+
);
|
|
20678
|
+
if (!grants.ok || !grants.data)
|
|
20679
|
+
fail(
|
|
20680
|
+
this,
|
|
20681
|
+
grants.error ?? "Failed to get upload grants",
|
|
20682
|
+
classifyStatus(grants.status),
|
|
20683
|
+
grants.errorDetails
|
|
20684
|
+
);
|
|
20685
|
+
if (grants.data.data.uploads.length !== files.length)
|
|
20686
|
+
fail(this, "Invalid upload grant response", 1);
|
|
20687
|
+
for (const [index, file] of files.entries()) {
|
|
20688
|
+
const grant = grants.data.data.uploads[index];
|
|
20689
|
+
const bytes = file.bytes;
|
|
20690
|
+
await uploadSosAttachment(grant.signed_url, file.content_type, bytes);
|
|
20691
|
+
attachmentPaths.push(grant.path);
|
|
20692
|
+
attachmentDigests.push((0, import_node_crypto7.createHash)("sha256").update(bytes).digest("hex"));
|
|
20693
|
+
}
|
|
20694
|
+
}
|
|
19645
20695
|
const description = readText(opts.description) ?? descriptionArg ?? `Apex CLI ${opts.type} SOS request${opts.loom ? ` with Loom evidence: ${opts.loom}` : "."}`;
|
|
19646
|
-
const res = await createSos(
|
|
19647
|
-
|
|
19648
|
-
|
|
19649
|
-
|
|
19650
|
-
|
|
19651
|
-
|
|
19652
|
-
|
|
19653
|
-
|
|
20696
|
+
const res = await createSos(
|
|
20697
|
+
creds,
|
|
20698
|
+
{
|
|
20699
|
+
...attachmentPaths.length ? { attachment_paths: attachmentPaths } : {},
|
|
20700
|
+
type: opts.type,
|
|
20701
|
+
description,
|
|
20702
|
+
experiment_id: opts.experiment ?? null,
|
|
20703
|
+
backlog_item_id: opts.backlogItem ?? null,
|
|
20704
|
+
sub_class: opts.subClass ?? null,
|
|
20705
|
+
loom_url: opts.loom ?? null
|
|
20706
|
+
},
|
|
20707
|
+
{ attachmentDigests }
|
|
20708
|
+
);
|
|
19654
20709
|
if (!res.ok || !res.data) {
|
|
19655
20710
|
fail(
|
|
19656
20711
|
this,
|
|
@@ -19955,6 +21010,7 @@ function optionSchema(option) {
|
|
|
19955
21010
|
description: option.description,
|
|
19956
21011
|
required: option.required,
|
|
19957
21012
|
optional: option.optional,
|
|
21013
|
+
mandatory: Boolean(option.mandatory),
|
|
19958
21014
|
variadic: option.variadic,
|
|
19959
21015
|
...option.defaultValue === void 0 ? {} : { defaultValue: option.defaultValue }
|
|
19960
21016
|
};
|
|
@@ -19977,8 +21033,17 @@ function commandSchema(command) {
|
|
|
19977
21033
|
};
|
|
19978
21034
|
}
|
|
19979
21035
|
function createSchemaCommand(program3) {
|
|
19980
|
-
return new Command("schema").description("Print the current Apex CLI command and
|
|
19981
|
-
const schema =
|
|
21036
|
+
return new Command("schema").description("Print the current Apex CLI command, flag, and mandatory-option schema").option("--json", "Print JSON schema").action(function action78() {
|
|
21037
|
+
const schema = {
|
|
21038
|
+
schemaVersion: 2,
|
|
21039
|
+
contract: {
|
|
21040
|
+
optionValueArity: "Options' required and optional fields describe whether the option's value is required or optional when the flag is given.",
|
|
21041
|
+
mandatoryOptions: "An option's mandatory field is true only when the flag itself must be passed.",
|
|
21042
|
+
requiredArguments: "An argument's required field means the positional is mandatory."
|
|
21043
|
+
},
|
|
21044
|
+
...commandSchema(program3),
|
|
21045
|
+
files: experiment_filesystem_schema_default
|
|
21046
|
+
};
|
|
19982
21047
|
output(this, schema, () => {
|
|
19983
21048
|
process.stdout.write(`${JSON.stringify(schema, null, 2)}
|
|
19984
21049
|
`);
|
|
@@ -20618,7 +21683,15 @@ var skillsCommand = new Command("skills").description("Print or install the Apex
|
|
|
20618
21683
|
});
|
|
20619
21684
|
});
|
|
20620
21685
|
|
|
21686
|
+
// ../../ops/org-split/move-plan.mjs
|
|
21687
|
+
var uuid = /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/;
|
|
21688
|
+
function validateMoveInput(shopIds, toOrgId) {
|
|
21689
|
+
if (typeof toOrgId !== "string" || !uuid.test(toOrgId) || !Array.isArray(shopIds) || !shopIds.length || shopIds.some((id) => typeof id !== "string" || !uuid.test(id))) throw new Error("Choose valid store and destination UUIDs");
|
|
21690
|
+
return { shopIds: [...new Set(shopIds.map((id) => id.toLowerCase()))].sort(), toOrgId: toOrgId.toLowerCase() };
|
|
21691
|
+
}
|
|
21692
|
+
|
|
20621
21693
|
// src/commands/admin.ts
|
|
21694
|
+
var uuid2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
20622
21695
|
function switchValue(value) {
|
|
20623
21696
|
if (value === "on") return true;
|
|
20624
21697
|
if (value === "off") return false;
|
|
@@ -20745,8 +21818,7 @@ var create6 = new Command("create").description("Create an internal, demo or dog
|
|
|
20745
21818
|
fail(this, "--name must be 1 to 60 characters", 3);
|
|
20746
21819
|
if (opts.reason.trim().length < 8 || opts.reason.trim().length > 500)
|
|
20747
21820
|
fail(this, "--reason must be 8 to 500 characters", 3);
|
|
20748
|
-
|
|
20749
|
-
if (opts.claimShop && !uuid.test(opts.claimShop)) fail(this, "--claim-shop must be a UUID", 3);
|
|
21821
|
+
if (opts.claimShop && !uuid2.test(opts.claimShop)) fail(this, "--claim-shop must be a UUID", 3);
|
|
20750
21822
|
if (opts.claimShop && (opts.domain || opts.platform))
|
|
20751
21823
|
fail(this, "Use --claim-shop or --domain with --platform", 3);
|
|
20752
21824
|
if (!!opts.domain !== !!opts.platform)
|
|
@@ -20767,7 +21839,7 @@ var create6 = new Command("create").description("Create an internal, demo or dog
|
|
|
20767
21839
|
fail(this, "--owner requires a store for the invitation", 3);
|
|
20768
21840
|
if (opts.plan !== "self_managed" && (!opts.consultant || !opts.consultantTitle?.trim()))
|
|
20769
21841
|
fail(this, "Managed plans require --consultant and --consultant-title", 3);
|
|
20770
|
-
if (opts.consultant && !
|
|
21842
|
+
if (opts.consultant && !uuid2.test(opts.consultant))
|
|
20771
21843
|
fail(this, "--consultant must be a platform access UUID", 3);
|
|
20772
21844
|
if (!!opts.consultant !== !!opts.consultantTitle?.trim())
|
|
20773
21845
|
fail(this, "Provide --consultant and --consultant-title together", 3);
|
|
@@ -20831,14 +21903,169 @@ var create6 = new Command("create").description("Create an internal, demo or dog
|
|
|
20831
21903
|
);
|
|
20832
21904
|
});
|
|
20833
21905
|
});
|
|
20834
|
-
var
|
|
21906
|
+
var setCategory = new Command("set-category").description("Change an existing account's category with an audit reason").argument("<org-id>", "Organization UUID").requiredOption("--category <category>", "internal, demo, dogfood or customer").requiredOption("--reason <text>", "Audit reason, 8 to 500 characters").option("--confirm", "Confirm category change").option("--json", "Output JSON").action(async function(orgId, opts) {
|
|
21907
|
+
if (!uuid2.test(orgId)) fail(this, "<org-id> must be a UUID", 3);
|
|
21908
|
+
if (!["internal", "demo", "dogfood", "customer"].includes(opts.category))
|
|
21909
|
+
fail(this, "--category must be internal, demo, dogfood or customer", 3);
|
|
21910
|
+
const reason2 = opts.reason.trim();
|
|
21911
|
+
if (reason2.length < 8 || reason2.length > 500)
|
|
21912
|
+
fail(this, "--reason must be 8 to 500 characters", 3);
|
|
21913
|
+
if (!opts.confirm) {
|
|
21914
|
+
const quote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
21915
|
+
confirmationRequired(
|
|
21916
|
+
this,
|
|
21917
|
+
"--confirm is required to change an account category",
|
|
21918
|
+
`apex admin customers set-category ${orgId} --category ${quote(opts.category)} --reason ${quote(reason2)} --confirm`
|
|
21919
|
+
);
|
|
21920
|
+
}
|
|
21921
|
+
const body5 = {
|
|
21922
|
+
category: opts.category,
|
|
21923
|
+
reason: reason2
|
|
21924
|
+
};
|
|
21925
|
+
const res = await setAdminCustomerCategory(requireUserCredentials(), orgId, body5);
|
|
21926
|
+
if (!res.ok || !res.data)
|
|
21927
|
+
fail(
|
|
21928
|
+
this,
|
|
21929
|
+
res.error ?? "Failed to change account category",
|
|
21930
|
+
classifyStatus(res.status),
|
|
21931
|
+
res.errorDetails
|
|
21932
|
+
);
|
|
21933
|
+
output(this, res.data, () => {
|
|
21934
|
+
const result = res.data.data;
|
|
21935
|
+
printTable(["RESULT"], [[`Customer ${result.customerId} moved to ${result.category}`]]);
|
|
21936
|
+
});
|
|
21937
|
+
});
|
|
21938
|
+
var setManagedBy = new Command("set-managed-by").description("Assign or clear a managing agency with an audit reason").argument("<org-id>", "Organization UUID").option("--by <org-id>", "Managing agency UUID").option("--clear", "Clear the managing agency").requiredOption("--reason <text>", "Audit reason, 8 to 500 characters").option("--confirm", "Confirm manager change").option("--json", "Output JSON").action(async function(orgId, opts) {
|
|
21939
|
+
if (!uuid2.test(orgId)) fail(this, "<org-id> must be a UUID", 3);
|
|
21940
|
+
if (!!opts.by === !!opts.clear) fail(this, "Use exactly one of --by or --clear", 3);
|
|
21941
|
+
if (opts.by && !uuid2.test(opts.by)) fail(this, "--by must be a UUID", 3);
|
|
21942
|
+
const reason2 = opts.reason.trim();
|
|
21943
|
+
if (reason2.length < 8 || reason2.length > 500)
|
|
21944
|
+
fail(this, "--reason must be 8 to 500 characters", 3);
|
|
21945
|
+
if (!opts.confirm) {
|
|
21946
|
+
const quote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
21947
|
+
confirmationRequired(
|
|
21948
|
+
this,
|
|
21949
|
+
"--confirm is required to change a managing agency",
|
|
21950
|
+
`apex admin customers set-managed-by ${orgId} ${opts.by ? `--by ${opts.by}` : "--clear"} --reason ${quote(reason2)} --confirm`
|
|
21951
|
+
);
|
|
21952
|
+
}
|
|
21953
|
+
const res = await setAdminCustomerManagedBy(requireUserCredentials(), orgId, {
|
|
21954
|
+
managed_by_org_id: opts.by ?? null,
|
|
21955
|
+
reason: reason2
|
|
21956
|
+
});
|
|
21957
|
+
if (!res.ok || !res.data)
|
|
21958
|
+
fail(
|
|
21959
|
+
this,
|
|
21960
|
+
res.error ?? "Failed to change managing agency",
|
|
21961
|
+
classifyStatus(res.status),
|
|
21962
|
+
res.errorDetails
|
|
21963
|
+
);
|
|
21964
|
+
output(
|
|
21965
|
+
this,
|
|
21966
|
+
res.data,
|
|
21967
|
+
() => printTable(
|
|
21968
|
+
["CUSTOMER", "MANAGING AGENCY"],
|
|
21969
|
+
[[res.data.data.customerId, res.data.data.managed_by_org_id ?? "None"]]
|
|
21970
|
+
)
|
|
21971
|
+
);
|
|
21972
|
+
});
|
|
21973
|
+
var setWorkspaceType = new Command("set-workspace-type").description("Change a workspace type with an audit reason").argument("<org-id>", "Organization UUID").requiredOption("--type <company|agency>", "Workspace type").requiredOption("--reason <text>", "Audit reason, 8 to 500 characters").option("--confirm", "Confirm workspace type change").option("--json", "Output JSON").action(async function(orgId, opts) {
|
|
21974
|
+
if (!uuid2.test(orgId)) fail(this, "<org-id> must be a UUID", 3);
|
|
21975
|
+
if (opts.type !== "company" && opts.type !== "agency")
|
|
21976
|
+
fail(this, "--type must be company or agency", 3);
|
|
21977
|
+
const reason2 = opts.reason.trim();
|
|
21978
|
+
if (reason2.length < 8 || reason2.length > 500)
|
|
21979
|
+
fail(this, "--reason must be 8 to 500 characters", 3);
|
|
21980
|
+
if (!opts.confirm) {
|
|
21981
|
+
const quote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
21982
|
+
confirmationRequired(
|
|
21983
|
+
this,
|
|
21984
|
+
"--confirm is required to change a workspace type",
|
|
21985
|
+
`apex admin customers set-workspace-type ${orgId} --type ${opts.type} --reason ${quote(reason2)} --confirm`
|
|
21986
|
+
);
|
|
21987
|
+
}
|
|
21988
|
+
const res = await setAdminCustomerWorkspaceType(requireUserCredentials(), orgId, {
|
|
21989
|
+
workspace_type: opts.type,
|
|
21990
|
+
reason: reason2
|
|
21991
|
+
});
|
|
21992
|
+
if (!res.ok || !res.data)
|
|
21993
|
+
fail(
|
|
21994
|
+
this,
|
|
21995
|
+
res.error ?? "Failed to change workspace type",
|
|
21996
|
+
classifyStatus(res.status),
|
|
21997
|
+
res.errorDetails
|
|
21998
|
+
);
|
|
21999
|
+
output(
|
|
22000
|
+
this,
|
|
22001
|
+
res.data,
|
|
22002
|
+
() => printTable(
|
|
22003
|
+
["CUSTOMER", "WORKSPACE TYPE"],
|
|
22004
|
+
[[res.data.data.customerId, res.data.data.workspace_type]]
|
|
22005
|
+
)
|
|
22006
|
+
);
|
|
22007
|
+
});
|
|
22008
|
+
function storeMoveRequest(opts) {
|
|
22009
|
+
const { shopIds, toOrgId } = validateMoveInput(opts.shop, opts.to);
|
|
22010
|
+
const reason2 = opts.reason.trim();
|
|
22011
|
+
if (reason2.length < 8 || reason2.length > 500)
|
|
22012
|
+
throw new Error("--reason must be 8 to 500 characters");
|
|
22013
|
+
return {
|
|
22014
|
+
toOrgId,
|
|
22015
|
+
body: {
|
|
22016
|
+
shop_ids: shopIds,
|
|
22017
|
+
reason: reason2,
|
|
22018
|
+
keep_grants: !!opts.keepGrants,
|
|
22019
|
+
dry_run: !opts.confirm
|
|
22020
|
+
}
|
|
22021
|
+
};
|
|
22022
|
+
}
|
|
22023
|
+
var moveStores = new Command("move-stores").description("Preview or confirm an audited set of store moves").requiredOption("--to <org-id>", "Destination organization UUID").requiredOption(
|
|
22024
|
+
"--shop <shop-id>",
|
|
22025
|
+
"Store UUID, repeat for each store",
|
|
22026
|
+
(value, previous) => [...previous, value],
|
|
22027
|
+
[]
|
|
22028
|
+
).requiredOption("--reason <text>", "Audit reason, 8 to 500 characters").option("--keep-grants", "Preserve explicit store roles and overrides").option("--confirm", "Perform the move; otherwise return a read-only plan").option("--json", "Output JSON").action(async function(opts) {
|
|
22029
|
+
let request2;
|
|
22030
|
+
try {
|
|
22031
|
+
request2 = storeMoveRequest(opts);
|
|
22032
|
+
} catch (error) {
|
|
22033
|
+
fail(this, error instanceof Error ? error.message : "Invalid move request", 3);
|
|
22034
|
+
}
|
|
22035
|
+
const res = await moveAdminCustomerStores(
|
|
22036
|
+
requireUserCredentials(),
|
|
22037
|
+
request2.toOrgId,
|
|
22038
|
+
request2.body
|
|
22039
|
+
);
|
|
22040
|
+
if (!res.ok || !res.data)
|
|
22041
|
+
fail(
|
|
22042
|
+
this,
|
|
22043
|
+
res.error ?? "Failed to move stores",
|
|
22044
|
+
classifyStatus(res.status),
|
|
22045
|
+
res.errorDetails
|
|
22046
|
+
);
|
|
22047
|
+
output(this, res.data, () => {
|
|
22048
|
+
printTable(
|
|
22049
|
+
["FIELD", "VALUE"],
|
|
22050
|
+
Object.entries(res.data.data).map(([key, value]) => [key, JSON.stringify(value)])
|
|
22051
|
+
);
|
|
22052
|
+
});
|
|
22053
|
+
});
|
|
22054
|
+
var customers = new Command("customers").description("Create engineering accounts and manage existing account categories").addCommand(create6).addCommand(setCategory).addCommand(setManagedBy).addCommand(setWorkspaceType).addCommand(moveStores);
|
|
20835
22055
|
var adminCommand = new Command("admin").description("Operate allowlisted platform controls").addCommand(settingsGet).addCommand(quarantine).addCommand(shops).addCommand(customers);
|
|
20836
22056
|
|
|
20837
22057
|
// src/commands/membership.ts
|
|
20838
22058
|
init_source();
|
|
20839
22059
|
async function run2(c, p, label) {
|
|
20840
22060
|
const r = await p;
|
|
20841
|
-
if (!r.ok)
|
|
22061
|
+
if (!r.ok)
|
|
22062
|
+
fail(
|
|
22063
|
+
c,
|
|
22064
|
+
r.error ?? `Failed to ${label}`,
|
|
22065
|
+
classifyStatus(r.status),
|
|
22066
|
+
r.errorDetails,
|
|
22067
|
+
apiFailureOf(r)
|
|
22068
|
+
);
|
|
20842
22069
|
output(c, r.data ?? {}, () => process.stdout.write(`${JSON.stringify(r.data, null, 2)}
|
|
20843
22070
|
`));
|
|
20844
22071
|
}
|
|
@@ -20874,7 +22101,27 @@ orgCommand.command("update").option("--name <name>").option("--slug <slug>").opt
|
|
|
20874
22101
|
);
|
|
20875
22102
|
});
|
|
20876
22103
|
orgCommand.command("members").action(async function() {
|
|
20877
|
-
await
|
|
22104
|
+
const r = await listOrgMembers(requireUserCredentials());
|
|
22105
|
+
if (!r.ok)
|
|
22106
|
+
fail(
|
|
22107
|
+
this,
|
|
22108
|
+
r.error ?? "Failed to list members",
|
|
22109
|
+
classifyStatus(r.status),
|
|
22110
|
+
r.errorDetails,
|
|
22111
|
+
apiFailureOf(r)
|
|
22112
|
+
);
|
|
22113
|
+
const rows = r.data?.data ?? [];
|
|
22114
|
+
output(this, r.data ?? {}, () => {
|
|
22115
|
+
printTable(
|
|
22116
|
+
["ID", "MEMBER", "ROLE", "SUSPENDED"],
|
|
22117
|
+
rows.map((row) => [
|
|
22118
|
+
String(row.id),
|
|
22119
|
+
String(row.email ?? row.name ?? "-"),
|
|
22120
|
+
String(row.role),
|
|
22121
|
+
String(row.suspended_at ?? "-")
|
|
22122
|
+
])
|
|
22123
|
+
);
|
|
22124
|
+
});
|
|
20878
22125
|
});
|
|
20879
22126
|
orgCommand.command("invite").argument("<email>").option("--role <role>", "Alias for --org-role").option("--org-role <role>", "Organization role", "member").addOption(
|
|
20880
22127
|
new Option(
|
|
@@ -20911,6 +22158,26 @@ orgCommand.command("set-role").argument("<memberId>").argument("<role>").option(
|
|
|
20911
22158
|
"set role"
|
|
20912
22159
|
);
|
|
20913
22160
|
});
|
|
22161
|
+
orgCommand.command("suspend-member").argument("<memberId>").option("--reason <text>").option("-f, --force").action(async function(id, o) {
|
|
22162
|
+
if (!await confirm(
|
|
22163
|
+
this,
|
|
22164
|
+
o.force,
|
|
22165
|
+
`Suspend organization member ${id}`,
|
|
22166
|
+
`apex org suspend-member ${id} --force`
|
|
22167
|
+
))
|
|
22168
|
+
return;
|
|
22169
|
+
await run2(this, suspendOrgMember(requireUserCredentials(), id, o.reason), "suspend member");
|
|
22170
|
+
});
|
|
22171
|
+
orgCommand.command("reinstate-member").argument("<memberId>").option("-f, --force").action(async function(id, o) {
|
|
22172
|
+
if (!await confirm(
|
|
22173
|
+
this,
|
|
22174
|
+
o.force,
|
|
22175
|
+
`Reinstate organization member ${id}`,
|
|
22176
|
+
`apex org reinstate-member ${id} --force`
|
|
22177
|
+
))
|
|
22178
|
+
return;
|
|
22179
|
+
await run2(this, reinstateOrgMember(requireUserCredentials(), id), "reinstate member");
|
|
22180
|
+
});
|
|
20914
22181
|
orgCommand.command("remove-member").argument("<memberId>").option("-f, --force").action(async function(id, o) {
|
|
20915
22182
|
if (!await confirm(
|
|
20916
22183
|
this,
|
|
@@ -21568,7 +22835,7 @@ var import_node_fs29 = __toESM(require("node:fs"));
|
|
|
21568
22835
|
var import_node_path26 = __toESM(require("node:path"));
|
|
21569
22836
|
|
|
21570
22837
|
// src/commands/landing-pages.ts
|
|
21571
|
-
var
|
|
22838
|
+
var import_node_crypto8 = __toESM(require("node:crypto"));
|
|
21572
22839
|
var import_node_fs28 = __toESM(require("node:fs"));
|
|
21573
22840
|
var import_node_path25 = __toESM(require("node:path"));
|
|
21574
22841
|
function apiFailure(command, response, fallback2) {
|
|
@@ -21582,7 +22849,7 @@ function apiFailure(command, response, fallback2) {
|
|
|
21582
22849
|
}
|
|
21583
22850
|
}
|
|
21584
22851
|
function sha256(body5) {
|
|
21585
|
-
return
|
|
22852
|
+
return import_node_crypto8.default.createHash("sha256").update(body5).digest("hex");
|
|
21586
22853
|
}
|
|
21587
22854
|
function markerPath(folder) {
|
|
21588
22855
|
return import_node_path25.default.join(folder, LANDING_PAGE_CHECKOUT_MARKER);
|
|
@@ -21910,7 +23177,10 @@ async function pullCheckout(command, creds, id, folder, force = false) {
|
|
|
21910
23177
|
notes: listing.data.notes ?? []
|
|
21911
23178
|
};
|
|
21912
23179
|
}
|
|
21913
|
-
var create9 = new Command("create").description("Create a landing page to build outside Apex").requiredOption("--name <name>", "Page name").option(
|
|
23180
|
+
var create9 = new Command("create").description("Create a landing page to build outside Apex").requiredOption("--name <name>", "Page name").option(
|
|
23181
|
+
"--slug <slug>",
|
|
23182
|
+
"URL slug (default: from the name; -2, -3 added only if the shop already uses it)"
|
|
23183
|
+
).option("--idea <key>", "Idea key or id the page is built for").option("--out <folder>", "Also pull the new page into this folder").action(async function(opts) {
|
|
21914
23184
|
const creds = requireCredentials();
|
|
21915
23185
|
const created = await landingPages(creds).create({
|
|
21916
23186
|
name: opts.name,
|
|
@@ -22405,6 +23675,35 @@ program2.hook("preAction", (_thisCommand, actionCommand) => {
|
|
|
22405
23675
|
process.on("beforeExit", () => {
|
|
22406
23676
|
flushFallbackEnvelope();
|
|
22407
23677
|
});
|
|
22408
|
-
|
|
23678
|
+
function isExplicitJsonVersionRequest(args) {
|
|
23679
|
+
let hasJson = false;
|
|
23680
|
+
let hasVersion = false;
|
|
23681
|
+
for (let index = 0; index < args.length; index++) {
|
|
23682
|
+
const arg = args[index];
|
|
23683
|
+
if (arg === "--") break;
|
|
23684
|
+
if (arg === "--json") {
|
|
23685
|
+
hasJson = true;
|
|
23686
|
+
} else if (arg === "--version" || arg === "-V") {
|
|
23687
|
+
hasVersion = true;
|
|
23688
|
+
} else if (arg === "--org" || arg === "--shop") {
|
|
23689
|
+
const value = args[++index];
|
|
23690
|
+
if (!value || value.startsWith("-")) return false;
|
|
23691
|
+
} else if (arg.startsWith("--org=") || arg.startsWith("--shop=")) {
|
|
23692
|
+
const value = arg.slice(arg.indexOf("=") + 1);
|
|
23693
|
+
if (!value || value.startsWith("-")) return false;
|
|
23694
|
+
} else {
|
|
23695
|
+
return false;
|
|
23696
|
+
}
|
|
23697
|
+
}
|
|
23698
|
+
return hasJson && hasVersion;
|
|
23699
|
+
}
|
|
23700
|
+
var explicitJsonVersionRequest = isExplicitJsonVersionRequest(process.argv.slice(2));
|
|
23701
|
+
maybeCheckForUpdate().then(() => {
|
|
23702
|
+
if (explicitJsonVersionRequest) {
|
|
23703
|
+
writeEnvelope({ name: "@drip-apex/apex-cli", version: CLI_VERSION }, "version");
|
|
23704
|
+
return;
|
|
23705
|
+
}
|
|
23706
|
+
return program2.parseAsync(process.argv);
|
|
23707
|
+
}).then(async () => {
|
|
22409
23708
|
await finishInvocation({ exitCode: process.exitCode ?? 0 });
|
|
22410
23709
|
}).catch((err) => handleCliError(err, program2));
|