adapty 0.11.0 → 0.13.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.
Files changed (122) hide show
  1. package/README.md +45 -0
  2. package/dist/cli/base/adapty/flags.d.ts +65 -0
  3. package/dist/cli/base/adapty/flags.js +95 -0
  4. package/dist/cli/base/adapty/index.d.ts +1 -0
  5. package/dist/cli/base/adapty/index.js +1 -0
  6. package/dist/cli/base/attribution/flags.d.ts +3 -3
  7. package/dist/cli/base/attribution/flags.js +5 -20
  8. package/dist/cli/commands/analytics/catalog.d.ts +10 -0
  9. package/dist/cli/commands/analytics/catalog.js +52 -0
  10. package/dist/cli/commands/analytics/chart.d.ts +21 -0
  11. package/dist/cli/commands/analytics/chart.js +47 -0
  12. package/dist/cli/commands/analytics/cohort.d.ts +19 -0
  13. package/dist/cli/commands/analytics/cohort.js +40 -0
  14. package/dist/cli/commands/analytics/conversion.d.ts +20 -0
  15. package/dist/cli/commands/analytics/conversion.js +44 -0
  16. package/dist/cli/commands/analytics/funnel.d.ts +16 -0
  17. package/dist/cli/commands/analytics/funnel.js +32 -0
  18. package/dist/cli/commands/analytics/ltv.d.ts +19 -0
  19. package/dist/cli/commands/analytics/ltv.js +40 -0
  20. package/dist/cli/commands/analytics/retention.d.ts +17 -0
  21. package/dist/cli/commands/analytics/retention.js +41 -0
  22. package/dist/cli/commands/analytics/values.d.ts +11 -0
  23. package/dist/cli/commands/analytics/values.js +33 -0
  24. package/dist/cli/commands/attribution/report/command.js +6 -11
  25. package/dist/cli/commands/product-offers/create.d.ts +15 -0
  26. package/dist/cli/commands/product-offers/create.js +41 -0
  27. package/dist/cli/commands/product-offers/get.d.ts +13 -0
  28. package/dist/cli/commands/product-offers/get.js +16 -0
  29. package/dist/cli/commands/product-offers/list.d.ts +11 -0
  30. package/dist/cli/commands/product-offers/list.js +15 -0
  31. package/dist/cli/commands/product-offers/update.d.ts +20 -0
  32. package/dist/cli/commands/product-offers/update.js +41 -0
  33. package/dist/cli/errors.js +8 -1
  34. package/dist/cli/input/app.js +1 -11
  35. package/dist/cli/input/filter.d.ts +11 -0
  36. package/dist/cli/input/filter.js +16 -0
  37. package/dist/cli/input/offer.d.ts +3 -0
  38. package/dist/cli/input/offer.js +9 -0
  39. package/dist/cli/input/period.d.ts +10 -0
  40. package/dist/cli/input/period.js +23 -0
  41. package/dist/cli/input/product.d.ts +3 -0
  42. package/dist/cli/input/product.js +9 -0
  43. package/dist/cli/input/uuid.d.ts +6 -0
  44. package/dist/cli/input/uuid.js +11 -0
  45. package/dist/cli/views/analytics/csv.d.ts +3 -0
  46. package/dist/cli/views/analytics/csv.js +16 -0
  47. package/dist/cli/views/analytics/human.d.ts +41 -0
  48. package/dist/cli/views/analytics/human.js +134 -0
  49. package/dist/cli/views/analytics/segments.d.ts +68 -0
  50. package/dist/cli/views/analytics/segments.js +189 -0
  51. package/dist/commands/analytics/catalog.d.ts +1 -0
  52. package/dist/commands/analytics/catalog.js +2 -0
  53. package/dist/commands/analytics/chart.d.ts +1 -0
  54. package/dist/commands/analytics/chart.js +2 -0
  55. package/dist/commands/analytics/cohort.d.ts +1 -0
  56. package/dist/commands/analytics/cohort.js +2 -0
  57. package/dist/commands/analytics/conversion.d.ts +1 -0
  58. package/dist/commands/analytics/conversion.js +2 -0
  59. package/dist/commands/analytics/funnel.d.ts +1 -0
  60. package/dist/commands/analytics/funnel.js +2 -0
  61. package/dist/commands/analytics/ltv.d.ts +1 -0
  62. package/dist/commands/analytics/ltv.js +2 -0
  63. package/dist/commands/analytics/retention.d.ts +1 -0
  64. package/dist/commands/analytics/retention.js +2 -0
  65. package/dist/commands/analytics/values.d.ts +1 -0
  66. package/dist/commands/analytics/values.js +2 -0
  67. package/dist/commands/product-offers/create.d.ts +1 -0
  68. package/dist/commands/product-offers/create.js +2 -0
  69. package/dist/commands/product-offers/get.d.ts +1 -0
  70. package/dist/commands/product-offers/get.js +2 -0
  71. package/dist/commands/product-offers/list.d.ts +1 -0
  72. package/dist/commands/product-offers/list.js +2 -0
  73. package/dist/commands/product-offers/update.d.ts +1 -0
  74. package/dist/commands/product-offers/update.js +2 -0
  75. package/dist/lib/api-schemas.d.ts +1 -0
  76. package/dist/sdk/adapty/analytics/catalog.d.ts +51 -0
  77. package/dist/sdk/adapty/analytics/catalog.js +5 -0
  78. package/dist/sdk/adapty/analytics/chart.d.ts +41 -0
  79. package/dist/sdk/adapty/analytics/chart.js +6 -0
  80. package/dist/sdk/adapty/analytics/cohort.d.ts +79 -0
  81. package/dist/sdk/adapty/analytics/cohort.js +4 -0
  82. package/dist/sdk/adapty/analytics/conversion.d.ts +55 -0
  83. package/dist/sdk/adapty/analytics/conversion.js +7 -0
  84. package/dist/sdk/adapty/analytics/funnel.d.ts +41 -0
  85. package/dist/sdk/adapty/analytics/funnel.js +3 -0
  86. package/dist/sdk/adapty/analytics/index.d.ts +24 -0
  87. package/dist/sdk/adapty/analytics/index.js +13 -0
  88. package/dist/sdk/adapty/analytics/ltv.d.ts +27 -0
  89. package/dist/sdk/adapty/analytics/ltv.js +4 -0
  90. package/dist/sdk/adapty/analytics/model.d.ts +53 -0
  91. package/dist/sdk/adapty/analytics/model.js +12 -0
  92. package/dist/sdk/adapty/analytics/query.d.ts +48 -0
  93. package/dist/sdk/adapty/analytics/query.js +52 -0
  94. package/dist/sdk/adapty/analytics/renewal-periods.d.ts +23 -0
  95. package/dist/sdk/adapty/analytics/renewal-periods.js +27 -0
  96. package/dist/sdk/adapty/analytics/resource.d.ts +29 -0
  97. package/dist/sdk/adapty/analytics/resource.js +72 -0
  98. package/dist/sdk/adapty/analytics/retention.d.ts +32 -0
  99. package/dist/sdk/adapty/analytics/retention.js +6 -0
  100. package/dist/sdk/adapty/analytics/values.d.ts +20 -0
  101. package/dist/sdk/adapty/analytics/values.js +1 -0
  102. package/dist/sdk/adapty/index.d.ts +8 -0
  103. package/dist/sdk/adapty/index.js +6 -0
  104. package/dist/sdk/adapty/product-offers/create.d.ts +5 -0
  105. package/dist/sdk/adapty/product-offers/create.js +12 -0
  106. package/dist/sdk/adapty/product-offers/index.d.ts +9 -0
  107. package/dist/sdk/adapty/product-offers/index.js +5 -0
  108. package/dist/sdk/adapty/product-offers/model.d.ts +48 -0
  109. package/dist/sdk/adapty/product-offers/model.js +30 -0
  110. package/dist/sdk/adapty/product-offers/resource.d.ts +21 -0
  111. package/dist/sdk/adapty/product-offers/resource.js +28 -0
  112. package/dist/sdk/adapty/product-offers/update.d.ts +19 -0
  113. package/dist/sdk/adapty/product-offers/update.js +42 -0
  114. package/dist/sdk/attribution/index.d.ts +1 -1
  115. package/dist/sdk/attribution/index.js +1 -1
  116. package/dist/sdk/attribution/report.d.ts +0 -2
  117. package/dist/sdk/attribution/report.js +2 -26
  118. package/dist/sdk/core/dates.d.ts +15 -0
  119. package/dist/sdk/core/dates.js +34 -0
  120. package/dist/sdk/core/errors.d.ts +3 -0
  121. package/oclif.manifest.json +1163 -128
  122. package/package.json +7 -1
