@zanfia/cli 0.6.4 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +127 -37
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5249,9 +5249,9 @@ var require_lodash7 = __commonJS({
|
|
|
5249
5249
|
}
|
|
5250
5250
|
});
|
|
5251
5251
|
|
|
5252
|
-
// ../../node_modules/
|
|
5252
|
+
// ../../node_modules/lodash.uniqby/index.js
|
|
5253
5253
|
var require_lodash8 = __commonJS({
|
|
5254
|
-
"../../node_modules/
|
|
5254
|
+
"../../node_modules/lodash.uniqby/index.js"(exports, module) {
|
|
5255
5255
|
"use strict";
|
|
5256
5256
|
var baseIteratee = require_lodash4();
|
|
5257
5257
|
var baseUniq = require_lodash7();
|
|
@@ -10324,11 +10324,11 @@ var listCertificateTemplatesColumns = ["id", "name", "version", "isDefault", "up
|
|
|
10324
10324
|
function registerCertificatesCommands(program2) {
|
|
10325
10325
|
const certificates = program2.command("certificates");
|
|
10326
10326
|
certificates.description(
|
|
10327
|
-
"Course-completion certificates \u2014 issue for course clients, revoke/reinstate, resend the email, and manage designer templates (`certificates templates \u2026`)"
|
|
10327
|
+
"Course-completion and event-participation certificates \u2014 issue for course or event clients, revoke/reinstate, resend the email, and manage designer templates (`certificates templates \u2026`)"
|
|
10328
10328
|
);
|
|
10329
10329
|
certificates.command("list").summary("List certificates").description(
|
|
10330
|
-
"Issued
|
|
10331
|
-
).option("--product <value>", "Course product id \u2014 omit for all
|
|
10330
|
+
"Issued certificates of the workspace (course completion and event participation), newest first, optionally filtered to one product (`productId`); `limit` caps the rows (service default applies). Each row is the credential: public `certificateId` (`cert_\u2026`, the id every certificate write takes), `productType` (`course` / `event`), recipient name/email, product (`courseName`) and issuer names, completion/issued dates, `issuedBy` (manual vs automatic), template id/version, PDF and verification URLs, `status` (active/revoked), `expiresAt` and delivery/engagement signals (email status, views, downloads, LinkedIn add). Also the polling endpoint after issue-certificates \u2014 a newly enqueued client shows up here once its PDF has been rendered."
|
|
10331
|
+
).option("--product <value>", "Course or event product id \u2014 omit for all products.").option(
|
|
10332
10332
|
"--limit <n>",
|
|
10333
10333
|
"Max rows, newest first (max 500; the service default applies when omitted).",
|
|
10334
10334
|
parseNumberOption("--limit", NumberFormat.Integer)
|
|
@@ -10345,17 +10345,17 @@ function registerCertificatesCommands(program2) {
|
|
|
10345
10345
|
emit(flags, result ?? {}, () => printTable(rows, listCertificatesColumns));
|
|
10346
10346
|
});
|
|
10347
10347
|
certificates.command("issue").summary("Issue certificates (async)").description(
|
|
10348
|
-
'Issues
|
|
10349
|
-
).argument("<productId>", "Course product id (
|
|
10348
|
+
'Issues certificates to the listed clients of ONE product: a course (completion certificate) or an event \u2014 webinar, workshop, also one held outside Zanfia (participation certificate, "for taking part in", no attendance check). The call validates the batch (404 unknown product or `templateId`, 400 when the product is neither a course nor an event), skips clients who already hold a certificate for it (one certificate per client per product, ever) and enqueues one issuance task per remaining client \u2014 PDF rendering and the "certificate issued" email happen asynchronously, so the response is 202 with `enqueued` / `skippedExistingCertificate` counts, not the certificates. Poll list-certificates (`productId` filter) a little later for the issued rows. A single issuance is this call with one `clientId`. With `onlyCompleted: true` a client only gets a certificate if their course progress is completed (checked per client at issuance time; courses only, 400 for an event). `clientIds` is capped at 1000 and de-duplicated. Every issuance EMAILS the student \u2014 the emails cannot be recalled.'
|
|
10349
|
+
).argument("<productId>", "Course or event product id (other product types answer 400).").requiredOption(
|
|
10350
10350
|
"--client <a,b,...>",
|
|
10351
10351
|
"Workspace client ids; one id = single issuance. Max 1000, de-duplicated.",
|
|
10352
10352
|
parseCommaList("--client")
|
|
10353
10353
|
).option(
|
|
10354
10354
|
"--only-completed",
|
|
10355
|
-
"
|
|
10355
|
+
"Course products only (400 for an event): skip clients who have not completed the course (checked per client at issuance time). Default false = issue to everyone listed."
|
|
10356
10356
|
).option("--no-only-completed", "Send `onlyCompleted: false` explicitly.").option(
|
|
10357
10357
|
"--template <value>",
|
|
10358
|
-
"Explicit template id; omitted = the
|
|
10358
|
+
"Explicit template id; omitted = the product template, else the workspace default."
|
|
10359
10359
|
).option(
|
|
10360
10360
|
"--reissue-revoked",
|
|
10361
10361
|
"Re-issue clients whose current certificate is REVOKED (new document in the current template, new number and verification link, the revoked one stays revoked). ACTIVE certificates are still skipped. Default false = any existing certificate skips the client."
|
|
@@ -10593,7 +10593,10 @@ function registerBlogCommands(program2) {
|
|
|
10593
10593
|
).option("--content <value>", "The body as Markdown (headings, lists, links, images by URL).").option(
|
|
10594
10594
|
"--excerpt <value>",
|
|
10595
10595
|
"Short plain-text summary (list, RSS, meta description); derived from the body on publish when empty."
|
|
10596
|
-
).option("--cover <value>", "Cover image URL (Media Library).").option("--tags <a,b,...>", "Lowercase labels, at most 10.", parseCommaList("--tags")).option("--seo <json|file>", "seo", parseJsonObjectFileOrInline("--seo")).option("--publish", "Publish right away (needs a non-empty body); default false = draft.").option("--no-publish", "Send `publish: false` explicitly.").
|
|
10596
|
+
).option("--cover <value>", "Cover image URL (Media Library).").option("--tags <a,b,...>", "Lowercase labels, at most 10.", parseCommaList("--tags")).option("--seo <json|file>", "seo", parseJsonObjectFileOrInline("--seo")).option("--publish", "Publish right away (needs a non-empty body); default false = draft.").option("--no-publish", "Send `publish: false` explicitly.").option(
|
|
10597
|
+
"--published-at <value>",
|
|
10598
|
+
"The post's date, ISO 8601 date (2025-10-16) or date-time, not in the future - e.g. the original date of an imported post. Omitted = the moment of the first publication."
|
|
10599
|
+
).action(async (options, command) => {
|
|
10597
10600
|
const flags = getGlobalFlags(command);
|
|
10598
10601
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
10599
10602
|
const params = {};
|
|
@@ -10605,7 +10608,8 @@ function registerBlogCommands(program2) {
|
|
|
10605
10608
|
coverImageUrl: options.cover,
|
|
10606
10609
|
tags: options.tags,
|
|
10607
10610
|
seo: options.seo,
|
|
10608
|
-
publish: options.publish
|
|
10611
|
+
publish: options.publish,
|
|
10612
|
+
publishedAt: options.publishedAt
|
|
10609
10613
|
};
|
|
10610
10614
|
const result = await client.call("POST", "/blog-posts", params, void 0, payload);
|
|
10611
10615
|
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
@@ -10633,6 +10637,9 @@ function registerBlogCommands(program2) {
|
|
|
10633
10637
|
"--seo <json|file>",
|
|
10634
10638
|
"Replaces the whole SEO section.",
|
|
10635
10639
|
parseJsonObjectFileOrInline("--seo")
|
|
10640
|
+
).option(
|
|
10641
|
+
"--published-at <value>",
|
|
10642
|
+
"The post's date (ISO 8601, not in the future); null clears it on a draft - a published post keeps one."
|
|
10636
10643
|
).action(async (id, options, command) => {
|
|
10637
10644
|
const flags = getGlobalFlags(command);
|
|
10638
10645
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
@@ -10646,7 +10653,8 @@ function registerBlogCommands(program2) {
|
|
|
10646
10653
|
excerpt: options.excerpt,
|
|
10647
10654
|
coverImageUrl: options.cover,
|
|
10648
10655
|
tags: options.tags,
|
|
10649
|
-
seo: options.seo
|
|
10656
|
+
seo: options.seo,
|
|
10657
|
+
publishedAt: options.publishedAt
|
|
10650
10658
|
};
|
|
10651
10659
|
const result = await client.call("PATCH", "/blog-posts/:postId", params, void 0, payload);
|
|
10652
10660
|
emit(flags, result ?? {}, () => printJson(result ?? {}));
|
|
@@ -13248,6 +13256,11 @@ function buildConsentInput(options) {
|
|
|
13248
13256
|
import { readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "fs";
|
|
13249
13257
|
|
|
13250
13258
|
// src/client/types.ts
|
|
13259
|
+
var GRANT_PAYMENT_METHODS = [
|
|
13260
|
+
"bankTransfer",
|
|
13261
|
+
"cash",
|
|
13262
|
+
"other"
|
|
13263
|
+
];
|
|
13251
13264
|
var AIImageAspectRatio = /* @__PURE__ */ ((AIImageAspectRatio2) => {
|
|
13252
13265
|
AIImageAspectRatio2["Square"] = "1:1";
|
|
13253
13266
|
AIImageAspectRatio2["Portrait"] = "4:5";
|
|
@@ -13776,6 +13789,40 @@ function registerClientTagsCommands(clients2) {
|
|
|
13776
13789
|
var IMPORT_CHUNK_SIZE = 100;
|
|
13777
13790
|
var SCHEDULER_UNAVAILABLE_STATUS = 503;
|
|
13778
13791
|
var MAX_BULK_FILTERED_PRODUCTS = 10;
|
|
13792
|
+
function parseGrantPayment(options) {
|
|
13793
|
+
const { paid, currency, paidAt, paymentMethod, paymentNote } = options;
|
|
13794
|
+
if (paid === void 0) {
|
|
13795
|
+
if (currency || paidAt || paymentMethod || paymentNote) {
|
|
13796
|
+
throw new Error("--currency, --paid-at, --payment-method and --payment-note need --paid.");
|
|
13797
|
+
}
|
|
13798
|
+
return void 0;
|
|
13799
|
+
}
|
|
13800
|
+
if (!/^\d+([.,]\d{1,2})?$/.test(paid)) {
|
|
13801
|
+
throw new Error(`Invalid --paid amount: ${paid} (expected e.g. 99 or 99.50)`);
|
|
13802
|
+
}
|
|
13803
|
+
const amountCents = Math.round(Number(paid.replace(",", ".")) * 100);
|
|
13804
|
+
if (amountCents <= 0) {
|
|
13805
|
+
throw new Error("--paid must be above 0.");
|
|
13806
|
+
}
|
|
13807
|
+
if (!currency) {
|
|
13808
|
+
throw new Error("--paid needs --currency (pln, eur, usd or gbp).");
|
|
13809
|
+
}
|
|
13810
|
+
if (paidAt !== void 0 && Number.isNaN(Date.parse(paidAt))) {
|
|
13811
|
+
throw new Error(`Invalid --paid-at date: ${paidAt} (expected ISO, e.g. 2026-10-14)`);
|
|
13812
|
+
}
|
|
13813
|
+
if (paymentMethod !== void 0 && !GRANT_PAYMENT_METHODS.includes(paymentMethod)) {
|
|
13814
|
+
throw new Error(
|
|
13815
|
+
`Invalid --payment-method: ${paymentMethod} (one of ${GRANT_PAYMENT_METHODS.join(", ")})`
|
|
13816
|
+
);
|
|
13817
|
+
}
|
|
13818
|
+
return {
|
|
13819
|
+
amountCents,
|
|
13820
|
+
currency: currency.toLowerCase(),
|
|
13821
|
+
...paidAt !== void 0 ? { paidAt: new Date(paidAt).toISOString() } : {},
|
|
13822
|
+
...paymentMethod !== void 0 ? { method: paymentMethod } : {},
|
|
13823
|
+
...paymentNote !== void 0 ? { note: paymentNote } : {}
|
|
13824
|
+
};
|
|
13825
|
+
}
|
|
13779
13826
|
function requireConfirm4(confirm, what) {
|
|
13780
13827
|
if (!confirm) {
|
|
13781
13828
|
throw new Error(`Refusing to ${what} without --confirm. Re-run with --confirm to proceed.`);
|
|
@@ -14263,7 +14310,13 @@ Next page: --cursor ${page.nextCursor}
|
|
|
14263
14310
|
});
|
|
14264
14311
|
}
|
|
14265
14312
|
);
|
|
14266
|
-
clients2.command("grant").description("Grant a client access to a product without a purchase").argument("<productId>", "product id").requiredOption("--email <email>", "client email").option("--first-name <name>", "client first name").option("--last-name <name>", "client last name").option("--price <priceId>", "enroll under an existing price without billing").option("--plan <planId>", "plan to enroll in (multi-plan products)").option("--until <date>", "ISO date the access ends (free grants only); omit for lifetime").option("--welcome-email", "send the welcome/sign-in email (default: grant silently)").
|
|
14313
|
+
clients2.command("grant").description("Grant a client access to a product without a purchase").argument("<productId>", "product id").requiredOption("--email <email>", "client email").option("--first-name <name>", "client first name").option("--last-name <name>", "client last name").option("--price <priceId>", "enroll under an existing price without billing").option("--plan <planId>", "plan to enroll in (multi-plan products)").option("--until <date>", "ISO date the access ends (free grants only); omit for lifetime").option("--welcome-email", "send the welcome/sign-in email (default: grant silently)").option(
|
|
14314
|
+
"--run-purchase-automations",
|
|
14315
|
+
"also run the product's purchase automations, marked as a grant (default: off)"
|
|
14316
|
+
).option(
|
|
14317
|
+
"--paid <amount>",
|
|
14318
|
+
"the client already paid outside Zanfia (e.g. 99.50): records one paid order"
|
|
14319
|
+
).option("--currency <code>", "currency of --paid: pln, eur, usd or gbp").option("--paid-at <date>", "ISO date the client paid (default: now; not in the future)").option("--payment-method <method>", `how they paid: ${GRANT_PAYMENT_METHODS.join(" | ")}`).option("--payment-note <text>", "note on the payment, e.g. the proforma number (max 300)").action(
|
|
14267
14320
|
async (productId, options, command) => {
|
|
14268
14321
|
const flags = getGlobalFlags(command);
|
|
14269
14322
|
if (options.until !== void 0 && Number.isNaN(Date.parse(options.until))) {
|
|
@@ -14272,6 +14325,7 @@ Next page: --cursor ${page.nextCursor}
|
|
|
14272
14325
|
if (options.until !== void 0 && options.price !== void 0) {
|
|
14273
14326
|
throw new Error("--until applies to free grants only \u2014 omit --price.");
|
|
14274
14327
|
}
|
|
14328
|
+
const payment = parseGrantPayment(options);
|
|
14275
14329
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
14276
14330
|
const result = await client.grantClientAccess(productId, {
|
|
14277
14331
|
email: options.email,
|
|
@@ -14280,13 +14334,14 @@ Next page: --cursor ${page.nextCursor}
|
|
|
14280
14334
|
priceId: options.price,
|
|
14281
14335
|
planId: options.plan,
|
|
14282
14336
|
accessUntil: options.until,
|
|
14283
|
-
sendWelcomeEmail: options.welcomeEmail
|
|
14337
|
+
sendWelcomeEmail: options.welcomeEmail,
|
|
14338
|
+
runPurchaseAutomations: options.runPurchaseAutomations,
|
|
14339
|
+
...payment ? { payment } : {}
|
|
14284
14340
|
});
|
|
14285
14341
|
emit(flags, result, () => {
|
|
14286
14342
|
const kind = result.isFree ? "free" : "paid";
|
|
14287
14343
|
process.stdout.write(
|
|
14288
|
-
`Granted ${options.email} ${kind} access to product ${productId}
|
|
14289
|
-
`
|
|
14344
|
+
`Granted ${options.email} ${kind} access to product ${productId}` + (payment ? " and recorded the payment as a paid order" : "") + ".\n"
|
|
14290
14345
|
);
|
|
14291
14346
|
});
|
|
14292
14347
|
}
|
|
@@ -19905,7 +19960,7 @@ var NoopUrlStorage = /* @__PURE__ */ (function() {
|
|
|
19905
19960
|
}]);
|
|
19906
19961
|
})();
|
|
19907
19962
|
|
|
19908
|
-
// ../../node_modules/
|
|
19963
|
+
// ../../node_modules/js-base64/base64.mjs
|
|
19909
19964
|
var version = "3.7.8";
|
|
19910
19965
|
var VERSION = version;
|
|
19911
19966
|
var _hasBuffer = typeof Buffer === "function";
|
|
@@ -25028,6 +25083,7 @@ var PAGE_INTENTS = [
|
|
|
25028
25083
|
var SOURCE_SITE_MODES = ["rebuild", "inspired"];
|
|
25029
25084
|
var EVENT_MODES2 = ["live", "on_demand"];
|
|
25030
25085
|
var OPT_IN_MODES2 = ["single", "double"];
|
|
25086
|
+
var TRACKING_MODES = ["inherit", "selected", "none"];
|
|
25031
25087
|
var SCHEDULER_UNAVAILABLE_STATUS3 = 503;
|
|
25032
25088
|
function parseChoice5(value, allowed, flag) {
|
|
25033
25089
|
const match = allowed.find((candidate) => candidate === value);
|
|
@@ -25164,6 +25220,11 @@ function registerPagesCommands(program2) {
|
|
|
25164
25220
|
`);
|
|
25165
25221
|
process.stdout.write(` Cookie banner: ${page.hideCookieBanner ? "hidden" : "shown"}
|
|
25166
25222
|
`);
|
|
25223
|
+
const trackingIds = page.tracking?.integrationIds ?? [];
|
|
25224
|
+
process.stdout.write(
|
|
25225
|
+
` Tracking: ${page.tracking?.mode ?? "inherit"}${trackingIds.length > 0 ? ` (${trackingIds.join(", ")})` : ""}
|
|
25226
|
+
`
|
|
25227
|
+
);
|
|
25167
25228
|
});
|
|
25168
25229
|
});
|
|
25169
25230
|
pages.command("create").description("Create a hosted page from an HTML file or inline HTML").requiredOption("--slug <slug>", "public URL slug (served at /page/<slug>)").requiredOption("--title <title>", "page title").option("--file <path>", "path to an .html file to host").option("--html <html>", "inline HTML body (alternative to --file)").option("--publish", "publish immediately instead of saving as draft").action(async (options, command) => {
|
|
@@ -25185,17 +25246,33 @@ function registerPagesCommands(program2) {
|
|
|
25185
25246
|
`);
|
|
25186
25247
|
});
|
|
25187
25248
|
});
|
|
25188
|
-
pages.command("update").description(
|
|
25249
|
+
pages.command("update").description(
|
|
25250
|
+
"Update a page (slug, title, HTML body, cookie-banner visibility, or its tracking tools)"
|
|
25251
|
+
).argument("<id>", "page id").option("--slug <slug>", "new slug").option("--title <title>", "new title").option("--file <path>", "path to a new .html file").option("--html <html>", "new inline HTML body").option("--cookie-banner", "show the workspace cookie banner on this page again").option(
|
|
25189
25252
|
"--no-cookie-banner",
|
|
25190
25253
|
"hide the workspace cookie banner on this page (pixels still wait for consent given elsewhere)"
|
|
25254
|
+
).option(
|
|
25255
|
+
"--tracking <mode>",
|
|
25256
|
+
"tracking tools on this page: inherit (workspace defaults), selected (--tracking-integrations) or none"
|
|
25257
|
+
).option(
|
|
25258
|
+
"--tracking-integrations <ids>",
|
|
25259
|
+
"comma-separated tracking integration ids for --tracking selected (one per platform)"
|
|
25191
25260
|
).action(async (id, options, command) => {
|
|
25192
25261
|
const flags = getGlobalFlags(command);
|
|
25193
25262
|
const html = await resolveHtml2(options);
|
|
25263
|
+
const trackingIds = options.trackingIntegrations?.split(",").map((value) => value.trim()).filter((value) => value.length > 0);
|
|
25264
|
+
const trackingMode = options.tracking !== void 0 ? parseChoice5(options.tracking, TRACKING_MODES, "--tracking") : trackingIds !== void 0 ? "selected" : void 0;
|
|
25194
25265
|
const input = {
|
|
25195
25266
|
slug: options.slug,
|
|
25196
25267
|
title: options.title,
|
|
25197
25268
|
html,
|
|
25198
|
-
...options.cookieBanner !== void 0 ? { hideCookieBanner: !options.cookieBanner } : {}
|
|
25269
|
+
...options.cookieBanner !== void 0 ? { hideCookieBanner: !options.cookieBanner } : {},
|
|
25270
|
+
...trackingMode !== void 0 ? {
|
|
25271
|
+
tracking: {
|
|
25272
|
+
mode: trackingMode,
|
|
25273
|
+
...trackingIds !== void 0 ? { integrationIds: trackingIds } : {}
|
|
25274
|
+
}
|
|
25275
|
+
} : {}
|
|
25199
25276
|
};
|
|
25200
25277
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
25201
25278
|
const result = await client.updatePage(id, input);
|
|
@@ -28365,7 +28442,8 @@ var CONVERSION_EVENT_STATUSES = [
|
|
|
28365
28442
|
"sent",
|
|
28366
28443
|
"delivered",
|
|
28367
28444
|
"failed",
|
|
28368
|
-
"consent_blocked"
|
|
28445
|
+
"consent_blocked",
|
|
28446
|
+
"test_skipped"
|
|
28369
28447
|
];
|
|
28370
28448
|
var CONVERSION_TRACKING_CLIENTS = [
|
|
28371
28449
|
"metaConversionApi",
|
|
@@ -28468,12 +28546,12 @@ function registerStatsCommands(program2) {
|
|
|
28468
28546
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
28469
28547
|
const report = await client.statsSales(range);
|
|
28470
28548
|
emit(flags, report, () => {
|
|
28471
|
-
const c = report.
|
|
28549
|
+
const c = report.currency;
|
|
28472
28550
|
process.stdout.write(
|
|
28473
28551
|
`Sales (${report.period.from} \u2192 ${report.period.to}), currency ${c}
|
|
28474
|
-
Net revenue: ${money(report.
|
|
28552
|
+
Net revenue: ${money(report.totalRevenueCents)} ${c}
|
|
28475
28553
|
Transactions: ${report.transactions} Refunds: ${report.refunds}
|
|
28476
|
-
AOV: ${money(report.
|
|
28554
|
+
AOV: ${money(report.averageOrderValueCents)} ${c} New subscribers: ${report.newSubscribers}
|
|
28477
28555
|
|
|
28478
28556
|
`
|
|
28479
28557
|
);
|
|
@@ -28483,7 +28561,7 @@ function registerStatsCommands(program2) {
|
|
|
28483
28561
|
report.perProduct.map((r) => ({
|
|
28484
28562
|
id: r.id,
|
|
28485
28563
|
name: r.name,
|
|
28486
|
-
revenue: money(r.
|
|
28564
|
+
revenue: money(r.revenueCents),
|
|
28487
28565
|
transactions: r.transactions,
|
|
28488
28566
|
refunds: r.refunds
|
|
28489
28567
|
})),
|
|
@@ -28497,11 +28575,11 @@ function registerStatsCommands(program2) {
|
|
|
28497
28575
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
28498
28576
|
const report = await client.statsRecurring(options.currency);
|
|
28499
28577
|
emit(flags, report, () => {
|
|
28500
|
-
const c = report.
|
|
28578
|
+
const c = report.currency;
|
|
28501
28579
|
process.stdout.write(
|
|
28502
28580
|
`Recurring revenue (currency ${c})
|
|
28503
|
-
MRR: ${money(report.
|
|
28504
|
-
Active subscriptions: ${report.
|
|
28581
|
+
MRR: ${money(report.mrrCents)} ${c} ARR: ${money(report.arrCents)} ${c}
|
|
28582
|
+
Active subscriptions: ${report.activeSubscriptions} Ending soon: ${report.endingSoonCount}
|
|
28505
28583
|
|
|
28506
28584
|
`
|
|
28507
28585
|
);
|
|
@@ -28510,8 +28588,8 @@ function registerStatsCommands(program2) {
|
|
|
28510
28588
|
report.perProduct.map((r) => ({
|
|
28511
28589
|
id: r.productId,
|
|
28512
28590
|
name: r.productName,
|
|
28513
|
-
mrr: money(r.
|
|
28514
|
-
subs: r.
|
|
28591
|
+
mrr: money(r.mrrCents),
|
|
28592
|
+
subs: r.activeSubscriptions
|
|
28515
28593
|
})),
|
|
28516
28594
|
["id", "name", "mrr", "subs"]
|
|
28517
28595
|
);
|
|
@@ -28526,13 +28604,13 @@ function registerStatsCommands(program2) {
|
|
|
28526
28604
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
28527
28605
|
const report = await client.statsMrrHistory(range);
|
|
28528
28606
|
emit(flags, report, () => {
|
|
28529
|
-
process.stdout.write(`MRR history (currency ${report.
|
|
28607
|
+
process.stdout.write(`MRR history (currency ${report.currency})
|
|
28530
28608
|
`);
|
|
28531
28609
|
printTable(
|
|
28532
28610
|
report.points.map((p) => ({
|
|
28533
28611
|
date: p.date,
|
|
28534
28612
|
mrr: money(p.mrrCents),
|
|
28535
|
-
subs: p.
|
|
28613
|
+
subs: p.activeSubscriptions,
|
|
28536
28614
|
new: money(p.newMrrCents),
|
|
28537
28615
|
churned: money(p.churnedMrrCents),
|
|
28538
28616
|
churnRate: `${(p.churnRate * 100).toFixed(1)}%`
|
|
@@ -28612,11 +28690,23 @@ function registerStatsCommands(program2) {
|
|
|
28612
28690
|
checkout: s.checkoutId,
|
|
28613
28691
|
stage: s.furthestStage,
|
|
28614
28692
|
status: s.status,
|
|
28615
|
-
|
|
28616
|
-
source: s.source ?? "",
|
|
28693
|
+
converted: s.converted ? "yes" : "",
|
|
28694
|
+
source: s.source.utmSource ?? s.source.referrer ?? "",
|
|
28695
|
+
country: s.country ?? "",
|
|
28696
|
+
declined: s.paymentFailure?.reason ?? "",
|
|
28617
28697
|
startedAt: s.startedAt
|
|
28618
28698
|
})),
|
|
28619
|
-
[
|
|
28699
|
+
[
|
|
28700
|
+
"id",
|
|
28701
|
+
"checkout",
|
|
28702
|
+
"stage",
|
|
28703
|
+
"status",
|
|
28704
|
+
"converted",
|
|
28705
|
+
"source",
|
|
28706
|
+
"country",
|
|
28707
|
+
"declined",
|
|
28708
|
+
"startedAt"
|
|
28709
|
+
]
|
|
28620
28710
|
);
|
|
28621
28711
|
});
|
|
28622
28712
|
});
|
|
@@ -28625,7 +28715,7 @@ function registerStatsCommands(program2) {
|
|
|
28625
28715
|
const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
|
|
28626
28716
|
const report = await client.statsProduct(productId, options.currency);
|
|
28627
28717
|
emit(flags, report, () => {
|
|
28628
|
-
const c = report.
|
|
28718
|
+
const c = report.currency;
|
|
28629
28719
|
process.stdout.write(
|
|
28630
28720
|
`Product ${report.productId} (${report.window.startDate} \u2192 ${report.window.endDate}), currency ${c}
|
|
28631
28721
|
Revenue: ${money(report.revenue.totalCents)} ${c} (prev ${money(report.revenue.previousTotalCents)} ${c})
|
|
@@ -30286,8 +30376,8 @@ function applyShortHelp(program2) {
|
|
|
30286
30376
|
|
|
30287
30377
|
// src/program.ts
|
|
30288
30378
|
function resolveCliVersion() {
|
|
30289
|
-
if ("0.
|
|
30290
|
-
return "0.
|
|
30379
|
+
if ("0.9.0".length > 0) {
|
|
30380
|
+
return "0.9.0";
|
|
30291
30381
|
}
|
|
30292
30382
|
try {
|
|
30293
30383
|
const raw = readFileSync28(new URL("../package.json", import.meta.url), "utf8");
|