@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.
Files changed (3) hide show
  1. package/README.md +33 -4
  2. package/dist/cli.js +210 -14
  3. 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 claimed host in apex-landing-page.json
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 25 MB; html, css, js, json, svg, images, fonts, txt, xml, webmanifest),
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
- claimed `host` (deploy needs one, or `--host`: claim it with
254
- `apex domains claim <host>`; Apex has no shared pages host yet), the `path`, whether Apex injects the shop snippet
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.0",
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 adminCommand = new Command("admin").description("Operate allowlisted platform controls").addCommand(settingsGet).addCommand(quarantine).addCommand(shops);
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 create6 = new Command("create").requiredOption("--name <name>").requiredOption("--type <type>").option("--permissions <csv>").action(async function(opts) {
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(create6).addCommand(revoke);
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 create7 = new Command("create").description(`Create a webhook. Prints the signing secret once. Signature docs: ${SIGNATURE_DOC}`).requiredOption("--url <url>", "HTTPS callback URL").option(
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(create7).addCommand(update5).addCommand(remove3).addCommand(deliveries).addCommand(test3);
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 yet: set host in apex-landing-page.json to a domain this shop holds"}`
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 body5 = file.encoding === "base64" ? Buffer.from(file.content ?? "", "base64") : Buffer.from(file.content ?? "", "utf8");
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 create8 = 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) {
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 batch of landingPageBatches(toUpload, (file) => file.bytes)) {
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 yet: set host in apex-landing-page.json to a domain this shop holds"}`
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("Deploy a version to a domain this shop holds").argument("[folder-or-id]", "Checkout folder or landing page id", ".").option(
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; one of the two is required)"
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
- create8,
22173
+ create9,
21978
22174
  pull,
21979
22175
  plan,
21980
22176
  apply,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drip-apex/apex-cli",
3
- "version": "0.15.0",
3
+ "version": "0.15.1",
4
4
  "license": "MIT",
5
5
  "description": "Apex CLI for managing experiments, approved roadmap ideas, and draft workspaces.",
6
6
  "private": false,