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.
- package/README.md +45 -0
- package/dist/cli/base/adapty/flags.d.ts +65 -0
- package/dist/cli/base/adapty/flags.js +95 -0
- package/dist/cli/base/adapty/index.d.ts +1 -0
- package/dist/cli/base/adapty/index.js +1 -0
- package/dist/cli/base/attribution/flags.d.ts +3 -3
- package/dist/cli/base/attribution/flags.js +5 -20
- package/dist/cli/commands/analytics/catalog.d.ts +10 -0
- package/dist/cli/commands/analytics/catalog.js +52 -0
- package/dist/cli/commands/analytics/chart.d.ts +21 -0
- package/dist/cli/commands/analytics/chart.js +47 -0
- package/dist/cli/commands/analytics/cohort.d.ts +19 -0
- package/dist/cli/commands/analytics/cohort.js +40 -0
- package/dist/cli/commands/analytics/conversion.d.ts +20 -0
- package/dist/cli/commands/analytics/conversion.js +44 -0
- package/dist/cli/commands/analytics/funnel.d.ts +16 -0
- package/dist/cli/commands/analytics/funnel.js +32 -0
- package/dist/cli/commands/analytics/ltv.d.ts +19 -0
- package/dist/cli/commands/analytics/ltv.js +40 -0
- package/dist/cli/commands/analytics/retention.d.ts +17 -0
- package/dist/cli/commands/analytics/retention.js +41 -0
- package/dist/cli/commands/analytics/values.d.ts +11 -0
- package/dist/cli/commands/analytics/values.js +33 -0
- package/dist/cli/commands/attribution/report/command.js +6 -11
- package/dist/cli/commands/product-offers/create.d.ts +15 -0
- package/dist/cli/commands/product-offers/create.js +41 -0
- package/dist/cli/commands/product-offers/get.d.ts +13 -0
- package/dist/cli/commands/product-offers/get.js +16 -0
- package/dist/cli/commands/product-offers/list.d.ts +11 -0
- package/dist/cli/commands/product-offers/list.js +15 -0
- package/dist/cli/commands/product-offers/update.d.ts +20 -0
- package/dist/cli/commands/product-offers/update.js +41 -0
- package/dist/cli/errors.js +8 -1
- package/dist/cli/input/app.js +1 -11
- package/dist/cli/input/filter.d.ts +11 -0
- package/dist/cli/input/filter.js +16 -0
- package/dist/cli/input/offer.d.ts +3 -0
- package/dist/cli/input/offer.js +9 -0
- package/dist/cli/input/period.d.ts +10 -0
- package/dist/cli/input/period.js +23 -0
- package/dist/cli/input/product.d.ts +3 -0
- package/dist/cli/input/product.js +9 -0
- package/dist/cli/input/uuid.d.ts +6 -0
- package/dist/cli/input/uuid.js +11 -0
- package/dist/cli/views/analytics/csv.d.ts +3 -0
- package/dist/cli/views/analytics/csv.js +16 -0
- package/dist/cli/views/analytics/human.d.ts +41 -0
- package/dist/cli/views/analytics/human.js +134 -0
- package/dist/cli/views/analytics/segments.d.ts +68 -0
- package/dist/cli/views/analytics/segments.js +189 -0
- package/dist/commands/analytics/catalog.d.ts +1 -0
- package/dist/commands/analytics/catalog.js +2 -0
- package/dist/commands/analytics/chart.d.ts +1 -0
- package/dist/commands/analytics/chart.js +2 -0
- package/dist/commands/analytics/cohort.d.ts +1 -0
- package/dist/commands/analytics/cohort.js +2 -0
- package/dist/commands/analytics/conversion.d.ts +1 -0
- package/dist/commands/analytics/conversion.js +2 -0
- package/dist/commands/analytics/funnel.d.ts +1 -0
- package/dist/commands/analytics/funnel.js +2 -0
- package/dist/commands/analytics/ltv.d.ts +1 -0
- package/dist/commands/analytics/ltv.js +2 -0
- package/dist/commands/analytics/retention.d.ts +1 -0
- package/dist/commands/analytics/retention.js +2 -0
- package/dist/commands/analytics/values.d.ts +1 -0
- package/dist/commands/analytics/values.js +2 -0
- package/dist/commands/product-offers/create.d.ts +1 -0
- package/dist/commands/product-offers/create.js +2 -0
- package/dist/commands/product-offers/get.d.ts +1 -0
- package/dist/commands/product-offers/get.js +2 -0
- package/dist/commands/product-offers/list.d.ts +1 -0
- package/dist/commands/product-offers/list.js +2 -0
- package/dist/commands/product-offers/update.d.ts +1 -0
- package/dist/commands/product-offers/update.js +2 -0
- package/dist/lib/api-schemas.d.ts +1 -0
- package/dist/sdk/adapty/analytics/catalog.d.ts +51 -0
- package/dist/sdk/adapty/analytics/catalog.js +5 -0
- package/dist/sdk/adapty/analytics/chart.d.ts +41 -0
- package/dist/sdk/adapty/analytics/chart.js +6 -0
- package/dist/sdk/adapty/analytics/cohort.d.ts +79 -0
- package/dist/sdk/adapty/analytics/cohort.js +4 -0
- package/dist/sdk/adapty/analytics/conversion.d.ts +55 -0
- package/dist/sdk/adapty/analytics/conversion.js +7 -0
- package/dist/sdk/adapty/analytics/funnel.d.ts +41 -0
- package/dist/sdk/adapty/analytics/funnel.js +3 -0
- package/dist/sdk/adapty/analytics/index.d.ts +24 -0
- package/dist/sdk/adapty/analytics/index.js +13 -0
- package/dist/sdk/adapty/analytics/ltv.d.ts +27 -0
- package/dist/sdk/adapty/analytics/ltv.js +4 -0
- package/dist/sdk/adapty/analytics/model.d.ts +53 -0
- package/dist/sdk/adapty/analytics/model.js +12 -0
- package/dist/sdk/adapty/analytics/query.d.ts +48 -0
- package/dist/sdk/adapty/analytics/query.js +52 -0
- package/dist/sdk/adapty/analytics/renewal-periods.d.ts +23 -0
- package/dist/sdk/adapty/analytics/renewal-periods.js +27 -0
- package/dist/sdk/adapty/analytics/resource.d.ts +29 -0
- package/dist/sdk/adapty/analytics/resource.js +72 -0
- package/dist/sdk/adapty/analytics/retention.d.ts +32 -0
- package/dist/sdk/adapty/analytics/retention.js +6 -0
- package/dist/sdk/adapty/analytics/values.d.ts +20 -0
- package/dist/sdk/adapty/analytics/values.js +1 -0
- package/dist/sdk/adapty/index.d.ts +8 -0
- package/dist/sdk/adapty/index.js +6 -0
- package/dist/sdk/adapty/product-offers/create.d.ts +5 -0
- package/dist/sdk/adapty/product-offers/create.js +12 -0
- package/dist/sdk/adapty/product-offers/index.d.ts +9 -0
- package/dist/sdk/adapty/product-offers/index.js +5 -0
- package/dist/sdk/adapty/product-offers/model.d.ts +48 -0
- package/dist/sdk/adapty/product-offers/model.js +30 -0
- package/dist/sdk/adapty/product-offers/resource.d.ts +21 -0
- package/dist/sdk/adapty/product-offers/resource.js +28 -0
- package/dist/sdk/adapty/product-offers/update.d.ts +19 -0
- package/dist/sdk/adapty/product-offers/update.js +42 -0
- package/dist/sdk/attribution/index.d.ts +1 -1
- package/dist/sdk/attribution/index.js +1 -1
- package/dist/sdk/attribution/report.d.ts +0 -2
- package/dist/sdk/attribution/report.js +2 -26
- package/dist/sdk/core/dates.d.ts +15 -0
- package/dist/sdk/core/dates.js +34 -0
- package/dist/sdk/core/errors.d.ts +3 -0
- package/oclif.manifest.json +1163 -128
- 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
|
-
/**
|
|
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
|
|
13
|
-
const
|
|
14
|
-
|
|
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
|
+
}
|
package/dist/cli/errors.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
package/dist/cli/input/app.js
CHANGED
|
@@ -1,17 +1,7 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
|
-
import {
|
|
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,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,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,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 {};
|