@drip-apex/apex-cli 0.15.0 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -4
- package/dist/cli.js +210 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -227,6 +227,7 @@ command inventory:
|
|
|
227
227
|
|
|
228
228
|
| Command | Purpose |
|
|
229
229
|
| --- | --- |
|
|
230
|
+
| `apex admin customers create` | Create internal, demo or dogfood accounts with a platform user key. Requires `--name`, `--category`, an 8 to 500 character `--reason`, and `--confirm`. |
|
|
230
231
|
| `apex admin shops archive <shopId>` | Archive a shop and disable Apex delivery; use `--force` in non-interactive environments. |
|
|
231
232
|
| `apex admin shops restore <shopId>` | Restore an archived shop while leaving its runtime kill switch unchanged. |
|
|
232
233
|
|
|
@@ -240,18 +241,19 @@ apex landing-pages create --name "Fall sale" --out ./fall-sale # or: pull <id>
|
|
|
240
241
|
# edit ./fall-sale/index.html and its assets; context/ holds brand, catalog, offers and reviews
|
|
241
242
|
apex landing-pages plan ./fall-sale # what would change
|
|
242
243
|
apex landing-pages apply ./fall-sale --yes # new version + signed preview link (24 hours)
|
|
243
|
-
apex landing-pages deploy ./fall-sale # live on the
|
|
244
|
+
apex landing-pages deploy ./fall-sale # live on <shop>.apex-page.com, or the host in apex-landing-page.json
|
|
244
245
|
apex landing-pages rollback ./fall-sale --version 1
|
|
245
246
|
apex landing-pages experiment ./fall-sale --control 1 --variant 2 # draft split; start it in the dashboard
|
|
246
247
|
```
|
|
247
248
|
|
|
248
249
|
The folder holds the page files (index.html at the root, at most 300 files
|
|
249
|
-
and
|
|
250
|
+
and 100 MB; html, css, js, json, svg, images, fonts, txt, xml, webmanifest, mp4 and webm),
|
|
250
251
|
`apex-landing-page.json` and the `context/` folder (each pull replaces its JSON
|
|
251
252
|
files with the server's current set). Build with relative asset
|
|
252
253
|
URLs (Vite `base: "./"`). `apex-landing-page.json` sets the name, slug, an
|
|
253
|
-
|
|
254
|
-
|
|
254
|
+
optional `host`, a domain claimed with `apex domains claim <host>` (without
|
|
255
|
+
one, or `--host`, a deploy goes to the shop's own instant host
|
|
256
|
+
`<shop>.apex-page.com`), the `path` (default `/<slug>`), whether Apex injects the shop snippet
|
|
255
257
|
(`snippet`, on by default), the `shop_hosts` that receive the visitor's identity
|
|
256
258
|
on outbound links (default: the shop's install domains), the `goals`
|
|
257
259
|
(key, name, type click, pageview or custom) and an optional `experiment`
|
|
@@ -522,3 +524,30 @@ are sent. Login/logout, tests, the mock transport, and runs with no resolvable c
|
|
|
522
524
|
(`APEX_API_KEY` or `DRIP_API_KEY`) are recorded with credential kind `env` unless
|
|
523
525
|
`APEX_TELEMETRY=0`. Set `APEX_TELEMETRY=0` to disable the beacon. The best-effort request has an
|
|
524
526
|
800 ms timeout and never changes command output or exit status.
|
|
527
|
+
|
|
528
|
+
MP4 (`video/mp4`) and WebM (`video/webm`) videos are hosted directly by Apex.
|
|
529
|
+
Each version holds at most 300 files and 100 MB; each video is limited to 50 MB,
|
|
530
|
+
and other files to 3 MB. `apply` automatically obtains a 15-minute upload grant
|
|
531
|
+
for videos above 3 MB and sends resumable 8 MB parts straight to the Worker,
|
|
532
|
+
retrying failed parts with progress output. The Worker verifies the declared
|
|
533
|
+
size and SHA-256 before apply records the file. Pull and signed previews also
|
|
534
|
+
fetch large videos directly from the Worker. Video supports byte ranges,
|
|
535
|
+
including Safari's `bytes=0-1` probe; rollback preserves the older bytes.
|
|
536
|
+
|
|
537
|
+
Engineering account creation uses `apex login --user` credentials with `admin:write`
|
|
538
|
+
and a live platform role with onboarding write permission. Real customer accounts
|
|
539
|
+
are created in **Admin, Links**.
|
|
540
|
+
|
|
541
|
+
```sh
|
|
542
|
+
apex admin customers create --name "Demo workspace" --category demo --reason "Workshop demo account" --confirm --json
|
|
543
|
+
apex admin customers create --name "Dogfood store" --category dogfood --domain dogfood.example.com --platform woo --owner owner@example.com --reason "Engineering dogfood account" --confirm
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
`--type` defaults to `company` (`company|agency`); `--plan` defaults to
|
|
547
|
+
`self_managed` (`self_managed|apex_managed|drip_managed`). Managed plans require
|
|
548
|
+
`--consultant <platform_access UUID>` and `--consultant-title <text>`.
|
|
549
|
+
An optional first store uses `--domain <host> --platform <shopify|shopware|woo|other>`
|
|
550
|
+
or `--claim-shop <UUID>`, exclusively. `woo` maps to `woocommerce` and `other` to
|
|
551
|
+
`custom`. `--owner <email>` sends an owner invitation and requires a store;
|
|
552
|
+
accounts without a store have no invitations. Output includes the organization ID,
|
|
553
|
+
store ID (or null), invitation results and partial delivery status.
|
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.1",
|
|
7329
7329
|
license: "MIT",
|
|
7330
7330
|
description: "Apex CLI for managing experiments, approved roadmap ideas, and draft workspaces.",
|
|
7331
7331
|
private: false,
|
|
@@ -8236,6 +8236,7 @@ function landingPagesApi(request2) {
|
|
|
8236
8236
|
),
|
|
8237
8237
|
plan: (id, body5) => call("POST", pagePath(id, "filesystem/plan"), body5),
|
|
8238
8238
|
upload: (id, files) => call("POST", pagePath(id, "filesystem/upload"), { files }),
|
|
8239
|
+
uploadGrant: (id, file) => call("POST", pagePath(id, "filesystem/upload-grant"), file),
|
|
8239
8240
|
apply: (id, body5) => call("POST", pagePath(id, "filesystem/apply"), body5),
|
|
8240
8241
|
deploy: (id, body5) => call("POST", pagePath(id, "deploy"), body5),
|
|
8241
8242
|
versions: (id) => call("GET", pagePath(id, "versions")),
|
|
@@ -8261,6 +8262,53 @@ function landingPageBatches(items, size) {
|
|
|
8261
8262
|
if (current.length) batches.push(current);
|
|
8262
8263
|
return batches;
|
|
8263
8264
|
}
|
|
8265
|
+
async function uploadLandingVideo(grant, body5, options = {}) {
|
|
8266
|
+
if (!Number.isSafeInteger(grant.part_bytes) || grant.part_bytes !== 8 * 1024 * 1024) throw new Error("Invalid video part size");
|
|
8267
|
+
const fetcher = options.fetch ?? fetch;
|
|
8268
|
+
const send3 = async (action78, method = "POST", part, bytes) => {
|
|
8269
|
+
const url = new URL(grant.upload_url);
|
|
8270
|
+
url.searchParams.set("action", action78);
|
|
8271
|
+
if (part !== void 0) url.searchParams.set("part", String(part));
|
|
8272
|
+
let error;
|
|
8273
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
8274
|
+
try {
|
|
8275
|
+
const response = await fetcher(url, { method, ...bytes ? { body: bytes } : {}, signal: AbortSignal.timeout(6e4) });
|
|
8276
|
+
if (!response.ok) {
|
|
8277
|
+
const message = await response.text();
|
|
8278
|
+
if (response.status < 500 && response.status !== 429) throw new Error(`Video upload refused (${response.status}): ${message}`);
|
|
8279
|
+
error = new Error(`Video upload failed (${response.status}): ${message}`);
|
|
8280
|
+
} else return await response.json();
|
|
8281
|
+
} catch (caught) {
|
|
8282
|
+
if (caught instanceof Error && caught.message.startsWith("Video upload refused")) throw caught;
|
|
8283
|
+
error = caught;
|
|
8284
|
+
}
|
|
8285
|
+
}
|
|
8286
|
+
throw error;
|
|
8287
|
+
};
|
|
8288
|
+
const started2 = await send3("start");
|
|
8289
|
+
if (!started2.complete) {
|
|
8290
|
+
for (let offset = 0, part = 1; offset < body5.byteLength; offset += grant.part_bytes, part++) {
|
|
8291
|
+
const bytes = body5.subarray(offset, Math.min(body5.byteLength, offset + grant.part_bytes));
|
|
8292
|
+
await send3("part", "PUT", part, bytes);
|
|
8293
|
+
options.progress?.(offset + bytes.byteLength, body5.byteLength);
|
|
8294
|
+
}
|
|
8295
|
+
const result = await send3("complete");
|
|
8296
|
+
if (!result.complete) throw new Error("Worker did not confirm the video upload");
|
|
8297
|
+
}
|
|
8298
|
+
}
|
|
8299
|
+
function validateLandingPageLocalFiles(files) {
|
|
8300
|
+
const allowed2 = new Set("html css js mjs json svg png jpg jpeg webp avif gif ico woff woff2 txt xml webmanifest mp4 webm".split(" "));
|
|
8301
|
+
if (files.length > 300) throw new Error("A bundle is limited to 300 files");
|
|
8302
|
+
let total = 0;
|
|
8303
|
+
for (const file of files) {
|
|
8304
|
+
const ext = file.path.split(".").at(-1)?.toLowerCase() ?? "";
|
|
8305
|
+
if (!allowed2.has(ext)) throw new Error(`File type is not allowed: ${file.path}`);
|
|
8306
|
+
const cap = ext === "mp4" || ext === "webm" ? 50 * 1024 * 1024 : LANDING_PAGE_BATCH_BYTES;
|
|
8307
|
+
if (!Number.isSafeInteger(file.bytes) || file.bytes < 0 || file.bytes > cap) throw new Error(`File exceeds ${cap} bytes: ${file.path}`);
|
|
8308
|
+
total += file.bytes;
|
|
8309
|
+
}
|
|
8310
|
+
if (total > 100 * 1024 * 1024) throw new Error("A bundle is limited to 100 MB");
|
|
8311
|
+
}
|
|
8264
8312
|
|
|
8265
8313
|
// src/api.ts
|
|
8266
8314
|
function simulateTriggers(creds, body5) {
|
|
@@ -8813,6 +8861,14 @@ function markInboxRead(creds, channel) {
|
|
|
8813
8861
|
function userCredentials(creds) {
|
|
8814
8862
|
return { ...creds, apiKey: creds.userApiKey ?? "", baseUrl: creds.userBaseUrl ?? creds.baseUrl };
|
|
8815
8863
|
}
|
|
8864
|
+
function createAdminCustomer(creds, body5) {
|
|
8865
|
+
return request(
|
|
8866
|
+
userCredentials(creds),
|
|
8867
|
+
"POST",
|
|
8868
|
+
"/api/v1/admin/customers",
|
|
8869
|
+
body5
|
|
8870
|
+
);
|
|
8871
|
+
}
|
|
8816
8872
|
function getAdminSettings(creds) {
|
|
8817
8873
|
return request(userCredentials(creds), "GET", "/api/v1/admin/settings");
|
|
8818
8874
|
}
|
|
@@ -20677,7 +20733,106 @@ var restore2 = new Command("restore").description("Restore an archived shop").ar
|
|
|
20677
20733
|
);
|
|
20678
20734
|
});
|
|
20679
20735
|
var shops = new Command("shops").description("Archive and restore shops on the platform admin plane").addCommand(archive2).addCommand(restore2);
|
|
20680
|
-
var
|
|
20736
|
+
var create6 = new Command("create").description("Create an internal, demo or dogfood account").requiredOption("--name <text>").requiredOption("--category <category>", "internal, demo or dogfood").option("--type <type>", "company or agency", "company").option("--plan <plan>", "self_managed, apex_managed or drip_managed", "self_managed").option("--domain <host>").option("--platform <platform>", "shopify, shopware, woo or other").option("--claim-shop <uuid>").option("--owner <email>").option("--consultant <uuid>", "Platform access ID").option("--consultant-title <text>").requiredOption("--reason <text>", "Audit reason, 8 to 500 characters").option("--confirm", "Confirm account creation").option("--json", "Output JSON").action(async function(opts) {
|
|
20737
|
+
if (opts.category === "customer") fail(this, "Create customer accounts in Admin, Links", 3);
|
|
20738
|
+
if (!["internal", "demo", "dogfood"].includes(opts.category))
|
|
20739
|
+
fail(this, "--category must be internal, demo or dogfood", 3);
|
|
20740
|
+
if (!["company", "agency"].includes(opts.type))
|
|
20741
|
+
fail(this, "--type must be company or agency", 3);
|
|
20742
|
+
if (!["self_managed", "apex_managed", "drip_managed"].includes(opts.plan))
|
|
20743
|
+
fail(this, "Invalid --plan", 3);
|
|
20744
|
+
if (!opts.name.trim() || opts.name.trim().length > 60)
|
|
20745
|
+
fail(this, "--name must be 1 to 60 characters", 3);
|
|
20746
|
+
if (opts.reason.trim().length < 8 || opts.reason.trim().length > 500)
|
|
20747
|
+
fail(this, "--reason must be 8 to 500 characters", 3);
|
|
20748
|
+
const 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;
|
|
20749
|
+
if (opts.claimShop && !uuid.test(opts.claimShop)) fail(this, "--claim-shop must be a UUID", 3);
|
|
20750
|
+
if (opts.claimShop && (opts.domain || opts.platform))
|
|
20751
|
+
fail(this, "Use --claim-shop or --domain with --platform", 3);
|
|
20752
|
+
if (!!opts.domain !== !!opts.platform)
|
|
20753
|
+
fail(this, "--domain and --platform must be provided together", 3);
|
|
20754
|
+
if (opts.domain && !/^[a-z0-9-]+(\.[a-z0-9-]+)+$/i.test(opts.domain.trim()))
|
|
20755
|
+
fail(this, "--domain must be a hostname without a protocol or path", 3);
|
|
20756
|
+
const platforms = {
|
|
20757
|
+
shopify: "shopify",
|
|
20758
|
+
shopware: "shopware",
|
|
20759
|
+
woo: "woocommerce",
|
|
20760
|
+
other: "custom"
|
|
20761
|
+
};
|
|
20762
|
+
if (opts.platform && !Object.hasOwn(platforms, opts.platform))
|
|
20763
|
+
fail(this, "--platform must be shopify, shopware, woo or other", 3);
|
|
20764
|
+
if (opts.owner && !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(opts.owner.trim()))
|
|
20765
|
+
fail(this, "--owner must be an email address", 3);
|
|
20766
|
+
if (opts.owner && !opts.domain && !opts.claimShop)
|
|
20767
|
+
fail(this, "--owner requires a store for the invitation", 3);
|
|
20768
|
+
if (opts.plan !== "self_managed" && (!opts.consultant || !opts.consultantTitle?.trim()))
|
|
20769
|
+
fail(this, "Managed plans require --consultant and --consultant-title", 3);
|
|
20770
|
+
if (opts.consultant && !uuid.test(opts.consultant))
|
|
20771
|
+
fail(this, "--consultant must be a platform access UUID", 3);
|
|
20772
|
+
if (!!opts.consultant !== !!opts.consultantTitle?.trim())
|
|
20773
|
+
fail(this, "Provide --consultant and --consultant-title together", 3);
|
|
20774
|
+
if (opts.consultantTitle && opts.consultantTitle.trim().length > 240)
|
|
20775
|
+
fail(this, "--consultant-title must be at most 240 characters", 3);
|
|
20776
|
+
if (!opts.confirm) {
|
|
20777
|
+
const flags = [
|
|
20778
|
+
["name", opts.name],
|
|
20779
|
+
["category", opts.category],
|
|
20780
|
+
["type", opts.type],
|
|
20781
|
+
["plan", opts.plan],
|
|
20782
|
+
["reason", opts.reason],
|
|
20783
|
+
["domain", opts.domain],
|
|
20784
|
+
["platform", opts.platform],
|
|
20785
|
+
["claim-shop", opts.claimShop],
|
|
20786
|
+
["owner", opts.owner],
|
|
20787
|
+
["consultant", opts.consultant],
|
|
20788
|
+
["consultant-title", opts.consultantTitle]
|
|
20789
|
+
];
|
|
20790
|
+
const retry = flags.filter((entry) => entry[1] !== void 0).map(([flag, value]) => `--${flag} '${value.replaceAll("'", "'\\''")}'`).join(" ");
|
|
20791
|
+
confirmationRequired(
|
|
20792
|
+
this,
|
|
20793
|
+
"--confirm is required to create an account",
|
|
20794
|
+
`apex admin customers create ${retry} --confirm`
|
|
20795
|
+
);
|
|
20796
|
+
}
|
|
20797
|
+
const body5 = {
|
|
20798
|
+
name: opts.name.trim(),
|
|
20799
|
+
category: opts.category,
|
|
20800
|
+
type: opts.type,
|
|
20801
|
+
plan: opts.plan,
|
|
20802
|
+
reason: opts.reason.trim(),
|
|
20803
|
+
teammates: [],
|
|
20804
|
+
consultant: opts.consultant ? { platformAccessId: opts.consultant, title: opts.consultantTitle.trim() } : null,
|
|
20805
|
+
...opts.claimShop ? { store: { claimShopId: opts.claimShop } } : opts.domain ? {
|
|
20806
|
+
store: {
|
|
20807
|
+
domain: opts.domain.trim().toLowerCase(),
|
|
20808
|
+
platform: platforms[opts.platform]
|
|
20809
|
+
}
|
|
20810
|
+
} : {},
|
|
20811
|
+
...opts.owner ? { ownerEmail: opts.owner.trim() } : {}
|
|
20812
|
+
};
|
|
20813
|
+
const res = await createAdminCustomer(requireUserCredentials(), body5);
|
|
20814
|
+
if (!res.ok || !res.data)
|
|
20815
|
+
fail(
|
|
20816
|
+
this,
|
|
20817
|
+
res.error ?? "Failed to create account",
|
|
20818
|
+
classifyStatus(res.status),
|
|
20819
|
+
res.errorDetails
|
|
20820
|
+
);
|
|
20821
|
+
output(this, res.data, () => {
|
|
20822
|
+
const result = res.data.data;
|
|
20823
|
+
printTable(
|
|
20824
|
+
["ORGANIZATION", "STORE", "INVITATIONS"],
|
|
20825
|
+
[[result.orgId, result.shopId ?? "-", String(result.invitations.length)]]
|
|
20826
|
+
);
|
|
20827
|
+
if (result.invitations.length)
|
|
20828
|
+
printTable(
|
|
20829
|
+
["EMAIL", "ROLE", "STATUS", "NOTE"],
|
|
20830
|
+
result.invitations.map((i) => [i.email, i.role, i.status, i.note])
|
|
20831
|
+
);
|
|
20832
|
+
});
|
|
20833
|
+
});
|
|
20834
|
+
var customers = new Command("customers").description("Create engineering accounts; customer accounts use Admin, Links").addCommand(create6);
|
|
20835
|
+
var adminCommand = new Command("admin").description("Operate allowlisted platform controls").addCommand(settingsGet).addCommand(quarantine).addCommand(shops).addCommand(customers);
|
|
20681
20836
|
|
|
20682
20837
|
// src/commands/membership.ts
|
|
20683
20838
|
init_source();
|
|
@@ -20873,7 +21028,7 @@ var list13 = new Command("list").action(async function() {
|
|
|
20873
21028
|
)
|
|
20874
21029
|
);
|
|
20875
21030
|
});
|
|
20876
|
-
var
|
|
21031
|
+
var create7 = new Command("create").requiredOption("--name <name>").requiredOption("--type <type>").option("--permissions <csv>").action(async function(opts) {
|
|
20877
21032
|
if (!["live_secret", "test_secret", "publishable"].includes(opts.type))
|
|
20878
21033
|
throw new CliError(
|
|
20879
21034
|
"--type must be live_secret, test_secret, or publishable",
|
|
@@ -20913,7 +21068,7 @@ var revoke = new Command("revoke").argument("<keyId>").action(async function(key
|
|
|
20913
21068
|
);
|
|
20914
21069
|
output(this, res.data, () => console.log(`Revoked ${keyId}`));
|
|
20915
21070
|
});
|
|
20916
|
-
var keysCommand = new Command("keys").description("Manage shop API keys in the current context").addCommand(list13).addCommand(
|
|
21071
|
+
var keysCommand = new Command("keys").description("Manage shop API keys in the current context").addCommand(list13).addCommand(create7).addCommand(revoke);
|
|
20917
21072
|
|
|
20918
21073
|
// src/commands/prospect.ts
|
|
20919
21074
|
init_source();
|
|
@@ -21172,7 +21327,7 @@ var list14 = new Command("list").alias("ls").description(
|
|
|
21172
21327
|
const data = await loadWebhookIndex(this);
|
|
21173
21328
|
output(this, data, () => printWebhooks(data.data));
|
|
21174
21329
|
});
|
|
21175
|
-
var
|
|
21330
|
+
var create8 = new Command("create").description(`Create a webhook. Prints the signing secret once. Signature docs: ${SIGNATURE_DOC}`).requiredOption("--url <url>", "HTTPS callback URL").option(
|
|
21176
21331
|
"--event <event>",
|
|
21177
21332
|
"Webhook event to subscribe to; repeat for multiple events",
|
|
21178
21333
|
(value, previous = []) => [...previous, value],
|
|
@@ -21280,7 +21435,7 @@ var test3 = new Command("test").description("Send a signed test event to a webho
|
|
|
21280
21435
|
)
|
|
21281
21436
|
);
|
|
21282
21437
|
});
|
|
21283
|
-
var webhooksCommand = new Command("webhooks").description("Manage shop webhooks").addCommand(list14).addCommand(
|
|
21438
|
+
var webhooksCommand = new Command("webhooks").description("Manage shop webhooks").addCommand(list14).addCommand(create8).addCommand(update5).addCommand(remove3).addCommand(deliveries).addCommand(test3);
|
|
21284
21439
|
|
|
21285
21440
|
// src/commands/simulate.ts
|
|
21286
21441
|
init_source();
|
|
@@ -21502,12 +21657,31 @@ function readCheckoutFiles(folder) {
|
|
|
21502
21657
|
if (!isLandingPageCheckoutPath(relative)) continue;
|
|
21503
21658
|
if (entry.isDirectory()) visit2(absolute);
|
|
21504
21659
|
else if (entry.isFile()) {
|
|
21660
|
+
try {
|
|
21661
|
+
validateLandingPageLocalFiles([
|
|
21662
|
+
...files,
|
|
21663
|
+
{ path: relative, bytes: import_node_fs28.default.statSync(absolute).size, sha256: "" }
|
|
21664
|
+
]);
|
|
21665
|
+
} catch (error) {
|
|
21666
|
+
throw new CliError(
|
|
21667
|
+
error instanceof Error ? error.message : "Invalid bundle",
|
|
21668
|
+
EXIT_CODES.validation
|
|
21669
|
+
);
|
|
21670
|
+
}
|
|
21505
21671
|
const body5 = import_node_fs28.default.readFileSync(absolute);
|
|
21506
21672
|
files.push({ path: relative, sha256: sha256(body5), bytes: body5.byteLength, body: body5 });
|
|
21507
21673
|
}
|
|
21508
21674
|
}
|
|
21509
21675
|
};
|
|
21510
21676
|
visit2(folder);
|
|
21677
|
+
try {
|
|
21678
|
+
validateLandingPageLocalFiles(files);
|
|
21679
|
+
} catch (error) {
|
|
21680
|
+
throw new CliError(
|
|
21681
|
+
error instanceof Error ? error.message : "Invalid bundle",
|
|
21682
|
+
EXIT_CODES.validation
|
|
21683
|
+
);
|
|
21684
|
+
}
|
|
21511
21685
|
return files;
|
|
21512
21686
|
}
|
|
21513
21687
|
function readManifest2(command, folder) {
|
|
@@ -21564,7 +21738,7 @@ function printPlan(plan2) {
|
|
|
21564
21738
|
);
|
|
21565
21739
|
}
|
|
21566
21740
|
console.log(
|
|
21567
|
-
`Deploy target: ${plan2.deploy_target ?? "none
|
|
21741
|
+
`Deploy target: ${plan2.deploy_target ?? "none: set host in apex-landing-page.json to a domain this shop holds"}`
|
|
21568
21742
|
);
|
|
21569
21743
|
}
|
|
21570
21744
|
async function confirm2(message) {
|
|
@@ -21653,7 +21827,10 @@ async function pullCheckout(command, creds, id, folder, force = false) {
|
|
|
21653
21827
|
const target = import_node_path25.default.resolve(folder, file.path);
|
|
21654
21828
|
if (!target.startsWith(folder + import_node_path25.default.sep))
|
|
21655
21829
|
fail(command, `Refusing to write outside the checkout: ${file.path}`);
|
|
21656
|
-
const
|
|
21830
|
+
const download = file.download_url ? await fetch(file.download_url, { signal: AbortSignal.timeout(6e4) }) : null;
|
|
21831
|
+
if (download && !download.ok)
|
|
21832
|
+
fail(command, `Failed to download ${file.path}: ${download.status}`);
|
|
21833
|
+
const body5 = download ? Buffer.from(await download.arrayBuffer()) : file.encoding === "base64" ? Buffer.from(file.content ?? "", "base64") : Buffer.from(file.content ?? "", "utf8");
|
|
21657
21834
|
if (sha256(body5) !== file.sha256)
|
|
21658
21835
|
fail(command, `The server returned ${file.path} incomplete. Pull again.`);
|
|
21659
21836
|
downloaded.push({ path: file.path, target, body: body5, sha256: file.sha256 });
|
|
@@ -21733,7 +21910,7 @@ async function pullCheckout(command, creds, id, folder, force = false) {
|
|
|
21733
21910
|
notes: listing.data.notes ?? []
|
|
21734
21911
|
};
|
|
21735
21912
|
}
|
|
21736
|
-
var
|
|
21913
|
+
var create9 = new Command("create").description("Create a landing page to build outside Apex").requiredOption("--name <name>", "Page name").option("--slug <slug>", "URL slug (default: from the name)").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) {
|
|
21737
21914
|
const creds = requireCredentials();
|
|
21738
21915
|
const created = await landingPages(creds).create({
|
|
21739
21916
|
name: opts.name,
|
|
@@ -21811,7 +21988,24 @@ var apply = new Command("apply").description("Apply a checkout as a new version"
|
|
|
21811
21988
|
}
|
|
21812
21989
|
const required = new Set(planned.data.files.upload_required);
|
|
21813
21990
|
const toUpload = files.filter((file) => required.has(file.path));
|
|
21814
|
-
for (const
|
|
21991
|
+
for (const file of toUpload.filter((file2) => file2.bytes > LANDING_PAGE_BATCH_BYTES)) {
|
|
21992
|
+
const granted = await api.uploadGrant(marker.landingPageId, {
|
|
21993
|
+
path: file.path,
|
|
21994
|
+
bytes: file.bytes,
|
|
21995
|
+
sha256: file.sha256,
|
|
21996
|
+
base_version_id: planned.data.base_version_id
|
|
21997
|
+
});
|
|
21998
|
+
apiFailure(this, granted, "Failed to grant video upload");
|
|
21999
|
+
await uploadLandingVideo(granted.data, file.body, {
|
|
22000
|
+
progress: (uploaded, total) => {
|
|
22001
|
+
console.error(`Uploading ${file.path}: ${uploaded}/${total} bytes`);
|
|
22002
|
+
}
|
|
22003
|
+
});
|
|
22004
|
+
}
|
|
22005
|
+
for (const batch of landingPageBatches(
|
|
22006
|
+
toUpload.filter((file) => file.bytes <= LANDING_PAGE_BATCH_BYTES),
|
|
22007
|
+
(file) => file.bytes
|
|
22008
|
+
)) {
|
|
21815
22009
|
const uploaded = await api.upload(
|
|
21816
22010
|
marker.landingPageId,
|
|
21817
22011
|
batch.map((file) => ({
|
|
@@ -21860,7 +22054,7 @@ var apply = new Command("apply").description("Apply a checkout as a new version"
|
|
|
21860
22054
|
console.log(`Version ${applied.data.version_number}`);
|
|
21861
22055
|
if (applied.data.preview_url) console.log(`Preview: ${applied.data.preview_url}`);
|
|
21862
22056
|
console.log(
|
|
21863
|
-
`Deploy target: ${applied.data.deploy_target ?? "none
|
|
22057
|
+
`Deploy target: ${applied.data.deploy_target ?? "none: set host in apex-landing-page.json to a domain this shop holds"}`
|
|
21864
22058
|
);
|
|
21865
22059
|
});
|
|
21866
22060
|
});
|
|
@@ -21888,9 +22082,11 @@ async function versionIdFor(command, creds, id, raw) {
|
|
|
21888
22082
|
return match2.id;
|
|
21889
22083
|
}
|
|
21890
22084
|
var SPLIT_KEEPS_SERVING_HELP = "\nWhile an experiment splits this page, visitors keep getting its two arms and its goals stay as they are (new manifest goals are added). The deployed version serves when the experiment ends; its goal changes apply at the next deploy after that.\n";
|
|
21891
|
-
var deploy = new Command("deploy").description(
|
|
22085
|
+
var deploy = new Command("deploy").description(
|
|
22086
|
+
"Deploy a version to the shop's instant host (<shop>.apex-page.com) or a domain this shop holds"
|
|
22087
|
+
).argument("[folder-or-id]", "Checkout folder or landing page id", ".").option(
|
|
21892
22088
|
"--host <host>",
|
|
21893
|
-
"Domain claimed with apex domains claim (default: the manifest host
|
|
22089
|
+
"Domain claimed with apex domains claim (default: the manifest host, else the shop's apex-page.com subdomain)"
|
|
21894
22090
|
).option("--path <path>", "Path on the host").option("--version <number>", "Version number (default: current)").action(async function(target, opts) {
|
|
21895
22091
|
const creds = requireCredentials();
|
|
21896
22092
|
const id = resolvePageId(target);
|
|
@@ -21974,7 +22170,7 @@ var open2 = new Command("open").description("Print the page's dashboard URL").ar
|
|
|
21974
22170
|
output(this, { url }, () => console.log(url));
|
|
21975
22171
|
});
|
|
21976
22172
|
var landingPageWorkspaceCommands = [
|
|
21977
|
-
|
|
22173
|
+
create9,
|
|
21978
22174
|
pull,
|
|
21979
22175
|
plan,
|
|
21980
22176
|
apply,
|