adapty 0.8.4 → 0.8.5
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 +1 -1
- package/bin/dev.js +2 -2
- package/bin/run.js +2 -2
- package/dist/commands/access-levels/create.js +1 -1
- package/dist/commands/access-levels/list.d.ts +1 -1
- package/dist/commands/apps/create.js +9 -6
- package/dist/commands/apps/list.d.ts +1 -1
- package/dist/commands/apps/update.js +7 -4
- package/dist/commands/asa/ad-groups/create.js +9 -7
- package/dist/commands/asa/ad-groups/get.js +3 -2
- package/dist/commands/asa/ad-groups/list.d.ts +1 -1
- package/dist/commands/asa/ad-groups/list.js +1 -1
- package/dist/commands/asa/ad-groups/update.js +22 -12
- package/dist/commands/asa/ads/create.js +7 -5
- package/dist/commands/asa/ads/get.js +3 -2
- package/dist/commands/asa/ads/list.d.ts +1 -1
- package/dist/commands/asa/ads/list.js +1 -1
- package/dist/commands/asa/ads/update.js +9 -5
- package/dist/commands/asa/apps/list.d.ts +1 -1
- package/dist/commands/asa/apps/list.js +1 -1
- package/dist/commands/asa/automations/create.js +9 -6
- package/dist/commands/asa/automations/get.js +3 -2
- package/dist/commands/asa/automations/list.d.ts +1 -1
- package/dist/commands/asa/automations/list.js +1 -1
- package/dist/commands/asa/automations/run.js +5 -3
- package/dist/commands/asa/automations/runs.d.ts +1 -1
- package/dist/commands/asa/automations/runs.js +4 -4
- package/dist/commands/asa/automations/update.js +17 -9
- package/dist/commands/asa/campaigns/bulk-create.js +31 -18
- package/dist/commands/asa/campaigns/bulk-list.js +7 -5
- package/dist/commands/asa/campaigns/bulk-status.js +3 -2
- package/dist/commands/asa/campaigns/create.js +10 -8
- package/dist/commands/asa/campaigns/get.js +3 -2
- package/dist/commands/asa/campaigns/list.d.ts +1 -1
- package/dist/commands/asa/campaigns/list.js +1 -1
- package/dist/commands/asa/campaigns/update.js +25 -15
- package/dist/commands/asa/competitors/summary.js +5 -5
- package/dist/commands/asa/connect.js +5 -3
- package/dist/commands/asa/creatives/list.d.ts +1 -1
- package/dist/commands/asa/creatives/list.js +1 -1
- package/dist/commands/asa/keywords/add.js +13 -10
- package/dist/commands/asa/keywords/list.d.ts +1 -1
- package/dist/commands/asa/keywords/list.js +1 -1
- package/dist/commands/asa/keywords/update.js +15 -10
- package/dist/commands/asa/metrics/index.d.ts +2 -2
- package/dist/commands/asa/metrics/index.js +22 -8
- package/dist/commands/asa/metrics/overview.js +3 -3
- package/dist/commands/asa/negative-keywords/add.js +10 -7
- package/dist/commands/asa/negative-keywords/list.d.ts +1 -1
- package/dist/commands/asa/negative-keywords/list.js +1 -1
- package/dist/commands/asa/orgs/list.d.ts +1 -1
- package/dist/commands/asa/orgs/list.js +1 -1
- package/dist/commands/asa/product-pages/list.d.ts +1 -1
- package/dist/commands/asa/product-pages/list.js +1 -1
- package/dist/commands/asa/product-pages/sync.js +1 -1
- package/dist/commands/asa/search-terms/list.d.ts +1 -1
- package/dist/commands/asa/search-terms/list.js +1 -1
- package/dist/commands/asa/whoami.js +7 -2
- package/dist/commands/auth/login.js +3 -1
- package/dist/commands/flows/config/get.js +2 -2
- package/dist/commands/flows/config/preview.d.ts +2 -2
- package/dist/commands/flows/config/preview.js +6 -4
- package/dist/commands/flows/config/update.js +5 -3
- package/dist/commands/flows/config/validate.js +5 -3
- package/dist/commands/flows/list.d.ts +1 -1
- package/dist/commands/flows/media/upload.js +1 -1
- package/dist/commands/flows/publish.js +5 -5
- package/dist/commands/paywalls/create.js +1 -1
- package/dist/commands/paywalls/list.d.ts +1 -1
- package/dist/commands/paywalls/placements.d.ts +2 -2
- package/dist/commands/paywalls/update.js +1 -1
- package/dist/commands/placements/create.js +17 -12
- package/dist/commands/placements/list.d.ts +1 -1
- package/dist/commands/placements/update.js +20 -15
- package/dist/commands/products/create.js +20 -13
- package/dist/commands/products/list.d.ts +1 -1
- package/dist/commands/products/update.js +1 -1
- package/dist/commands/segments/list.d.ts +1 -1
- package/dist/lib/api-client.d.ts +10 -6
- package/dist/lib/api-client.js +56 -35
- package/dist/lib/api-schemas.d.ts +56 -56
- package/dist/lib/app-url.js +2 -1
- package/dist/lib/asa-client.d.ts +13 -8
- package/dist/lib/asa-client.js +9 -4
- package/dist/lib/asa-flags.d.ts +19 -19
- package/dist/lib/asa-flags.js +38 -25
- package/dist/lib/asa-keyword-action.d.ts +10 -10
- package/dist/lib/asa-keyword-action.js +40 -25
- package/dist/lib/asa-schemas.d.ts +100 -80
- package/dist/lib/auth.js +2 -1
- package/dist/lib/client-from-config.d.ts +1 -1
- package/dist/lib/client-from-config.js +2 -1
- package/dist/lib/config.d.ts +2 -2
- package/dist/lib/confirm.d.ts +4 -4
- package/dist/lib/confirm.js +12 -7
- package/dist/lib/errors.d.ts +18 -18
- package/dist/lib/errors.js +12 -9
- package/dist/lib/flags.d.ts +3 -3
- package/dist/lib/flags.js +2 -1
- package/dist/lib/flow-help.js +11 -6
- package/dist/lib/output.js +23 -12
- package/dist/lib/preview.d.ts +5 -5
- package/dist/lib/preview.js +2 -1
- package/oclif.manifest.json +131 -131
- package/package.json +10 -8
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Command, Flags } from '@oclif/core';
|
|
2
1
|
import { readFile } from 'node:fs/promises';
|
|
3
2
|
import { basename } from 'node:path';
|
|
3
|
+
import { Command, Flags } from '@oclif/core';
|
|
4
4
|
import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
|
|
5
5
|
import { idempotencyFlags } from '../../../lib/asa-flags.js';
|
|
6
6
|
import { confirmFlags, confirmMutation } from '../../../lib/confirm.js';
|
|
@@ -18,7 +18,7 @@ export default class AsaCampaignsBulkCreate extends Command {
|
|
|
18
18
|
static flags = {
|
|
19
19
|
...confirmFlags,
|
|
20
20
|
...idempotencyFlags,
|
|
21
|
-
file: Flags.string({
|
|
21
|
+
'file': Flags.string({
|
|
22
22
|
description: 'JSON file with the campaign structure (the POST /bulk-operations/ body); use "-" for stdin',
|
|
23
23
|
exactlyOne: ['file', 'from-file'],
|
|
24
24
|
}),
|
|
@@ -29,11 +29,11 @@ export default class AsaCampaignsBulkCreate extends Command {
|
|
|
29
29
|
description: 'Apple org id (campaign_group_id) the converted template will target; required with --from-file',
|
|
30
30
|
}),
|
|
31
31
|
'poll-interval': Flags.integer({ default: 5, description: 'Seconds between progress polls' }),
|
|
32
|
-
preview: Flags.boolean({
|
|
32
|
+
'preview': Flags.boolean({
|
|
33
33
|
description: 'With --from-file: print the converted request and stop, nothing is created',
|
|
34
34
|
}),
|
|
35
|
-
timeout: Flags.integer({ default: 900, description: 'Max seconds to wait for the operation to finish' }),
|
|
36
|
-
wait: Flags.boolean({
|
|
35
|
+
'timeout': Flags.integer({ default: 900, description: 'Max seconds to wait for the operation to finish' }),
|
|
36
|
+
'wait': Flags.boolean({
|
|
37
37
|
allowNo: true,
|
|
38
38
|
default: true,
|
|
39
39
|
description: 'Poll the operation until it finishes; --no-wait returns the operation id immediately',
|
|
@@ -41,15 +41,16 @@ export default class AsaCampaignsBulkCreate extends Command {
|
|
|
41
41
|
};
|
|
42
42
|
async run() {
|
|
43
43
|
const { flags } = await this.parse(AsaCampaignsBulkCreate);
|
|
44
|
-
const client = await createAsaClient(this
|
|
44
|
+
const client = await createAsaClient(this);
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- FIXME if you see this
|
|
45
46
|
const body = flags['from-file'] ? await this.convertTemplate(client, flags) : await this.readStructure(flags.file);
|
|
46
47
|
if (flags.preview) {
|
|
47
48
|
this.log(JSON.stringify(body, null, 2));
|
|
48
49
|
return body;
|
|
49
50
|
}
|
|
50
51
|
const counts = this.countStructure(body);
|
|
51
|
-
const summary = `Bulk-create: ${counts.campaigns} campaign node(s), ${counts.adGroups} ad group(s), `
|
|
52
|
-
`${counts.keywords} keyword(s), ${counts.negativeKeywords} negative keyword(s), ${counts.ads} ad(s)`;
|
|
52
|
+
const summary = `Bulk-create: ${counts.campaigns} campaign node(s), ${counts.adGroups} ad group(s), `
|
|
53
|
+
+ `${counts.keywords} keyword(s), ${counts.negativeKeywords} negative keyword(s), ${counts.ads} ad(s)`;
|
|
53
54
|
await confirmMutation(this, { body, method: 'POST', path: '/bulk-operations/', summary }, flags.yes);
|
|
54
55
|
const { replayed, result } = await asaWrite(client, 'post', '/bulk-operations', {
|
|
55
56
|
body,
|
|
@@ -66,8 +67,10 @@ export default class AsaCampaignsBulkCreate extends Command {
|
|
|
66
67
|
return state;
|
|
67
68
|
}
|
|
68
69
|
async convertTemplate(client, flags) {
|
|
69
|
-
if (flags['org-id'] === undefined)
|
|
70
|
+
if (flags['org-id'] === undefined) {
|
|
70
71
|
this.error('--org-id is required with --from-file.', { exit: 2 });
|
|
72
|
+
}
|
|
73
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- FIXME if you see this
|
|
71
74
|
const path = flags['from-file'];
|
|
72
75
|
let content;
|
|
73
76
|
try {
|
|
@@ -80,11 +83,13 @@ export default class AsaCampaignsBulkCreate extends Command {
|
|
|
80
83
|
form.append('file', new Blob([new Uint8Array(content)]), basename(path));
|
|
81
84
|
form.append('campaign_group_id', String(flags['org-id']));
|
|
82
85
|
const result = await client.postForm('/bulk-operations/convert', form);
|
|
83
|
-
for (const warning of result.warnings)
|
|
86
|
+
for (const warning of result.warnings) {
|
|
84
87
|
this.warn(this.formatIssue(warning));
|
|
88
|
+
}
|
|
85
89
|
if (result.errors.length > 0 || result.request === null) {
|
|
86
|
-
for (const issue of result.errors)
|
|
90
|
+
for (const issue of result.errors) {
|
|
87
91
|
this.log(this.formatIssue(issue));
|
|
92
|
+
}
|
|
88
93
|
this.error(`Template conversion failed with ${result.errors.length} error(s); nothing was created.`, { exit: 2 });
|
|
89
94
|
}
|
|
90
95
|
return result.request;
|
|
@@ -94,17 +99,21 @@ export default class AsaCampaignsBulkCreate extends Command {
|
|
|
94
99
|
const campaigns = Array.isArray(body.campaigns) ? body.campaigns : [];
|
|
95
100
|
counts.campaigns = campaigns.length;
|
|
96
101
|
for (const campaign of campaigns) {
|
|
97
|
-
if (typeof campaign !== 'object' || campaign === null)
|
|
102
|
+
if (typeof campaign !== 'object' || campaign === null) {
|
|
98
103
|
continue;
|
|
104
|
+
}
|
|
99
105
|
const campaignNode = campaign;
|
|
106
|
+
// eslint-disable-next-line @stylistic/max-len -- FIXME if you see this
|
|
100
107
|
counts.negativeKeywords += Array.isArray(campaignNode.negative_keywords) ? campaignNode.negative_keywords.length : 0;
|
|
101
108
|
const adGroups = Array.isArray(campaignNode.ad_groups) ? campaignNode.ad_groups : [];
|
|
102
109
|
counts.adGroups += adGroups.length;
|
|
103
110
|
for (const adGroup of adGroups) {
|
|
104
|
-
if (typeof adGroup !== 'object' || adGroup === null)
|
|
111
|
+
if (typeof adGroup !== 'object' || adGroup === null) {
|
|
105
112
|
continue;
|
|
113
|
+
}
|
|
106
114
|
const adGroupNode = adGroup;
|
|
107
115
|
counts.keywords += Array.isArray(adGroupNode.keywords) ? adGroupNode.keywords.length : 0;
|
|
116
|
+
// eslint-disable-next-line @stylistic/max-len -- FIXME if you see this
|
|
108
117
|
counts.negativeKeywords += Array.isArray(adGroupNode.negative_keywords) ? adGroupNode.negative_keywords.length : 0;
|
|
109
118
|
counts.ads += Array.isArray(adGroupNode.ads) ? adGroupNode.ads.length : 0;
|
|
110
119
|
}
|
|
@@ -121,8 +130,9 @@ export default class AsaCampaignsBulkCreate extends Command {
|
|
|
121
130
|
let raw;
|
|
122
131
|
if (source === '-') {
|
|
123
132
|
const chunks = [];
|
|
124
|
-
for await (const chunk of process.stdin)
|
|
133
|
+
for await (const chunk of process.stdin) {
|
|
125
134
|
chunks.push(chunk);
|
|
135
|
+
}
|
|
126
136
|
raw = Buffer.concat(chunks).toString('utf8');
|
|
127
137
|
}
|
|
128
138
|
else {
|
|
@@ -143,15 +153,17 @@ export default class AsaCampaignsBulkCreate extends Command {
|
|
|
143
153
|
report(state) {
|
|
144
154
|
const { counts } = state;
|
|
145
155
|
this.log(`Finished: ${state.status} — ${counts.applied}/${counts.total} applied, ${counts.failed} failed`);
|
|
146
|
-
const failedObjects = state.objects.filter(
|
|
156
|
+
const failedObjects = state.objects.filter(object => object.status === 'failed');
|
|
147
157
|
for (const object of failedObjects) {
|
|
148
158
|
printResponse(object, this.log.bind(this));
|
|
149
159
|
this.log('---');
|
|
150
160
|
}
|
|
151
|
-
if (state.status === 'failed')
|
|
161
|
+
if (state.status === 'failed') {
|
|
152
162
|
this.error('Bulk operation failed.', { exit: 1 });
|
|
153
|
-
|
|
163
|
+
}
|
|
164
|
+
if (state.status === 'partial') {
|
|
154
165
|
this.warn('Some objects were not created — see the failures above.');
|
|
166
|
+
}
|
|
155
167
|
}
|
|
156
168
|
async waitForCompletion(client, operationId, pollIntervalSeconds, timeoutSeconds) {
|
|
157
169
|
const startedAt = Date.now();
|
|
@@ -163,8 +175,9 @@ export default class AsaCampaignsBulkCreate extends Command {
|
|
|
163
175
|
this.log(progress);
|
|
164
176
|
lastProgress = progress;
|
|
165
177
|
}
|
|
166
|
-
if (TERMINAL_STATUSES.has(state.status))
|
|
178
|
+
if (TERMINAL_STATUSES.has(state.status)) {
|
|
167
179
|
return state;
|
|
180
|
+
}
|
|
168
181
|
if ((Date.now() - startedAt) / 1000 >= timeoutSeconds) {
|
|
169
182
|
this.warn(`Still ${state.status} after ${timeoutSeconds}s — check later with: adapty asa campaigns bulk-status ${operationId}`);
|
|
170
183
|
return state;
|
|
@@ -3,13 +3,15 @@ import { createAsaClient } from '../../../lib/asa-client.js';
|
|
|
3
3
|
import { asaPaginationFlags, parseDate } from '../../../lib/asa-flags.js';
|
|
4
4
|
import { isValidUuid, paginationParams } from '../../../lib/flags.js';
|
|
5
5
|
import { printList } from '../../../lib/output.js';
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/require-await -- FIXME if you see this
|
|
6
7
|
async function parseAppId(input) {
|
|
7
|
-
if (!isValidUuid(input))
|
|
8
|
+
if (!isValidUuid(input)) {
|
|
8
9
|
throw new Error('Invalid app ID format. Run `adapty asa apps list` to find your app ID.');
|
|
10
|
+
}
|
|
9
11
|
return input;
|
|
10
12
|
}
|
|
11
13
|
export default class AsaCampaignsBulkList extends Command {
|
|
12
|
-
static description =
|
|
14
|
+
static description = 'This company\'s bulk operations, newest first; inspect one with bulk-status';
|
|
13
15
|
static enableJsonFlag = true;
|
|
14
16
|
static examples = [
|
|
15
17
|
'<%= config.bin %> asa campaigns bulk-list',
|
|
@@ -18,7 +20,7 @@ export default class AsaCampaignsBulkList extends Command {
|
|
|
18
20
|
];
|
|
19
21
|
static flags = {
|
|
20
22
|
...asaPaginationFlags,
|
|
21
|
-
app: Flags.string({ description: 'Keep only operations of this app (UUID)', parse: parseAppId }),
|
|
23
|
+
'app': Flags.string({ description: 'Keep only operations of this app (UUID)', parse: parseAppId }),
|
|
22
24
|
'created-from': Flags.string({
|
|
23
25
|
description: 'Keep only operations created on or after this date (YYYY-MM-DD)',
|
|
24
26
|
parse: parseDate,
|
|
@@ -27,7 +29,7 @@ export default class AsaCampaignsBulkList extends Command {
|
|
|
27
29
|
description: 'Keep only operations created on or before this date (YYYY-MM-DD)',
|
|
28
30
|
parse: parseDate,
|
|
29
31
|
}),
|
|
30
|
-
status: Flags.string({
|
|
32
|
+
'status': Flags.string({
|
|
31
33
|
description: 'Keep only operations in this state; repeatable',
|
|
32
34
|
multiple: true,
|
|
33
35
|
options: ['failed', 'partial', 'pending', 'running', 'success'],
|
|
@@ -35,7 +37,7 @@ export default class AsaCampaignsBulkList extends Command {
|
|
|
35
37
|
};
|
|
36
38
|
async run() {
|
|
37
39
|
const { flags } = await this.parse(AsaCampaignsBulkList);
|
|
38
|
-
const client = await createAsaClient(this
|
|
40
|
+
const client = await createAsaClient(this);
|
|
39
41
|
const result = await client.get('/bulk-operations', {
|
|
40
42
|
...paginationParams(flags),
|
|
41
43
|
app_id: flags.app,
|
|
@@ -16,9 +16,10 @@ export default class AsaCampaignsBulkStatus extends Command {
|
|
|
16
16
|
async run() {
|
|
17
17
|
const { args, flags } = await this.parse(AsaCampaignsBulkStatus);
|
|
18
18
|
const operationId = args['operation-id'];
|
|
19
|
-
if (!isValidUuid(operationId))
|
|
19
|
+
if (!isValidUuid(operationId)) {
|
|
20
20
|
this.error('Invalid operation ID format.', { exit: 2 });
|
|
21
|
-
|
|
21
|
+
}
|
|
22
|
+
const client = await createAsaClient(this);
|
|
22
23
|
const state = await client.get(`/bulk-operations/${operationId}`, paginationParams(flags));
|
|
23
24
|
printResponse(state, this.log.bind(this));
|
|
24
25
|
return state;
|
|
@@ -24,12 +24,12 @@ export default class AsaCampaignsCreate extends Command {
|
|
|
24
24
|
options: ['MANUAL_CPT', 'MAX_CONVERSIONS'],
|
|
25
25
|
}),
|
|
26
26
|
'billing-event': Flags.string({ default: 'TAPS', description: 'Billing event', options: ['IMPRESSIONS', 'TAPS'] }),
|
|
27
|
-
budget: moneyFlag('Lifetime budget'),
|
|
28
|
-
country: Flags.string({ description: 'Country or region code, repeatable', multiple: true, required: true }),
|
|
27
|
+
'budget': moneyFlag('Lifetime budget'),
|
|
28
|
+
'country': Flags.string({ description: 'Country or region code, repeatable', multiple: true, required: true }),
|
|
29
29
|
'daily-budget': moneyFlag('Daily budget', { required: true }),
|
|
30
|
-
name: Flags.string({ description: 'Campaign name', required: true }),
|
|
31
|
-
org: Flags.string({ description: 'Campaign group ID (UUID) — see `adapty asa orgs list`', required: true }),
|
|
32
|
-
status: Flags.string({ description: 'Initial status', options: ['ENABLED', 'PAUSED'] }),
|
|
30
|
+
'name': Flags.string({ description: 'Campaign name', required: true }),
|
|
31
|
+
'org': Flags.string({ description: 'Campaign group ID (UUID) — see `adapty asa orgs list`', required: true }),
|
|
32
|
+
'status': Flags.string({ description: 'Initial status', options: ['ENABLED', 'PAUSED'] }),
|
|
33
33
|
'supply-source': Flags.string({
|
|
34
34
|
default: ['APPSTORE_SEARCH_RESULTS'],
|
|
35
35
|
description: 'Supply source, repeatable',
|
|
@@ -39,8 +39,9 @@ export default class AsaCampaignsCreate extends Command {
|
|
|
39
39
|
};
|
|
40
40
|
async run() {
|
|
41
41
|
const { flags } = await this.parse(AsaCampaignsCreate);
|
|
42
|
-
if (!isValidUuid(flags.org))
|
|
42
|
+
if (!isValidUuid(flags.org)) {
|
|
43
43
|
this.error('Invalid org ID format.', { exit: 2 });
|
|
44
|
+
}
|
|
44
45
|
let invoice;
|
|
45
46
|
try {
|
|
46
47
|
invoice = locInvoiceDetails(flags);
|
|
@@ -64,14 +65,15 @@ export default class AsaCampaignsCreate extends Command {
|
|
|
64
65
|
target_cpa: money(flags['target-cpa'], flags.currency),
|
|
65
66
|
};
|
|
66
67
|
await confirmMutation(this, { body, method: 'POST', path: '/campaigns/', summary: `Create campaign ${flags.name}` }, flags.yes);
|
|
67
|
-
const client = await createAsaClient(this
|
|
68
|
+
const client = await createAsaClient(this);
|
|
68
69
|
const { replayed, result } = await asaWrite(client, 'post', '/campaigns', {
|
|
69
70
|
body,
|
|
70
71
|
idempotencyKey: flags['idempotency-key'],
|
|
71
72
|
});
|
|
72
73
|
noteReplay(replayed, this.log.bind(this));
|
|
73
|
-
if (result.campaign && !replayed)
|
|
74
|
+
if (result.campaign && !replayed) {
|
|
74
75
|
this.log('Campaign created!');
|
|
76
|
+
}
|
|
75
77
|
printResponse(result, this.log.bind(this));
|
|
76
78
|
reportServingState(result.campaign, this.warn.bind(this));
|
|
77
79
|
return result;
|
|
@@ -11,9 +11,10 @@ export default class AsaCampaignsGet extends Command {
|
|
|
11
11
|
static examples = ['<%= config.bin %> asa campaigns get 550e8400-e29b-41d4-a716-446655440000'];
|
|
12
12
|
async run() {
|
|
13
13
|
const { args } = await this.parse(AsaCampaignsGet);
|
|
14
|
-
if (!isValidUuid(args.campaign_id))
|
|
14
|
+
if (!isValidUuid(args.campaign_id)) {
|
|
15
15
|
this.error('Invalid campaign ID format.', { exit: 2 });
|
|
16
|
-
|
|
16
|
+
}
|
|
17
|
+
const client = await createAsaClient(this);
|
|
17
18
|
const result = await client.get(`/campaigns/${args.campaign_id}`);
|
|
18
19
|
printResponse(result, this.log.bind(this));
|
|
19
20
|
return result;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
2
|
import type { AsaCampaignDTO } from '../../../lib/asa-schemas.js';
|
|
3
|
-
import {
|
|
3
|
+
import type { PaginatedResponse } from '../../../lib/flags.js';
|
|
4
4
|
export default class AsaCampaignsList extends Command {
|
|
5
5
|
static description: string;
|
|
6
6
|
static enableJsonFlag: boolean;
|
|
@@ -13,7 +13,7 @@ export default class AsaCampaignsList extends Command {
|
|
|
13
13
|
static flags = { ...asaPaginationFlags, ...orgScopeFlags, ...statusFilter(['ENABLED', 'PAUSED']) };
|
|
14
14
|
async run() {
|
|
15
15
|
const { flags } = await this.parse(AsaCampaignsList);
|
|
16
|
-
const client = await createAsaClient(this
|
|
16
|
+
const client = await createAsaClient(this);
|
|
17
17
|
const result = await client.get('/campaigns', {
|
|
18
18
|
...paginationParams(flags),
|
|
19
19
|
...scopeParams(flags),
|
|
@@ -24,36 +24,45 @@ export default class AsaCampaignsUpdate extends Command {
|
|
|
24
24
|
description: 'Bidding strategy',
|
|
25
25
|
options: ['MANUAL_CPT', 'MAX_CONVERSIONS'],
|
|
26
26
|
}),
|
|
27
|
-
budget: moneyFlag('Lifetime budget'),
|
|
28
|
-
country: Flags.string({ description: 'Replace the country list, repeatable', multiple: true }),
|
|
27
|
+
'budget': moneyFlag('Lifetime budget'),
|
|
28
|
+
'country': Flags.string({ description: 'Replace the country list, repeatable', multiple: true }),
|
|
29
29
|
'daily-budget': moneyFlag('Daily budget'),
|
|
30
|
-
name: Flags.string({ description: 'Campaign name' }),
|
|
31
|
-
status: Flags.string({ description: 'Campaign status', options: ['ENABLED', 'PAUSED'] }),
|
|
30
|
+
'name': Flags.string({ description: 'Campaign name' }),
|
|
31
|
+
'status': Flags.string({ description: 'Campaign status', options: ['ENABLED', 'PAUSED'] }),
|
|
32
32
|
'target-cpa': moneyFlag('Target CPA'),
|
|
33
33
|
};
|
|
34
34
|
async run() {
|
|
35
35
|
const { args, flags } = await this.parse(AsaCampaignsUpdate);
|
|
36
|
-
if (!isValidUuid(args.campaign_id))
|
|
36
|
+
if (!isValidUuid(args.campaign_id)) {
|
|
37
37
|
this.error('Invalid campaign ID format.', { exit: 2 });
|
|
38
|
+
}
|
|
38
39
|
const body = {};
|
|
39
|
-
if (flags.name !== undefined)
|
|
40
|
+
if (flags.name !== undefined) {
|
|
40
41
|
body.name = flags.name;
|
|
41
|
-
|
|
42
|
+
}
|
|
43
|
+
if (flags.status !== undefined) {
|
|
42
44
|
body.status = flags.status;
|
|
43
|
-
|
|
45
|
+
}
|
|
46
|
+
if (flags.country !== undefined) {
|
|
44
47
|
body.countries_or_regions = flags.country;
|
|
45
|
-
|
|
48
|
+
}
|
|
49
|
+
if (flags['daily-budget'] !== undefined) {
|
|
46
50
|
body.daily_budget_amount = money(flags['daily-budget'], flags.currency);
|
|
47
|
-
|
|
51
|
+
}
|
|
52
|
+
if (flags.budget !== undefined) {
|
|
48
53
|
body.budget_amount = money(flags.budget, flags.currency);
|
|
49
|
-
|
|
54
|
+
}
|
|
55
|
+
if (flags['target-cpa'] !== undefined) {
|
|
50
56
|
body.target_cpa = money(flags['target-cpa'], flags.currency);
|
|
51
|
-
|
|
57
|
+
}
|
|
58
|
+
if (flags['bidding-strategy'] !== undefined) {
|
|
52
59
|
body.bidding_strategy = flags['bidding-strategy'];
|
|
60
|
+
}
|
|
53
61
|
try {
|
|
54
62
|
const details = locInvoiceDetails(flags);
|
|
55
|
-
if (details)
|
|
63
|
+
if (details) {
|
|
56
64
|
body.loc_invoice_details = details;
|
|
65
|
+
}
|
|
57
66
|
}
|
|
58
67
|
catch (error) {
|
|
59
68
|
this.error(error.message, { exit: 2 });
|
|
@@ -62,14 +71,15 @@ export default class AsaCampaignsUpdate extends Command {
|
|
|
62
71
|
this.error('Nothing to change. Pass at least one field, e.g. --status PAUSED, or the five --invoice-* flags for a line-of-credit organization.', { exit: 2 });
|
|
63
72
|
}
|
|
64
73
|
await confirmMutation(this, { body, method: 'PUT', path: `/campaigns/${args.campaign_id}/`, summary: 'Update campaign' }, flags.yes);
|
|
65
|
-
const client = await createAsaClient(this
|
|
74
|
+
const client = await createAsaClient(this);
|
|
66
75
|
const { replayed, result } = await asaWrite(client, 'put', `/campaigns/${args.campaign_id}`, {
|
|
67
76
|
body,
|
|
68
77
|
idempotencyKey: flags['idempotency-key'],
|
|
69
78
|
});
|
|
70
79
|
noteReplay(replayed, this.log.bind(this));
|
|
71
|
-
if (result.campaign && !replayed)
|
|
80
|
+
if (result.campaign && !replayed) {
|
|
72
81
|
this.log('Campaign updated!');
|
|
82
|
+
}
|
|
73
83
|
printResponse(result, this.log.bind(this));
|
|
74
84
|
reportServingState(result.campaign, this.warn.bind(this));
|
|
75
85
|
return result;
|
|
@@ -16,15 +16,15 @@ export default class AsaCompetitorsSummary extends Command {
|
|
|
16
16
|
const { flags } = await this.parse(AsaCompetitorsSummary);
|
|
17
17
|
const appIds = flags['app-ids']
|
|
18
18
|
.split(',')
|
|
19
|
-
.map(
|
|
19
|
+
.map(part => part.trim())
|
|
20
20
|
.filter(Boolean);
|
|
21
21
|
if (appIds.length === 0 || appIds.length > MAX_APP_IDS) {
|
|
22
22
|
this.error(`Pass 1 to ${MAX_APP_IDS} App Store IDs, got ${appIds.length}.`, { exit: 2 });
|
|
23
23
|
}
|
|
24
|
-
if (appIds.some(
|
|
24
|
+
if (appIds.some(id => !/^\d+$/.test(id))) {
|
|
25
25
|
this.error('App Store IDs are numbers, e.g. --app-ids 1668337467,6503873027.', { exit: 2 });
|
|
26
26
|
}
|
|
27
|
-
const client = await createAsaClient(this
|
|
27
|
+
const client = await createAsaClient(this);
|
|
28
28
|
const { result } = await asaWrite(client, 'post', '/competitors/summary', {
|
|
29
29
|
body: { app_ids: appIds.map(Number) },
|
|
30
30
|
});
|
|
@@ -37,7 +37,7 @@ export default class AsaCompetitorsSummary extends Command {
|
|
|
37
37
|
}, this.log.bind(this));
|
|
38
38
|
this.log('');
|
|
39
39
|
this.log('Top apps by performance:');
|
|
40
|
-
printList(total.topAppsByPerformance.map(
|
|
40
|
+
printList(total.topAppsByPerformance.map(app => ({
|
|
41
41
|
adam_id: app.adamId,
|
|
42
42
|
avg_sov: app.avgSov,
|
|
43
43
|
countries: app.countries,
|
|
@@ -46,7 +46,7 @@ export default class AsaCompetitorsSummary extends Command {
|
|
|
46
46
|
})), this.log.bind(this));
|
|
47
47
|
this.log('');
|
|
48
48
|
this.log('Most contested terms:');
|
|
49
|
-
printList(total.mostContestedTerms.map(
|
|
49
|
+
printList(total.mostContestedTerms.map(term => ({
|
|
50
50
|
competitor_count: term.competitorCount,
|
|
51
51
|
max_sov: term.maxSov,
|
|
52
52
|
term: term.term,
|
|
@@ -12,15 +12,17 @@ export default class AsaConnect extends Command {
|
|
|
12
12
|
};
|
|
13
13
|
async run() {
|
|
14
14
|
const { flags } = await this.parse(AsaConnect);
|
|
15
|
-
const client = await createAsaClient(this
|
|
15
|
+
const client = await createAsaClient(this);
|
|
16
16
|
const { auth_url: authUrl } = await client.get('/apple/oauth');
|
|
17
17
|
this.log(`If the browser doesn't open, visit: ${authUrl}\n`);
|
|
18
18
|
this.log('The link is valid for one hour. Sign in to the Adapty dashboard in that browser first — the last');
|
|
19
19
|
this.log('step is authorized by the dashboard session, not by this CLI.');
|
|
20
|
-
if (process.stdin.isTTY
|
|
20
|
+
if (process.stdin.isTTY) {
|
|
21
21
|
await open(authUrl).catch(() => false);
|
|
22
|
-
|
|
22
|
+
}
|
|
23
|
+
if (!flags.wait) {
|
|
23
24
|
return { auth_url: authUrl };
|
|
25
|
+
}
|
|
24
26
|
const deadline = Date.now() + flags.timeout * 1000;
|
|
25
27
|
let status;
|
|
26
28
|
while (Date.now() < deadline) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
2
|
import type { AsaCreativeDTO } from '../../../lib/asa-schemas.js';
|
|
3
|
-
import {
|
|
3
|
+
import type { PaginatedResponse } from '../../../lib/flags.js';
|
|
4
4
|
export default class AsaCreativesList extends Command {
|
|
5
5
|
static description: string;
|
|
6
6
|
static enableJsonFlag: boolean;
|
|
@@ -13,7 +13,7 @@ export default class AsaCreativesList extends Command {
|
|
|
13
13
|
static flags = { ...asaPaginationFlags, ...assetScopeFlags };
|
|
14
14
|
async run() {
|
|
15
15
|
const { flags } = await this.parse(AsaCreativesList);
|
|
16
|
-
const client = await createAsaClient(this
|
|
16
|
+
const client = await createAsaClient(this);
|
|
17
17
|
const result = await client.get('/creatives', {
|
|
18
18
|
...paginationParams(flags),
|
|
19
19
|
...scopeParams(flags),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Command, Flags } from '@oclif/core';
|
|
2
1
|
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { Command, Flags } from '@oclif/core';
|
|
3
3
|
import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
|
|
4
4
|
import { currencyFlag, idempotencyFlags, MAX_BULK_ITEMS, money, moneyFlag, reportBulkOutcome } from '../../../lib/asa-flags.js';
|
|
5
5
|
import { confirmFlags, confirmMutation } from '../../../lib/confirm.js';
|
|
@@ -16,24 +16,26 @@ export default class AsaKeywordsAdd extends Command {
|
|
|
16
16
|
...confirmFlags,
|
|
17
17
|
...idempotencyFlags,
|
|
18
18
|
'ad-group': Flags.string({ description: 'Ad group ID (UUID) — the campaign is resolved from it', required: true }),
|
|
19
|
-
bid: moneyFlag('Bid per keyword'),
|
|
19
|
+
'bid': moneyFlag('Bid per keyword'),
|
|
20
20
|
'from-file': Flags.string({ description: 'File with one keyword per line, combined with any --text values' }),
|
|
21
21
|
'match-type': Flags.string({ default: 'BROAD', description: 'Match type', options: ['BROAD', 'EXACT'] }),
|
|
22
|
-
status: Flags.string({ default: 'ACTIVE', description: 'Keyword status', options: ['ACTIVE', 'PAUSED'] }),
|
|
23
|
-
text: Flags.string({ description: 'Keyword text, repeatable', multiple: true }),
|
|
22
|
+
'status': Flags.string({ default: 'ACTIVE', description: 'Keyword status', options: ['ACTIVE', 'PAUSED'] }),
|
|
23
|
+
'text': Flags.string({ description: 'Keyword text, repeatable', multiple: true }),
|
|
24
24
|
};
|
|
25
25
|
async run() {
|
|
26
26
|
const { flags } = await this.parse(AsaKeywordsAdd);
|
|
27
|
-
if (!isValidUuid(flags['ad-group']))
|
|
27
|
+
if (!isValidUuid(flags['ad-group'])) {
|
|
28
28
|
this.error('Invalid ad group ID format.', { exit: 2 });
|
|
29
|
+
}
|
|
29
30
|
const texts = [...(flags.text ?? []), ...(await this.readTexts(flags['from-file']))];
|
|
30
|
-
if (texts.length === 0)
|
|
31
|
+
if (texts.length === 0) {
|
|
31
32
|
this.error('Pass at least one --text or a --from-file with keywords.', { exit: 2 });
|
|
33
|
+
}
|
|
32
34
|
if (texts.length > MAX_BULK_ITEMS) {
|
|
33
35
|
this.error(`A single call takes at most ${MAX_BULK_ITEMS} keywords, got ${texts.length}.`, { exit: 2 });
|
|
34
36
|
}
|
|
35
37
|
const body = {
|
|
36
|
-
keywords: texts.map(
|
|
38
|
+
keywords: texts.map(text => ({
|
|
37
39
|
ad_group_id: flags['ad-group'],
|
|
38
40
|
bid_amount: money(flags.bid, flags.currency),
|
|
39
41
|
match_type: flags['match-type'],
|
|
@@ -42,7 +44,7 @@ export default class AsaKeywordsAdd extends Command {
|
|
|
42
44
|
})),
|
|
43
45
|
};
|
|
44
46
|
await confirmMutation(this, { body, method: 'POST', path: '/keywords/', summary: `Add ${texts.length} keyword(s)` }, flags.yes);
|
|
45
|
-
const client = await createAsaClient(this
|
|
47
|
+
const client = await createAsaClient(this);
|
|
46
48
|
const { replayed, result } = await asaWrite(client, 'post', '/keywords', {
|
|
47
49
|
body,
|
|
48
50
|
idempotencyKey: flags['idempotency-key'],
|
|
@@ -57,13 +59,14 @@ export default class AsaKeywordsAdd extends Command {
|
|
|
57
59
|
return result;
|
|
58
60
|
}
|
|
59
61
|
async readTexts(path) {
|
|
60
|
-
if (!path)
|
|
62
|
+
if (!path) {
|
|
61
63
|
return [];
|
|
64
|
+
}
|
|
62
65
|
try {
|
|
63
66
|
const raw = await readFile(path, 'utf8');
|
|
64
67
|
return raw
|
|
65
68
|
.split('\n')
|
|
66
|
-
.map(
|
|
69
|
+
.map(line => line.trim())
|
|
67
70
|
.filter(Boolean);
|
|
68
71
|
}
|
|
69
72
|
catch {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
2
|
import type { AsaKeywordDTO } from '../../../lib/asa-schemas.js';
|
|
3
|
-
import {
|
|
3
|
+
import type { PaginatedResponse } from '../../../lib/flags.js';
|
|
4
4
|
export default class AsaKeywordsList extends Command {
|
|
5
5
|
static description: string;
|
|
6
6
|
static enableJsonFlag: boolean;
|
|
@@ -13,7 +13,7 @@ export default class AsaKeywordsList extends Command {
|
|
|
13
13
|
static flags = { ...asaPaginationFlags, ...adGroupScopeFlags, ...statusFilter(['ACTIVE', 'PAUSED']) };
|
|
14
14
|
async run() {
|
|
15
15
|
const { flags } = await this.parse(AsaKeywordsList);
|
|
16
|
-
const client = await createAsaClient(this
|
|
16
|
+
const client = await createAsaClient(this);
|
|
17
17
|
const result = await client.get('/keywords', {
|
|
18
18
|
...paginationParams(flags),
|
|
19
19
|
...scopeParams(flags),
|
|
@@ -17,37 +17,42 @@ export default class AsaKeywordsUpdate extends Command {
|
|
|
17
17
|
...currencyFlag,
|
|
18
18
|
...confirmFlags,
|
|
19
19
|
...idempotencyFlags,
|
|
20
|
-
bid: moneyFlag('Bid'),
|
|
20
|
+
'bid': moneyFlag('Bid'),
|
|
21
21
|
'match-type': Flags.string({ description: 'Match type', options: ['BROAD', 'EXACT'] }),
|
|
22
|
-
status: Flags.string({ description: 'Keyword status', options: ['ACTIVE', 'PAUSED'] }),
|
|
23
|
-
text: Flags.string({ description: 'Keyword text (only meaningful for a single keyword)' }),
|
|
22
|
+
'status': Flags.string({ description: 'Keyword status', options: ['ACTIVE', 'PAUSED'] }),
|
|
23
|
+
'text': Flags.string({ description: 'Keyword text (only meaningful for a single keyword)' }),
|
|
24
24
|
};
|
|
25
25
|
static strict = false;
|
|
26
26
|
async run() {
|
|
27
27
|
const { argv, flags } = await this.parse(AsaKeywordsUpdate);
|
|
28
28
|
const keywordIds = argv;
|
|
29
29
|
for (const id of keywordIds) {
|
|
30
|
-
if (!isValidUuid(id))
|
|
30
|
+
if (!isValidUuid(id)) {
|
|
31
31
|
this.error(`Invalid keyword ID format: ${id}`, { exit: 2 });
|
|
32
|
+
}
|
|
32
33
|
}
|
|
33
34
|
const change = {};
|
|
34
|
-
if (flags.bid !== undefined)
|
|
35
|
+
if (flags.bid !== undefined) {
|
|
35
36
|
change.bid_amount = money(flags.bid, flags.currency);
|
|
36
|
-
|
|
37
|
+
}
|
|
38
|
+
if (flags.status !== undefined) {
|
|
37
39
|
change.status = flags.status;
|
|
38
|
-
|
|
40
|
+
}
|
|
41
|
+
if (flags['match-type'] !== undefined) {
|
|
39
42
|
change.match_type = flags['match-type'];
|
|
40
|
-
|
|
43
|
+
}
|
|
44
|
+
if (flags.text !== undefined) {
|
|
41
45
|
change.text = flags.text;
|
|
46
|
+
}
|
|
42
47
|
if (Object.keys(change).length === 0) {
|
|
43
48
|
this.error('Nothing to change. Pass at least one field, e.g. --bid 2.00.', { exit: 2 });
|
|
44
49
|
}
|
|
45
50
|
if (flags.text !== undefined && keywordIds.length > 1) {
|
|
46
51
|
this.error('--text would give every keyword the same text. Update them one at a time.', { exit: 2 });
|
|
47
52
|
}
|
|
48
|
-
const body = { keywords: keywordIds.map(
|
|
53
|
+
const body = { keywords: keywordIds.map(id => ({ internal_id: id, ...change })) };
|
|
49
54
|
await confirmMutation(this, { body, method: 'PUT', path: '/keywords/', summary: `Update ${keywordIds.length} keyword(s)` }, flags.yes);
|
|
50
|
-
const client = await createAsaClient(this
|
|
55
|
+
const client = await createAsaClient(this);
|
|
51
56
|
const { replayed, result } = await asaWrite(client, 'put', '/keywords', {
|
|
52
57
|
body,
|
|
53
58
|
idempotencyKey: flags['idempotency-key'],
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
|
-
import {
|
|
2
|
+
import type { AsaMetricsResponse } from '../../../lib/asa-schemas.js';
|
|
3
3
|
export default class AsaMetrics extends Command {
|
|
4
4
|
static description: string;
|
|
5
5
|
static enableJsonFlag: boolean;
|
|
@@ -20,5 +20,5 @@ export default class AsaMetrics extends Command {
|
|
|
20
20
|
page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
21
21
|
'page-size': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
22
22
|
};
|
|
23
|
-
run(): Promise<
|
|
23
|
+
run(): Promise<AsaMetricsResponse>;
|
|
24
24
|
}
|