adapty 0.8.4-beta.1 → 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.
Files changed (107) hide show
  1. package/README.md +133 -3
  2. package/bin/dev.js +2 -2
  3. package/bin/run.js +2 -2
  4. package/dist/commands/access-levels/create.js +1 -1
  5. package/dist/commands/access-levels/list.d.ts +1 -1
  6. package/dist/commands/apps/create.js +9 -6
  7. package/dist/commands/apps/list.d.ts +1 -1
  8. package/dist/commands/apps/update.js +7 -4
  9. package/dist/commands/asa/ad-groups/create.js +9 -7
  10. package/dist/commands/asa/ad-groups/get.js +3 -2
  11. package/dist/commands/asa/ad-groups/list.d.ts +1 -1
  12. package/dist/commands/asa/ad-groups/list.js +1 -1
  13. package/dist/commands/asa/ad-groups/update.js +22 -12
  14. package/dist/commands/asa/ads/create.js +7 -5
  15. package/dist/commands/asa/ads/get.js +3 -2
  16. package/dist/commands/asa/ads/list.d.ts +1 -1
  17. package/dist/commands/asa/ads/list.js +1 -1
  18. package/dist/commands/asa/ads/update.js +9 -5
  19. package/dist/commands/asa/apps/list.d.ts +1 -1
  20. package/dist/commands/asa/apps/list.js +1 -1
  21. package/dist/commands/asa/automations/create.d.ts +9 -0
  22. package/dist/commands/asa/automations/create.js +26 -7
  23. package/dist/commands/asa/automations/get.js +3 -2
  24. package/dist/commands/asa/automations/list.d.ts +1 -1
  25. package/dist/commands/asa/automations/list.js +1 -1
  26. package/dist/commands/asa/automations/run.js +5 -3
  27. package/dist/commands/asa/automations/runs.d.ts +1 -1
  28. package/dist/commands/asa/automations/runs.js +4 -4
  29. package/dist/commands/asa/automations/update.d.ts +9 -0
  30. package/dist/commands/asa/automations/update.js +36 -12
  31. package/dist/commands/asa/campaigns/bulk-create.js +31 -18
  32. package/dist/commands/asa/campaigns/bulk-list.js +7 -5
  33. package/dist/commands/asa/campaigns/bulk-status.js +3 -2
  34. package/dist/commands/asa/campaigns/create.js +10 -8
  35. package/dist/commands/asa/campaigns/get.js +3 -2
  36. package/dist/commands/asa/campaigns/list.d.ts +1 -1
  37. package/dist/commands/asa/campaigns/list.js +1 -1
  38. package/dist/commands/asa/campaigns/update.js +25 -15
  39. package/dist/commands/asa/competitors/summary.js +5 -5
  40. package/dist/commands/asa/connect.js +5 -3
  41. package/dist/commands/asa/creatives/list.d.ts +1 -1
  42. package/dist/commands/asa/creatives/list.js +1 -1
  43. package/dist/commands/asa/keywords/add.js +13 -10
  44. package/dist/commands/asa/keywords/list.d.ts +1 -1
  45. package/dist/commands/asa/keywords/list.js +1 -1
  46. package/dist/commands/asa/keywords/update.js +15 -10
  47. package/dist/commands/asa/metrics/index.d.ts +6 -3
  48. package/dist/commands/asa/metrics/index.js +38 -14
  49. package/dist/commands/asa/metrics/overview.js +3 -3
  50. package/dist/commands/asa/negative-keywords/add.js +10 -7
  51. package/dist/commands/asa/negative-keywords/list.d.ts +1 -1
  52. package/dist/commands/asa/negative-keywords/list.js +1 -1
  53. package/dist/commands/asa/orgs/list.d.ts +1 -1
  54. package/dist/commands/asa/orgs/list.js +1 -1
  55. package/dist/commands/asa/product-pages/list.d.ts +1 -1
  56. package/dist/commands/asa/product-pages/list.js +1 -1
  57. package/dist/commands/asa/product-pages/sync.js +1 -1
  58. package/dist/commands/asa/search-terms/list.d.ts +1 -1
  59. package/dist/commands/asa/search-terms/list.js +1 -1
  60. package/dist/commands/asa/whoami.js +7 -2
  61. package/dist/commands/auth/login.js +3 -1
  62. package/dist/commands/flows/config/get.js +2 -2
  63. package/dist/commands/flows/config/preview.d.ts +2 -2
  64. package/dist/commands/flows/config/preview.js +6 -4
  65. package/dist/commands/flows/config/update.js +5 -3
  66. package/dist/commands/flows/config/validate.js +5 -3
  67. package/dist/commands/flows/list.d.ts +1 -1
  68. package/dist/commands/flows/media/upload.js +1 -1
  69. package/dist/commands/flows/publish.js +5 -5
  70. package/dist/commands/paywalls/create.js +1 -1
  71. package/dist/commands/paywalls/list.d.ts +1 -1
  72. package/dist/commands/paywalls/placements.d.ts +2 -2
  73. package/dist/commands/paywalls/update.js +1 -1
  74. package/dist/commands/placements/create.js +17 -12
  75. package/dist/commands/placements/list.d.ts +1 -1
  76. package/dist/commands/placements/update.js +20 -15
  77. package/dist/commands/products/create.js +20 -13
  78. package/dist/commands/products/list.d.ts +1 -1
  79. package/dist/commands/products/update.js +1 -1
  80. package/dist/commands/segments/list.d.ts +1 -1
  81. package/dist/lib/api-client.d.ts +10 -6
  82. package/dist/lib/api-client.js +56 -35
  83. package/dist/lib/api-schemas.d.ts +56 -56
  84. package/dist/lib/app-url.js +2 -1
  85. package/dist/lib/asa-client.d.ts +13 -8
  86. package/dist/lib/asa-client.js +9 -4
  87. package/dist/lib/asa-flags.d.ts +37 -13
  88. package/dist/lib/asa-flags.js +87 -20
  89. package/dist/lib/asa-keyword-action.d.ts +18 -0
  90. package/dist/lib/asa-keyword-action.js +175 -0
  91. package/dist/lib/asa-schemas.d.ts +100 -80
  92. package/dist/lib/auth.js +2 -1
  93. package/dist/lib/client-from-config.d.ts +1 -1
  94. package/dist/lib/client-from-config.js +2 -1
  95. package/dist/lib/config.d.ts +2 -2
  96. package/dist/lib/confirm.d.ts +4 -4
  97. package/dist/lib/confirm.js +12 -7
  98. package/dist/lib/errors.d.ts +18 -18
  99. package/dist/lib/errors.js +14 -10
  100. package/dist/lib/flags.d.ts +3 -3
  101. package/dist/lib/flags.js +2 -1
  102. package/dist/lib/flow-help.js +11 -6
  103. package/dist/lib/output.js +23 -12
  104. package/dist/lib/preview.d.ts +5 -5
  105. package/dist/lib/preview.js +2 -1
  106. package/oclif.manifest.json +372 -200
  107. package/package.json +10 -8
