adapty 0.12.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 +13 -0
- 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/input/app.js +1 -11
- package/dist/cli/input/offer.d.ts +3 -0
- package/dist/cli/input/offer.js +9 -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/segments.d.ts +2 -2
- package/dist/cli/views/analytics/segments.js +7 -3
- 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/index.d.ts +4 -0
- package/dist/sdk/adapty/index.js +3 -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/oclif.manifest.json +321 -50
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -72,6 +72,19 @@ adapty products create --app UUID [flags]
|
|
|
72
72
|
adapty products update --app UUID PRODUCT_ID [flags]
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
+
### Product offers
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
adapty product-offers list --app UUID --product UUID
|
|
79
|
+
adapty product-offers get OFFER_ID --app UUID
|
|
80
|
+
adapty product-offers create --app UUID --product UUID --title "Spring" --type promotional|win_back [--ios-offer-id ID] [--android-offer-id ID]
|
|
81
|
+
adapty product-offers update OFFER_ID --app UUID [--title ...] [--type ...] [--ios-offer-id ID | --clear-ios-offer-id] [--android-offer-id ID | --clear-android-offer-id]
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`list` returns every offer on one page. `create` needs at least one store offer id; `win_back` is App Store only.
|
|
85
|
+
`update` reads the offer and writes it back whole: it keeps every field you leave out, overwrites a dashboard edit made
|
|
86
|
+
in between, and refuses to clear the last store side. Introductory offers are read-only. Offers are deleted in the dashboard.
|
|
87
|
+
|
|
75
88
|
### Paywalls
|
|
76
89
|
|
|
77
90
|
```sh
|
|
@@ -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/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,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)));
|
|
@@ -37,8 +37,8 @@ export declare const formatValue: (value: Cell) => string;
|
|
|
37
37
|
/** A point is found by its `x`. */
|
|
38
38
|
export declare const pointId: (point: object) => string;
|
|
39
39
|
/**
|
|
40
|
-
* How the human view names a point: its `x
|
|
41
|
-
* number once (`-2 Install`, `1 Paid`, `2 Period`).
|
|
40
|
+
* How the human view names a point: its `x` (a period start as its date, `2026-09-22`), and for a funnel step or a
|
|
41
|
+
* retention period also its title, the number once (`-2 Install`, `1 Paid`, `2 Period`). The CSV keeps the raw `x`.
|
|
42
42
|
*/
|
|
43
43
|
export declare const pointLabel: (point: object) => string;
|
|
44
44
|
/**
|
|
@@ -29,13 +29,17 @@ const fieldsOf = (value, skip) => leaves(Object.fromEntries(Object.entries(value
|
|
|
29
29
|
const totalFirst = (segments) => [...segments.filter(segment => segment.is_total), ...segments.filter(segment => !segment.is_total)];
|
|
30
30
|
/** A point is found by its `x`. */
|
|
31
31
|
export const pointId = (point) => String(point.x);
|
|
32
|
+
/** A period start as the backend gives it: a date, a time and an offset, as in `2026-09-22T00:00:00.000000+0200`. */
|
|
33
|
+
const TIMESTAMP = /^(\d{4}-\d{2}-\d{2})T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}(?::?\d{2})?)?$/;
|
|
34
|
+
/** The date of a period start in the period's own time zone: the date part as given, with no conversion. */
|
|
35
|
+
const dateOf = (id) => TIMESTAMP.exec(id)?.[1] ?? id;
|
|
32
36
|
/**
|
|
33
|
-
* How the human view names a point: its `x
|
|
34
|
-
* number once (`-2 Install`, `1 Paid`, `2 Period`).
|
|
37
|
+
* How the human view names a point: its `x` (a period start as its date, `2026-09-22`), and for a funnel step or a
|
|
38
|
+
* retention period also its title, the number once (`-2 Install`, `1 Paid`, `2 Period`). The CSV keeps the raw `x`.
|
|
35
39
|
*/
|
|
36
40
|
export const pointLabel = (point) => {
|
|
37
41
|
const { title } = point;
|
|
38
|
-
const id = pointId(point);
|
|
42
|
+
const id = dateOf(pointId(point));
|
|
39
43
|
if (typeof title !== 'string') {
|
|
40
44
|
return id;
|
|
41
45
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '../../cli/commands/product-offers/create.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '../../cli/commands/product-offers/get.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '../../cli/commands/product-offers/list.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '../../cli/commands/product-offers/update.js';
|
|
@@ -4,10 +4,12 @@ import type { AppsApi } from './apps/index.js';
|
|
|
4
4
|
import type { AuthApi } from './auth/index.js';
|
|
5
5
|
import type { FeedbackApi } from './feedback/index.js';
|
|
6
6
|
import type { MigrationApi } from './migrations/index.js';
|
|
7
|
+
import type { ProductOffersApi } from './product-offers/index.js';
|
|
7
8
|
import type { Clock } from '../core/clock.js';
|
|
8
9
|
import type { RetryAttempt } from '../core/http/index.js';
|
|
9
10
|
export { periodTypes, periodUnits, revenueBases, validateChart, validateCohort, validateConversion, validateFilterValues, validateFunnel, validateLtv, validateRetention, } from './analytics/index.js';
|
|
10
11
|
export { platforms } from './apps/index.js';
|
|
12
|
+
export { offerTypes } from './product-offers/index.js';
|
|
11
13
|
export { developerErrorParser } from './errors.js';
|
|
12
14
|
export { sentiments } from './feedback/index.js';
|
|
13
15
|
export type { AccessLevel, AccessLevelList, AccessLevelsApi } from './access-levels.js';
|
|
@@ -16,6 +18,7 @@ export type { AppDetail, AppsApi, AppSummary, CreateAppInput, UpdateAppInput } f
|
|
|
16
18
|
export type { AuthApi, AuthUser, IssuedToken } from './auth/index.js';
|
|
17
19
|
export type { FeedbackApi, SendFeedbackInput, Sentiment } from './feedback/index.js';
|
|
18
20
|
export type { Action, ActionKind, AvailableFlow, CreateMigrationInput, Envelope, Issue, JsonSchema, Migration, MigrationApi, MigrationList, MigrationState, Progress, ResourceRef, Step, StepStatus, WaitOptions, WizardError, } from './migrations/index.js';
|
|
21
|
+
export type { CreateProductOfferInput, OfferType, ProductOffer, ProductOffersApi, StoreOffer, UpdateProductOfferInput, } from './product-offers/index.js';
|
|
19
22
|
export type { PageParams, Paginated, Pagination } from './pagination.js';
|
|
20
23
|
/** Exported because the adapter compares the resolved URL with it and warns when they differ. */
|
|
21
24
|
export declare const DEFAULT_ADAPTY_API_URL = "https://api-admin.adapty.io/api/v1/developer";
|
|
@@ -44,6 +47,7 @@ export type Adapty = {
|
|
|
44
47
|
auth: AuthApi;
|
|
45
48
|
feedback: FeedbackApi;
|
|
46
49
|
migrations: MigrationApi;
|
|
50
|
+
productOffers: ProductOffersApi;
|
|
47
51
|
};
|
|
48
52
|
/** The assembly point of the developer API: one transport, resources on top of it. */
|
|
49
53
|
export declare const createAdapty: (options?: AdaptyOptions) => Adapty;
|
package/dist/sdk/adapty/index.js
CHANGED
|
@@ -7,8 +7,10 @@ import { auth } from './auth/index.js';
|
|
|
7
7
|
import { developerErrorParser } from './errors.js';
|
|
8
8
|
import { feedback } from './feedback/index.js';
|
|
9
9
|
import { migrations } from './migrations/index.js';
|
|
10
|
+
import { productOffers } from './product-offers/index.js';
|
|
10
11
|
export { periodTypes, periodUnits, revenueBases, validateChart, validateCohort, validateConversion, validateFilterValues, validateFunnel, validateLtv, validateRetention, } from './analytics/index.js';
|
|
11
12
|
export { platforms } from './apps/index.js';
|
|
13
|
+
export { offerTypes } from './product-offers/index.js';
|
|
12
14
|
export { developerErrorParser } from './errors.js';
|
|
13
15
|
export { sentiments } from './feedback/index.js';
|
|
14
16
|
/** Exported because the adapter compares the resolved URL with it and warns when they differ. */
|
|
@@ -55,5 +57,6 @@ export const createAdapty = (options = {}) => {
|
|
|
55
57
|
// The clock is a dependency here too, not only in the transport: `waitFor` sleeps
|
|
56
58
|
// between polls, and a test must be able to do that instantly.
|
|
57
59
|
migrations: migrations(wizard, options.clock ?? systemClock),
|
|
60
|
+
productOffers: productOffers(http),
|
|
58
61
|
};
|
|
59
62
|
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { OfferWrite } from './model.js';
|
|
2
|
+
import type { Issue } from '../../core/errors.js';
|
|
3
|
+
export type CreateProductOfferInput = OfferWrite;
|
|
4
|
+
/** A list of problems, knowing nothing about flags or the network. A path names an input field. */
|
|
5
|
+
export declare const validateCreateProductOffer: (input: CreateProductOfferInput) => Issue[];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { hasStoreId, winBackIssues } from './model.js';
|
|
2
|
+
/** A list of problems, knowing nothing about flags or the network. A path names an input field. */
|
|
3
|
+
export const validateCreateProductOffer = (input) => {
|
|
4
|
+
const issues = [];
|
|
5
|
+
if (input.title.trim() === '') {
|
|
6
|
+
issues.push({ message: 'must not be empty', path: 'title' });
|
|
7
|
+
}
|
|
8
|
+
if (!hasStoreId(input)) {
|
|
9
|
+
issues.push({ fields: ['iosOfferId', 'androidOfferId'], message: 'pass {iosOfferId}, {androidOfferId} or both' });
|
|
10
|
+
}
|
|
11
|
+
return [...issues, ...winBackIssues(input)];
|
|
12
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** The door of the product-offers resource: re-exports only, no code of its own. */
|
|
2
|
+
export { offerTypes } from './model.js';
|
|
3
|
+
export { validateCreateProductOffer } from './create.js';
|
|
4
|
+
export { productOffers } from './resource.js';
|
|
5
|
+
export { validateUpdateProductOffer } from './update.js';
|
|
6
|
+
export type { OfferType, ProductOffer, StoreOffer } from './model.js';
|
|
7
|
+
export type { CreateProductOfferInput } from './create.js';
|
|
8
|
+
export type { ProductOffersApi } from './resource.js';
|
|
9
|
+
export type { UpdateProductOfferInput } from './update.js';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** The door of the product-offers resource: re-exports only, no code of its own. */
|
|
2
|
+
export { offerTypes } from './model.js';
|
|
3
|
+
export { validateCreateProductOffer } from './create.js';
|
|
4
|
+
export { productOffers } from './resource.js';
|
|
5
|
+
export { validateUpdateProductOffer } from './update.js';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What every operation of the resource shares: the offer as the server sends it, and the type
|
|
3
|
+
* vocabulary. Answers pass through exactly as they arrive, in snake_case, which is what --json prints.
|
|
4
|
+
*/
|
|
5
|
+
import type { Issue } from '../../core/errors.js';
|
|
6
|
+
/** The types a write accepts, as the server names them. */
|
|
7
|
+
export declare const offerTypes: readonly ["promotional", "win_back"];
|
|
8
|
+
export type OfferType = (typeof offerTypes)[number];
|
|
9
|
+
export type StoreOffer = {
|
|
10
|
+
offer_id: string;
|
|
11
|
+
};
|
|
12
|
+
export type ProductOffer = {
|
|
13
|
+
/** Null when the offer has no fixed duration. */
|
|
14
|
+
duration: null | string;
|
|
15
|
+
id: string;
|
|
16
|
+
/** Not the OfferType union: the server also answers `introductory`, which a write refuses. */
|
|
17
|
+
offer_type: string;
|
|
18
|
+
product_id: string;
|
|
19
|
+
/** Keyed as the server stored it: `app_store`, `play_store`, and any other key it may hold. */
|
|
20
|
+
store_offers: Record<string, StoreOffer>;
|
|
21
|
+
title: string;
|
|
22
|
+
};
|
|
23
|
+
/** The fields of a write. */
|
|
24
|
+
export type OfferWrite = {
|
|
25
|
+
androidOfferId?: string | undefined;
|
|
26
|
+
iosOfferId?: string | undefined;
|
|
27
|
+
offerType: OfferType;
|
|
28
|
+
title: string;
|
|
29
|
+
};
|
|
30
|
+
/** An update merged onto the stored offer, before the rules have checked its type. */
|
|
31
|
+
export type OfferDraft = Omit<OfferWrite, 'offerType'> & {
|
|
32
|
+
offerType: string;
|
|
33
|
+
};
|
|
34
|
+
export declare const isOfferType: (value: string) => value is OfferType;
|
|
35
|
+
/** Trimmed, and a blank id is not an id: `--ios-offer-id ""` counts as absent. */
|
|
36
|
+
export declare const storeId: (value: string | undefined) => string | undefined;
|
|
37
|
+
export declare const hasStoreId: (write: OfferDraft) => boolean;
|
|
38
|
+
/** Google Play has no win-back offers: the server refuses a win_back with an android id. */
|
|
39
|
+
export declare const winBackIssues: (write: OfferDraft) => Issue[];
|
|
40
|
+
type OfferWriteRequest = {
|
|
41
|
+
android_offer_id?: string;
|
|
42
|
+
ios_offer_id?: string;
|
|
43
|
+
offer_type: string;
|
|
44
|
+
title: string;
|
|
45
|
+
};
|
|
46
|
+
/** The body of create and update alike: the server replaces the whole offer. Blank ids are left out. */
|
|
47
|
+
export declare const toWriteRequest: (write: OfferDraft) => OfferWriteRequest;
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What every operation of the resource shares: the offer as the server sends it, and the type
|
|
3
|
+
* vocabulary. Answers pass through exactly as they arrive, in snake_case, which is what --json prints.
|
|
4
|
+
*/
|
|
5
|
+
/** The types a write accepts, as the server names them. */
|
|
6
|
+
export const offerTypes = ['promotional', 'win_back'];
|
|
7
|
+
export const isOfferType = (value) => offerTypes.includes(value);
|
|
8
|
+
/** Trimmed, and a blank id is not an id: `--ios-offer-id ""` counts as absent. */
|
|
9
|
+
export const storeId = (value) => {
|
|
10
|
+
const trimmed = value?.trim();
|
|
11
|
+
return trimmed === '' ? undefined : trimmed;
|
|
12
|
+
};
|
|
13
|
+
export const hasStoreId = (write) => storeId(write.iosOfferId) !== undefined || storeId(write.androidOfferId) !== undefined;
|
|
14
|
+
/** Google Play has no win-back offers: the server refuses a win_back with an android id. */
|
|
15
|
+
export const winBackIssues = (write) => (write.offerType === 'win_back' && storeId(write.androidOfferId) !== undefined
|
|
16
|
+
? [{ message: 'a win_back offer takes only an App Store offer id', path: 'androidOfferId' }]
|
|
17
|
+
: []);
|
|
18
|
+
/** The body of create and update alike: the server replaces the whole offer. Blank ids are left out. */
|
|
19
|
+
export const toWriteRequest = (write) => {
|
|
20
|
+
const body = { offer_type: write.offerType, title: write.title };
|
|
21
|
+
const ios = storeId(write.iosOfferId);
|
|
22
|
+
const android = storeId(write.androidOfferId);
|
|
23
|
+
if (ios !== undefined) {
|
|
24
|
+
body.ios_offer_id = ios;
|
|
25
|
+
}
|
|
26
|
+
if (android !== undefined) {
|
|
27
|
+
body.android_offer_id = android;
|
|
28
|
+
}
|
|
29
|
+
return body;
|
|
30
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CreateProductOfferInput } from './create.js';
|
|
2
|
+
import type { ProductOffer } from './model.js';
|
|
3
|
+
import type { UpdateProductOfferInput } from './update.js';
|
|
4
|
+
import type { Http } from '../../core/http/index.js';
|
|
5
|
+
import type { Paginated } from '../pagination.js';
|
|
6
|
+
/**
|
|
7
|
+
* Offers are listed and created under a product and read and changed by their own id under the app:
|
|
8
|
+
* the two path shapes the server routes. There is no delete: offers are deleted in the dashboard only.
|
|
9
|
+
*
|
|
10
|
+
* The validating methods are async on purpose: a broken rule arrives as a rejected promise, like a
|
|
11
|
+
* network failure.
|
|
12
|
+
*/
|
|
13
|
+
export declare const productOffers: (http: Http) => {
|
|
14
|
+
get: (appId: string, offerId: string) => Promise<ProductOffer>;
|
|
15
|
+
/** The server answers every offer of the product on one page. */
|
|
16
|
+
list: (appId: string, productId: string) => Promise<Paginated<ProductOffer>>;
|
|
17
|
+
create: (appId: string, productId: string, input: CreateProductOfferInput) => Promise<ProductOffer>;
|
|
18
|
+
/** A full replace on the server, so this reads the stored offer and sends it back merged. */
|
|
19
|
+
update: (appId: string, offerId: string, input: UpdateProductOfferInput) => Promise<ProductOffer>;
|
|
20
|
+
};
|
|
21
|
+
export type ProductOffersApi = ReturnType<typeof productOffers>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { assertValid } from '../../core/validation.js';
|
|
2
|
+
import { validateCreateProductOffer } from './create.js';
|
|
3
|
+
import { toWriteRequest } from './model.js';
|
|
4
|
+
import { mergeProductOffer, validateMergedProductOffer, validateUpdateProductOffer } from './update.js';
|
|
5
|
+
/**
|
|
6
|
+
* Offers are listed and created under a product and read and changed by their own id under the app:
|
|
7
|
+
* the two path shapes the server routes. There is no delete: offers are deleted in the dashboard only.
|
|
8
|
+
*
|
|
9
|
+
* The validating methods are async on purpose: a broken rule arrives as a rejected promise, like a
|
|
10
|
+
* network failure.
|
|
11
|
+
*/
|
|
12
|
+
export const productOffers = (http) => ({
|
|
13
|
+
get: (appId, offerId) => http.get(`/apps/${appId}/offers/${offerId}`),
|
|
14
|
+
/** The server answers every offer of the product on one page. */
|
|
15
|
+
list: (appId, productId) => http.get(`/apps/${appId}/products/${productId}/offers`),
|
|
16
|
+
create: async (appId, productId, input) => {
|
|
17
|
+
assertValid(validateCreateProductOffer(input));
|
|
18
|
+
return http.post(`/apps/${appId}/products/${productId}/offers`, toWriteRequest(input));
|
|
19
|
+
},
|
|
20
|
+
/** A full replace on the server, so this reads the stored offer and sends it back merged. */
|
|
21
|
+
update: async (appId, offerId, input) => {
|
|
22
|
+
assertValid(validateUpdateProductOffer(input));
|
|
23
|
+
const stored = await http.get(`/apps/${appId}/offers/${offerId}`);
|
|
24
|
+
const merged = mergeProductOffer(stored, input);
|
|
25
|
+
assertValid(validateMergedProductOffer(stored, input, merged));
|
|
26
|
+
return http.put(`/apps/${appId}/offers/${offerId}`, toWriteRequest(merged));
|
|
27
|
+
},
|
|
28
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { OfferDraft, OfferType, ProductOffer } from './model.js';
|
|
2
|
+
import type { Issue } from '../../core/errors.js';
|
|
3
|
+
/** A store id is replaced by a string and removed by null; undefined or blank keeps what is stored. */
|
|
4
|
+
export type UpdateProductOfferInput = {
|
|
5
|
+
androidOfferId?: null | string | undefined;
|
|
6
|
+
iosOfferId?: null | string | undefined;
|
|
7
|
+
offerType?: OfferType | undefined;
|
|
8
|
+
title?: string | undefined;
|
|
9
|
+
};
|
|
10
|
+
/** Rules that need no stored offer, so they run before the first request. */
|
|
11
|
+
export declare const validateUpdateProductOffer: (input: UpdateProductOfferInput) => Issue[];
|
|
12
|
+
/** The offer after the update: the server replaces the whole offer, so untouched fields come from the stored one. */
|
|
13
|
+
export declare const mergeProductOffer: (offer: ProductOffer, input: UpdateProductOfferInput) => OfferDraft;
|
|
14
|
+
/**
|
|
15
|
+
* Rules on the stored offer and the merged one, so they run after the read and before the write. The
|
|
16
|
+
* write carries only `app_store` and `play_store`, so an offer with any other store key is refused
|
|
17
|
+
* rather than losing that side to the replace.
|
|
18
|
+
*/
|
|
19
|
+
export declare const validateMergedProductOffer: (offer: ProductOffer, input: UpdateProductOfferInput, merged: OfferDraft) => Issue[];
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { hasStoreId, isOfferType, storeId, winBackIssues } from './model.js';
|
|
2
|
+
const given = (value) => (value === null ? null : storeId(value));
|
|
3
|
+
/** Rules that need no stored offer, so they run before the first request. */
|
|
4
|
+
export const validateUpdateProductOffer = (input) => {
|
|
5
|
+
const fields = [input.title, input.offerType, given(input.iosOfferId), given(input.androidOfferId)];
|
|
6
|
+
if (fields.every(value => value === undefined)) {
|
|
7
|
+
return [{ message: 'nothing to update: pass a title, a type or a store offer id' }];
|
|
8
|
+
}
|
|
9
|
+
if (input.title?.trim() === '') {
|
|
10
|
+
return [{ message: 'must not be empty', path: 'title' }];
|
|
11
|
+
}
|
|
12
|
+
return [];
|
|
13
|
+
};
|
|
14
|
+
const pick = (value, stored) => (value === undefined ? stored : (value ?? undefined));
|
|
15
|
+
/** The offer after the update: the server replaces the whole offer, so untouched fields come from the stored one. */
|
|
16
|
+
export const mergeProductOffer = (offer, input) => ({
|
|
17
|
+
androidOfferId: pick(given(input.androidOfferId), storeId(offer.store_offers.play_store?.offer_id)),
|
|
18
|
+
iosOfferId: pick(given(input.iosOfferId), storeId(offer.store_offers.app_store?.offer_id)),
|
|
19
|
+
offerType: input.offerType ?? offer.offer_type,
|
|
20
|
+
title: input.title ?? offer.title,
|
|
21
|
+
});
|
|
22
|
+
/**
|
|
23
|
+
* Rules on the stored offer and the merged one, so they run after the read and before the write. The
|
|
24
|
+
* write carries only `app_store` and `play_store`, so an offer with any other store key is refused
|
|
25
|
+
* rather than losing that side to the replace.
|
|
26
|
+
*/
|
|
27
|
+
export const validateMergedProductOffer = (offer, input, merged) => {
|
|
28
|
+
if (!isOfferType(offer.offer_type)) {
|
|
29
|
+
return [{ message: `${offer.offer_type} offers are read-only` }];
|
|
30
|
+
}
|
|
31
|
+
const otherKeys = Object.keys(offer.store_offers).filter(key => key !== 'app_store' && key !== 'play_store');
|
|
32
|
+
if (otherKeys.length > 0) {
|
|
33
|
+
return [{ message: `this offer has store sides the update would drop (${otherKeys.join(', ')}): edit it in the dashboard` }];
|
|
34
|
+
}
|
|
35
|
+
if (!hasStoreId(merged)) {
|
|
36
|
+
return [{ message: 'an offer needs at least one store offer id: delete it in the dashboard instead of clearing both' }];
|
|
37
|
+
}
|
|
38
|
+
if (given(input.androidOfferId) === undefined && winBackIssues(merged).length > 0) {
|
|
39
|
+
return [{ message: 'a win_back offer takes only an App Store offer id: clear the stored Google Play one' }];
|
|
40
|
+
}
|
|
41
|
+
return winBackIssues(merged);
|
|
42
|
+
};
|
package/oclif.manifest.json
CHANGED
|
@@ -3135,6 +3135,277 @@
|
|
|
3135
3135
|
"update.js"
|
|
3136
3136
|
]
|
|
3137
3137
|
},
|
|
3138
|
+
"product-offers:create": {
|
|
3139
|
+
"aliases": [],
|
|
3140
|
+
"args": {},
|
|
3141
|
+
"description": "Create an offer for a product",
|
|
3142
|
+
"examples": [
|
|
3143
|
+
"<%= config.bin %> product-offers create --app 550e8400-... --product 660e8400-... --title \"Spring\" --type promotional --ios-offer-id spring_ios --android-offer-id spring-android",
|
|
3144
|
+
"<%= config.bin %> product-offers create --app 550e8400-... --product 660e8400-... --title \"Come back\" --type win_back --ios-offer-id winback_ios"
|
|
3145
|
+
],
|
|
3146
|
+
"flags": {
|
|
3147
|
+
"json": {
|
|
3148
|
+
"description": "Format output as json.",
|
|
3149
|
+
"helpGroup": "GLOBAL",
|
|
3150
|
+
"name": "json",
|
|
3151
|
+
"allowNo": false,
|
|
3152
|
+
"type": "boolean"
|
|
3153
|
+
},
|
|
3154
|
+
"app": {
|
|
3155
|
+
"description": "App ID (UUID)",
|
|
3156
|
+
"name": "app",
|
|
3157
|
+
"required": true,
|
|
3158
|
+
"hasDynamicHelp": false,
|
|
3159
|
+
"multiple": false,
|
|
3160
|
+
"type": "option"
|
|
3161
|
+
},
|
|
3162
|
+
"product": {
|
|
3163
|
+
"description": "Product ID (UUID)",
|
|
3164
|
+
"name": "product",
|
|
3165
|
+
"required": true,
|
|
3166
|
+
"hasDynamicHelp": false,
|
|
3167
|
+
"multiple": false,
|
|
3168
|
+
"type": "option"
|
|
3169
|
+
},
|
|
3170
|
+
"title": {
|
|
3171
|
+
"description": "Offer title",
|
|
3172
|
+
"name": "title",
|
|
3173
|
+
"required": true,
|
|
3174
|
+
"hasDynamicHelp": false,
|
|
3175
|
+
"multiple": false,
|
|
3176
|
+
"type": "option"
|
|
3177
|
+
},
|
|
3178
|
+
"type": {
|
|
3179
|
+
"description": "Offer type (win_back is App Store only)",
|
|
3180
|
+
"name": "type",
|
|
3181
|
+
"required": true,
|
|
3182
|
+
"hasDynamicHelp": false,
|
|
3183
|
+
"multiple": false,
|
|
3184
|
+
"options": [
|
|
3185
|
+
"promotional",
|
|
3186
|
+
"win_back"
|
|
3187
|
+
],
|
|
3188
|
+
"type": "option"
|
|
3189
|
+
},
|
|
3190
|
+
"ios-offer-id": {
|
|
3191
|
+
"description": "App Store offer ID",
|
|
3192
|
+
"name": "ios-offer-id",
|
|
3193
|
+
"hasDynamicHelp": false,
|
|
3194
|
+
"multiple": false,
|
|
3195
|
+
"type": "option"
|
|
3196
|
+
},
|
|
3197
|
+
"android-offer-id": {
|
|
3198
|
+
"description": "Google Play offer ID",
|
|
3199
|
+
"name": "android-offer-id",
|
|
3200
|
+
"hasDynamicHelp": false,
|
|
3201
|
+
"multiple": false,
|
|
3202
|
+
"type": "option"
|
|
3203
|
+
}
|
|
3204
|
+
},
|
|
3205
|
+
"hasDynamicHelp": false,
|
|
3206
|
+
"hiddenAliases": [],
|
|
3207
|
+
"id": "product-offers:create",
|
|
3208
|
+
"pluginAlias": "adapty",
|
|
3209
|
+
"pluginName": "adapty",
|
|
3210
|
+
"pluginType": "core",
|
|
3211
|
+
"strict": true,
|
|
3212
|
+
"isESM": true,
|
|
3213
|
+
"relativePath": [
|
|
3214
|
+
"dist",
|
|
3215
|
+
"commands",
|
|
3216
|
+
"product-offers",
|
|
3217
|
+
"create.js"
|
|
3218
|
+
]
|
|
3219
|
+
},
|
|
3220
|
+
"product-offers:get": {
|
|
3221
|
+
"aliases": [],
|
|
3222
|
+
"args": {
|
|
3223
|
+
"offer_id": {
|
|
3224
|
+
"description": "Offer ID (UUID)",
|
|
3225
|
+
"name": "offer_id",
|
|
3226
|
+
"required": true
|
|
3227
|
+
}
|
|
3228
|
+
},
|
|
3229
|
+
"description": "Get a product offer",
|
|
3230
|
+
"examples": [
|
|
3231
|
+
"<%= config.bin %> product-offers get 770e8400-... --app 550e8400-..."
|
|
3232
|
+
],
|
|
3233
|
+
"flags": {
|
|
3234
|
+
"json": {
|
|
3235
|
+
"description": "Format output as json.",
|
|
3236
|
+
"helpGroup": "GLOBAL",
|
|
3237
|
+
"name": "json",
|
|
3238
|
+
"allowNo": false,
|
|
3239
|
+
"type": "boolean"
|
|
3240
|
+
},
|
|
3241
|
+
"app": {
|
|
3242
|
+
"description": "App ID (UUID)",
|
|
3243
|
+
"name": "app",
|
|
3244
|
+
"required": true,
|
|
3245
|
+
"hasDynamicHelp": false,
|
|
3246
|
+
"multiple": false,
|
|
3247
|
+
"type": "option"
|
|
3248
|
+
}
|
|
3249
|
+
},
|
|
3250
|
+
"hasDynamicHelp": false,
|
|
3251
|
+
"hiddenAliases": [],
|
|
3252
|
+
"id": "product-offers:get",
|
|
3253
|
+
"pluginAlias": "adapty",
|
|
3254
|
+
"pluginName": "adapty",
|
|
3255
|
+
"pluginType": "core",
|
|
3256
|
+
"strict": true,
|
|
3257
|
+
"isESM": true,
|
|
3258
|
+
"relativePath": [
|
|
3259
|
+
"dist",
|
|
3260
|
+
"commands",
|
|
3261
|
+
"product-offers",
|
|
3262
|
+
"get.js"
|
|
3263
|
+
]
|
|
3264
|
+
},
|
|
3265
|
+
"product-offers:list": {
|
|
3266
|
+
"aliases": [],
|
|
3267
|
+
"args": {},
|
|
3268
|
+
"description": "List the offers of a product (all on one page)",
|
|
3269
|
+
"examples": [
|
|
3270
|
+
"<%= config.bin %> product-offers list --app 550e8400-... --product 660e8400-..."
|
|
3271
|
+
],
|
|
3272
|
+
"flags": {
|
|
3273
|
+
"json": {
|
|
3274
|
+
"description": "Format output as json.",
|
|
3275
|
+
"helpGroup": "GLOBAL",
|
|
3276
|
+
"name": "json",
|
|
3277
|
+
"allowNo": false,
|
|
3278
|
+
"type": "boolean"
|
|
3279
|
+
},
|
|
3280
|
+
"app": {
|
|
3281
|
+
"description": "App ID (UUID)",
|
|
3282
|
+
"name": "app",
|
|
3283
|
+
"required": true,
|
|
3284
|
+
"hasDynamicHelp": false,
|
|
3285
|
+
"multiple": false,
|
|
3286
|
+
"type": "option"
|
|
3287
|
+
},
|
|
3288
|
+
"product": {
|
|
3289
|
+
"description": "Product ID (UUID)",
|
|
3290
|
+
"name": "product",
|
|
3291
|
+
"required": true,
|
|
3292
|
+
"hasDynamicHelp": false,
|
|
3293
|
+
"multiple": false,
|
|
3294
|
+
"type": "option"
|
|
3295
|
+
}
|
|
3296
|
+
},
|
|
3297
|
+
"hasDynamicHelp": false,
|
|
3298
|
+
"hiddenAliases": [],
|
|
3299
|
+
"id": "product-offers:list",
|
|
3300
|
+
"pluginAlias": "adapty",
|
|
3301
|
+
"pluginName": "adapty",
|
|
3302
|
+
"pluginType": "core",
|
|
3303
|
+
"strict": true,
|
|
3304
|
+
"isESM": true,
|
|
3305
|
+
"relativePath": [
|
|
3306
|
+
"dist",
|
|
3307
|
+
"commands",
|
|
3308
|
+
"product-offers",
|
|
3309
|
+
"list.js"
|
|
3310
|
+
]
|
|
3311
|
+
},
|
|
3312
|
+
"product-offers:update": {
|
|
3313
|
+
"aliases": [],
|
|
3314
|
+
"args": {
|
|
3315
|
+
"offer_id": {
|
|
3316
|
+
"description": "Offer ID (UUID)",
|
|
3317
|
+
"name": "offer_id",
|
|
3318
|
+
"required": true
|
|
3319
|
+
}
|
|
3320
|
+
},
|
|
3321
|
+
"description": "Reads the offer and writes it back whole: fields you leave out keep their stored value, a change made in the dashboard in between is overwritten, and at least one store offer id must remain.",
|
|
3322
|
+
"examples": [
|
|
3323
|
+
"<%= config.bin %> product-offers update 770e8400-... --app 550e8400-... --title \"Spring 2\"",
|
|
3324
|
+
"<%= config.bin %> product-offers update 770e8400-... --app 550e8400-... --clear-android-offer-id"
|
|
3325
|
+
],
|
|
3326
|
+
"flags": {
|
|
3327
|
+
"json": {
|
|
3328
|
+
"description": "Format output as json.",
|
|
3329
|
+
"helpGroup": "GLOBAL",
|
|
3330
|
+
"name": "json",
|
|
3331
|
+
"allowNo": false,
|
|
3332
|
+
"type": "boolean"
|
|
3333
|
+
},
|
|
3334
|
+
"app": {
|
|
3335
|
+
"description": "App ID (UUID)",
|
|
3336
|
+
"name": "app",
|
|
3337
|
+
"required": true,
|
|
3338
|
+
"hasDynamicHelp": false,
|
|
3339
|
+
"multiple": false,
|
|
3340
|
+
"type": "option"
|
|
3341
|
+
},
|
|
3342
|
+
"title": {
|
|
3343
|
+
"description": "Offer title",
|
|
3344
|
+
"name": "title",
|
|
3345
|
+
"hasDynamicHelp": false,
|
|
3346
|
+
"multiple": false,
|
|
3347
|
+
"type": "option"
|
|
3348
|
+
},
|
|
3349
|
+
"type": {
|
|
3350
|
+
"description": "Offer type (win_back is App Store only: clear the Google Play side too)",
|
|
3351
|
+
"name": "type",
|
|
3352
|
+
"hasDynamicHelp": false,
|
|
3353
|
+
"multiple": false,
|
|
3354
|
+
"options": [
|
|
3355
|
+
"promotional",
|
|
3356
|
+
"win_back"
|
|
3357
|
+
],
|
|
3358
|
+
"type": "option"
|
|
3359
|
+
},
|
|
3360
|
+
"ios-offer-id": {
|
|
3361
|
+
"description": "App Store offer ID",
|
|
3362
|
+
"exclusive": [
|
|
3363
|
+
"clear-ios-offer-id"
|
|
3364
|
+
],
|
|
3365
|
+
"name": "ios-offer-id",
|
|
3366
|
+
"hasDynamicHelp": false,
|
|
3367
|
+
"multiple": false,
|
|
3368
|
+
"type": "option"
|
|
3369
|
+
},
|
|
3370
|
+
"android-offer-id": {
|
|
3371
|
+
"description": "Google Play offer ID",
|
|
3372
|
+
"exclusive": [
|
|
3373
|
+
"clear-android-offer-id"
|
|
3374
|
+
],
|
|
3375
|
+
"name": "android-offer-id",
|
|
3376
|
+
"hasDynamicHelp": false,
|
|
3377
|
+
"multiple": false,
|
|
3378
|
+
"type": "option"
|
|
3379
|
+
},
|
|
3380
|
+
"clear-ios-offer-id": {
|
|
3381
|
+
"description": "Remove the App Store side of the offer",
|
|
3382
|
+
"name": "clear-ios-offer-id",
|
|
3383
|
+
"allowNo": false,
|
|
3384
|
+
"type": "boolean"
|
|
3385
|
+
},
|
|
3386
|
+
"clear-android-offer-id": {
|
|
3387
|
+
"description": "Remove the Google Play side of the offer",
|
|
3388
|
+
"name": "clear-android-offer-id",
|
|
3389
|
+
"allowNo": false,
|
|
3390
|
+
"type": "boolean"
|
|
3391
|
+
}
|
|
3392
|
+
},
|
|
3393
|
+
"hasDynamicHelp": false,
|
|
3394
|
+
"hiddenAliases": [],
|
|
3395
|
+
"id": "product-offers:update",
|
|
3396
|
+
"pluginAlias": "adapty",
|
|
3397
|
+
"pluginName": "adapty",
|
|
3398
|
+
"pluginType": "core",
|
|
3399
|
+
"strict": true,
|
|
3400
|
+
"summary": "Update a product offer",
|
|
3401
|
+
"isESM": true,
|
|
3402
|
+
"relativePath": [
|
|
3403
|
+
"dist",
|
|
3404
|
+
"commands",
|
|
3405
|
+
"product-offers",
|
|
3406
|
+
"update.js"
|
|
3407
|
+
]
|
|
3408
|
+
},
|
|
3138
3409
|
"products:create": {
|
|
3139
3410
|
"aliases": [],
|
|
3140
3411
|
"args": {},
|
|
@@ -6752,55 +7023,6 @@
|
|
|
6752
7023
|
"list.js"
|
|
6753
7024
|
]
|
|
6754
7025
|
},
|
|
6755
|
-
"asa:orgs:list": {
|
|
6756
|
-
"aliases": [],
|
|
6757
|
-
"args": {},
|
|
6758
|
-
"description": "List the Apple Search Ads organizations this company can spend from",
|
|
6759
|
-
"examples": [
|
|
6760
|
-
"<%= config.bin %> asa orgs list"
|
|
6761
|
-
],
|
|
6762
|
-
"flags": {
|
|
6763
|
-
"json": {
|
|
6764
|
-
"description": "Format output as json.",
|
|
6765
|
-
"helpGroup": "GLOBAL",
|
|
6766
|
-
"name": "json",
|
|
6767
|
-
"allowNo": false,
|
|
6768
|
-
"type": "boolean"
|
|
6769
|
-
},
|
|
6770
|
-
"page": {
|
|
6771
|
-
"description": "Page number",
|
|
6772
|
-
"name": "page",
|
|
6773
|
-
"default": 1,
|
|
6774
|
-
"hasDynamicHelp": false,
|
|
6775
|
-
"multiple": false,
|
|
6776
|
-
"type": "option"
|
|
6777
|
-
},
|
|
6778
|
-
"page-size": {
|
|
6779
|
-
"description": "Items per page (max 1000); prefer one big page over a pagination loop",
|
|
6780
|
-
"name": "page-size",
|
|
6781
|
-
"default": 100,
|
|
6782
|
-
"hasDynamicHelp": false,
|
|
6783
|
-
"multiple": false,
|
|
6784
|
-
"type": "option"
|
|
6785
|
-
}
|
|
6786
|
-
},
|
|
6787
|
-
"hasDynamicHelp": false,
|
|
6788
|
-
"hiddenAliases": [],
|
|
6789
|
-
"id": "asa:orgs:list",
|
|
6790
|
-
"pluginAlias": "adapty",
|
|
6791
|
-
"pluginName": "adapty",
|
|
6792
|
-
"pluginType": "core",
|
|
6793
|
-
"strict": true,
|
|
6794
|
-
"enableJsonFlag": true,
|
|
6795
|
-
"isESM": true,
|
|
6796
|
-
"relativePath": [
|
|
6797
|
-
"dist",
|
|
6798
|
-
"commands",
|
|
6799
|
-
"asa",
|
|
6800
|
-
"orgs",
|
|
6801
|
-
"list.js"
|
|
6802
|
-
]
|
|
6803
|
-
},
|
|
6804
7026
|
"asa:product-pages:list": {
|
|
6805
7027
|
"aliases": [],
|
|
6806
7028
|
"args": {},
|
|
@@ -7019,6 +7241,55 @@
|
|
|
7019
7241
|
"list.js"
|
|
7020
7242
|
]
|
|
7021
7243
|
},
|
|
7244
|
+
"asa:orgs:list": {
|
|
7245
|
+
"aliases": [],
|
|
7246
|
+
"args": {},
|
|
7247
|
+
"description": "List the Apple Search Ads organizations this company can spend from",
|
|
7248
|
+
"examples": [
|
|
7249
|
+
"<%= config.bin %> asa orgs list"
|
|
7250
|
+
],
|
|
7251
|
+
"flags": {
|
|
7252
|
+
"json": {
|
|
7253
|
+
"description": "Format output as json.",
|
|
7254
|
+
"helpGroup": "GLOBAL",
|
|
7255
|
+
"name": "json",
|
|
7256
|
+
"allowNo": false,
|
|
7257
|
+
"type": "boolean"
|
|
7258
|
+
},
|
|
7259
|
+
"page": {
|
|
7260
|
+
"description": "Page number",
|
|
7261
|
+
"name": "page",
|
|
7262
|
+
"default": 1,
|
|
7263
|
+
"hasDynamicHelp": false,
|
|
7264
|
+
"multiple": false,
|
|
7265
|
+
"type": "option"
|
|
7266
|
+
},
|
|
7267
|
+
"page-size": {
|
|
7268
|
+
"description": "Items per page (max 1000); prefer one big page over a pagination loop",
|
|
7269
|
+
"name": "page-size",
|
|
7270
|
+
"default": 100,
|
|
7271
|
+
"hasDynamicHelp": false,
|
|
7272
|
+
"multiple": false,
|
|
7273
|
+
"type": "option"
|
|
7274
|
+
}
|
|
7275
|
+
},
|
|
7276
|
+
"hasDynamicHelp": false,
|
|
7277
|
+
"hiddenAliases": [],
|
|
7278
|
+
"id": "asa:orgs:list",
|
|
7279
|
+
"pluginAlias": "adapty",
|
|
7280
|
+
"pluginName": "adapty",
|
|
7281
|
+
"pluginType": "core",
|
|
7282
|
+
"strict": true,
|
|
7283
|
+
"enableJsonFlag": true,
|
|
7284
|
+
"isESM": true,
|
|
7285
|
+
"relativePath": [
|
|
7286
|
+
"dist",
|
|
7287
|
+
"commands",
|
|
7288
|
+
"asa",
|
|
7289
|
+
"orgs",
|
|
7290
|
+
"list.js"
|
|
7291
|
+
]
|
|
7292
|
+
},
|
|
7022
7293
|
"asa:segments:create": {
|
|
7023
7294
|
"aliases": [],
|
|
7024
7295
|
"args": {},
|
|
@@ -7554,5 +7825,5 @@
|
|
|
7554
7825
|
]
|
|
7555
7826
|
}
|
|
7556
7827
|
},
|
|
7557
|
-
"version": "0.
|
|
7828
|
+
"version": "0.13.0"
|
|
7558
7829
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adapty",
|
|
3
3
|
"description": "Adapty command line interface",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.13.0",
|
|
5
5
|
"author": "Adapty team <support@adapty.io>",
|
|
6
6
|
"bin": {
|
|
7
7
|
"adapty": "./bin/run.js"
|
|
@@ -81,6 +81,9 @@
|
|
|
81
81
|
"products": {
|
|
82
82
|
"description": "Manage products"
|
|
83
83
|
},
|
|
84
|
+
"product-offers": {
|
|
85
|
+
"description": "Manage product offers"
|
|
86
|
+
},
|
|
84
87
|
"access-levels": {
|
|
85
88
|
"description": "Manage access levels"
|
|
86
89
|
},
|