adapty 0.8.3-beta.0 → 0.8.3

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 (37) hide show
  1. package/README.md +132 -2
  2. package/dist/commands/asa/ad-groups/create.js +1 -1
  3. package/dist/commands/asa/ad-groups/update.js +1 -1
  4. package/dist/commands/asa/ads/create.js +1 -1
  5. package/dist/commands/asa/ads/update.js +1 -1
  6. package/dist/commands/asa/automations/create.d.ts +9 -0
  7. package/dist/commands/asa/automations/create.js +19 -3
  8. package/dist/commands/asa/automations/run.js +1 -1
  9. package/dist/commands/asa/automations/update.d.ts +9 -0
  10. package/dist/commands/asa/automations/update.js +22 -6
  11. package/dist/commands/asa/campaigns/bulk-create.js +1 -1
  12. package/dist/commands/asa/campaigns/create.js +1 -1
  13. package/dist/commands/asa/campaigns/update.js +1 -1
  14. package/dist/commands/asa/keywords/add.js +1 -1
  15. package/dist/commands/asa/keywords/update.js +1 -1
  16. package/dist/commands/asa/metrics/index.d.ts +4 -1
  17. package/dist/commands/asa/metrics/index.js +18 -8
  18. package/dist/commands/asa/negative-keywords/add.js +1 -1
  19. package/dist/commands/asa/product-pages/sync.js +1 -1
  20. package/dist/commands/placements/create.js +3 -18
  21. package/dist/commands/placements/update.js +3 -18
  22. package/dist/lib/api-schemas.d.ts +1 -10
  23. package/dist/lib/asa-flags.d.ts +24 -0
  24. package/dist/lib/asa-flags.js +54 -0
  25. package/dist/lib/asa-keyword-action.d.ts +18 -0
  26. package/dist/lib/asa-keyword-action.js +160 -0
  27. package/dist/lib/errors.js +2 -1
  28. package/oclif.manifest.json +1273 -1210
  29. package/package.json +2 -3
  30. package/dist/commands/flows/publish.d.ts +0 -15
  31. package/dist/commands/flows/publish.js +0 -34
  32. package/dist/commands/flows/update.d.ts +0 -15
  33. package/dist/commands/flows/update.js +0 -28
  34. package/dist/lib/placement-audiences.d.ts +0 -6
  35. package/dist/lib/placement-audiences.js +0 -25
  36. /package/dist/lib/{confirm.d.ts → asa-confirm.d.ts} +0 -0
  37. /package/dist/lib/{confirm.js → asa-confirm.js} +0 -0
@@ -129,20 +129,11 @@ export interface SegmentDTO {
129
129
  id: string;
130
130
  title: string;
131
131
  }