@@ -11,9 +11,10 @@ export default class AsaAutomationsGet extends Command {
11
11
  static examples = ['<%= config.bin %> asa automations get 550e8400-e29b-41d4-a716-446655440000'];
12
12
  async run() {
13
13
  const { args } = await this.parse(AsaAutomationsGet);
14
- if (!isValidUuid(args.automation_id))
14
+ if (!isValidUuid(args.automation_id)) {
15
15
  this.error('Invalid automation ID format.', { exit: 2 });
16
- const client = await createAsaClient(this.config);
16
+ }
17
+ const client = await createAsaClient(this);
17
18
  const result = await client.get(`/automations/${args.automation_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 { AsaAutomationDTO } from '../../../lib/asa-schemas.js';
3
- import { type PaginatedResponse } from '../../../lib/flags.js';
3
+ import type { PaginatedResponse } from '../../../lib/flags.js';
4
4
  export default class AsaAutomationsList extends Command {
5
5
  static description: string;
6
6
  static enableJsonFlag: boolean;
@@ -10,7 +10,7 @@ export default class AsaAutomationsList extends Command {
10
10
  static flags = { ...asaPaginationFlags };
11
11
  async run() {
12
12
  const { flags } = await this.parse(AsaAutomationsList);
13
- const client = await createAsaClient(this.config);
13
+ const client = await createAsaClient(this);
14
14
  const result = await client.get('/automations', paginationParams(flags));
15
15
  printList(result.data, this.log.bind(this), result.meta.pagination);
16
16
  return result;
@@ -20,8 +20,9 @@ export default class AsaAutomationsRun extends Command {
20
20
  };
21
21
  async run() {
22
22
  const { args, flags } = await this.parse(AsaAutomationsRun);
23
- if (!isValidUuid(args.automation_id))
23
+ if (!isValidUuid(args.automation_id)) {
24
24
  this.error('Invalid automation ID format.', { exit: 2 });
25
+ }
25
26
  if (!flags['dry-run']) {
26
27
  await confirmMutation(this, {
27
28
  method: 'POST',
@@ -29,11 +30,12 @@ export default class AsaAutomationsRun extends Command {
29
30
  summary: 'Run the rule for real — it applies its actions to Apple',
30
31
  }, flags.yes);
31
32
  }
32
- const client = await createAsaClient(this.config);
33
+ const client = await createAsaClient(this);
33
34
  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
35
  noteReplay(replayed, this.log.bind(this));
35
- if (!replayed)
36
+ if (!replayed) {
36
37
  this.log(flags['dry-run'] ? 'Dry run queued.' : 'Run queued.');
38
+ }
37
39
  this.log(`Run ID: ${result.run_id ?? 'unknown'}`);
38
40
  this.log(`Follow it with: ${this.config.bin} asa automations runs ${args.automation_id}`);
39
41
  return result;
@@ -1,6 +1,6 @@
1
1
  import { Command } from '@oclif/core';
2
2
  import type { AsaAutomationRunDTO } from '../../../lib/asa-schemas.js';
3
- import { type PaginatedResponse } from '../../../lib/flags.js';
3
+ import type { PaginatedResponse } from '../../../lib/flags.js';
4
4
  export default class AsaAutomationsRuns extends Command {
5
5
  static args: {
6
6
  automation_id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
@@ -1,8 +1,7 @@
1
1
  import { Args, Command } from '@oclif/core';
2
2
  import { createAsaClient } from '../../../lib/asa-client.js';
3
3
  import { asaPaginationFlags } from '../../../lib/asa-flags.js';
4
- import { isValidUuid } from '../../../lib/flags.js';
5
- import { paginationParams } from '../../../lib/flags.js';
4
+ import { isValidUuid, paginationParams } from '../../../lib/flags.js';
6
5
  import { printList } from '../../../lib/output.js';
7
6
  export default class AsaAutomationsRuns extends Command {
8
7
  static args = {
@@ -14,9 +13,10 @@ export default class AsaAutomationsRuns extends Command {
14
13
  static flags = { ...asaPaginationFlags };
15
14
  async run() {
16
15
  const { args, flags } = await this.parse(AsaAutomationsRuns);
17
- if (!isValidUuid(args.automation_id))
16
+ if (!isValidUuid(args.automation_id)) {
18
17
  this.error('Invalid automation ID format.', { exit: 2 });
19
- const client = await createAsaClient(this.config);
18
+ }
19
+ const client = await createAsaClient(this);
20
20
  const result = await client.get(`/automations/${args.automation_id}/runs`, paginationParams(flags));
21
21
  printList(result.data, this.log.bind(this), result.meta.pagination);
22
22
  return result;
@@ -14,8 +14,17 @@ export default class AsaAutomationsUpdate extends Command {
14
14
  stop: import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
15
  'idempotency-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
16
  yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
+ 'cpt-bid': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
+ 'cpt-bid-type': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
+ 'match-type': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
20
+ negate: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
21
+ 'no-negate': import("@oclif/core/interfaces").BooleanFlag<boolean>;
22
+ 'pause-original': import("@oclif/core/interfaces").BooleanFlag<boolean>;
23
+ 'skip-enable-duplicates': import("@oclif/core/interfaces").BooleanFlag<boolean>;
24
+ 'target-ad-group': import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
25
  };
18
26
  run(): Promise<AsaAutomationMutationDTO>;
19
27
  private readRule;
20
28
  private readStdin;
29
+ private rebuildAction;
21
30
  }
@@ -1,7 +1,8 @@
1
- import { Args, Command, Flags } from '@oclif/core';
2
1
  import { readFile } from 'node:fs/promises';
2
+ import { Args, Command, Flags } from '@oclif/core';
3
3
  import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
4
- import { idempotencyFlags } from '../../../lib/asa-flags.js';
4
+ import { addKeywordActionFlags, idempotencyFlags } from '../../../lib/asa-flags.js';
5
+ import { hasAddKeywordActionFlags, rebuildAddKeywordAction, } from '../../../lib/asa-keyword-action.js';
5
6
  import { confirmFlags, confirmMutation } from '../../../lib/confirm.js';
6
7
  import { isValidUuid } from '../../../lib/flags.js';
7
8
  import { printResponse } from '../../../lib/output.js';
@@ -9,13 +10,15 @@ export default class AsaAutomationsUpdate extends Command {
9
10
  static args = {
10
11
  automation_id: Args.string({ description: 'Automation rule ID (UUID)', required: true }),
11
12
  };
12
- static description = 'Change an automation rule: stop it, rename it, or replace parts of the rule';
13
+ static description = 'Change an automation rule: stop it, rename it, or replace parts of the rule. An add-as-keyword action flag turns the call into a read-modify-write: the rule is read, actions[0].params is rebuilt from the flags and the whole actions list is written back — an edit someone makes in the dashboard in between is overwritten. This is also the way to repair a rule whose stored params carry the wrong shape.';
13
14
  static enableJsonFlag = true;
14
15
  static examples = [
15
16
  '<%= config.bin %> asa automations update UUID --stop',
16
17
  '<%= config.bin %> asa automations update UUID --file rule.json',
18
+ '<%= config.bin %> asa automations update UUID --target-ad-group AD_GROUP_ID --match-type EXACT --cpt-bid-type search_term_current_cpt',
17
19
  ];
18
20
  static flags = {
21
+ ...addKeywordActionFlags,
19
22
  ...confirmFlags,
20
23
  ...idempotencyFlags,
21
24
  file: Flags.string({ description: 'JSON file with the parts to change, or - to read stdin' }),
@@ -25,27 +28,36 @@ export default class AsaAutomationsUpdate extends Command {
25
28
  };
26
29
  async run() {
27
30
  const { args, flags } = await this.parse(AsaAutomationsUpdate);
28
- if (!isValidUuid(args.automation_id))
31
+ if (!isValidUuid(args.automation_id)) {
29
32
  this.error('Invalid automation ID format.', { exit: 2 });
33
+ }
30
34
  const body = flags.file ? await this.readRule(flags.file) : {};
31
- if (flags.name !== undefined)
35
+ if (flags.name !== undefined) {
32
36
  body.name = flags.name;
33
- if (flags.start)
37
+ }
38
+ if (flags.start) {
34
39
  body.status = 1;
35
- if (flags.stop)
40
+ }
41
+ if (flags.stop) {
36
42
  body.status = 0;
37
- if (Object.keys(body).length === 0) {
38
- this.error('Nothing to change. Pass --stop, --start, --name or --file.', { exit: 2 });
43
+ }
44
+ const actionFlags = hasAddKeywordActionFlags(flags);
45
+ if (Object.keys(body).length === 0 && !actionFlags) {
46
+ this.error('Nothing to change. Pass --stop, --start, --name, --file or an action flag.', { exit: 2 });
39
47
  }
40
48
  if ('internal_id' in body) {
41
49
  this.error('Remove internal_id from the file: the rule ID comes from the command line.', { exit: 2 });
42
50
  }
51
+ const client = await createAsaClient(this);
52
+ if (actionFlags) {
53
+ await this.rebuildAction(client, args.automation_id, body, flags);
54
+ }
43
55
  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
56
  const { replayed, result } = await asaWrite(client, 'put', `/automations/${args.automation_id}`, { body, idempotencyKey: flags['idempotency-key'] });
46
57
  noteReplay(replayed, this.log.bind(this));
47
- if (result.automation && !replayed)
58
+ if (result.automation && !replayed) {
48
59
  this.log('Automation updated!');
60
+ }
49
61
  printResponse(result, this.log.bind(this));
50
62
  return result;
51
63
  }
@@ -66,8 +78,20 @@ export default class AsaAutomationsUpdate extends Command {
66
78
  }
67
79
  async readStdin() {
68
80
  const chunks = [];
69
- for await (const chunk of process.stdin)
81
+ for await (const chunk of process.stdin) {
70
82
  chunks.push(chunk);
83
+ }
71
84
  return Buffer.concat(chunks).toString('utf8');
72
85
  }
86
+ // Read-modify-write: the API replaces `actions` wholesale, so the whole list has to be sent back.
87
+ async rebuildAction(client, automationId, body, flags) {
88
+ const rule = await client.get(`/automations/${automationId}`);
89
+ try {
90
+ // eslint-disable-next-line @stylistic/max-len -- FIXME if you see this
91
+ body.actions = rebuildAddKeywordAction(body.actions ?? rule.actions, body.operate_with ?? rule.operate_with, flags);
92
+ }
93
+ catch (error) {
94
+ this.error(error instanceof Error ? error.message : String(error), { exit: 2 });
95
+ }
96
+ }
73
97
  }
@@ -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.config);
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((object) => object.status === 'failed');
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
- if (state.status === 'partial')
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 = "This company's bulk operations, newest first; inspect one with bulk-status";
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.config);
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
- const client = await createAsaClient(this.config);
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.config);
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
- const client = await createAsaClient(this.config);
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 { type PaginatedResponse } from '../../../lib/flags.js';
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.config);
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
- if (flags.status !== undefined)
42
+ }
43
+ if (flags.status !== undefined) {
42
44
  body.status = flags.status;
43
- if (flags.country !== undefined)
45
+ }
46
+ if (flags.country !== undefined) {
44
47
  body.countries_or_regions = flags.country;
45
- if (flags['daily-budget'] !== undefined)
48
+ }
49
+ if (flags['daily-budget'] !== undefined) {
46
50
  body.daily_budget_amount = money(flags['daily-budget'], flags.currency);
47
- if (flags.budget !== undefined)
51
+ }
52
+ if (flags.budget !== undefined) {
48
53
  body.budget_amount = money(flags.budget, flags.currency);
49
- if (flags['target-cpa'] !== undefined)
54
+ }
55
+ if (flags['target-cpa'] !== undefined) {
50
56
  body.target_cpa = money(flags['target-cpa'], flags.currency);
51
- if (flags['bidding-strategy'] !== undefined)
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.config);
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((part) => part.trim())
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((id) => !/^\d+$/.test(id))) {
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.config);
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((app) => ({
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((term) => ({
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.config);
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 === true)
20
+ if (process.stdin.isTTY) {
21
21
  await open(authUrl).catch(() => false);
22
- if (!flags.wait)
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 { type PaginatedResponse } from '../../../lib/flags.js';
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;