adapty 0.3.0 → 0.5.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 +156 -5
- package/dist/commands/asa/ad-groups/create.d.ts +22 -0
- package/dist/commands/asa/ad-groups/create.js +54 -0
- package/dist/commands/asa/ad-groups/get.d.ts +11 -0
- package/dist/commands/asa/ad-groups/get.js +21 -0
- package/dist/commands/asa/ad-groups/list.d.ts +18 -0
- package/dist/commands/asa/ad-groups/list.js +24 -0
- package/dist/commands/asa/ad-groups/update.d.ts +23 -0
- package/dist/commands/asa/ad-groups/update.js +62 -0
- package/dist/commands/asa/ads/create.d.ts +16 -0
- package/dist/commands/asa/ads/create.js +41 -0
- package/dist/commands/asa/ads/get.d.ts +11 -0
- package/dist/commands/asa/ads/get.js +21 -0
- package/dist/commands/asa/ads/list.d.ts +18 -0
- package/dist/commands/asa/ads/list.js +24 -0
- package/dist/commands/asa/ads/update.d.ts +17 -0
- package/dist/commands/asa/ads/update.js +44 -0
- package/dist/commands/asa/apps/list.d.ts +13 -0
- package/dist/commands/asa/apps/list.js +18 -0
- package/dist/commands/asa/automations/create.d.ts +16 -0
- package/dist/commands/asa/automations/create.js +60 -0
- package/dist/commands/asa/automations/get.d.ts +11 -0
- package/dist/commands/asa/automations/get.js +21 -0
- package/dist/commands/asa/automations/list.d.ts +13 -0
- package/dist/commands/asa/automations/list.js +18 -0
- package/dist/commands/asa/automations/run.d.ts +16 -0
- package/dist/commands/asa/automations/run.js +41 -0
- package/dist/commands/asa/automations/runs.d.ts +16 -0
- package/dist/commands/asa/automations/runs.js +24 -0
- package/dist/commands/asa/automations/update.d.ts +21 -0
- package/dist/commands/asa/automations/update.js +73 -0
- package/dist/commands/asa/campaigns/bulk-create.d.ts +25 -0
- package/dist/commands/asa/campaigns/bulk-create.js +177 -0
- package/dist/commands/asa/campaigns/bulk-status.d.ts +15 -0
- package/dist/commands/asa/campaigns/bulk-status.js +26 -0
- package/dist/commands/asa/campaigns/create.d.ts +25 -0
- package/dist/commands/asa/campaigns/create.js +67 -0
- package/dist/commands/asa/campaigns/get.d.ts +11 -0
- package/dist/commands/asa/campaigns/get.js +21 -0
- package/dist/commands/asa/campaigns/list.d.ts +17 -0
- package/dist/commands/asa/campaigns/list.js +24 -0
- package/dist/commands/asa/campaigns/update.d.ts +23 -0
- package/dist/commands/asa/campaigns/update.js +66 -0
- package/dist/commands/asa/competitors/summary.d.ts +11 -0
- package/dist/commands/asa/competitors/summary.js +56 -0
- package/dist/commands/asa/connect.d.ts +12 -0
- package/dist/commands/asa/connect.js +39 -0
- package/dist/commands/asa/creatives/list.d.ts +15 -0
- package/dist/commands/asa/creatives/list.js +24 -0
- package/dist/commands/asa/keywords/add.d.ts +20 -0
- package/dist/commands/asa/keywords/add.js +73 -0
- package/dist/commands/asa/keywords/list.d.ts +19 -0
- package/dist/commands/asa/keywords/list.js +24 -0
- package/dist/commands/asa/keywords/update.d.ts +21 -0
- package/dist/commands/asa/keywords/update.js +64 -0
- package/dist/commands/asa/metrics/index.d.ts +21 -0
- package/dist/commands/asa/metrics/index.js +68 -0
- package/dist/commands/asa/metrics/overview.d.ts +15 -0
- package/dist/commands/asa/metrics/overview.js +51 -0
- package/dist/commands/asa/negative-keywords/add.d.ts +18 -0
- package/dist/commands/asa/negative-keywords/add.js +70 -0
- package/dist/commands/asa/negative-keywords/list.d.ts +19 -0
- package/dist/commands/asa/negative-keywords/list.js +29 -0
- package/dist/commands/asa/orgs/list.d.ts +13 -0
- package/dist/commands/asa/orgs/list.js +18 -0
- package/dist/commands/asa/product-pages/list.d.ts +15 -0
- package/dist/commands/asa/product-pages/list.js +24 -0
- package/dist/commands/asa/product-pages/sync.d.ts +13 -0
- package/dist/commands/asa/product-pages/sync.js +28 -0
- package/dist/commands/asa/search-terms/list.d.ts +20 -0
- package/dist/commands/asa/search-terms/list.js +25 -0
- package/dist/commands/asa/whoami.d.ts +8 -0
- package/dist/commands/asa/whoami.js +22 -0
- package/dist/lib/api-client.d.ts +16 -3
- package/dist/lib/api-client.js +53 -18
- package/dist/lib/asa-client.d.ts +16 -0
- package/dist/lib/asa-client.js +46 -0
- package/dist/lib/asa-confirm.d.ts +18 -0
- package/dist/lib/asa-confirm.js +41 -0
- package/dist/lib/asa-flags.d.ts +88 -0
- package/dist/lib/asa-flags.js +137 -0
- package/dist/lib/asa-schemas.d.ts +327 -0
- package/dist/lib/asa-schemas.js +1 -0
- package/dist/lib/errors.d.ts +25 -2
- package/dist/lib/errors.js +48 -7
- package/dist/lib/output.js +45 -15
- package/oclif.manifest.json +2956 -157
- package/package.json +41 -1
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import type { AsaAppDTO } from '../../../lib/asa-schemas.js';
|
|
3
|
+
import { type PaginatedResponse } from '../../../lib/flags.js';
|
|
4
|
+
export default class AsaAppsList extends Command {
|
|
5
|
+
static description: string;
|
|
6
|
+
static enableJsonFlag: boolean;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
'page-size': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<PaginatedResponse<AsaAppDTO>>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import { createAsaClient } from '../../../lib/asa-client.js';
|
|
3
|
+
import { asaPaginationFlags } from '../../../lib/asa-flags.js';
|
|
4
|
+
import { paginationParams } from '../../../lib/flags.js';
|
|
5
|
+
import { printList } from '../../../lib/output.js';
|
|
6
|
+
export default class AsaAppsList extends Command {
|
|
7
|
+
static description = 'List the apps promoted by this company in Apple Search Ads';
|
|
8
|
+
static enableJsonFlag = true;
|
|
9
|
+
static examples = ['<%= config.bin %> asa apps list', '<%= config.bin %> asa apps list --page 2 --page-size 50'];
|
|
10
|
+
static flags = { ...asaPaginationFlags };
|
|
11
|
+
async run() {
|
|
12
|
+
const { flags } = await this.parse(AsaAppsList);
|
|
13
|
+
const client = await createAsaClient(this.config);
|
|
14
|
+
const result = await client.get('/apps', paginationParams(flags));
|
|
15
|
+
printList(result.data, this.log.bind(this), result.meta.pagination);
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import type { AsaAutomationMutationDTO } from '../../../lib/asa-schemas.js';
|
|
3
|
+
export default class AsaAutomationsCreate extends Command {
|
|
4
|
+
static description: string;
|
|
5
|
+
static enableJsonFlag: boolean;
|
|
6
|
+
static examples: string[];
|
|
7
|
+
static flags: {
|
|
8
|
+
file: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
'run-now': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
'idempotency-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<AsaAutomationMutationDTO>;
|
|
14
|
+
private readRule;
|
|
15
|
+
private readStdin;
|
|
16
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
|
|
4
|
+
import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
|
|
5
|
+
import { idempotencyFlags } from '../../../lib/asa-flags.js';
|
|
6
|
+
import { printResponse } from '../../../lib/output.js';
|
|
7
|
+
export default class AsaAutomationsCreate extends Command {
|
|
8
|
+
static description = 'Create an automation rule from a JSON rule file';
|
|
9
|
+
static enableJsonFlag = true;
|
|
10
|
+
static examples = [
|
|
11
|
+
'<%= config.bin %> asa automations create --file rule.json',
|
|
12
|
+
'<%= config.bin %> asa automations create --file rule.json --run-now',
|
|
13
|
+
];
|
|
14
|
+
static flags = {
|
|
15
|
+
...confirmFlags,
|
|
16
|
+
...idempotencyFlags,
|
|
17
|
+
file: Flags.string({ description: 'JSON file with the rule body, or - to read stdin', required: true }),
|
|
18
|
+
'run-now': Flags.boolean({ description: 'Queue the first run right after the rule is stored' }),
|
|
19
|
+
};
|
|
20
|
+
async run() {
|
|
21
|
+
const { flags } = await this.parse(AsaAutomationsCreate);
|
|
22
|
+
const body = await this.readRule(flags.file);
|
|
23
|
+
if (flags['run-now'])
|
|
24
|
+
body.run_immediately = true;
|
|
25
|
+
const summary = flags['run-now'] ? 'Create automation rule and run it immediately' : 'Create automation rule';
|
|
26
|
+
await confirmMutation(this, { body, method: 'POST', path: '/automations/', summary }, flags.yes);
|
|
27
|
+
const client = await createAsaClient(this.config);
|
|
28
|
+
const { replayed, result } = await asaWrite(client, 'post', '/automations', {
|
|
29
|
+
body,
|
|
30
|
+
idempotencyKey: flags['idempotency-key'],
|
|
31
|
+
});
|
|
32
|
+
noteReplay(replayed, this.log.bind(this));
|
|
33
|
+
if (result.automation && !replayed) {
|
|
34
|
+
this.log(flags['run-now'] ? 'Automation created and the first run queued!' : 'Automation created!');
|
|
35
|
+
}
|
|
36
|
+
printResponse(result, this.log.bind(this));
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
async readRule(path) {
|
|
40
|
+
let raw;
|
|
41
|
+
try {
|
|
42
|
+
raw = path === '-' ? await this.readStdin() : await readFile(path, 'utf8');
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
this.error(`Could not read ${path}.`, { exit: 2 });
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
return JSON.parse(raw);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
this.error(`${path} is not valid JSON.`, { exit: 2 });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async readStdin() {
|
|
55
|
+
const chunks = [];
|
|
56
|
+
for await (const chunk of process.stdin)
|
|
57
|
+
chunks.push(chunk);
|
|
58
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import type { AsaAutomationDTO } from '../../../lib/asa-schemas.js';
|
|
3
|
+
export default class AsaAutomationsGet extends Command {
|
|
4
|
+
static args: {
|
|
5
|
+
automation_id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
6
|
+
};
|
|
7
|
+
static description: string;
|
|
8
|
+
static enableJsonFlag: boolean;
|
|
9
|
+
static examples: string[];
|
|
10
|
+
run(): Promise<AsaAutomationDTO>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Args, Command } from '@oclif/core';
|
|
2
|
+
import { createAsaClient } from '../../../lib/asa-client.js';
|
|
3
|
+
import { isValidUuid } from '../../../lib/flags.js';
|
|
4
|
+
import { printResponse } from '../../../lib/output.js';
|
|
5
|
+
export default class AsaAutomationsGet extends Command {
|
|
6
|
+
static args = {
|
|
7
|
+
automation_id: Args.string({ description: 'Automation rule ID (UUID)', required: true }),
|
|
8
|
+
};
|
|
9
|
+
static description = 'Show one automation rule with its conditions and actions';
|
|
10
|
+
static enableJsonFlag = true;
|
|
11
|
+
static examples = ['<%= config.bin %> asa automations get 550e8400-e29b-41d4-a716-446655440000'];
|
|
12
|
+
async run() {
|
|
13
|
+
const { args } = await this.parse(AsaAutomationsGet);
|
|
14
|
+
if (!isValidUuid(args.automation_id))
|
|
15
|
+
this.error('Invalid automation ID format.', { exit: 2 });
|
|
16
|
+
const client = await createAsaClient(this.config);
|
|
17
|
+
const result = await client.get(`/automations/${args.automation_id}`);
|
|
18
|
+
printResponse(result, this.log.bind(this));
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import type { AsaAutomationDTO } from '../../../lib/asa-schemas.js';
|
|
3
|
+
import { type PaginatedResponse } from '../../../lib/flags.js';
|
|
4
|
+
export default class AsaAutomationsList extends Command {
|
|
5
|
+
static description: string;
|
|
6
|
+
static enableJsonFlag: boolean;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
'page-size': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<PaginatedResponse<AsaAutomationDTO>>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import { createAsaClient } from '../../../lib/asa-client.js';
|
|
3
|
+
import { asaPaginationFlags } from '../../../lib/asa-flags.js';
|
|
4
|
+
import { paginationParams } from '../../../lib/flags.js';
|
|
5
|
+
import { printList } from '../../../lib/output.js';
|
|
6
|
+
export default class AsaAutomationsList extends Command {
|
|
7
|
+
static description = 'List automation rules; status 1 is active, 0 is stopped';
|
|
8
|
+
static enableJsonFlag = true;
|
|
9
|
+
static examples = ['<%= config.bin %> asa automations list'];
|
|
10
|
+
static flags = { ...asaPaginationFlags };
|
|
11
|
+
async run() {
|
|
12
|
+
const { flags } = await this.parse(AsaAutomationsList);
|
|
13
|
+
const client = await createAsaClient(this.config);
|
|
14
|
+
const result = await client.get('/automations', paginationParams(flags));
|
|
15
|
+
printList(result.data, this.log.bind(this), result.meta.pagination);
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import type { AsaAutomationRunEnqueuedDTO } from '../../../lib/asa-schemas.js';
|
|
3
|
+
export default class AsaAutomationsRun extends Command {
|
|
4
|
+
static args: {
|
|
5
|
+
automation_id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
6
|
+
};
|
|
7
|
+
static description: string;
|
|
8
|
+
static enableJsonFlag: boolean;
|
|
9
|
+
static examples: string[];
|
|
10
|
+
static flags: {
|
|
11
|
+
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
'idempotency-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
};
|
|
15
|
+
run(): Promise<AsaAutomationRunEnqueuedDTO>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
|
|
3
|
+
import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
|
|
4
|
+
import { idempotencyFlags } from '../../../lib/asa-flags.js';
|
|
5
|
+
import { isValidUuid } from '../../../lib/flags.js';
|
|
6
|
+
export default class AsaAutomationsRun extends Command {
|
|
7
|
+
static args = {
|
|
8
|
+
automation_id: Args.string({ description: 'Automation rule ID (UUID)', required: true }),
|
|
9
|
+
};
|
|
10
|
+
static description = 'Run an automation rule now; the run is queued, not awaited';
|
|
11
|
+
static enableJsonFlag = true;
|
|
12
|
+
static examples = [
|
|
13
|
+
'<%= config.bin %> asa automations run UUID',
|
|
14
|
+
'<%= config.bin %> asa automations run UUID --dry-run',
|
|
15
|
+
];
|
|
16
|
+
static flags = {
|
|
17
|
+
...confirmFlags,
|
|
18
|
+
...idempotencyFlags,
|
|
19
|
+
'dry-run': Flags.boolean({ description: 'Evaluate and log the rule without touching Apple' }),
|
|
20
|
+
};
|
|
21
|
+
async run() {
|
|
22
|
+
const { args, flags } = await this.parse(AsaAutomationsRun);
|
|
23
|
+
if (!isValidUuid(args.automation_id))
|
|
24
|
+
this.error('Invalid automation ID format.', { exit: 2 });
|
|
25
|
+
if (!flags['dry-run']) {
|
|
26
|
+
await confirmMutation(this, {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
path: `/automations/${args.automation_id}/run/`,
|
|
29
|
+
summary: 'Run the rule for real — it applies its actions to Apple',
|
|
30
|
+
}, flags.yes);
|
|
31
|
+
}
|
|
32
|
+
const client = await createAsaClient(this.config);
|
|
33
|
+
const { replayed, result } = await asaWrite(client, 'post', `/automations/${args.automation_id}/run`, { idempotencyKey: flags['idempotency-key'], params: flags['dry-run'] ? { dry_run: 'true' } : undefined });
|
|
34
|
+
noteReplay(replayed, this.log.bind(this));
|
|
35
|
+
if (!replayed)
|
|
36
|
+
this.log(flags['dry-run'] ? 'Dry run queued.' : 'Run queued.');
|
|
37
|
+
this.log(`Run ID: ${result.run_id ?? 'unknown'}`);
|
|
38
|
+
this.log(`Follow it with: ${this.config.bin} asa automations runs ${args.automation_id}`);
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import type { AsaAutomationRunDTO } from '../../../lib/asa-schemas.js';
|
|
3
|
+
import { type PaginatedResponse } from '../../../lib/flags.js';
|
|
4
|
+
export default class AsaAutomationsRuns extends Command {
|
|
5
|
+
static args: {
|
|
6
|
+
automation_id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static description: string;
|
|
9
|
+
static enableJsonFlag: boolean;
|
|
10
|
+
static examples: string[];
|
|
11
|
+
static flags: {
|
|
12
|
+
page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
'page-size': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
};
|
|
15
|
+
run(): Promise<PaginatedResponse<AsaAutomationRunDTO>>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Args, Command } from '@oclif/core';
|
|
2
|
+
import { createAsaClient } from '../../../lib/asa-client.js';
|
|
3
|
+
import { asaPaginationFlags } from '../../../lib/asa-flags.js';
|
|
4
|
+
import { isValidUuid } from '../../../lib/flags.js';
|
|
5
|
+
import { paginationParams } from '../../../lib/flags.js';
|
|
6
|
+
import { printList } from '../../../lib/output.js';
|
|
7
|
+
export default class AsaAutomationsRuns extends Command {
|
|
8
|
+
static args = {
|
|
9
|
+
automation_id: Args.string({ description: 'Automation rule ID (UUID)', required: true }),
|
|
10
|
+
};
|
|
11
|
+
static description = 'List past runs of an automation rule, including dry runs';
|
|
12
|
+
static enableJsonFlag = true;
|
|
13
|
+
static examples = ['<%= config.bin %> asa automations runs 550e8400-e29b-41d4-a716-446655440000'];
|
|
14
|
+
static flags = { ...asaPaginationFlags };
|
|
15
|
+
async run() {
|
|
16
|
+
const { args, flags } = await this.parse(AsaAutomationsRuns);
|
|
17
|
+
if (!isValidUuid(args.automation_id))
|
|
18
|
+
this.error('Invalid automation ID format.', { exit: 2 });
|
|
19
|
+
const client = await createAsaClient(this.config);
|
|
20
|
+
const result = await client.get(`/automations/${args.automation_id}/runs`, paginationParams(flags));
|
|
21
|
+
printList(result.data, this.log.bind(this), result.meta.pagination);
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import type { AsaAutomationMutationDTO } from '../../../lib/asa-schemas.js';
|
|
3
|
+
export default class AsaAutomationsUpdate extends Command {
|
|
4
|
+
static args: {
|
|
5
|
+
automation_id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
6
|
+
};
|
|
7
|
+
static description: string;
|
|
8
|
+
static enableJsonFlag: boolean;
|
|
9
|
+
static examples: string[];
|
|
10
|
+
static flags: {
|
|
11
|
+
file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
start: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
stop: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
|
+
'idempotency-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
17
|
+
};
|
|
18
|
+
run(): Promise<AsaAutomationMutationDTO>;
|
|
19
|
+
private readRule;
|
|
20
|
+
private readStdin;
|
|
21
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
|
|
4
|
+
import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
|
|
5
|
+
import { idempotencyFlags } from '../../../lib/asa-flags.js';
|
|
6
|
+
import { isValidUuid } from '../../../lib/flags.js';
|
|
7
|
+
import { printResponse } from '../../../lib/output.js';
|
|
8
|
+
export default class AsaAutomationsUpdate extends Command {
|
|
9
|
+
static args = {
|
|
10
|
+
automation_id: Args.string({ description: 'Automation rule ID (UUID)', required: true }),
|
|
11
|
+
};
|
|
12
|
+
static description = 'Change an automation rule: stop it, rename it, or replace parts of the rule';
|
|
13
|
+
static enableJsonFlag = true;
|
|
14
|
+
static examples = [
|
|
15
|
+
'<%= config.bin %> asa automations update UUID --stop',
|
|
16
|
+
'<%= config.bin %> asa automations update UUID --file rule.json',
|
|
17
|
+
];
|
|
18
|
+
static flags = {
|
|
19
|
+
...confirmFlags,
|
|
20
|
+
...idempotencyFlags,
|
|
21
|
+
file: Flags.string({ description: 'JSON file with the parts to change, or - to read stdin' }),
|
|
22
|
+
name: Flags.string({ description: 'Rule name' }),
|
|
23
|
+
start: Flags.boolean({ description: 'Activate the rule', exclusive: ['stop'] }),
|
|
24
|
+
stop: Flags.boolean({ description: 'Stop the rule and clear its next run', exclusive: ['start'] }),
|
|
25
|
+
};
|
|
26
|
+
async run() {
|
|
27
|
+
const { args, flags } = await this.parse(AsaAutomationsUpdate);
|
|
28
|
+
if (!isValidUuid(args.automation_id))
|
|
29
|
+
this.error('Invalid automation ID format.', { exit: 2 });
|
|
30
|
+
const body = flags.file ? await this.readRule(flags.file) : {};
|
|
31
|
+
if (flags.name !== undefined)
|
|
32
|
+
body.name = flags.name;
|
|
33
|
+
if (flags.start)
|
|
34
|
+
body.status = 1;
|
|
35
|
+
if (flags.stop)
|
|
36
|
+
body.status = 0;
|
|
37
|
+
if (Object.keys(body).length === 0) {
|
|
38
|
+
this.error('Nothing to change. Pass --stop, --start, --name or --file.', { exit: 2 });
|
|
39
|
+
}
|
|
40
|
+
if ('internal_id' in body) {
|
|
41
|
+
this.error('Remove internal_id from the file: the rule ID comes from the command line.', { exit: 2 });
|
|
42
|
+
}
|
|
43
|
+
await confirmMutation(this, { body, method: 'PUT', path: `/automations/${args.automation_id}/`, summary: 'Update automation rule' }, flags.yes);
|
|
44
|
+
const client = await createAsaClient(this.config);
|
|
45
|
+
const { replayed, result } = await asaWrite(client, 'put', `/automations/${args.automation_id}`, { body, idempotencyKey: flags['idempotency-key'] });
|
|
46
|
+
noteReplay(replayed, this.log.bind(this));
|
|
47
|
+
if (result.automation && !replayed)
|
|
48
|
+
this.log('Automation updated!');
|
|
49
|
+
printResponse(result, this.log.bind(this));
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
async readRule(path) {
|
|
53
|
+
let raw;
|
|
54
|
+
try {
|
|
55
|
+
raw = path === '-' ? await this.readStdin() : await readFile(path, 'utf8');
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
this.error(`Could not read ${path}.`, { exit: 2 });
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
return JSON.parse(raw);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
this.error(`${path} is not valid JSON.`, { exit: 2 });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async readStdin() {
|
|
68
|
+
const chunks = [];
|
|
69
|
+
for await (const chunk of process.stdin)
|
|
70
|
+
chunks.push(chunk);
|
|
71
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import type { AsaBulkOperationStateDTO } from '../../../lib/asa-schemas.js';
|
|
3
|
+
export default class AsaCampaignsBulkCreate extends Command {
|
|
4
|
+
static description: string;
|
|
5
|
+
static enableJsonFlag: boolean;
|
|
6
|
+
static examples: string[];
|
|
7
|
+
static flags: {
|
|
8
|
+
file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
'from-file': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
'org-id': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
'poll-interval': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
preview: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
timeout: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
wait: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
|
+
'idempotency-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
17
|
+
};
|
|
18
|
+
run(): Promise<AsaBulkOperationStateDTO | Record<string, unknown>>;
|
|
19
|
+
private convertTemplate;
|
|
20
|
+
private countStructure;
|
|
21
|
+
private formatIssue;
|
|
22
|
+
private readStructure;
|
|
23
|
+
private report;
|
|
24
|
+
private waitForCompletion;
|
|
25
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { basename } from 'node:path';
|
|
4
|
+
import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
|
|
5
|
+
import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
|
|
6
|
+
import { idempotencyFlags } from '../../../lib/asa-flags.js';
|
|
7
|
+
import { printResponse } from '../../../lib/output.js';
|
|
8
|
+
const TERMINAL_STATUSES = new Set(['failed', 'partial', 'success']);
|
|
9
|
+
export default class AsaCampaignsBulkCreate extends Command {
|
|
10
|
+
static description = 'Create a whole campaign structure (campaigns → ad groups → keywords/negative keywords/ads) in one bulk operation';
|
|
11
|
+
static enableJsonFlag = true;
|
|
12
|
+
static examples = [
|
|
13
|
+
'<%= config.bin %> asa campaigns bulk-create --file structure.json',
|
|
14
|
+
'cat structure.json | <%= config.bin %> asa campaigns bulk-create --file -',
|
|
15
|
+
'<%= config.bin %> asa campaigns bulk-create --from-file Campaign_And_Adgroup_Template.xlsx --org-id 1234567',
|
|
16
|
+
'<%= config.bin %> asa campaigns bulk-create --from-file keywords_template.csv --org-id 1234567 --preview',
|
|
17
|
+
];
|
|
18
|
+
static flags = {
|
|
19
|
+
...confirmFlags,
|
|
20
|
+
...idempotencyFlags,
|
|
21
|
+
file: Flags.string({
|
|
22
|
+
description: 'JSON file with the campaign structure (the POST /bulk-operations/ body); use "-" for stdin',
|
|
23
|
+
exactlyOne: ['file', 'from-file'],
|
|
24
|
+
}),
|
|
25
|
+
'from-file': Flags.string({
|
|
26
|
+
description: 'Apple Ads bulk template (Campaign_And_Adgroup_Template.xlsx or keywords .csv) to convert and submit',
|
|
27
|
+
}),
|
|
28
|
+
'org-id': Flags.integer({
|
|
29
|
+
description: 'Apple org id (campaign_group_id) the converted template will target; required with --from-file',
|
|
30
|
+
}),
|
|
31
|
+
'poll-interval': Flags.integer({ default: 5, description: 'Seconds between progress polls' }),
|
|
32
|
+
preview: Flags.boolean({
|
|
33
|
+
description: 'With --from-file: print the converted request and stop, nothing is created',
|
|
34
|
+
}),
|
|
35
|
+
timeout: Flags.integer({ default: 900, description: 'Max seconds to wait for the operation to finish' }),
|
|
36
|
+
wait: Flags.boolean({
|
|
37
|
+
allowNo: true,
|
|
38
|
+
default: true,
|
|
39
|
+
description: 'Poll the operation until it finishes; --no-wait returns the operation id immediately',
|
|
40
|
+
}),
|
|
41
|
+
};
|
|
42
|
+
async run() {
|
|
43
|
+
const { flags } = await this.parse(AsaCampaignsBulkCreate);
|
|
44
|
+
const client = await createAsaClient(this.config);
|
|
45
|
+
const body = flags['from-file'] ? await this.convertTemplate(client, flags) : await this.readStructure(flags.file);
|
|
46
|
+
if (flags.preview) {
|
|
47
|
+
this.log(JSON.stringify(body, null, 2));
|
|
48
|
+
return body;
|
|
49
|
+
}
|
|
50
|
+
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)`;
|
|
53
|
+
await confirmMutation(this, { body, method: 'POST', path: '/bulk-operations/', summary }, flags.yes);
|
|
54
|
+
const { replayed, result } = await asaWrite(client, 'post', '/bulk-operations', {
|
|
55
|
+
body,
|
|
56
|
+
idempotencyKey: flags['idempotency-key'],
|
|
57
|
+
});
|
|
58
|
+
noteReplay(replayed, this.log.bind(this));
|
|
59
|
+
this.log(`Operation accepted: ${result.operation_id}`);
|
|
60
|
+
if (!flags.wait) {
|
|
61
|
+
this.log(`Check progress with: adapty asa campaigns bulk-status ${result.operation_id}`);
|
|
62
|
+
return { operation_id: result.operation_id };
|
|
63
|
+
}
|
|
64
|
+
const state = await this.waitForCompletion(client, result.operation_id, flags['poll-interval'], flags.timeout);
|
|
65
|
+
this.report(state);
|
|
66
|
+
return state;
|
|
67
|
+
}
|
|
68
|
+
async convertTemplate(client, flags) {
|
|
69
|
+
if (flags['org-id'] === undefined)
|
|
70
|
+
this.error('--org-id is required with --from-file.', { exit: 2 });
|
|
71
|
+
const path = flags['from-file'];
|
|
72
|
+
let content;
|
|
73
|
+
try {
|
|
74
|
+
content = await readFile(path);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
this.error(`Cannot read file: ${path}`, { exit: 2 });
|
|
78
|
+
}
|
|
79
|
+
const form = new FormData();
|
|
80
|
+
form.append('file', new Blob([new Uint8Array(content)]), basename(path));
|
|
81
|
+
form.append('campaign_group_id', String(flags['org-id']));
|
|
82
|
+
const result = await client.postForm('/bulk-operations/convert', form);
|
|
83
|
+
for (const warning of result.warnings)
|
|
84
|
+
this.warn(this.formatIssue(warning));
|
|
85
|
+
if (result.errors.length > 0 || result.request === null) {
|
|
86
|
+
for (const issue of result.errors)
|
|
87
|
+
this.log(this.formatIssue(issue));
|
|
88
|
+
this.error(`Template conversion failed with ${result.errors.length} error(s); nothing was created.`, { exit: 2 });
|
|
89
|
+
}
|
|
90
|
+
return result.request;
|
|
91
|
+
}
|
|
92
|
+
countStructure(body) {
|
|
93
|
+
const counts = { adGroups: 0, ads: 0, campaigns: 0, keywords: 0, negativeKeywords: 0 };
|
|
94
|
+
const campaigns = Array.isArray(body.campaigns) ? body.campaigns : [];
|
|
95
|
+
counts.campaigns = campaigns.length;
|
|
96
|
+
for (const campaign of campaigns) {
|
|
97
|
+
if (typeof campaign !== 'object' || campaign === null)
|
|
98
|
+
continue;
|
|
99
|
+
const campaignNode = campaign;
|
|
100
|
+
counts.negativeKeywords += Array.isArray(campaignNode.negative_keywords) ? campaignNode.negative_keywords.length : 0;
|
|
101
|
+
const adGroups = Array.isArray(campaignNode.ad_groups) ? campaignNode.ad_groups : [];
|
|
102
|
+
counts.adGroups += adGroups.length;
|
|
103
|
+
for (const adGroup of adGroups) {
|
|
104
|
+
if (typeof adGroup !== 'object' || adGroup === null)
|
|
105
|
+
continue;
|
|
106
|
+
const adGroupNode = adGroup;
|
|
107
|
+
counts.keywords += Array.isArray(adGroupNode.keywords) ? adGroupNode.keywords.length : 0;
|
|
108
|
+
counts.negativeKeywords += Array.isArray(adGroupNode.negative_keywords) ? adGroupNode.negative_keywords.length : 0;
|
|
109
|
+
counts.ads += Array.isArray(adGroupNode.ads) ? adGroupNode.ads.length : 0;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return counts;
|
|
113
|
+
}
|
|
114
|
+
formatIssue(issue) {
|
|
115
|
+
const place = [issue.sheet, issue.row === null ? undefined : `row ${issue.row}`, issue.column ?? undefined]
|
|
116
|
+
.filter(Boolean)
|
|
117
|
+
.join(', ');
|
|
118
|
+
return place ? `${place}: ${issue.message}` : issue.message;
|
|
119
|
+
}
|
|
120
|
+
async readStructure(source) {
|
|
121
|
+
let raw;
|
|
122
|
+
if (source === '-') {
|
|
123
|
+
const chunks = [];
|
|
124
|
+
for await (const chunk of process.stdin)
|
|
125
|
+
chunks.push(chunk);
|
|
126
|
+
raw = Buffer.concat(chunks).toString('utf8');
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
try {
|
|
130
|
+
raw = await readFile(source, 'utf8');
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
this.error(`Cannot read file: ${source}`, { exit: 2 });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
return JSON.parse(raw);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
this.error('The structure is not valid JSON.', { exit: 2 });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
report(state) {
|
|
144
|
+
const { counts } = state;
|
|
145
|
+
this.log(`Finished: ${state.status} — ${counts.applied}/${counts.total} applied, ${counts.failed} failed`);
|
|
146
|
+
const failedObjects = state.objects.filter((object) => object.status === 'failed');
|
|
147
|
+
for (const object of failedObjects) {
|
|
148
|
+
printResponse(object, this.log.bind(this));
|
|
149
|
+
this.log('---');
|
|
150
|
+
}
|
|
151
|
+
if (state.status === 'failed')
|
|
152
|
+
this.error('Bulk operation failed.', { exit: 1 });
|
|
153
|
+
if (state.status === 'partial')
|
|
154
|
+
this.warn('Some objects were not created — see the failures above.');
|
|
155
|
+
}
|
|
156
|
+
async waitForCompletion(client, operationId, pollIntervalSeconds, timeoutSeconds) {
|
|
157
|
+
const startedAt = Date.now();
|
|
158
|
+
let lastProgress = '';
|
|
159
|
+
for (;;) {
|
|
160
|
+
const state = await client.get(`/bulk-operations/${operationId}`);
|
|
161
|
+
const progress = `${state.status}: ${state.counts.applied}/${state.counts.total} applied, ${state.counts.failed} failed`;
|
|
162
|
+
if (progress !== lastProgress) {
|
|
163
|
+
this.log(progress);
|
|
164
|
+
lastProgress = progress;
|
|
165
|
+
}
|
|
166
|
+
if (TERMINAL_STATUSES.has(state.status))
|
|
167
|
+
return state;
|
|
168
|
+
if ((Date.now() - startedAt) / 1000 >= timeoutSeconds) {
|
|
169
|
+
this.warn(`Still ${state.status} after ${timeoutSeconds}s — check later with: adapty asa campaigns bulk-status ${operationId}`);
|
|
170
|
+
return state;
|
|
171
|
+
}
|
|
172
|
+
await new Promise((resolve) => {
|
|
173
|
+
setTimeout(resolve, pollIntervalSeconds * 1000);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import type { AsaBulkOperationStateDTO } from '../../../lib/asa-schemas.js';
|
|
3
|
+
export default class AsaCampaignsBulkStatus extends Command {
|
|
4
|
+
static args: {
|
|
5
|
+
'operation-id': import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
6
|
+
};
|
|
7
|
+
static description: string;
|
|
8
|
+
static enableJsonFlag: boolean;
|
|
9
|
+
static examples: string[];
|
|
10
|
+
static flags: {
|
|
11
|
+
page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
'page-size': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
run(): Promise<AsaBulkOperationStateDTO>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Args, Command } from '@oclif/core';
|
|
2
|
+
import { createAsaClient } from '../../../lib/asa-client.js';
|
|
3
|
+
import { asaPaginationFlags } from '../../../lib/asa-flags.js';
|
|
4
|
+
import { isValidUuid, paginationParams } from '../../../lib/flags.js';
|
|
5
|
+
import { printResponse } from '../../../lib/output.js';
|
|
6
|
+
export default class AsaCampaignsBulkStatus extends Command {
|
|
7
|
+
static args = {
|
|
8
|
+
'operation-id': Args.string({ description: 'Bulk operation ID returned by bulk-create', required: true }),
|
|
9
|
+
};
|
|
10
|
+
static description = 'Progress board of one bulk operation: counts, pipelines and the per-object log';
|
|
11
|
+
static enableJsonFlag = true;
|
|
12
|
+
static examples = ['<%= config.bin %> asa campaigns bulk-status 660e8400-e29b-41d4-a716-446655440001'];
|
|
13
|
+
static flags = {
|
|
14
|
+
...asaPaginationFlags,
|
|
15
|
+
};
|
|
16
|
+
async run() {
|
|
17
|
+
const { args, flags } = await this.parse(AsaCampaignsBulkStatus);
|
|
18
|
+
const operationId = args['operation-id'];
|
|
19
|
+
if (!isValidUuid(operationId))
|
|
20
|
+
this.error('Invalid operation ID format.', { exit: 2 });
|
|
21
|
+
const client = await createAsaClient(this.config);
|
|
22
|
+
const state = await client.get(`/bulk-operations/${operationId}`, paginationParams(flags));
|
|
23
|
+
printResponse(state, this.log.bind(this));
|
|
24
|
+
return state;
|
|
25
|
+
}
|
|
26
|
+
}
|