adapty 0.8.5 → 0.8.7
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 +7 -1
- package/dist/cli/base/adapty/adapty-command.d.ts +21 -0
- package/dist/cli/base/adapty/adapty-command.js +47 -0
- package/dist/cli/base/adapty/build.d.ts +11 -0
- package/dist/cli/base/adapty/build.js +13 -0
- package/dist/cli/base/adapty/index.d.ts +5 -0
- package/dist/cli/base/adapty/index.js +3 -0
- package/dist/cli/base/adapty/openSession.d.ts +22 -0
- package/dist/cli/base/adapty/openSession.js +25 -0
- package/dist/cli/base/base-command.d.ts +30 -0
- package/dist/cli/base/base-command.js +64 -0
- package/dist/cli/commands/apps/create.d.ts +19 -0
- package/dist/cli/commands/apps/create.js +68 -0
- package/dist/cli/commands/apps/get.d.ts +10 -0
- package/dist/cli/commands/apps/get.js +14 -0
- package/dist/cli/commands/apps/list.d.ts +11 -0
- package/dist/cli/commands/apps/list.js +14 -0
- package/dist/cli/commands/apps/update.d.ts +15 -0
- package/dist/cli/commands/apps/update.js +30 -0
- package/dist/cli/commands/auth/login.d.ts +11 -0
- package/dist/cli/commands/auth/login.js +68 -0
- package/dist/cli/commands/auth/logout.d.ts +12 -0
- package/dist/cli/commands/auth/logout.js +28 -0
- package/dist/cli/commands/auth/revoke.d.ts +13 -0
- package/dist/cli/commands/auth/revoke.js +32 -0
- package/dist/cli/commands/auth/status/index.d.ts +7 -0
- package/dist/cli/commands/auth/status/index.js +22 -0
- package/dist/cli/commands/auth/status/lib/render.d.ts +3 -0
- package/dist/cli/commands/auth/status/lib/render.js +14 -0
- package/dist/cli/commands/auth/status/lib/result.d.ts +15 -0
- package/dist/cli/commands/auth/status/lib/result.js +1 -0
- package/dist/cli/commands/auth/whoami.d.ts +10 -0
- package/dist/cli/commands/auth/whoami.js +16 -0
- package/dist/cli/errors.d.ts +39 -0
- package/dist/cli/errors.js +92 -0
- package/dist/cli/flags.d.ts +16 -0
- package/dist/cli/flags.js +27 -0
- package/dist/cli/views/list.d.ts +6 -0
- package/dist/cli/views/list.js +10 -0
- package/dist/cli/views/record.d.ts +9 -0
- package/dist/cli/views/record.js +14 -0
- package/dist/commands/apps/create.d.ts +1 -14
- package/dist/commands/apps/create.js +2 -57
- package/dist/commands/apps/get.d.ts +1 -11
- package/dist/commands/apps/get.js +2 -22
- package/dist/commands/apps/list.d.ts +1 -13
- package/dist/commands/apps/list.js +2 -19
- package/dist/commands/apps/update.d.ts +1 -16
- package/dist/commands/apps/update.js +2 -41
- package/dist/commands/asa/keywords/recommend.d.ts +20 -0
- package/dist/commands/asa/keywords/recommend.js +102 -0
- package/dist/commands/asa/whoami.js +6 -2
- package/dist/commands/auth/login.d.ts +1 -8
- package/dist/commands/auth/login.js +2 -122
- package/dist/commands/auth/logout.d.ts +1 -9
- package/dist/commands/auth/logout.js +2 -18
- package/dist/commands/auth/revoke.d.ts +1 -9
- package/dist/commands/auth/revoke.js +2 -21
- package/dist/commands/auth/status.d.ts +1 -7
- package/dist/commands/auth/status.js +2 -26
- package/dist/commands/auth/whoami.d.ts +1 -7
- package/dist/commands/auth/whoami.js +2 -15
- package/dist/lib/api-schemas.d.ts +7 -3
- package/dist/lib/asa-schemas.d.ts +41 -1
- package/dist/sdk/adapty/access-levels.d.ts +15 -0
- package/dist/sdk/adapty/access-levels.js +4 -0
- package/dist/sdk/adapty/apps/create.d.ts +26 -0
- package/dist/sdk/adapty/apps/create.js +36 -0
- package/dist/sdk/adapty/apps/index.d.ts +13 -0
- package/dist/sdk/adapty/apps/index.js +9 -0
- package/dist/sdk/adapty/apps/model.d.ts +24 -0
- package/dist/sdk/adapty/apps/model.js +10 -0
- package/dist/sdk/adapty/apps/resource.d.ts +19 -0
- package/dist/sdk/adapty/apps/resource.js +23 -0
- package/dist/sdk/adapty/apps/update.d.ts +16 -0
- package/dist/sdk/adapty/apps/update.js +25 -0
- package/dist/sdk/adapty/auth/device-code.d.ts +8 -0
- package/dist/sdk/adapty/auth/device-code.js +19 -0
- package/dist/sdk/adapty/auth/index.d.ts +7 -0
- package/dist/sdk/adapty/auth/index.js +5 -0
- package/dist/sdk/adapty/auth/model.d.ts +15 -0
- package/dist/sdk/adapty/auth/model.js +6 -0
- package/dist/sdk/adapty/auth/poll-token.d.ts +8 -0
- package/dist/sdk/adapty/auth/poll-token.js +58 -0
- package/dist/sdk/adapty/auth/resource.d.ts +17 -0
- package/dist/sdk/adapty/auth/resource.js +21 -0
- package/dist/sdk/adapty/errors.d.ts +10 -0
- package/dist/sdk/adapty/errors.js +38 -0
- package/dist/sdk/adapty/index.d.ts +36 -0
- package/dist/sdk/adapty/index.js +27 -0
- package/dist/sdk/adapty/pagination.d.ts +23 -0
- package/dist/sdk/adapty/pagination.js +1 -0
- package/dist/sdk/core/auth/device-flow.d.ts +46 -0
- package/dist/sdk/core/auth/device-flow.js +62 -0
- package/dist/sdk/core/clock.d.ts +7 -0
- package/dist/sdk/core/clock.js +21 -0
- package/dist/sdk/core/errors.d.ts +81 -0
- package/dist/sdk/core/errors.js +84 -0
- package/dist/sdk/core/http/client.d.ts +46 -0
- package/dist/sdk/core/http/client.js +127 -0
- package/dist/sdk/core/http/index.d.ts +11 -0
- package/dist/sdk/core/http/index.js +7 -0
- package/dist/sdk/core/http/policies.d.ts +14 -0
- package/dist/sdk/core/http/policies.js +29 -0
- package/dist/sdk/core/http/retry.d.ts +27 -0
- package/dist/sdk/core/http/retry.js +22 -0
- package/dist/sdk/core/http/url.d.ts +3 -0
- package/dist/sdk/core/http/url.js +19 -0
- package/dist/sdk/core/session.d.ts +20 -0
- package/dist/sdk/core/session.js +69 -0
- package/dist/sdk/core/testing.d.ts +25 -0
- package/dist/sdk/core/testing.js +46 -0
- package/dist/sdk/core/validation.d.ts +10 -0
- package/dist/sdk/core/validation.js +14 -0
- package/oclif.manifest.json +211 -148
- package/package.json +10 -2
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { createAsaClient } from '../../../lib/asa-client.js';
|
|
3
|
+
import { printList, printResponse } from '../../../lib/output.js';
|
|
4
|
+
const TYPES = ['brand', 'generic', 'competitor'];
|
|
5
|
+
const PATHS = {
|
|
6
|
+
brand: '/keyword-recommendations/brand',
|
|
7
|
+
competitor: '/keyword-recommendations/competitor-brand',
|
|
8
|
+
generic: '/keyword-recommendations/generic',
|
|
9
|
+
};
|
|
10
|
+
export default class AsaKeywordsRecommend extends Command {
|
|
11
|
+
static description = 'Keyword recommendations for one of your apps: its brand terms, generic terms, or the brand terms of its selected competitors';
|
|
12
|
+
static enableJsonFlag = true;
|
|
13
|
+
static examples = [
|
|
14
|
+
'<%= config.bin %> asa keywords recommend --adam-id 1668337467 --type brand',
|
|
15
|
+
'<%= config.bin %> asa keywords recommend --adam-id 1668337467 --type generic --country US',
|
|
16
|
+
'<%= config.bin %> asa keywords recommend --adam-id 1668337467 --type competitor',
|
|
17
|
+
];
|
|
18
|
+
static flags = {
|
|
19
|
+
'adam-id': Flags.integer({
|
|
20
|
+
description: 'Apple App Store ID (adam_id) of your app, from `asa apps list`',
|
|
21
|
+
required: true,
|
|
22
|
+
}),
|
|
23
|
+
'country': Flags.string({
|
|
24
|
+
description: 'ISO country code (e.g. US); repeatable, default every country in the pool',
|
|
25
|
+
multiple: true,
|
|
26
|
+
}),
|
|
27
|
+
'type': Flags.string({ description: 'Which pool to read', options: [...TYPES], required: true }),
|
|
28
|
+
};
|
|
29
|
+
async run() {
|
|
30
|
+
const { flags } = await this.parse(AsaKeywordsRecommend);
|
|
31
|
+
const countries = (flags.country ?? []).map(code => code.trim().toUpperCase());
|
|
32
|
+
if (countries.some(code => !/^[A-Z]{2}$/.test(code))) {
|
|
33
|
+
this.error('--country takes two-letter ISO codes, e.g. --country US.', { exit: 2 });
|
|
34
|
+
}
|
|
35
|
+
const type = flags.type;
|
|
36
|
+
const client = await createAsaClient(this);
|
|
37
|
+
const params = { adam_id: String(flags['adam-id']), country: countries.length > 0 ? countries : undefined };
|
|
38
|
+
if (type === 'competitor') {
|
|
39
|
+
const pools = await client.get(PATHS.competitor, params);
|
|
40
|
+
this.printCompetitorPools(pools);
|
|
41
|
+
return pools;
|
|
42
|
+
}
|
|
43
|
+
const pool = await client.get(PATHS[type], params);
|
|
44
|
+
this.printPool(pool, type);
|
|
45
|
+
return pool;
|
|
46
|
+
}
|
|
47
|
+
printCompetitorPools(result) {
|
|
48
|
+
if (result.pools.length === 0) {
|
|
49
|
+
this.log('No competitors selected for this app yet — select them in the Adapty dashboard (Autopilot setup).');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
for (const [i, pool] of result.pools.entries()) {
|
|
53
|
+
printResponse({
|
|
54
|
+
competitor_adam_id: pool.competitor_adam_id,
|
|
55
|
+
status: pool.status,
|
|
56
|
+
keywords_count: pool.keywords.length,
|
|
57
|
+
}, this.log.bind(this));
|
|
58
|
+
const hint = this.statusHint(pool.status);
|
|
59
|
+
if (hint !== undefined) {
|
|
60
|
+
this.log(hint);
|
|
61
|
+
}
|
|
62
|
+
if (pool.keywords.length > 0) {
|
|
63
|
+
this.log('');
|
|
64
|
+
printList(pool.keywords.map(keyword => this.omitAdamId(keyword)), this.log.bind(this));
|
|
65
|
+
}
|
|
66
|
+
if (i < result.pools.length - 1) {
|
|
67
|
+
this.log('===');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
printPool(pool, type) {
|
|
72
|
+
const header = { status: pool.status, keywords_count: pool.keywords.length };
|
|
73
|
+
if (type === 'brand') {
|
|
74
|
+
header.brand_terms = pool.brand?.brand_terms ?? [];
|
|
75
|
+
}
|
|
76
|
+
printResponse(header, this.log.bind(this));
|
|
77
|
+
const hint = this.statusHint(pool.status);
|
|
78
|
+
if (hint !== undefined) {
|
|
79
|
+
this.log(hint);
|
|
80
|
+
}
|
|
81
|
+
if (pool.keywords.length > 0) {
|
|
82
|
+
this.log('');
|
|
83
|
+
const keywords = type === 'brand'
|
|
84
|
+
? pool.keywords.map(keyword => this.omitAdamId(keyword))
|
|
85
|
+
: pool.keywords;
|
|
86
|
+
printList(keywords, this.log.bind(this));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
omitAdamId(keyword) {
|
|
90
|
+
const { adam_id: _adamId, ...rest } = keyword;
|
|
91
|
+
return rest;
|
|
92
|
+
}
|
|
93
|
+
statusHint(status) {
|
|
94
|
+
if (status === 'building') {
|
|
95
|
+
return 'Still building — retry in about a minute.';
|
|
96
|
+
}
|
|
97
|
+
if (status === 'failed') {
|
|
98
|
+
return 'The pool build failed — retry later or contact support.';
|
|
99
|
+
}
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -19,8 +19,12 @@ grouped page may project to before it is refused.`;
|
|
|
19
19
|
this.log('\nApple Ads is not connected. Run `adapty asa connect` to link an account.');
|
|
20
20
|
}
|
|
21
21
|
if (result.access_source === 'none') {
|
|
22
|
-
this.log('\nNo
|
|
23
|
-
this.log('
|
|
22
|
+
this.log('\nNo Ads Manager access for this company: connecting an account works, but every data command');
|
|
23
|
+
this.log('answers 402 until the Ads Manager trial is started or a subscription is in place.');
|
|
24
|
+
}
|
|
25
|
+
if (result.access_source === 'trial') {
|
|
26
|
+
this.log('\nAccess comes from the Ads Manager trial: every command works, and they stop working when');
|
|
27
|
+
this.log('the trial ends without a subscription.');
|
|
24
28
|
}
|
|
25
29
|
return result;
|
|
26
30
|
}
|
|
@@ -1,8 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export default class AuthLogin extends Command {
|
|
3
|
-
static description: string;
|
|
4
|
-
static examples: string[];
|
|
5
|
-
run(): Promise<void>;
|
|
6
|
-
/** Keeps the browser on the configured dashboard host, when ADAPTY_APP_URL asks for one. */
|
|
7
|
-
private verificationUrl;
|
|
8
|
-
}
|
|
1
|
+
export { default } from '../../cli/commands/auth/login.js';
|
|
@@ -1,122 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { ApiClient } from '../../lib/api-client.js';
|
|
4
|
-
import { onAppHost } from '../../lib/app-url.js';
|
|
5
|
-
import { buildUserAgent } from '../../lib/client-from-config.js';
|
|
6
|
-
import { readConfig, writeConfig } from '../../lib/config.js';
|
|
7
|
-
import { ApiError } from '../../lib/errors.js';
|
|
8
|
-
function sleep(ms) {
|
|
9
|
-
return new Promise((resolve) => {
|
|
10
|
-
setTimeout(resolve, ms);
|
|
11
|
-
});
|
|
12
|
-
}
|
|
13
|
-
export default class AuthLogin extends Command {
|
|
14
|
-
static description = 'Authenticate with Adapty via browser';
|
|
15
|
-
static examples = ['<%= config.bin %> auth login'];
|
|
16
|
-
async run() {
|
|
17
|
-
await this.parse(AuthLogin);
|
|
18
|
-
const config = await readConfig(this.config.configDir);
|
|
19
|
-
if (config.access_token && config.user) {
|
|
20
|
-
this.log(`Already authenticated as ${config.user.email}. Re-authenticating...`);
|
|
21
|
-
}
|
|
22
|
-
const client = new ApiClient({
|
|
23
|
-
userAgent: buildUserAgent(this.config),
|
|
24
|
-
});
|
|
25
|
-
let device;
|
|
26
|
-
try {
|
|
27
|
-
device = await client.post('/auth/device', { client_id: 'adapty-cli' });
|
|
28
|
-
}
|
|
29
|
-
catch (error) {
|
|
30
|
-
this.error(error instanceof Error ? error.message : 'Failed to initiate auth flow', { exit: 1 });
|
|
31
|
-
}
|
|
32
|
-
const verificationUrl = this.verificationUrl(device.verification_uri_complete);
|
|
33
|
-
this.log(`\nYour code: ${device.user_code}\n`);
|
|
34
|
-
this.log(`If browser doesn't open, visit: ${verificationUrl}\n`);
|
|
35
|
-
if (process.stdin.isTTY) {
|
|
36
|
-
try {
|
|
37
|
-
await open(verificationUrl);
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
// browser open failed silently — URL already printed
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
this.log('Waiting for authorization...');
|
|
44
|
-
let interval = Math.max((device.interval_seconds || 5) * 1000, 5000);
|
|
45
|
-
const deadline = Date.now() + device.expires_in * 1000;
|
|
46
|
-
let consecutiveErrors = 0;
|
|
47
|
-
// Handle Ctrl+C
|
|
48
|
-
const onSignal = () => {
|
|
49
|
-
this.log('\nLogin cancelled.');
|
|
50
|
-
// eslint-disable-next-line n/no-process-exit -- FIXME if you see this
|
|
51
|
-
process.exit(0);
|
|
52
|
-
};
|
|
53
|
-
process.on('SIGINT', onSignal);
|
|
54
|
-
try {
|
|
55
|
-
while (Date.now() < deadline) {
|
|
56
|
-
await sleep(interval);
|
|
57
|
-
let result;
|
|
58
|
-
try {
|
|
59
|
-
result = await client.post('/auth/token', {
|
|
60
|
-
client_id: 'adapty-cli',
|
|
61
|
-
device_code: device.device_code,
|
|
62
|
-
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
catch (error) {
|
|
66
|
-
if (error instanceof ApiError) {
|
|
67
|
-
switch (error.errorCode) {
|
|
68
|
-
case 'authorization_pending': {
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
case 'slow_down': {
|
|
72
|
-
interval += 5000;
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
case 'access_denied': {
|
|
76
|
-
this.error('Authorization denied.', { exit: 1 });
|
|
77
|
-
break;
|
|
78
|
-
}
|
|
79
|
-
case 'expired_token': {
|
|
80
|
-
this.error('Code expired. Run `adapty auth login` again.', { exit: 1 });
|
|
81
|
-
break;
|
|
82
|
-
}
|
|
83
|
-
default:
|
|
84
|
-
// unexpected API error — fall through to network error handling
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
consecutiveErrors++;
|
|
88
|
-
if (consecutiveErrors >= 10) {
|
|
89
|
-
this.error('Too many consecutive network errors. Check your connection and try again.', { exit: 1 });
|
|
90
|
-
}
|
|
91
|
-
if (consecutiveErrors >= 3) {
|
|
92
|
-
process.stderr.write(`Warning: ${consecutiveErrors} consecutive network errors (${error instanceof Error ? error.message : 'unknown'})\n`);
|
|
93
|
-
}
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
consecutiveErrors = 0;
|
|
97
|
-
if ('access_token' in result) {
|
|
98
|
-
await writeConfig({
|
|
99
|
-
access_token: result.access_token,
|
|
100
|
-
user: result.user,
|
|
101
|
-
}, this.config.configDir);
|
|
102
|
-
this.log(`\nAuthenticated as ${result.user.email}`);
|
|
103
|
-
this.log(`Token saved to ${this.config.configDir}/config.json`);
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
this.error('Code expired. Run `adapty auth login` again.', { exit: 1 });
|
|
108
|
-
}
|
|
109
|
-
finally {
|
|
110
|
-
process.removeListener('SIGINT', onSignal);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
/** Keeps the browser on the configured dashboard host, when ADAPTY_APP_URL asks for one. */
|
|
114
|
-
verificationUrl(issuedUrl) {
|
|
115
|
-
try {
|
|
116
|
-
return onAppHost(issuedUrl);
|
|
117
|
-
}
|
|
118
|
-
catch (error) {
|
|
119
|
-
this.error(error instanceof Error ? error.message : String(error), { exit: 2 });
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
1
|
+
// Implementation lives in the new cli layer; oclif discovers commands only under src/commands.
|
|
2
|
+
export { default } from '../../cli/commands/auth/login.js';
|
|
@@ -1,9 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export default class AuthLogout extends Command {
|
|
3
|
-
static description: string;
|
|
4
|
-
static enableJsonFlag: boolean;
|
|
5
|
-
static examples: string[];
|
|
6
|
-
run(): Promise<{
|
|
7
|
-
status: string;
|
|
8
|
-
}>;
|
|
9
|
-
}
|
|
1
|
+
export { default } from '../../cli/commands/auth/logout.js';
|
|
@@ -1,18 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export default class AuthLogout extends Command {
|
|
4
|
-
static description = 'Remove stored authentication token';
|
|
5
|
-
static enableJsonFlag = true;
|
|
6
|
-
static examples = ['<%= config.bin %> auth logout'];
|
|
7
|
-
async run() {
|
|
8
|
-
await this.parse(AuthLogout);
|
|
9
|
-
const config = await readConfig(this.config.configDir);
|
|
10
|
-
if (!config.access_token) {
|
|
11
|
-
this.log('Not currently authenticated.');
|
|
12
|
-
return { status: 'not_authenticated' };
|
|
13
|
-
}
|
|
14
|
-
await writeConfig({}, this.config.configDir);
|
|
15
|
-
this.log('Logged out. Note: token remains valid server-side until expiry.');
|
|
16
|
-
return { status: 'logged_out' };
|
|
17
|
-
}
|
|
18
|
-
}
|
|
1
|
+
// Implementation lives in the new cli layer; oclif discovers commands only under src/commands.
|
|
2
|
+
export { default } from '../../cli/commands/auth/logout.js';
|
|
@@ -1,9 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export default class AuthRevoke extends Command {
|
|
3
|
-
static description: string;
|
|
4
|
-
static enableJsonFlag: boolean;
|
|
5
|
-
static examples: string[];
|
|
6
|
-
run(): Promise<{
|
|
7
|
-
status: string;
|
|
8
|
-
}>;
|
|
9
|
-
}
|
|
1
|
+
export { default } from '../../cli/commands/auth/revoke.js';
|
|
@@ -1,21 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { readConfig, writeConfig } from '../../lib/config.js';
|
|
4
|
-
export default class AuthRevoke extends Command {
|
|
5
|
-
static description = 'Revoke the current authentication token';
|
|
6
|
-
static enableJsonFlag = true;
|
|
7
|
-
static examples = ['<%= config.bin %> auth revoke'];
|
|
8
|
-
async run() {
|
|
9
|
-
await this.parse(AuthRevoke);
|
|
10
|
-
const config = await readConfig(this.config.configDir);
|
|
11
|
-
if (!config.access_token) {
|
|
12
|
-
this.log('Not currently authenticated.');
|
|
13
|
-
return { status: 'not_authenticated' };
|
|
14
|
-
}
|
|
15
|
-
const client = await createAuthenticatedClient(this.config);
|
|
16
|
-
await client.post('/auth/tokens/revoke', { token: config.access_token });
|
|
17
|
-
await writeConfig({}, this.config.configDir);
|
|
18
|
-
this.log('Token revoked and logged out.');
|
|
19
|
-
return { status: 'revoked' };
|
|
20
|
-
}
|
|
21
|
-
}
|
|
1
|
+
// Implementation lives in the new cli layer; oclif discovers commands only under src/commands.
|
|
2
|
+
export { default } from '../../cli/commands/auth/revoke.js';
|
|
@@ -1,7 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export default class AuthStatus extends Command {
|
|
3
|
-
static description: string;
|
|
4
|
-
static enableJsonFlag: boolean;
|
|
5
|
-
static examples: string[];
|
|
6
|
-
run(): Promise<Record<string, unknown>>;
|
|
7
|
-
}
|
|
1
|
+
export { default } from '../../cli/commands/auth/status/index.js';
|
|
@@ -1,26 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export default class AuthStatus extends Command {
|
|
4
|
-
static description = 'Show current authentication state (local only)';
|
|
5
|
-
static enableJsonFlag = true;
|
|
6
|
-
static examples = ['<%= config.bin %> auth status'];
|
|
7
|
-
async run() {
|
|
8
|
-
await this.parse(AuthStatus);
|
|
9
|
-
const config = await readConfig(this.config.configDir);
|
|
10
|
-
if (!config.access_token || !config.user) {
|
|
11
|
-
this.log('Not authenticated. Run `adapty auth login`.');
|
|
12
|
-
return { authenticated: false };
|
|
13
|
-
}
|
|
14
|
-
const masked = config.access_token.slice(0, 8) + '****';
|
|
15
|
-
const configPath = `${this.config.configDir}/config.json`;
|
|
16
|
-
this.log(`Email: ${config.user.email}`);
|
|
17
|
-
this.log(`Token: ${masked}`);
|
|
18
|
-
this.log(`Config: ${configPath}`);
|
|
19
|
-
return {
|
|
20
|
-
authenticated: true,
|
|
21
|
-
config_path: configPath,
|
|
22
|
-
email: config.user.email,
|
|
23
|
-
token_prefix: config.access_token.slice(0, 8),
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
}
|
|
1
|
+
// Implementation lives in the new cli layer; oclif discovers commands only under src/commands.
|
|
2
|
+
export { default } from '../../cli/commands/auth/status/index.js';
|
|
@@ -1,7 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export default class AuthWhoami extends Command {
|
|
3
|
-
static description: string;
|
|
4
|
-
static enableJsonFlag: boolean;
|
|
5
|
-
static examples: string[];
|
|
6
|
-
run(): Promise<Record<string, unknown>>;
|
|
7
|
-
}
|
|
1
|
+
export { default } from '../../cli/commands/auth/whoami.js';
|
|
@@ -1,15 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { printResponse } from '../../lib/output.js';
|
|
4
|
-
export default class AuthWhoami extends Command {
|
|
5
|
-
static description = 'Show current user info from server (verifies token)';
|
|
6
|
-
static enableJsonFlag = true;
|
|
7
|
-
static examples = ['<%= config.bin %> auth whoami'];
|
|
8
|
-
async run() {
|
|
9
|
-
await this.parse(AuthWhoami);
|
|
10
|
-
const client = await createAuthenticatedClient(this.config);
|
|
11
|
-
const me = await client.get('/me');
|
|
12
|
-
printResponse(me, this.log.bind(this));
|
|
13
|
-
return me;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
1
|
+
// Implementation lives in the new cli layer; oclif discovers commands only under src/commands.
|
|
2
|
+
export { default } from '../../cli/commands/auth/whoami.js';
|
|
@@ -78,7 +78,8 @@ export type FlowStatus = string;
|
|
|
78
78
|
export type FlowDTO = {
|
|
79
79
|
id: string;
|
|
80
80
|
name: string;
|
|
81
|
-
|
|
81
|
+
/** Optional: the flows-update (rename) response leaves status out. Read it from flows get. */
|
|
82
|
+
status?: FlowStatus;
|
|
82
83
|
updated_at: string;
|
|
83
84
|
};
|
|
84
85
|
export type FlowRemoteConfigDTO = {
|
|
@@ -152,8 +153,11 @@ export type PlacementAudienceEntryDTO = PlacementFlowAudienceEntryDTO | Placemen
|
|
|
152
153
|
export type PlacementSummaryDTO = {
|
|
153
154
|
developer_id: string;
|
|
154
155
|
id: string;
|
|
155
|
-
/**
|
|
156
|
-
|
|
156
|
+
/**
|
|
157
|
+
* Placement activation state: true = Live, false = Inactive.
|
|
158
|
+
* Optional — the paywall placements list leaves it out.
|
|
159
|
+
*/
|
|
160
|
+
is_active?: boolean;
|
|
157
161
|
title: string;
|
|
158
162
|
};
|
|
159
163
|
export type PlacementDetailDTO = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type AsaAccessSource = 'allowlist' | 'legacy' | 'none' | 'payg';
|
|
1
|
+
export type AsaAccessSource = 'allowlist' | 'legacy' | 'none' | 'payg' | 'trial';
|
|
2
2
|
export type AsaAppleCredentialsStatus = 'active' | 'expired' | 'invalid' | 'unset';
|
|
3
3
|
export type AsaKeywordMatchType = 'BROAD' | 'EXACT';
|
|
4
4
|
export type AsaKeywordStatus = 'ACTIVE' | 'PAUSED';
|
|
@@ -379,3 +379,43 @@ export type AsaBulkConvertResultDTO = {
|
|
|
379
379
|
request: null | Record<string, unknown>;
|
|
380
380
|
warnings: AsaTemplateIssueDTO[];
|
|
381
381
|
};
|
|
382
|
+
export type AsaKeywordPoolStatus = 'building' | 'empty' | 'failed' | 'missing' | 'ready';
|
|
383
|
+
export type AsaBrandKeywordDTO = {
|
|
384
|
+
adam_id: number;
|
|
385
|
+
country: string;
|
|
386
|
+
median_organic_rank: null | number;
|
|
387
|
+
popularity: null | number;
|
|
388
|
+
score: null | number;
|
|
389
|
+
source: 'llm' | 'name' | 'organic';
|
|
390
|
+
text: string;
|
|
391
|
+
};
|
|
392
|
+
export type AsaAppBrandDTO = {
|
|
393
|
+
adam_id: number;
|
|
394
|
+
brand_terms: string[];
|
|
395
|
+
source: 'llm' | 'manual';
|
|
396
|
+
};
|
|
397
|
+
export type AsaBrandKeywordPoolDTO = {
|
|
398
|
+
brand: AsaAppBrandDTO | null;
|
|
399
|
+
keywords: AsaBrandKeywordDTO[];
|
|
400
|
+
status: AsaKeywordPoolStatus;
|
|
401
|
+
};
|
|
402
|
+
export type AsaGenericKeywordDTO = {
|
|
403
|
+
country: string;
|
|
404
|
+
median_organic_rank: null | number;
|
|
405
|
+
popularity: null | number;
|
|
406
|
+
relevance_tier: 'other' | 'top_organic';
|
|
407
|
+
score: null | number;
|
|
408
|
+
text: string;
|
|
409
|
+
};
|
|
410
|
+
export type AsaGenericKeywordPoolDTO = {
|
|
411
|
+
keywords: AsaGenericKeywordDTO[];
|
|
412
|
+
status: AsaKeywordPoolStatus;
|
|
413
|
+
};
|
|
414
|
+
export type AsaCompetitorBrandKeywordPoolDTO = {
|
|
415
|
+
competitor_adam_id: number;
|
|
416
|
+
keywords: AsaBrandKeywordDTO[];
|
|
417
|
+
status: AsaKeywordPoolStatus;
|
|
418
|
+
};
|
|
419
|
+
export type AsaCompetitorBrandKeywordPoolsDTO = {
|
|
420
|
+
pools: AsaCompetitorBrandKeywordPoolDTO[];
|
|
421
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Http } from '../core/http/index.js';
|
|
2
|
+
export type AccessLevel = {
|
|
3
|
+
id: string;
|
|
4
|
+
sdk_id: string;
|
|
5
|
+
title: null | string;
|
|
6
|
+
};
|
|
7
|
+
/** Not paginated: this endpoint answers with a bare `items` list, and may omit it entirely. */
|
|
8
|
+
export type AccessLevelList = {
|
|
9
|
+
items?: AccessLevel[];
|
|
10
|
+
};
|
|
11
|
+
/** Only the read `apps create` needs; the rest arrives with the access-levels commands. */
|
|
12
|
+
export declare const accessLevels: (http: Http) => {
|
|
13
|
+
list: (appId: string) => Promise<AccessLevelList>;
|
|
14
|
+
};
|
|
15
|
+
export type AccessLevelsApi = ReturnType<typeof accessLevels>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Platform } from './model.js';
|
|
2
|
+
import type { Issue } from '../../core/errors.js';
|
|
3
|
+
export type CreateAppInput = {
|
|
4
|
+
appleBundleId?: string | undefined;
|
|
5
|
+
googleBundleId?: string | undefined;
|
|
6
|
+
platforms: readonly Platform[];
|
|
7
|
+
title: string;
|
|
8
|
+
};
|
|
9
|
+
type CreateAppRequest = {
|
|
10
|
+
apple_bundle_id?: string;
|
|
11
|
+
google_bundle_id?: string;
|
|
12
|
+
platforms: Platform[];
|
|
13
|
+
title: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* The business rules of creating an app: a pure function returning a list of problems, knowing
|
|
17
|
+
* nothing about flags or the network. An Issue path names an input field (camelCase) and the
|
|
18
|
+
* adapter turns it into the flag the user typed (src/cli/errors.ts).
|
|
19
|
+
*
|
|
20
|
+
* A list instead of a throw, so a table test walks the rules without an environment and the user
|
|
21
|
+
* sees every problem at once.
|
|
22
|
+
*/
|
|
23
|
+
export declare const validateCreateApp: (input: CreateAppInput) => Issue[];
|
|
24
|
+
/** An absent field is left out of the body: the server tells "not given" from "set to empty". */
|
|
25
|
+
export declare const toCreateRequest: (input: CreateAppInput) => CreateAppRequest;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The business rules of creating an app: a pure function returning a list of problems, knowing
|
|
3
|
+
* nothing about flags or the network. An Issue path names an input field (camelCase) and the
|
|
4
|
+
* adapter turns it into the flag the user typed (src/cli/errors.ts).
|
|
5
|
+
*
|
|
6
|
+
* A list instead of a throw, so a table test walks the rules without an environment and the user
|
|
7
|
+
* sees every problem at once.
|
|
8
|
+
*/
|
|
9
|
+
export const validateCreateApp = (input) => {
|
|
10
|
+
const issues = [];
|
|
11
|
+
if (input.title.trim() === '') {
|
|
12
|
+
issues.push({ message: 'must not be empty', path: 'title' });
|
|
13
|
+
}
|
|
14
|
+
if (input.platforms.length === 0) {
|
|
15
|
+
issues.push({ message: 'at least one platform is required', path: 'platform' });
|
|
16
|
+
}
|
|
17
|
+
// An empty string counts as missing: `--apple-bundle-id ""` is not a bundle id
|
|
18
|
+
if (input.platforms.includes('ios') && (input.appleBundleId ?? '') === '') {
|
|
19
|
+
issues.push({ message: 'required when platforms include ios', path: 'appleBundleId' });
|
|
20
|
+
}
|
|
21
|
+
if (input.platforms.includes('android') && (input.googleBundleId ?? '') === '') {
|
|
22
|
+
issues.push({ message: 'required when platforms include android', path: 'googleBundleId' });
|
|
23
|
+
}
|
|
24
|
+
return issues;
|
|
25
|
+
};
|
|
26
|
+
/** An absent field is left out of the body: the server tells "not given" from "set to empty". */
|
|
27
|
+
export const toCreateRequest = (input) => {
|
|
28
|
+
const body = { platforms: [...input.platforms], title: input.title };
|
|
29
|
+
if (input.appleBundleId !== undefined) {
|
|
30
|
+
body.apple_bundle_id = input.appleBundleId;
|
|
31
|
+
}
|
|
32
|
+
if (input.googleBundleId !== undefined) {
|
|
33
|
+
body.google_bundle_id = input.googleBundleId;
|
|
34
|
+
}
|
|
35
|
+
return body;
|
|
36
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The door of the apps resource: re-exports only, no code of its own. Everything outside the
|
|
3
|
+
* directory imports from here, which is what lets the files behind it be rearranged. Only what a
|
|
4
|
+
* consumer uses passes through — the request builders stay private to their operation.
|
|
5
|
+
*/
|
|
6
|
+
export { platforms } from './model.js';
|
|
7
|
+
export { validateCreateApp } from './create.js';
|
|
8
|
+
export { apps } from './resource.js';
|
|
9
|
+
export { validateUpdateApp } from './update.js';
|
|
10
|
+
export type { AppDetail, AppSummary } from './model.js';
|
|
11
|
+
export type { CreateAppInput } from './create.js';
|
|
12
|
+
export type { AppsApi } from './resource.js';
|
|
13
|
+
export type { UpdateAppInput } from './update.js';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The door of the apps resource: re-exports only, no code of its own. Everything outside the
|
|
3
|
+
* directory imports from here, which is what lets the files behind it be rearranged. Only what a
|
|
4
|
+
* consumer uses passes through — the request builders stay private to their operation.
|
|
5
|
+
*/
|
|
6
|
+
export { platforms } from './model.js';
|
|
7
|
+
export { validateCreateApp } from './create.js';
|
|
8
|
+
export { apps } from './resource.js';
|
|
9
|
+
export { validateUpdateApp } from './update.js';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What every operation of the resource shares: the entity as the server sends it, and the
|
|
3
|
+
* platform vocabulary. Answers pass through exactly as they arrive, in snake_case — that is what
|
|
4
|
+
* --json has printed since the first release.
|
|
5
|
+
*
|
|
6
|
+
* An input shape is not shared and does not belong here: it is camelCase, it is our API rather
|
|
7
|
+
* than the server's, and it lives in the file of the operation that takes it.
|
|
8
|
+
*/
|
|
9
|
+
/** In the order the published help lists them, so `--platform` keeps reading `(ios|android)`. */
|
|
10
|
+
export declare const platforms: readonly ["ios", "android"];
|
|
11
|
+
export type Platform = (typeof platforms)[number];
|
|
12
|
+
export type AppSummary = {
|
|
13
|
+
id: string;
|
|
14
|
+
sdk_key: null | string;
|
|
15
|
+
title: string;
|
|
16
|
+
};
|
|
17
|
+
/** What get and update answer with: the summary plus the fields only the detail view carries. */
|
|
18
|
+
export type AppDetail = AppSummary & {
|
|
19
|
+
apple_bundle_id: null | string;
|
|
20
|
+
google_bundle_id: null | string;
|
|
21
|
+
/** Not the Platform union: the server owns this list and may grow it without asking us. */
|
|
22
|
+
platforms: string[];
|
|
23
|
+
secret_key: null | string;
|
|
24
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What every operation of the resource shares: the entity as the server sends it, and the
|
|
3
|
+
* platform vocabulary. Answers pass through exactly as they arrive, in snake_case — that is what
|
|
4
|
+
* --json has printed since the first release.
|
|
5
|
+
*
|
|
6
|
+
* An input shape is not shared and does not belong here: it is camelCase, it is our API rather
|
|
7
|
+
* than the server's, and it lives in the file of the operation that takes it.
|
|
8
|
+
*/
|
|
9
|
+
/** In the order the published help lists them, so `--platform` keeps reading `(ios|android)`. */
|
|
10
|
+
export const platforms = ['ios', 'android'];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CreateAppInput } from './create.js';
|
|
2
|
+
import type { AppDetail, AppSummary } from './model.js';
|
|
3
|
+
import type { UpdateAppInput } from './update.js';
|
|
4
|
+
import type { Http } from '../../core/http/index.js';
|
|
5
|
+
import type { PageParams, Paginated } from '../pagination.js';
|
|
6
|
+
/**
|
|
7
|
+
* Every path of the apps resource in one place, so the endpoints can be read as a list. What each
|
|
8
|
+
* operation needs of its own — input shape, rules, request body — lives in its own file.
|
|
9
|
+
*
|
|
10
|
+
* The validating methods are async on purpose — a broken rule then arrives as a rejected promise,
|
|
11
|
+
* like a network failure, and one await covers both.
|
|
12
|
+
*/
|
|
13
|
+
export declare const apps: (http: Http) => {
|
|
14
|
+
get: (appId: string) => Promise<AppDetail>;
|
|
15
|
+
list: (params?: PageParams) => Promise<Paginated<AppSummary>>;
|
|
16
|
+
create: (input: CreateAppInput) => Promise<AppSummary>;
|
|
17
|
+
update: (appId: string, input: UpdateAppInput) => Promise<AppDetail>;
|
|
18
|
+
};
|
|
19
|
+
export type AppsApi = ReturnType<typeof apps>;
|