132
- export interface PlacementPaywallAudienceEntryDTO {
133
- content_type: 'paywall';
132
+ export interface PlacementAudienceEntryDTO {
134
133
  paywall_id: string;
135
134
  priority: number;
136
135
  segment_ids?: string[];
137
136
  }
138
- export interface PlacementFlowAudienceEntryDTO {
139
- content_type: 'flow';
140
- /** The flow must be `published` (not draft) — the backend returns 400 otherwise. */
141
- flow_id: string;
142
- priority: number;
143
- segment_ids?: string[];
144
- }
145
- export type PlacementAudienceEntryDTO = PlacementFlowAudienceEntryDTO | PlacementPaywallAudienceEntryDTO;
146
137
  export interface PlacementSummaryDTO {
147
138
  developer_id: string;
148
139
  id: string;
@@ -44,6 +44,20 @@ export declare const adScopeFlags: {
44
44
  campaign: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
45
45
  'campaign-group': import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
46
46
  };
47
+ export declare const metricsScopeFlags: {
48
+ 'ad-group': import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
49
+ app: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
50
+ campaign: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
51
+ };
52
+ export declare function metricsScopeBody(flags: {
53
+ 'ad-group'?: string[];
54
+ app?: string[];
55
+ campaign?: string[];
56
+ }): {
57
+ campaign_id?: string[] | undefined;
58
+ app_id?: string[] | undefined;
59
+ ad_group_id?: string[] | undefined;
60
+ };
47
61
  export declare const statusFilter: (options: string[]) => {
48
62
  status: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
49
63
  };
@@ -75,6 +89,16 @@ export declare function moneyFlag(description: string, opts?: {
75
89
  export declare const currencyFlag: {
76
90
  currency: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
77
91
  };
92
+ export declare const addKeywordActionFlags: {
93
+ 'cpt-bid': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
94
+ 'cpt-bid-type': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
95
+ 'match-type': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
96
+ negate: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
97
+ 'no-negate': import("@oclif/core/interfaces").BooleanFlag<boolean>;
98
+ 'pause-original': import("@oclif/core/interfaces").BooleanFlag<boolean>;
99
+ 'skip-enable-duplicates': import("@oclif/core/interfaces").BooleanFlag<boolean>;
100
+ 'target-ad-group': import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
101
+ };
78
102
  export declare const idempotencyFlags: {
79
103
  'idempotency-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
80
104
  };
@@ -1,4 +1,5 @@
1
1
  import { Flags } from '@oclif/core';
2
+ import { CPT_BID_TYPES, KEYWORD_MATCH_TYPES, NEGATE_TYPES } from './asa-keyword-action.js';
2
3
  import { describeListedError } from './errors.js';
3
4
  import { isValidUuid } from './flags.js';
4
5
  const DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/;
@@ -63,6 +64,18 @@ export const adScopeFlags = {
63
64
  'campaign-group': idFilter('campaign group'),
64
65
  ...searchFilter,
65
66
  };
67
+ export const metricsScopeFlags = {
68
+ 'ad-group': idFilter('ad group'),
69
+ app: idFilter('app'),
70
+ campaign: idFilter('campaign'),
71
+ };
72
+ export function metricsScopeBody(flags) {
73
+ return {
74
+ ...(flags['ad-group'] === undefined ? {} : { ad_group_id: flags['ad-group'] }),
75
+ ...(flags.app === undefined ? {} : { app_id: flags.app }),
76
+ ...(flags.campaign === undefined ? {} : { campaign_id: flags.campaign }),
77
+ };
78
+ }
66
79
  export const statusFilter = (options) => ({
67
80
  status: Flags.string({ description: 'Keep only rows in this state', options }),
68
81
  });
@@ -116,6 +129,47 @@ export function moneyFlag(description, opts = {}) {
116
129
  export const currencyFlag = {
117
130
  currency: Flags.string({ default: 'USD', description: 'Currency code for the amounts in this call' }),
118
131
  };
132
+ // Params of the add-as-keyword-to action, shared by `automations create` and `automations update`.
133
+ // The API picks the params variant by shape, so which of these apply depends on the rule's
134
+ // operate_with: --negate/--no-negate/--skip-enable-duplicates are search-term only,
135
+ // --pause-original is targeting-keyword only. See lib/asa-keyword-action.ts.
136
+ export const addKeywordActionFlags = {
137
+ 'cpt-bid': Flags.string({
138
+ description: 'cpt_bid.value: the bid itself with --cpt-bid-type set_to, or a percent markup on the entity bid with ' +
139
+ 'search_term_current_cpt / keyword_current_bid; not accepted with ad_group_default_bid',
140
+ parse: parseMoney,
141
+ }),
142
+ 'cpt-bid-type': Flags.string({
143
+ description: 'Where the bid of the created keyword comes from; the API has no default',
144
+ options: CPT_BID_TYPES,
145
+ }),
146
+ 'match-type': Flags.string({
147
+ description: 'Match type of the created keyword; the API has no default',
148
+ options: KEYWORD_MATCH_TYPES,
149
+ }),
150
+ negate: Flags.string({
151
+ description: 'Also add the search term as a negative keyword at this level (search-term rules)',
152
+ exclusive: ['no-negate'],
153
+ options: NEGATE_TYPES,
154
+ }),
155
+ 'no-negate': Flags.boolean({
156
+ description: 'Leave no negative keyword behind (search-term rules)',
157
+ exclusive: ['negate'],
158
+ }),
159
+ 'pause-original': Flags.boolean({
160
+ allowNo: true,
161
+ description: 'Pause the source keyword in its own ad group (targeting-keyword rules)',
162
+ }),
163
+ 'skip-enable-duplicates': Flags.boolean({
164
+ allowNo: true,
165
+ description: 'Skip a keyword that already exists in the target ad group instead of enabling it (search-term rules)',
166
+ }),
167
+ 'target-ad-group': Flags.string({
168
+ description: 'Ad group the keyword is added to (UUID), repeatable; a rule without one does nothing',
169
+ multiple: true,
170
+ parse: parseId,
171
+ }),
172
+ };
119
173
  export const idempotencyFlags = {
120
174
  'idempotency-key': Flags.string({
121
175
  description: 'Idempotency key for this write; re-running with the same key replays the stored result instead of applying twice',
@@ -0,0 +1,18 @@
1
+ export declare const ADD_AS_KEYWORD_ACTION = "add-as-keyword-to";
2
+ export declare const CPT_BID_TYPES: string[];
3
+ export declare const KEYWORD_MATCH_TYPES: string[];
4
+ export declare const NEGATE_TYPES: string[];
5
+ export interface AddKeywordActionFlags {
6
+ 'cpt-bid'?: string;
7
+ 'cpt-bid-type'?: string;
8
+ 'match-type'?: string;
9
+ negate?: string;
10
+ 'no-negate'?: boolean;
11
+ 'pause-original'?: boolean;
12
+ 'skip-enable-duplicates'?: boolean;
13
+ 'target-ad-group'?: string[];
14
+ }
15
+ export declare function hasAddKeywordActionFlags(flags: AddKeywordActionFlags): boolean;
16
+ export declare function buildAddKeywordParams(operateWith: unknown, stored: unknown, flags: AddKeywordActionFlags): Record<string, unknown>;
17
+ export declare function requireSingleAction(actions: unknown): Record<string, unknown>;
18
+ export declare function rebuildAddKeywordAction(actions: unknown, operateWith: unknown, flags: AddKeywordActionFlags): Record<string, unknown>[];
@@ -0,0 +1,160 @@
1
+ import { isValidUuid } from './flags.js';
2
+ export const ADD_AS_KEYWORD_ACTION = 'add-as-keyword-to';
3
+ export const CPT_BID_TYPES = ['ad_group_default_bid', 'keyword_current_bid', 'search_term_current_cpt', 'set_to'];
4
+ export const KEYWORD_MATCH_TYPES = ['BROAD', 'EXACT'];
5
+ export const NEGATE_TYPES = ['ad-group', 'campaign'];
6
+ const SEARCH_TERM = 'search-term';
7
+ const TARGETING_KEYWORD = 'targeting-keyword';
8
+ const BID_TYPE_IGNORING_VALUE = 'ad_group_default_bid';
9
+ const BID_TYPES_READING_MARKUP = new Set(['keyword_current_bid', 'search_term_current_cpt']);
10
+ // KeywordMatchType also carries AUTO. --match-type never offers it — an automation creates a
11
+ // keyword, and AUTO is not something a keyword can be — but a rule that already stores AUTO keeps
12
+ // it rather than being pushed onto another match type by an edit that never mentioned match types.
13
+ const STORED_MATCH_TYPES = new Set(['AUTO', ...KEYWORD_MATCH_TYPES]);
14
+ const SHARED_FLAGS = ['target-ad-group', 'match-type', 'cpt-bid-type', 'cpt-bid'];
15
+ const SEARCH_TERM_FLAGS = ['negate', 'no-negate', 'skip-enable-duplicates'];
16
+ const TARGETING_KEYWORD_FLAGS = ['pause-original'];
17
+ const ALL_FLAGS = [...SHARED_FLAGS, ...SEARCH_TERM_FLAGS, ...TARGETING_KEYWORD_FLAGS];
18
+ const MISSING_REASONS = {
19
+ 'cpt-bid': 'the bid to set, required by --cpt-bid-type set_to',
20
+ 'cpt-bid-type': 'where the bid of the new keyword comes from; the API has no default',
21
+ 'match-type': 'BROAD or EXACT; the API has no default',
22
+ 'target-ad-group': 'the ad groups the keyword is added to; a rule with none does nothing',
23
+ };
24
+ function dashed(name) {
25
+ return `--${name}`;
26
+ }
27
+ function asRecord(value) {
28
+ return typeof value === 'object' && value !== null && !Array.isArray(value) ? value : {};
29
+ }
30
+ function isFlagSet(flags, name) {
31
+ const value = flags[name];
32
+ if (value === undefined)
33
+ return false;
34
+ if (Array.isArray(value))
35
+ return value.length > 0;
36
+ // --no-negate has no negated form of its own, so only a true means it was passed.
37
+ return name === 'no-negate' ? value === true : true;
38
+ }
39
+ export function hasAddKeywordActionFlags(flags) {
40
+ return ALL_FLAGS.some((name) => isFlagSet(flags, name));
41
+ }
42
+ // Params are rebuilt, not patched: a rule may store the add-as-negative-keyword shape
43
+ // ({target_type, ids}) under an add-as-keyword-to action, and the API picks the params variant by
44
+ // shape without a discriminator — so every stray key silently changes which action gets run.
45
+ // Only what fits the expected shape is carried over; the rest has to come from the flags.
46
+ function storedInternalIds(params) {
47
+ const ids = asRecord(params.targets).internal_ids;
48
+ return Array.isArray(ids) ? ids.filter((id) => typeof id === 'string' && isValidUuid(id)) : [];
49
+ }
50
+ function storedMatchType(params) {
51
+ const matchType = params.match_type;
52
+ return typeof matchType === 'string' && STORED_MATCH_TYPES.has(matchType) ? matchType : undefined;
53
+ }
54
+ function storedCptBidType(params) {
55
+ const { type } = asRecord(params.cpt_bid);
56
+ return typeof type === 'string' && CPT_BID_TYPES.includes(type) ? type : undefined;
57
+ }
58
+ function storedCptBidValue(params) {
59
+ const { value } = asRecord(params.cpt_bid);
60
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
61
+ }
62
+ function storedBoolean(value) {
63
+ return typeof value === 'boolean' ? value : undefined;
64
+ }
65
+ function storedNegate(params) {
66
+ const { enabled, type } = asRecord(params.negate);
67
+ if (typeof enabled !== 'boolean')
68
+ return undefined;
69
+ if (type === null || type === undefined)
70
+ return { enabled, type: null };
71
+ return typeof type === 'string' && NEGATE_TYPES.includes(type) ? { enabled, type } : undefined;
72
+ }
73
+ function resolveNegate(flags, params) {
74
+ if (flags.negate !== undefined)
75
+ return { enabled: true, type: flags.negate };
76
+ if (flags['no-negate'] === true)
77
+ return { enabled: false, type: null };
78
+ return storedNegate(params) ?? { enabled: false, type: null };
79
+ }
80
+ function resolveCptBidValue(cptBidType, bid, params) {
81
+ if (cptBidType === 'set_to')
82
+ return bid === undefined ? storedCptBidValue(params) : Number(bid);
83
+ if (cptBidType !== undefined && BID_TYPES_READING_MARKUP.has(cptBidType)) {
84
+ return bid === undefined ? (storedCptBidValue(params) ?? null) : Number(bid);
85
+ }
86
+ return null;
87
+ }
88
+ function isSearchTermRule(operateWith) {
89
+ if (operateWith === SEARCH_TERM)
90
+ return true;
91
+ if (operateWith === TARGETING_KEYWORD)
92
+ return false;
93
+ const shown = typeof operateWith === 'string' && operateWith.length > 0 ? `"${operateWith}"` : 'missing';
94
+ throw new Error(`${ADD_AS_KEYWORD_ACTION} runs on a "${SEARCH_TERM}" or "${TARGETING_KEYWORD}" rule only; this rule's operate_with is ${shown}.`);
95
+ }
96
+ function rejectForeignFlags(searchTerm, flags) {
97
+ const foreign = (searchTerm ? TARGETING_KEYWORD_FLAGS : SEARCH_TERM_FLAGS).filter((name) => isFlagSet(flags, name));
98
+ if (foreign.length === 0)
99
+ return;
100
+ const applicable = [...SHARED_FLAGS, ...(searchTerm ? SEARCH_TERM_FLAGS : TARGETING_KEYWORD_FLAGS)];
101
+ throw new Error(`${foreign.map((name) => dashed(name)).join(', ')} ${foreign.length === 1 ? 'has' : 'have'} no place in the params of an ` +
102
+ `operate_with "${searchTerm ? SEARCH_TERM : TARGETING_KEYWORD}" rule, and the API drops params it cannot ` +
103
+ `place. Applicable action flags: ${applicable.map((name) => dashed(name)).join(', ')}.`);
104
+ }
105
+ function missingFlagsError(missing) {
106
+ const lines = missing.map((name) => ` ${dashed(name)} — ${MISSING_REASONS[name]}`);
107
+ return new Error(`The ${ADD_AS_KEYWORD_ACTION} params are incomplete. Pass:\n${lines.join('\n')}`);
108
+ }
109
+ export function buildAddKeywordParams(operateWith, stored, flags) {
110
+ const searchTerm = isSearchTermRule(operateWith);
111
+ rejectForeignFlags(searchTerm, flags);
112
+ const params = asRecord(stored);
113
+ const missing = [];
114
+ const internalIds = flags['target-ad-group'] ?? storedInternalIds(params);
115
+ if (internalIds.length === 0)
116
+ missing.push('target-ad-group');
117
+ const matchType = flags['match-type'] ?? storedMatchType(params);
118
+ if (matchType === undefined)
119
+ missing.push('match-type');
120
+ const cptBidType = flags['cpt-bid-type'] ?? storedCptBidType(params);
121
+ if (cptBidType === undefined)
122
+ missing.push('cpt-bid-type');
123
+ const bid = flags['cpt-bid'];
124
+ if (bid !== undefined && cptBidType === BID_TYPE_IGNORING_VALUE) {
125
+ throw new Error(`--cpt-bid has no meaning with --cpt-bid-type ${BID_TYPE_IGNORING_VALUE}; that type copies the target ad group's default bid.`);
126
+ }
127
+ const cptBidValue = resolveCptBidValue(cptBidType, bid, params);
128
+ if (cptBidValue === undefined)
129
+ missing.push('cpt-bid');
130
+ if (missing.length > 0)
131
+ throw missingFlagsError(missing);
132
+ const built = {
133
+ cpt_bid: { type: cptBidType, value: cptBidValue ?? null },
134
+ match_type: matchType,
135
+ targets: { internal_ids: internalIds, type: 'ad-group' },
136
+ };
137
+ if (searchTerm) {
138
+ built.negate = resolveNegate(flags, params);
139
+ built.skip_enable_duplicate_keywords =
140
+ flags['skip-enable-duplicates'] ?? storedBoolean(params.skip_enable_duplicate_keywords) ?? false;
141
+ }
142
+ else {
143
+ built.pause_in_original_ad_group =
144
+ flags['pause-original'] ?? storedBoolean(params.pause_in_original_ad_group) ?? false;
145
+ }
146
+ return built;
147
+ }
148
+ export function requireSingleAction(actions) {
149
+ if (!Array.isArray(actions) || actions.length !== 1) {
150
+ throw new Error('A rule needs exactly one action: the API stores one action and one condition per rule.');
151
+ }
152
+ return asRecord(actions[0]);
153
+ }
154
+ export function rebuildAddKeywordAction(actions, operateWith, flags) {
155
+ const action = requireSingleAction(actions);
156
+ if (action.type !== ADD_AS_KEYWORD_ACTION) {
157
+ throw new Error(`The action flags only apply to an ${ADD_AS_KEYWORD_ACTION} action; this rule's action is "${String(action.type)}".`);
158
+ }
159
+ return [{ ...action, params: buildAddKeywordParams(operateWith, action.params, flags) }];
160
+ }
@@ -2,7 +2,8 @@ export function describeListedError(error) {
2
2
  const code = error.error_code ?? error.apple_error_code ?? error.validation_error_code ?? undefined;
3
3
  const reason = error.message ?? error.apple_error_message ?? error.validation_error_message ?? code ?? 'rejected';
4
4
  const position = error.input_ref === null || error.input_ref === undefined ? '' : ` (item ${error.input_ref + 1})`;
5
- return { code, text: `${reason}${position}` };
5
+ const field = error.field_name ? `${error.field_name}: ` : '';
6
+ return { code, text: `${field}${reason}${position}` };
6
7
  }
7
8
  export class ApiError extends Error {
8
9
  statusCode;