adapty 0.8.3 → 0.8.4
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/dist/commands/asa/ad-groups/create.js +1 -1
- package/dist/commands/asa/ad-groups/update.js +1 -1
- package/dist/commands/asa/ads/create.js +1 -1
- package/dist/commands/asa/ads/update.js +1 -1
- package/dist/commands/asa/automations/create.js +1 -1
- package/dist/commands/asa/automations/run.js +1 -1
- package/dist/commands/asa/automations/update.js +1 -1
- package/dist/commands/asa/campaigns/bulk-create.js +1 -1
- package/dist/commands/asa/campaigns/create.js +1 -1
- package/dist/commands/asa/campaigns/update.js +1 -1
- package/dist/commands/asa/keywords/add.js +1 -1
- package/dist/commands/asa/keywords/update.js +1 -1
- package/dist/commands/asa/negative-keywords/add.js +1 -1
- package/dist/commands/asa/product-pages/sync.js +1 -1
- package/dist/commands/flows/config/get.js +2 -1
- package/dist/commands/flows/publish.d.ts +15 -0
- package/dist/commands/flows/publish.js +52 -0
- package/dist/commands/flows/update.d.ts +15 -0
- package/dist/commands/flows/update.js +28 -0
- package/dist/commands/placements/create.js +29 -4
- package/dist/commands/placements/update.js +29 -4
- package/dist/lib/api-schemas.d.ts +20 -1
- package/dist/lib/flow-help.d.ts +12 -0
- package/dist/lib/flow-help.js +27 -0
- package/dist/lib/placement-audiences.d.ts +6 -0
- package/dist/lib/placement-audiences.js +25 -0
- package/oclif.manifest.json +783 -674
- package/package.json +3 -2
- /package/dist/lib/{asa-confirm.d.ts → confirm.d.ts} +0 -0
- /package/dist/lib/{asa-confirm.js → confirm.js} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command, Flags } from '@oclif/core';
|
|
2
2
|
import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
|
|
3
|
-
import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
|
|
4
3
|
import { currencyFlag, idempotencyFlags, money, moneyFlag, pricingModelFlag, scheduleFlags, startOfDayUtc, todayUtc, } from '../../../lib/asa-flags.js';
|
|
4
|
+
import { confirmFlags, confirmMutation } from '../../../lib/confirm.js';
|
|
5
5
|
import { isValidUuid } from '../../../lib/flags.js';
|
|
6
6
|
import { printResponse } from '../../../lib/output.js';
|
|
7
7
|
export default class AsaAdGroupsCreate extends Command {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Args, Command, Flags } from '@oclif/core';
|
|
2
2
|
import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
|
|
3
|
-
import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
|
|
4
3
|
import { currencyFlag, idempotencyFlags, money, moneyFlag, scheduleFlags, startOfDayUtc } from '../../../lib/asa-flags.js';
|
|
4
|
+
import { confirmFlags, confirmMutation } from '../../../lib/confirm.js';
|
|
5
5
|
import { isValidUuid } from '../../../lib/flags.js';
|
|
6
6
|
import { printResponse } from '../../../lib/output.js';
|
|
7
7
|
export default class AsaAdGroupsUpdate extends Command {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command, Flags } from '@oclif/core';
|
|
2
2
|
import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
|
|
3
|
-
import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
|
|
4
3
|
import { idempotencyFlags } from '../../../lib/asa-flags.js';
|
|
4
|
+
import { confirmFlags, confirmMutation } from '../../../lib/confirm.js';
|
|
5
5
|
import { isValidUuid } from '../../../lib/flags.js';
|
|
6
6
|
import { printResponse } from '../../../lib/output.js';
|
|
7
7
|
export default class AsaAdsCreate extends Command {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Args, Command, Flags } from '@oclif/core';
|
|
2
2
|
import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
|
|
3
|
-
import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
|
|
4
3
|
import { idempotencyFlags } from '../../../lib/asa-flags.js';
|
|
4
|
+
import { confirmFlags, confirmMutation } from '../../../lib/confirm.js';
|
|
5
5
|
import { isValidUuid } from '../../../lib/flags.js';
|
|
6
6
|
import { printResponse } from '../../../lib/output.js';
|
|
7
7
|
export default class AsaAdsUpdate extends Command {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Command, Flags } from '@oclif/core';
|
|
2
2
|
import { readFile } from 'node:fs/promises';
|
|
3
3
|
import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
|
|
4
|
-
import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
|
|
5
4
|
import { addKeywordActionFlags, idempotencyFlags } from '../../../lib/asa-flags.js';
|
|
6
5
|
import { hasAddKeywordActionFlags, rebuildAddKeywordAction, requireSingleAction, } from '../../../lib/asa-keyword-action.js';
|
|
6
|
+
import { confirmFlags, confirmMutation } from '../../../lib/confirm.js';
|
|
7
7
|
import { printResponse } from '../../../lib/output.js';
|
|
8
8
|
export default class AsaAutomationsCreate extends Command {
|
|
9
9
|
static description = 'Create an automation rule from a JSON rule file. The add-as-keyword action flags fill in or override actions[0].params, so the bid, the match type and the target ad groups need no hand-written JSON.';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Args, Command, Flags } from '@oclif/core';
|
|
2
2
|
import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
|
|
3
|
-
import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
|
|
4
3
|
import { idempotencyFlags } from '../../../lib/asa-flags.js';
|
|
4
|
+
import { confirmFlags, confirmMutation } from '../../../lib/confirm.js';
|
|
5
5
|
import { isValidUuid } from '../../../lib/flags.js';
|
|
6
6
|
export default class AsaAutomationsRun extends Command {
|
|
7
7
|
static args = {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Args, Command, Flags } from '@oclif/core';
|
|
2
2
|
import { readFile } from 'node:fs/promises';
|
|
3
3
|
import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
|
|
4
|
-
import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
|
|
5
4
|
import { addKeywordActionFlags, idempotencyFlags } from '../../../lib/asa-flags.js';
|
|
6
5
|
import { hasAddKeywordActionFlags, rebuildAddKeywordAction, } from '../../../lib/asa-keyword-action.js';
|
|
6
|
+
import { confirmFlags, confirmMutation } from '../../../lib/confirm.js';
|
|
7
7
|
import { isValidUuid } from '../../../lib/flags.js';
|
|
8
8
|
import { printResponse } from '../../../lib/output.js';
|
|
9
9
|
export default class AsaAutomationsUpdate extends Command {
|
|
@@ -2,8 +2,8 @@ import { Command, Flags } from '@oclif/core';
|
|
|
2
2
|
import { readFile } from 'node:fs/promises';
|
|
3
3
|
import { basename } from 'node:path';
|
|
4
4
|
import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
|
|
5
|
-
import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
|
|
6
5
|
import { idempotencyFlags } from '../../../lib/asa-flags.js';
|
|
6
|
+
import { confirmFlags, confirmMutation } from '../../../lib/confirm.js';
|
|
7
7
|
import { printResponse } from '../../../lib/output.js';
|
|
8
8
|
const TERMINAL_STATUSES = new Set(['failed', 'partial', 'success']);
|
|
9
9
|
export default class AsaCampaignsBulkCreate extends Command {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command, Flags } from '@oclif/core';
|
|
2
2
|
import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
|
|
3
|
-
import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
|
|
4
3
|
import { currencyFlag, idempotencyFlags, invoiceFlags, locInvoiceDetails, money, moneyFlag, reportServingState, } from '../../../lib/asa-flags.js';
|
|
4
|
+
import { confirmFlags, confirmMutation } from '../../../lib/confirm.js';
|
|
5
5
|
import { isValidUuid } from '../../../lib/flags.js';
|
|
6
6
|
import { printResponse } from '../../../lib/output.js';
|
|
7
7
|
export default class AsaCampaignsCreate extends Command {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Args, Command, Flags } from '@oclif/core';
|
|
2
2
|
import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
|
|
3
|
-
import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
|
|
4
3
|
import { currencyFlag, idempotencyFlags, invoiceFlags, locInvoiceDetails, money, moneyFlag, reportServingState, } from '../../../lib/asa-flags.js';
|
|
4
|
+
import { confirmFlags, confirmMutation } from '../../../lib/confirm.js';
|
|
5
5
|
import { isValidUuid } from '../../../lib/flags.js';
|
|
6
6
|
import { printResponse } from '../../../lib/output.js';
|
|
7
7
|
export default class AsaCampaignsUpdate extends Command {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Command, Flags } from '@oclif/core';
|
|
2
2
|
import { readFile } from 'node:fs/promises';
|
|
3
3
|
import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
|
|
4
|
-
import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
|
|
5
4
|
import { currencyFlag, idempotencyFlags, MAX_BULK_ITEMS, money, moneyFlag, reportBulkOutcome } from '../../../lib/asa-flags.js';
|
|
5
|
+
import { confirmFlags, confirmMutation } from '../../../lib/confirm.js';
|
|
6
6
|
import { isValidUuid } from '../../../lib/flags.js';
|
|
7
7
|
export default class AsaKeywordsAdd extends Command {
|
|
8
8
|
static description = 'Add targeting keywords to an ad group';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Args, Command, Flags } from '@oclif/core';
|
|
2
2
|
import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
|
|
3
|
-
import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
|
|
4
3
|
import { currencyFlag, idempotencyFlags, money, moneyFlag, reportBulkOutcome } from '../../../lib/asa-flags.js';
|
|
4
|
+
import { confirmFlags, confirmMutation } from '../../../lib/confirm.js';
|
|
5
5
|
import { isValidUuid } from '../../../lib/flags.js';
|
|
6
6
|
export default class AsaKeywordsUpdate extends Command {
|
|
7
7
|
static args = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command, Flags } from '@oclif/core';
|
|
2
2
|
import { asaWrite, createAsaClient, noteReplay } from '../../../lib/asa-client.js';
|
|
3
|
-
import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
|
|
4
3
|
import { idempotencyFlags, MAX_BULK_ITEMS, reportBulkOutcome } from '../../../lib/asa-flags.js';
|
|
4
|
+
import { confirmFlags, confirmMutation } from '../../../lib/confirm.js';
|
|
5
5
|
import { isValidUuid } from '../../../lib/flags.js';
|
|
6
6
|
export default class AsaNegativeKeywordsAdd extends Command {
|
|
7
7
|
static description = 'Add negative keywords to an ad group or across a campaign';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command, Flags } from '@oclif/core';
|
|
2
2
|
import { asaWrite, createAsaClient } from '../../../lib/asa-client.js';
|
|
3
|
-
import { confirmFlags, confirmMutation } from '../../../lib/asa-confirm.js';
|
|
4
3
|
import { idempotencyFlags } from '../../../lib/asa-flags.js';
|
|
4
|
+
import { confirmFlags, confirmMutation } from '../../../lib/confirm.js';
|
|
5
5
|
import { printResponse } from '../../../lib/output.js';
|
|
6
6
|
export default class AsaProductPagesSync extends Command {
|
|
7
7
|
static description = 'Refresh custom product pages from Apple';
|
|
@@ -6,7 +6,8 @@ export default class FlowsConfigGet extends Command {
|
|
|
6
6
|
static args = {
|
|
7
7
|
flow_id: Args.string({ description: 'Flow ID (UUID)', required: true }),
|
|
8
8
|
};
|
|
9
|
-
static description = 'Read the flow builder config (404 until the config has been written at least once)'
|
|
9
|
+
static description = 'Read the flow builder config (404 until the config has been written at least once). ' +
|
|
10
|
+
'On a failed publish, `publication_status`, `transform_error` and `publication_error` show why.';
|
|
10
11
|
static enableJsonFlag = true;
|
|
11
12
|
static examples = ['<%= config.bin %> flows config get --app UUID 550e8400-e29b-41d4-a716-446655440000'];
|
|
12
13
|
static flags = {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import type { FlowDTO } from '../../lib/api-schemas.js';
|
|
3
|
+
export default class FlowsPublish extends Command {
|
|
4
|
+
static args: {
|
|
5
|
+
flow_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
|
+
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
run(): Promise<FlowDTO>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Args, Command } from '@oclif/core';
|
|
2
|
+
import { createAuthenticatedClient } from '../../lib/client-from-config.js';
|
|
3
|
+
import { confirmFlags, confirmMutation } from '../../lib/confirm.js';
|
|
4
|
+
import { ApiError } from '../../lib/errors.js';
|
|
5
|
+
import { appFlag, isValidUuid } from '../../lib/flags.js';
|
|
6
|
+
import { flowFixLinks } from '../../lib/flow-help.js';
|
|
7
|
+
import { printResponse } from '../../lib/output.js';
|
|
8
|
+
export default class FlowsPublish extends Command {
|
|
9
|
+
static args = {
|
|
10
|
+
flow_id: Args.string({ description: 'Flow ID (UUID)', required: true }),
|
|
11
|
+
};
|
|
12
|
+
static description = 'Publish a flow (async — publication completes in the background). ' +
|
|
13
|
+
'Publishing is async: returns status `publishing`. Poll `adapty flows get <flow_id>` for `published`/`publication_failed`; on failure, `adapty flows config get <flow_id>` shows why.';
|
|
14
|
+
static enableJsonFlag = true;
|
|
15
|
+
static examples = ['<%= config.bin %> flows publish --app UUID 550e8400-e29b-41d4-a716-446655440000'];
|
|
16
|
+
static flags = {
|
|
17
|
+
...appFlag,
|
|
18
|
+
...confirmFlags,
|
|
19
|
+
};
|
|
20
|
+
async run() {
|
|
21
|
+
const { args, flags } = await this.parse(FlowsPublish);
|
|
22
|
+
if (!isValidUuid(args.flow_id)) {
|
|
23
|
+
this.error('Invalid flow ID format.', { exit: 2 });
|
|
24
|
+
}
|
|
25
|
+
const client = await createAuthenticatedClient(this.config);
|
|
26
|
+
const flow = await client.get(`/apps/${flags.app}/flows/${args.flow_id}/`);
|
|
27
|
+
await confirmMutation(this, {
|
|
28
|
+
method: 'POST',
|
|
29
|
+
path: `/apps/${flags.app}/flows/${args.flow_id}/publish/`,
|
|
30
|
+
summary: `Publish flow "${flow.name}" live to end users`,
|
|
31
|
+
}, flags.yes);
|
|
32
|
+
let result;
|
|
33
|
+
try {
|
|
34
|
+
result = await client.post(`/apps/${flags.app}/flows/${args.flow_id}/publish/`);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (error instanceof ApiError && (error.statusCode === 400 || error.errorCode === 'validation_error')) {
|
|
38
|
+
this.error(`Flow publish failed: ${error.message}\n` +
|
|
39
|
+
'Publishing needs a flow with a valid config. Fix it, then run `adapty flows publish` again:\n' +
|
|
40
|
+
flowFixLinks(args.flow_id), { exit: 1 });
|
|
41
|
+
}
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
if (!this.jsonEnabled()) {
|
|
45
|
+
this.log('Publishing started — status: publishing. This is asynchronous; the flow is NOT published yet.');
|
|
46
|
+
this.log(`Check progress: adapty flows get --app ${flags.app} ${args.flow_id} (wait for status 'published' or 'publication_failed')`);
|
|
47
|
+
this.log(`If it fails: adapty flows config get --app ${flags.app} ${args.flow_id} (shows why)`);
|
|
48
|
+
}
|
|
49
|
+
printResponse(result, this.log.bind(this));
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import type { FlowDTO } from '../../lib/api-schemas.js';
|
|
3
|
+
export default class FlowsUpdate extends Command {
|
|
4
|
+
static args: {
|
|
5
|
+
flow_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
|
+
name: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
run(): Promise<FlowDTO>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { createAuthenticatedClient } from '../../lib/client-from-config.js';
|
|
3
|
+
import { appFlag, isValidUuid } from '../../lib/flags.js';
|
|
4
|
+
import { printResponse } from '../../lib/output.js';
|
|
5
|
+
export default class FlowsUpdate extends Command {
|
|
6
|
+
static args = {
|
|
7
|
+
flow_id: Args.string({ description: 'Flow ID (UUID)', required: true }),
|
|
8
|
+
};
|
|
9
|
+
static description = 'Rename a flow';
|
|
10
|
+
static enableJsonFlag = true;
|
|
11
|
+
static examples = ['<%= config.bin %> flows update --app UUID 550e8400-e29b-41d4-a716-446655440000 --name "Onboarding v2"'];
|
|
12
|
+
static flags = {
|
|
13
|
+
...appFlag,
|
|
14
|
+
name: Flags.string({ description: 'New flow name', required: true }),
|
|
15
|
+
};
|
|
16
|
+
async run() {
|
|
17
|
+
const { args, flags } = await this.parse(FlowsUpdate);
|
|
18
|
+
if (!isValidUuid(args.flow_id)) {
|
|
19
|
+
this.error('Invalid flow ID format.', { exit: 2 });
|
|
20
|
+
}
|
|
21
|
+
const body = { name: flags.name };
|
|
22
|
+
const client = await createAuthenticatedClient(this.config);
|
|
23
|
+
const result = await client.put(`/apps/${flags.app}/flows/${args.flow_id}/`, body);
|
|
24
|
+
this.log('Flow updated!');
|
|
25
|
+
printResponse(result, this.log.bind(this));
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
import { Command, Flags } from '@oclif/core';
|
|
2
2
|
import { createAuthenticatedClient } from '../../lib/client-from-config.js';
|
|
3
3
|
import { appFlag } from '../../lib/flags.js';
|
|
4
|
+
import { draftFlowError } from '../../lib/flow-help.js';
|
|
4
5
|
import { printResponse } from '../../lib/output.js';
|
|
6
|
+
import { audienceEntryProblem } from '../../lib/placement-audiences.js';
|
|
5
7
|
export default class PlacementsCreate extends Command {
|
|
6
8
|
static description = 'Create a placement with a paywall';
|
|
7
9
|
static enableJsonFlag = true;
|
|
8
10
|
static examples = [
|
|
9
|
-
'<%= config.bin %> placements create --app UUID --title "Default" --developer-id default --audiences \'[{"segment_ids":[],"paywall_id":"PAYWALL_UUID","priority":0}]\'',
|
|
11
|
+
'<%= config.bin %> placements create --app UUID --title "Default" --developer-id default --audiences \'[{"content_type":"paywall","segment_ids":[],"paywall_id":"PAYWALL_UUID","priority":0}]\'',
|
|
12
|
+
'<%= config.bin %> placements create --app UUID --title "Default" --developer-id default --audiences \'[{"content_type":"flow","segment_ids":[],"flow_id":"FLOW_UUID","priority":0}]\'',
|
|
10
13
|
'<%= config.bin %> placements create --app UUID --title "Default" --developer-id default --paywall-id PAYWALL_UUID',
|
|
11
14
|
];
|
|
12
15
|
static flags = {
|
|
13
16
|
...appFlag,
|
|
14
17
|
audiences: Flags.string({
|
|
15
|
-
description: 'JSON array of audience entries
|
|
18
|
+
description: 'JSON array of audience entries. Every entry needs an explicit content_type. ' +
|
|
19
|
+
'Paywall: {content_type:"paywall", segment_ids, paywall_id, priority}. ' +
|
|
20
|
+
'Flow: {content_type:"flow", segment_ids, flow_id, priority}. ' +
|
|
21
|
+
'A flow must be published first (flows publish) — attaching a draft flow returns 400.',
|
|
16
22
|
exactlyOne: ['paywall-id', 'audiences'],
|
|
17
23
|
}),
|
|
18
24
|
'developer-id': Flags.string({ description: 'Developer ID for the placement', required: true }),
|
|
@@ -36,15 +42,34 @@ export default class PlacementsCreate extends Command {
|
|
|
36
42
|
body.paywall_id = flags['paywall-id'];
|
|
37
43
|
}
|
|
38
44
|
else {
|
|
45
|
+
let parsed;
|
|
39
46
|
try {
|
|
40
|
-
|
|
47
|
+
parsed = JSON.parse(flags.audiences);
|
|
41
48
|
}
|
|
42
49
|
catch (error) {
|
|
43
50
|
this.error(`Invalid --audiences JSON: ${error instanceof Error ? error.message : String(error)}`, { exit: 2 });
|
|
44
51
|
}
|
|
52
|
+
if (!Array.isArray(parsed)) {
|
|
53
|
+
this.error('--audiences must be a JSON array of audience entries.', { exit: 2 });
|
|
54
|
+
}
|
|
55
|
+
for (const [index, entry] of parsed.entries()) {
|
|
56
|
+
const problem = audienceEntryProblem(entry);
|
|
57
|
+
if (problem)
|
|
58
|
+
this.error(`--audiences[${index}]: ${problem}`, { exit: 2 });
|
|
59
|
+
}
|
|
60
|
+
body.audiences = parsed;
|
|
45
61
|
}
|
|
46
62
|
const client = await createAuthenticatedClient(this.config);
|
|
47
|
-
|
|
63
|
+
let result;
|
|
64
|
+
try {
|
|
65
|
+
result = await client.post(`/apps/${flags.app}/placements`, body);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
const message = draftFlowError(error, flags.app, body.audiences);
|
|
69
|
+
if (message)
|
|
70
|
+
this.error(message, { exit: 2 });
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
48
73
|
this.log('Placement created!');
|
|
49
74
|
printResponse(result, this.log.bind(this));
|
|
50
75
|
return result;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Args, Command, Flags } from '@oclif/core';
|
|
2
2
|
import { createAuthenticatedClient } from '../../lib/client-from-config.js';
|
|
3
3
|
import { appFlag, isValidUuid } from '../../lib/flags.js';
|
|
4
|
+
import { draftFlowError } from '../../lib/flow-help.js';
|
|
4
5
|
import { printResponse } from '../../lib/output.js';
|
|
6
|
+
import { audienceEntryProblem } from '../../lib/placement-audiences.js';
|
|
5
7
|
export default class PlacementsUpdate extends Command {
|
|
6
8
|
static args = {
|
|
7
9
|
placement_id: Args.string({ description: 'Placement ID (UUID)', required: true }),
|
|
@@ -9,13 +11,17 @@ export default class PlacementsUpdate extends Command {
|
|
|
9
11
|
static description = 'Update a placement';
|
|
10
12
|
static enableJsonFlag = true;
|
|
11
13
|
static examples = [
|
|
12
|
-
'<%= config.bin %> placements update --app UUID 550e8400-... --title "Default" --developer-id default --audiences \'[{"segment_ids":[],"paywall_id":"PAYWALL_UUID","priority":0}]\'',
|
|
14
|
+
'<%= config.bin %> placements update --app UUID 550e8400-... --title "Default" --developer-id default --audiences \'[{"content_type":"paywall","segment_ids":[],"paywall_id":"PAYWALL_UUID","priority":0}]\'',
|
|
15
|
+
'<%= config.bin %> placements update --app UUID 550e8400-... --title "Default" --developer-id default --audiences \'[{"content_type":"flow","segment_ids":[],"flow_id":"FLOW_UUID","priority":0}]\'',
|
|
13
16
|
'<%= config.bin %> placements update --app UUID 550e8400-... --title "Default" --developer-id default --paywall-id PAYWALL_UUID',
|
|
14
17
|
];
|
|
15
18
|
static flags = {
|
|
16
19
|
...appFlag,
|
|
17
20
|
audiences: Flags.string({
|
|
18
|
-
description: 'JSON array of audience entries
|
|
21
|
+
description: 'JSON array of audience entries. Every entry needs an explicit content_type. ' +
|
|
22
|
+
'Paywall: {content_type:"paywall", segment_ids, paywall_id, priority}. ' +
|
|
23
|
+
'Flow: {content_type:"flow", segment_ids, flow_id, priority}. ' +
|
|
24
|
+
'A flow must be published first (flows publish) — attaching a draft flow returns 400.',
|
|
19
25
|
exactlyOne: ['paywall-id', 'audiences'],
|
|
20
26
|
}),
|
|
21
27
|
'developer-id': Flags.string({ description: 'Developer ID for the placement', required: true }),
|
|
@@ -45,15 +51,34 @@ export default class PlacementsUpdate extends Command {
|
|
|
45
51
|
body.paywall_id = flags['paywall-id'];
|
|
46
52
|
}
|
|
47
53
|
else {
|
|
54
|
+
let parsed;
|
|
48
55
|
try {
|
|
49
|
-
|
|
56
|
+
parsed = JSON.parse(flags.audiences);
|
|
50
57
|
}
|
|
51
58
|
catch (error) {
|
|
52
59
|
this.error(`Invalid --audiences JSON: ${error instanceof Error ? error.message : String(error)}`, { exit: 2 });
|
|
53
60
|
}
|
|
61
|
+
if (!Array.isArray(parsed)) {
|
|
62
|
+
this.error('--audiences must be a JSON array of audience entries.', { exit: 2 });
|
|
63
|
+
}
|
|
64
|
+
for (const [index, entry] of parsed.entries()) {
|
|
65
|
+
const problem = audienceEntryProblem(entry);
|
|
66
|
+
if (problem)
|
|
67
|
+
this.error(`--audiences[${index}]: ${problem}`, { exit: 2 });
|
|
68
|
+
}
|
|
69
|
+
body.audiences = parsed;
|
|
54
70
|
}
|
|
55
71
|
const client = await createAuthenticatedClient(this.config);
|
|
56
|
-
|
|
72
|
+
let result;
|
|
73
|
+
try {
|
|
74
|
+
result = await client.put(`/apps/${flags.app}/placements/${args.placement_id}`, body);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
const message = draftFlowError(error, flags.app, body.audiences);
|
|
78
|
+
if (message)
|
|
79
|
+
this.error(message, { exit: 2 });
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
57
82
|
this.log('Placement updated!');
|
|
58
83
|
printResponse(result, this.log.bind(this));
|
|
59
84
|
return result;
|
|
@@ -87,8 +87,14 @@ export interface FlowRemoteConfigDTO {
|
|
|
87
87
|
}
|
|
88
88
|
export interface FlowConfigDTO {
|
|
89
89
|
config: Record<string, unknown>;
|
|
90
|
+
/** Human message for a failed publication (raw passthrough from the flow version). */
|
|
91
|
+
publication_error?: null | string;
|
|
92
|
+
/** Publication lifecycle of the flow version: transforming|transformed|uploading|uploaded|published|failed. */
|
|
93
|
+
publication_status?: string;
|
|
90
94
|
remote_configs: FlowRemoteConfigDTO[];
|
|
91
95
|
status: FlowStatus;
|
|
96
|
+
/** Raw transform failure: a JSON issues payload or a summary string. Parse with `parseFlowPublicationError`. */
|
|
97
|
+
transform_error?: null | string;
|
|
92
98
|
/** Millisecond timestamp of the last content change; the value `expected_updated_at` is compared against on write. */
|
|
93
99
|
updated_at: number;
|
|
94
100
|
}
|
|
@@ -129,20 +135,33 @@ export interface SegmentDTO {
|
|
|
129
135
|
id: string;
|
|
130
136
|
title: string;
|
|
131
137
|
}
|
|
132
|
-
export interface
|
|
138
|
+
export interface PlacementPaywallAudienceEntryDTO {
|
|
139
|
+
content_type: 'paywall';
|
|
133
140
|
paywall_id: string;
|
|
134
141
|
priority: number;
|
|
135
142
|
segment_ids?: string[];
|
|
136
143
|
}
|
|
144
|
+
export interface PlacementFlowAudienceEntryDTO {
|
|
145
|
+
content_type: 'flow';
|
|
146
|
+
/** The flow must be `published` (not draft) — the backend returns 400 otherwise. */
|
|
147
|
+
flow_id: string;
|
|
148
|
+
priority: number;
|
|
149
|
+
segment_ids?: string[];
|
|
150
|
+
}
|
|
151
|
+
export type PlacementAudienceEntryDTO = PlacementFlowAudienceEntryDTO | PlacementPaywallAudienceEntryDTO;
|
|
137
152
|
export interface PlacementSummaryDTO {
|
|
138
153
|
developer_id: string;
|
|
139
154
|
id: string;
|
|
155
|
+
/** Placement activation state: true = Live, false = Inactive. */
|
|
156
|
+
is_active: boolean;
|
|
140
157
|
title: string;
|
|
141
158
|
}
|
|
142
159
|
export interface PlacementDetailDTO {
|
|
143
160
|
audiences?: PlacementAudienceEntryDTO[];
|
|
144
161
|
developer_id: string;
|
|
145
162
|
id: string;
|
|
163
|
+
/** Placement activation state: true = Live, false = Inactive. */
|
|
164
|
+
is_active: boolean;
|
|
146
165
|
title: string;
|
|
147
166
|
}
|
|
148
167
|
export interface PlacementWriteRequestDTO {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare function builderUrl(flowId: string): string;
|
|
2
|
+
/** The two "• …" footer lines pointing at the Builder UI and the flows agent skill. */
|
|
3
|
+
export declare function flowFixLinks(flowId: string): string;
|
|
4
|
+
/**
|
|
5
|
+
* When `error` is the backend's draft-flow rejection, returns an enriched message with the
|
|
6
|
+
* publish command and fix links; otherwise returns null so the caller rethrows unchanged.
|
|
7
|
+
* `flowId` is taken from the single flow audience, or `<FLOW_ID>` when it is ambiguous.
|
|
8
|
+
*/
|
|
9
|
+
export declare function draftFlowError(error: unknown, app: string, audiences: null | {
|
|
10
|
+
content_type?: string;
|
|
11
|
+
flow_id?: string;
|
|
12
|
+
}[]): null | string;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Shared "how to fix a flow" links, used by `flows publish` (config-error message)
|
|
2
|
+
// and by `placements create`/`update` (draft-attach error). One source for both URLs.
|
|
3
|
+
import { ApiError } from './errors.js';
|
|
4
|
+
export function builderUrl(flowId) {
|
|
5
|
+
return `https://app.adapty.io/flows/${flowId}/builder`;
|
|
6
|
+
}
|
|
7
|
+
/** The two "• …" footer lines pointing at the Builder UI and the flows agent skill. */
|
|
8
|
+
export function flowFixLinks(flowId) {
|
|
9
|
+
return ` • Builder UI: ${builderUrl(flowId)}\n • Adapty flows agent skill: https://adapty.io/docs/flow-generator-skill`;
|
|
10
|
+
}
|
|
11
|
+
// The backend message when a placement tries to attach an unpublished flow. There is no
|
|
12
|
+
// distinct error_code (it comes back as generic validation_error), so match on the text.
|
|
13
|
+
const DRAFT_FLOW_MARKER = 'Flow must be published before placing in a placement';
|
|
14
|
+
/**
|
|
15
|
+
* When `error` is the backend's draft-flow rejection, returns an enriched message with the
|
|
16
|
+
* publish command and fix links; otherwise returns null so the caller rethrows unchanged.
|
|
17
|
+
* `flowId` is taken from the single flow audience, or `<FLOW_ID>` when it is ambiguous.
|
|
18
|
+
*/
|
|
19
|
+
export function draftFlowError(error, app, audiences) {
|
|
20
|
+
if (!(error instanceof ApiError) || !error.message.includes(DRAFT_FLOW_MARKER))
|
|
21
|
+
return null;
|
|
22
|
+
const flows = (audiences ?? []).filter((a) => a.content_type === 'flow' && typeof a.flow_id === 'string');
|
|
23
|
+
const flowId = flows.length === 1 ? flows[0].flow_id : '<FLOW_ID>';
|
|
24
|
+
return ('Cannot attach a draft flow to a placement — publish it first.\n' +
|
|
25
|
+
`Publish: adapty flows publish --app ${app} ${flowId}\n` +
|
|
26
|
+
flowFixLinks(flowId));
|
|
27
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns a human-readable problem string when `entry` is not a valid placement
|
|
3
|
+
* audience entry, or `null` when it is well-formed. Every entry must carry an
|
|
4
|
+
* explicit `content_type` ('paywall' | 'flow'); there is no implicit paywall.
|
|
5
|
+
*/
|
|
6
|
+
export declare function audienceEntryProblem(entry: unknown): null | string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Runtime guard for --audiences entries parsed from user/agent-supplied JSON.
|
|
2
|
+
// The DTO union in api-schemas.ts requires content_type, but a JSON.parse gives
|
|
3
|
+
// back `unknown`, so the type alone cannot enforce it — this does, the same way
|
|
4
|
+
// for humans and agents (no prompting).
|
|
5
|
+
/**
|
|
6
|
+
* Returns a human-readable problem string when `entry` is not a valid placement
|
|
7
|
+
* audience entry, or `null` when it is well-formed. Every entry must carry an
|
|
8
|
+
* explicit `content_type` ('paywall' | 'flow'); there is no implicit paywall.
|
|
9
|
+
*/
|
|
10
|
+
export function audienceEntryProblem(entry) {
|
|
11
|
+
if (typeof entry !== 'object' || entry === null) {
|
|
12
|
+
return 'each entry must be a JSON object';
|
|
13
|
+
}
|
|
14
|
+
const { content_type: contentType, flow_id: flowId, paywall_id: paywallId } = entry;
|
|
15
|
+
if (contentType !== 'flow' && contentType !== 'paywall') {
|
|
16
|
+
return 'content_type is required and must be "paywall" or "flow"';
|
|
17
|
+
}
|
|
18
|
+
if (contentType === 'paywall' && typeof paywallId !== 'string') {
|
|
19
|
+
return 'a paywall entry requires paywall_id';
|
|
20
|
+
}
|
|
21
|
+
if (contentType === 'flow' && typeof flowId !== 'string') {
|
|
22
|
+
return 'a flow entry requires flow_id';
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|