@@ -0,0 +1,40 @@
1
+ import { validateLtv } from '../../../sdk/adapty/index.js';
2
+ import { assertValid } from '../../../sdk/core/validation.js';
3
+ import { AdaptyCommand, csvFlag, filterFlags, groupByFlag, groupingParams, periodFlags, queryParams, renewalPeriodFlags, revenueBasisFlag, } from '../../base/adapty/index.js';
4
+ import { appIdFlag } from '../../input/app.js';
5
+ import { analyticsView, ltvTable } from '../../views/analytics/human.js';
6
+ import { csvNotice } from '../../views/analytics/segments.js';
7
+ export default class AnalyticsLtv extends AdaptyCommand {
8
+ static description = 'Read the LTV of an app: the revenue per paying user after each renewal (or day), '
9
+ + 'per group, the total first (no compared period)';
10
+ static examples = [
11
+ '<%= config.bin %> analytics ltv --app APP_UUID --date-from 2026-07-01 --date-to 2026-09-30',
12
+ '<%= config.bin %> analytics ltv --app APP_UUID --date-from 2026-07-01 --date-to 2026-09-30 --revenue-basis net --period-type days --renewal-days 7,30,90 --csv',
13
+ ];
14
+ static flags = {
15
+ ...appIdFlag,
16
+ ...periodFlags,
17
+ ...groupByFlag,
18
+ ...filterFlags,
19
+ ...revenueBasisFlag,
20
+ ...renewalPeriodFlags,
21
+ ...csvFlag,
22
+ };
23
+ async run() {
24
+ const { flags } = await this.parse(AnalyticsLtv);
25
+ const input = {
26
+ ...queryParams(flags),
27
+ ...groupingParams(flags),
28
+ periodType: flags['period-type'],
29
+ renewalDays: flags['renewal-days'],
30
+ revenueBasis: flags['revenue-basis'],
31
+ };
32
+ // Before this.adapty: bad input is exit 2 even without a token
33
+ assertValid(validateLtv(input));
34
+ const answer = await this.adapty.analytics.ltv(input);
35
+ this.render(answer, analyticsView(flags.csv, ltvTable));
36
+ // Beside a CSV of a period without data, its summary; an empty text otherwise
37
+ process.stderr.write(csvNotice(answer, flags.csv));
38
+ return answer;
39
+ }
40
+ }
@@ -0,0 +1,17 @@
1
+ import { AdaptyCommand } from '../../base/adapty/index.js';
2
+ import type { RetentionResponse } from '../../../sdk/adapty/index.js';
3
+ export default class AnalyticsRetention extends AdaptyCommand {
4
+ static description: string;
5
+ static examples: string[];
6
+ static flags: {
7
+ csv: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ 'use-trial': import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
+ filter: import("@oclif/core/interfaces").OptionFlag<import("../../../sdk/adapty/index.js").AnalyticsFilter[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ timezone: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ 'group-by': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ 'date-from': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
+ 'date-to': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
14
+ app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
15
+ };
16
+ run(): Promise<RetentionResponse>;
17
+ }
@@ -0,0 +1,41 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { validateRetention } from '../../../sdk/adapty/index.js';
3
+ import { assertValid } from '../../../sdk/core/validation.js';
4
+ import { AdaptyCommand, csvFlag, filterFlags, groupByFlag, groupingParams, periodFlags, queryParams, } from '../../base/adapty/index.js';
5
+ import { appIdFlag } from '../../input/app.js';
6
+ import { analyticsView, retentionTable } from '../../views/analytics/human.js';
7
+ import { csvNotice } from '../../views/analytics/segments.js';
8
+ export default class AnalyticsRetention extends AdaptyCommand {
9
+ static description = 'Read the retention of an app: the subscribers left in each period, per group, '
10
+ + 'the total first (no compared period)';
11
+ static examples = [
12
+ '<%= config.bin %> analytics retention --app APP_UUID --date-from 2026-07-01 --date-to 2026-09-30',
13
+ '<%= config.bin %> analytics retention --app APP_UUID --date-from 2026-07-01 --date-to 2026-09-30 --group-by country --use-trial --csv',
14
+ ];
15
+ static flags = {
16
+ ...appIdFlag,
17
+ ...periodFlags,
18
+ ...groupByFlag,
19
+ ...filterFlags,
20
+ 'use-trial': Flags.boolean({
21
+ description: 'Count from the trial, not from the first paid period',
22
+ }),
23
+ ...csvFlag,
24
+ };
25
+ async run() {
26
+ const { flags } = await this.parse(AnalyticsRetention);
27
+ const input = {
28
+ ...queryParams(flags),
29
+ ...groupingParams(flags),
30
+ // Sent only when given: the backend default (false) applies otherwise, and meta.query says so
31
+ useTrial: flags['use-trial'] ? true : undefined,
32
+ };
33
+ // Before this.adapty: bad input is exit 2 even without a token
34
+ assertValid(validateRetention(input));
35
+ const answer = await this.adapty.analytics.retention(input);
36
+ this.render(answer, analyticsView(flags.csv, retentionTable));
37
+ // Beside a CSV of a period without data, its summary; an empty text otherwise
38
+ process.stderr.write(csvNotice(answer, flags.csv));
39
+ return answer;
40
+ }
41
+ }
@@ -0,0 +1,11 @@
1
+ import { AdaptyCommand } from '../../base/adapty/index.js';
2
+ import type { FilterValuesResponse } from '../../../sdk/adapty/index.js';
3
+ export default class AnalyticsValues extends AdaptyCommand {
4
+ static description: string;
5
+ static examples: string[];
6
+ static flags: {
7
+ dimension: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
8
+ app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
9
+ };
10
+ run(): Promise<FilterValuesResponse>;
11
+ }
@@ -0,0 +1,33 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { validateFilterValues } from '../../../sdk/adapty/index.js';
3
+ import { assertValid } from '../../../sdk/core/validation.js';
4
+ import { AdaptyCommand } from '../../base/adapty/index.js';
5
+ import { appIdFlag } from '../../input/app.js';
6
+ /** The value first, as `--filter <dimension>=<value>` takes it; an empty value is shown as `""`. */
7
+ const describeValue = (item) => `${item.value === '' ? '""' : item.value}: ${item.label}${item.group === null ? '' : ` (${item.group})`}`;
8
+ const renderValues = ({ data, meta }) => (data.length === 0
9
+ ? `No ${meta.dimension} values for this app.`
10
+ : data.map(item => describeValue(item)).join('\n'));
11
+ export default class AnalyticsValues extends AdaptyCommand {
12
+ static description = 'List the values one analytics filter takes for an app: what --filter accepts';
13
+ static examples = [
14
+ '<%= config.bin %> analytics values --app APP_UUID --dimension country',
15
+ '<%= config.bin %> analytics values --app APP_UUID --dimension store_product_id --json',
16
+ ];
17
+ static flags = {
18
+ ...appIdFlag,
19
+ dimension: Flags.string({
20
+ description: 'A filter dimension, as `adapty analytics catalog` lists it under Dimensions',
21
+ required: true,
22
+ }),
23
+ };
24
+ async run() {
25
+ const { flags } = await this.parse(AnalyticsValues);
26
+ const input = { appId: flags.app, dimension: flags.dimension };
27
+ // Before this.adapty: bad input is exit 2 even without a token
28
+ assertValid(validateFilterValues(input));
29
+ const values = await this.adapty.analytics.values(input);
30
+ this.render(values, renderValues);
31
+ return values;
32
+ }
33
+ }
@@ -4,20 +4,15 @@ import { assertValid } from '../../../../sdk/core/validation.js';
4
4
  import { AttributionCommand, periodFlags, periodParams, revenueBasisFlag } from '../../../base/attribution/index.js';
5
5
  import { usageError } from '../../../errors.js';
6
6
  import { appIdFlag } from '../../../input/app.js';
7
+ import { splitFilter } from '../../../input/filter.js';
7
8
  import { renderReport } from './lib/render.js';
8
- /** A comma not preceded by a backslash: `\,` keeps a comma inside a value, as oclif's own delimiter does. */
9
- const VALUE_SEPARATOR = /(?<!\\),/;
10
- /** `dimension=value[,value]`: one value filters by equality, several by any of them. */
9
+ /** `dimension=value[,value]`: one value filters by equality, several by any; values trimmed, empties dropped. */
11
10
  const parseFilter = (input) => {
12
- const separator = input.indexOf('=');
13
- const dimension = separator === -1 ? '' : input.slice(0, separator).trim();
14
- const values = separator === -1
15
- ? []
16
- : input.slice(separator + 1).split(VALUE_SEPARATOR).map(value => value.replaceAll('\\,', ',').trim())
17
- .filter(value => value !== '');
18
- return dimension === '' || values.length === 0
11
+ const filter = splitFilter(input);
12
+ const values = filter?.values.map(value => value.trim()).filter(value => value !== '') ?? [];
13
+ return filter === undefined || values.length === 0
19
14
  ? Promise.reject(usageError(`Expected dimension=value[,value], got "${input}".`))
20
- : Promise.resolve({ dimension, values });
15
+ : Promise.resolve({ dimension: filter.dimension, values });
21
16
  };
22
17
  const isDirection = (value) => sortDirections.includes(value);
23
18
  /** `field[:asc|desc]`, ascending when the direction is left out. */
@@ -0,0 +1,15 @@
1
+ import { AdaptyCommand } from '../../base/adapty/index.js';
2
+ import type { ProductOffer } from '../../../sdk/adapty/index.js';
3
+ export default class ProductOffersCreate extends AdaptyCommand {
4
+ static description: string;
5
+ static examples: string[];
6
+ static flags: {
7
+ title: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
8
+ type: import("@oclif/core/interfaces").OptionFlag<"promotional" | "win_back", import("@oclif/core/interfaces").CustomOptions>;
9
+ 'ios-offer-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ 'android-offer-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ product: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
12
+ app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
+ };
14
+ run(): Promise<ProductOffer>;
15
+ }
@@ -0,0 +1,41 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { offerTypes } from '../../../sdk/adapty/index.js';
3
+ import { validateCreateProductOffer } from '../../../sdk/adapty/product-offers/index.js';
4
+ import { assertValid } from '../../../sdk/core/validation.js';
5
+ import { AdaptyCommand } from '../../base/adapty/index.js';
6
+ import { appIdFlag } from '../../input/app.js';
7
+ import { productFlag } from '../../input/product.js';
8
+ import { renderRecord } from '../../views/record.js';
9
+ export default class ProductOffersCreate extends AdaptyCommand {
10
+ static description = 'Create an offer for a product';
11
+ static examples = [
12
+ '<%= config.bin %> product-offers create --app 550e8400-... --product 660e8400-... --title "Spring" --type promotional --ios-offer-id spring_ios --android-offer-id spring-android',
13
+ '<%= config.bin %> product-offers create --app 550e8400-... --product 660e8400-... --title "Come back" --type win_back --ios-offer-id winback_ios',
14
+ ];
15
+ static flags = {
16
+ ...appIdFlag,
17
+ ...productFlag,
18
+ 'title': Flags.string({ description: 'Offer title', required: true }),
19
+ 'type': Flags.option({
20
+ description: 'Offer type (win_back is App Store only)',
21
+ options: offerTypes,
22
+ required: true,
23
+ })(),
24
+ 'ios-offer-id': Flags.string({ description: 'App Store offer ID' }),
25
+ 'android-offer-id': Flags.string({ description: 'Google Play offer ID' }),
26
+ };
27
+ async run() {
28
+ const { flags } = await this.parse(ProductOffersCreate);
29
+ const input = {
30
+ androidOfferId: flags['android-offer-id'],
31
+ iosOfferId: flags['ios-offer-id'],
32
+ offerType: flags.type,
33
+ title: flags.title,
34
+ };
35
+ assertValid(validateCreateProductOffer(input));
36
+ const offer = await this.adapty.productOffers.create(flags.app, flags.product, input);
37
+ this.log('Offer created!');
38
+ this.render(offer, renderRecord);
39
+ return offer;
40
+ }
41
+ }
@@ -0,0 +1,13 @@
1
+ import { AdaptyCommand } from '../../base/adapty/index.js';
2
+ import type { ProductOffer } from '../../../sdk/adapty/index.js';
3
+ export default class ProductOffersGet extends AdaptyCommand {
4
+ static description: string;
5
+ static examples: string[];
6
+ static args: {
7
+ offer_id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
8
+ };
9
+ static flags: {
10
+ app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ };
12
+ run(): Promise<ProductOffer>;
13
+ }
@@ -0,0 +1,16 @@
1
+ import { AdaptyCommand } from '../../base/adapty/index.js';
2
+ import { appIdFlag } from '../../input/app.js';
3
+ import { offerIdArg } from '../../input/offer.js';
4
+ import { renderRecord } from '../../views/record.js';
5
+ export default class ProductOffersGet extends AdaptyCommand {
6
+ static description = 'Get a product offer';
7
+ static examples = ['<%= config.bin %> product-offers get 770e8400-... --app 550e8400-...'];
8
+ static args = { ...offerIdArg };
9
+ static flags = { ...appIdFlag };
10
+ async run() {
11
+ const { args, flags } = await this.parse(ProductOffersGet);
12
+ const offer = await this.adapty.productOffers.get(flags.app, args.offer_id);
13
+ this.render(offer, renderRecord);
14
+ return offer;
15
+ }
16
+ }
@@ -0,0 +1,11 @@
1
+ import { AdaptyCommand } from '../../base/adapty/index.js';
2
+ import type { Paginated, ProductOffer } from '../../../sdk/adapty/index.js';
3
+ export default class ProductOffersList extends AdaptyCommand {
4
+ static description: string;
5
+ static examples: string[];
6
+ static flags: {
7
+ product: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
8
+ app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
9
+ };
10
+ run(): Promise<Paginated<ProductOffer>>;
11
+ }
@@ -0,0 +1,15 @@
1
+ import { AdaptyCommand } from '../../base/adapty/index.js';
2
+ import { appIdFlag } from '../../input/app.js';
3
+ import { productFlag } from '../../input/product.js';
4
+ import { renderPage } from '../../views/list.js';
5
+ export default class ProductOffersList extends AdaptyCommand {
6
+ static description = 'List the offers of a product (all on one page)';
7
+ static examples = ['<%= config.bin %> product-offers list --app 550e8400-... --product 660e8400-...'];
8
+ static flags = { ...appIdFlag, ...productFlag };
9
+ async run() {
10
+ const { flags } = await this.parse(ProductOffersList);
11
+ const page = await this.adapty.productOffers.list(flags.app, flags.product);
12
+ this.render(page, renderPage);
13
+ return page;
14
+ }
15
+ }
@@ -0,0 +1,20 @@
1
+ import { AdaptyCommand } from '../../base/adapty/index.js';
2
+ import type { ProductOffer } from '../../../sdk/adapty/index.js';
3
+ export default class ProductOffersUpdate extends AdaptyCommand {
4
+ static summary: string;
5
+ static description: string;
6
+ static examples: string[];
7
+ static args: {
8
+ offer_id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
9
+ };
10
+ static flags: {
11
+ title: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ type: import("@oclif/core/interfaces").OptionFlag<"promotional" | "win_back" | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ 'ios-offer-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ 'android-offer-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ 'clear-ios-offer-id': import("@oclif/core/interfaces").BooleanFlag<boolean>;
16
+ 'clear-android-offer-id': import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
+ app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
18
+ };
19
+ run(): Promise<ProductOffer>;
20
+ }
@@ -0,0 +1,41 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { offerTypes } from '../../../sdk/adapty/index.js';
3
+ import { validateUpdateProductOffer } from '../../../sdk/adapty/product-offers/index.js';
4
+ import { assertValid } from '../../../sdk/core/validation.js';
5
+ import { AdaptyCommand } from '../../base/adapty/index.js';
6
+ import { appIdFlag } from '../../input/app.js';
7
+ import { offerIdArg } from '../../input/offer.js';
8
+ import { renderRecord } from '../../views/record.js';
9
+ export default class ProductOffersUpdate extends AdaptyCommand {
10
+ static summary = 'Update a product offer';
11
+ static description = 'Reads the offer and writes it back whole: fields you leave out keep their stored value, a change made in the '
12
+ + 'dashboard in between is overwritten, and at least one store offer id must remain.';
13
+ static examples = [
14
+ '<%= config.bin %> product-offers update 770e8400-... --app 550e8400-... --title "Spring 2"',
15
+ '<%= config.bin %> product-offers update 770e8400-... --app 550e8400-... --clear-android-offer-id',
16
+ ];
17
+ static args = { ...offerIdArg };
18
+ static flags = {
19
+ ...appIdFlag,
20
+ 'title': Flags.string({ description: 'Offer title' }),
21
+ 'type': Flags.option({ description: 'Offer type (win_back is App Store only: clear the Google Play side too)', options: offerTypes })(),
22
+ 'ios-offer-id': Flags.string({ description: 'App Store offer ID', exclusive: ['clear-ios-offer-id'] }),
23
+ 'android-offer-id': Flags.string({ description: 'Google Play offer ID', exclusive: ['clear-android-offer-id'] }),
24
+ 'clear-ios-offer-id': Flags.boolean({ description: 'Remove the App Store side of the offer' }),
25
+ 'clear-android-offer-id': Flags.boolean({ description: 'Remove the Google Play side of the offer' }),
26
+ };
27
+ async run() {
28
+ const { args, flags } = await this.parse(ProductOffersUpdate);
29
+ const input = {
30
+ androidOfferId: flags['clear-android-offer-id'] ? null : flags['android-offer-id'],
31
+ iosOfferId: flags['clear-ios-offer-id'] ? null : flags['ios-offer-id'],
32
+ offerType: flags.type,
33
+ title: flags.title,
34
+ };
35
+ assertValid(validateUpdateProductOffer(input));
36
+ const offer = await this.adapty.productOffers.update(flags.app, args.offer_id, input);
37
+ this.log('Offer updated!');
38
+ this.render(offer, renderRecord);
39
+ return offer;
40
+ }
41
+ }
@@ -88,7 +88,14 @@ export const describeCleanupFailures = (files, results) => {
88
88
  };
89
89
  /** Issue.path is a camelCase sdk field; the user typed a kebab-case flag. */
90
90
  const flagName = (path) => `--${path.replace(/[A-Z]/g, char => `-${char.toLowerCase()}`)}`;
91
- const describeIssue = (issue) => (issue.path === undefined ? issue.message : `${flagName(issue.path)}: ${issue.message}`);
91
+ /**
92
+ * A rule names another input field as `{fieldName}` and lists it in `fields`: the user reads the flag they would
93
+ * type. Only the listed names are written, so other braces in a message stay as they are.
94
+ */
95
+ const describeIssue = (issue) => {
96
+ const message = (issue.fields ?? []).reduce((text, field) => text.replaceAll(`{${field}}`, flagName(field)), issue.message);
97
+ return issue.path === undefined ? message : `${flagName(issue.path)}: ${message}`;
98
+ };
92
99
  /**
93
100
  * The one place where an sdk error gets a human text and an exit code. No default in the switch:
94
101
  * a new kind fails to compile until it is mapped here.
@@ -1,17 +1,7 @@
1
1
  import { Args, Flags } from '@oclif/core';
2
- import { usageError } from '../errors.js';
3
- const UUID_PATTERN = /^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/i;
2
+ import { uuidParser } from './uuid.js';
4
3
  /** The published wording of both the `app_id` arg and the `--app` flag. */
5
4
  const APP_ID_HINT = 'Invalid app ID format. Run `adapty apps list` to find your app ID.';
6
- const isUuid = (value) => UUID_PATTERN.test(value);
7
- /**
8
- * Checking the shape of a value is the parser's job, so a run() never starts with one. oclif turns
9
- * a *flag* parser's failure into exit 2 itself but passes an *arg* parser's error through, hence
10
- * the explicit code. The hint is per resource: `adapty apps list` only helps for an app id.
11
- */
12
- const uuidParser = (hint) => (input) => (isUuid(input)
13
- ? Promise.resolve(input)
14
- : Promise.reject(usageError(hint)));
15
5
  /** Spread into a command: `static args = { ...appIdArg }`. Texts kept as published. */
16
6
  export const appIdArg = {
17
7
  app_id: Args.string({
@@ -0,0 +1,11 @@
1
+ /** One `--filter`: a dimension and the values it may take (one value is equality, several any of them). */
2
+ export type DimensionFilter = {
3
+ dimension: string;
4
+ values: string[];
5
+ };
6
+ /**
7
+ * `dimension=value[,value]` split as written: the dimension before the first `=`, trimmed; every value after it
8
+ * as typed, `\,` turned into a comma. Undefined without `=` or without a dimension. What a product does with
9
+ * spaces and empty values is its own rule, so its flag parser decides that.
10
+ */
11
+ export declare const splitFilter: (input: string) => DimensionFilter | undefined;
@@ -0,0 +1,16 @@
1
+ /** A comma not preceded by a backslash: `\,` keeps a comma inside a value, as oclif's own delimiter does. */
2
+ const VALUE_SEPARATOR = /(?<!\\),/;
3
+ /**
4
+ * `dimension=value[,value]` split as written: the dimension before the first `=`, trimmed; every value after it
5
+ * as typed, `\,` turned into a comma. Undefined without `=` or without a dimension. What a product does with
6
+ * spaces and empty values is its own rule, so its flag parser decides that.
7
+ */
8
+ export const splitFilter = (input) => {
9
+ const separator = input.indexOf('=');
10
+ const dimension = separator === -1 ? '' : input.slice(0, separator).trim();
11
+ if (dimension === '') {
12
+ return undefined;
13
+ }
14
+ const values = input.slice(separator + 1).split(VALUE_SEPARATOR).map(value => value.replaceAll('\\,', ','));
15
+ return { dimension, values };
16
+ };
@@ -0,0 +1,3 @@
1
+ export declare const offerIdArg: {
2
+ offer_id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
3
+ };
@@ -0,0 +1,9 @@
1
+ import { Args } from '@oclif/core';
2
+ import { uuidParser } from './uuid.js';
3
+ export const offerIdArg = {
4
+ offer_id: Args.string({
5
+ description: 'Offer ID (UUID)',
6
+ parse: uuidParser('Invalid offer ID format.'),
7
+ required: true,
8
+ }),
9
+ };
@@ -0,0 +1,10 @@
1
+ /** The shape only: whether the day exists and the order of the two are sdk rules. */
2
+ export declare const dateParser: (input: string) => Promise<string>;
3
+ /**
4
+ * An inclusive day range, both ends required. `zone` completes the help text: which time zone the days are in,
5
+ * the one thing the products say differently.
6
+ */
7
+ export declare const dayRangeFlags: (zone: string) => {
8
+ 'date-from': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
9
+ 'date-to': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ };
@@ -0,0 +1,23 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { ISO_DATE } from '../../sdk/core/dates.js';
3
+ import { usageError } from '../errors.js';
4
+ /** The shape only: whether the day exists and the order of the two are sdk rules. */
5
+ export const dateParser = (input) => (ISO_DATE.test(input)
6
+ ? Promise.resolve(input)
7
+ : Promise.reject(usageError('Dates must be written as YYYY-MM-DD.')));
8
+ /**
9
+ * An inclusive day range, both ends required. `zone` completes the help text: which time zone the days are in,
10
+ * the one thing the products say differently.
11
+ */
12
+ export const dayRangeFlags = (zone) => ({
13
+ 'date-from': Flags.string({
14
+ description: `First day of the period, inclusive (YYYY-MM-DD, ${zone})`,
15
+ parse: dateParser,
16
+ required: true,
17
+ }),
18
+ 'date-to': Flags.string({
19
+ description: `Last day of the period, inclusive (YYYY-MM-DD, ${zone})`,
20
+ parse: dateParser,
21
+ required: true,
22
+ }),
23
+ });
@@ -0,0 +1,3 @@
1
+ export declare const productFlag: {
2
+ product: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
3
+ };
@@ -0,0 +1,9 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { uuidParser } from './uuid.js';
3
+ export const productFlag = {
4
+ product: Flags.string({
5
+ description: 'Product ID (UUID)',
6
+ parse: uuidParser('Invalid product ID format.'),
7
+ required: true,
8
+ }),
9
+ };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Checking the shape of a value is the parser's job, so a run() never starts with one. oclif turns
3
+ * a *flag* parser's failure into exit 2 itself but passes an *arg* parser's error through, hence
4
+ * the explicit code. The hint is per resource: `adapty apps list` only helps for an app id.
5
+ */
6
+ export declare const uuidParser: (hint: string) => (input: string) => Promise<string>;
@@ -0,0 +1,11 @@
1
+ import { usageError } from '../errors.js';
2
+ const UUID_PATTERN = /^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/i;
3
+ const isUuid = (value) => UUID_PATTERN.test(value);
4
+ /**
5
+ * Checking the shape of a value is the parser's job, so a run() never starts with one. oclif turns
6
+ * a *flag* parser's failure into exit 2 itself but passes an *arg* parser's error through, hence
7
+ * the explicit code. The hint is per resource: `adapty apps list` only helps for an app id.
8
+ */
9
+ export const uuidParser = (hint) => (input) => (isUuid(input)
10
+ ? Promise.resolve(input)
11
+ : Promise.reject(usageError(hint)));
@@ -0,0 +1,3 @@
1
+ /** What a CSV cell can hold: the scalar values of an API answer. */
2
+ export type Cell = boolean | null | number | string | undefined;
3
+ export declare const csvLine: (cells: readonly Cell[]) => string;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * RFC 4180: a field with a comma, a double quote or a line break goes in double quotes, and a quote inside is
3
+ * doubled. A missing value (null) is an empty cell, so it never reads as 0; an empty text (a group key, for
4
+ * example) is `""`, so it never reads as a missing one.
5
+ */
6
+ const csvCell = (value) => {
7
+ if (value === null || value === undefined) {
8
+ return '';
9
+ }
10
+ if (value === '') {
11
+ return '""';
12
+ }
13
+ const text = String(value);
14
+ return /[",\n\r]/.test(text) ? `"${text.replaceAll('"', '""')}"` : text;
15
+ };
16
+ export const csvLine = (cells) => cells.map(cell => csvCell(cell)).join(',');
@@ -0,0 +1,41 @@
1
+ import type { AnalyticsAnswer } from './segments.js';
2
+ /**
3
+ * The human view of the six analytics operations: a short table, one row per group (the total first), the main
4
+ * numbers of the group and one column per point (a date, a funnel step, a period). The resolved query gives the
5
+ * heading. Where an operation has more fields than one table holds, a line under it says that `--json` and
6
+ * `--csv` give every field.
7
+ */
8
+ type Query = Record<string, unknown>;
9
+ /** How a number reads: money with 2 decimals, any other number as the API gives it, null as `—`. */
10
+ type Format = (value: unknown) => string;
11
+ /** One number of a group: its header and the segment field it shows. */
12
+ type Column = {
13
+ field: string;
14
+ format: Format;
15
+ header: string;
16
+ };
17
+ export type HumanTable = {
18
+ /** The numbers of a group after its title, the main value first. */
19
+ columns: (query: Query) => Column[];
20
+ /** The fields the table leaves out; none when it shows every number of the answer. */
21
+ omitted?: string;
22
+ /** How the value of a point reads. */
23
+ points: (query: Query) => Format;
24
+ /** What the answer is, from the resolved query: the operation, then its options. */
25
+ title: (query: Query) => (string | undefined)[];
26
+ };
27
+ export declare const chartTable: HumanTable;
28
+ export declare const conversionTable: HumanTable;
29
+ export declare const funnelTable: HumanTable;
30
+ export declare const retentionTable: HumanTable;
31
+ export declare const ltvTable: HumanTable;
32
+ export declare const cohortTable: HumanTable;
33
+ /**
34
+ * The heading (what, by which grouping; the period, its time zone and filters), then the table of the asked
35
+ * period and, when there is one, of the compared period. A period without groups: `No data for this period.` and
36
+ * the summary of that period, every field, null as `—`.
37
+ */
38
+ export declare const humanView: (spec: HumanTable) => (answer: AnalyticsAnswer) => string;
39
+ /** The view of a data command: the CSV with `--csv`, else the human table (`--json` prints no view at all). */
40
+ export declare const analyticsView: (csv: boolean, spec: HumanTable) => (answer: AnalyticsAnswer) => string;
41
+ export {};