adapty 0.8.9 → 0.9.1
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 +147 -2
- package/dist/cli/base/adapty/adapty-command.d.ts +3 -2
- package/dist/cli/base/adapty/adapty-command.js +8 -5
- package/dist/cli/base/adapty/build.d.ts +6 -1
- package/dist/cli/base/adapty/build.js +1 -0
- package/dist/cli/base/adapty/index.d.ts +1 -0
- package/dist/cli/base/adapty/index.js +1 -0
- package/dist/cli/base/adapty/migration-command.d.ts +11 -0
- package/dist/cli/base/adapty/migration-command.js +17 -0
- package/dist/cli/base/base-command.d.ts +6 -0
- package/dist/cli/base/base-command.js +14 -0
- package/dist/cli/commands/apps/create.d.ts +1 -1
- package/dist/cli/commands/apps/get.js +1 -1
- package/dist/cli/commands/apps/list.js +1 -1
- package/dist/cli/commands/apps/update.js +1 -1
- package/dist/cli/commands/attribution/report/command.d.ts +18 -0
- package/dist/cli/commands/attribution/report/command.js +93 -0
- package/dist/cli/commands/attribution/report/index.d.ts +1 -18
- package/dist/cli/commands/attribution/report/index.js +1 -93
- package/dist/cli/commands/attribution/values.js +1 -1
- package/dist/cli/commands/auth/login.js +3 -2
- package/dist/cli/commands/auth/{logout.d.ts → logout/command.d.ts} +1 -1
- package/dist/cli/commands/auth/logout/command.js +33 -0
- package/dist/cli/commands/auth/logout/index.d.ts +1 -0
- package/dist/cli/commands/auth/logout/index.js +1 -0
- package/dist/cli/commands/auth/logout/lib/cleanup.d.ts +3 -0
- package/dist/cli/commands/auth/logout/lib/cleanup.js +21 -0
- package/dist/cli/commands/auth/{revoke.d.ts → revoke/command.d.ts} +1 -1
- package/dist/cli/commands/auth/{revoke.js → revoke/command.js} +7 -7
- package/dist/cli/commands/auth/revoke/index.d.ts +1 -0
- package/dist/cli/commands/auth/revoke/index.js +1 -0
- package/dist/cli/commands/auth/revoke/lib/cleanup.d.ts +4 -0
- package/dist/cli/commands/auth/revoke/lib/cleanup.js +29 -0
- package/dist/cli/commands/auth/status/command.d.ts +7 -0
- package/dist/cli/commands/auth/status/command.js +23 -0
- package/dist/cli/commands/auth/status/index.d.ts +1 -7
- package/dist/cli/commands/auth/status/index.js +1 -22
- package/dist/cli/commands/auth/status/lib/render.js +3 -0
- package/dist/cli/commands/auth/status/lib/result.d.ts +1 -0
- package/dist/cli/commands/migrations/close/command.d.ts +16 -0
- package/dist/cli/commands/migrations/close/command.js +58 -0
- package/dist/cli/commands/migrations/close/index.d.ts +1 -0
- package/dist/cli/commands/migrations/close/index.js +1 -0
- package/dist/cli/commands/migrations/create/command.d.ts +20 -0
- package/dist/cli/commands/migrations/create/command.js +86 -0
- package/dist/cli/commands/migrations/create/index.d.ts +1 -0
- package/dist/cli/commands/migrations/create/index.js +1 -0
- package/dist/cli/commands/migrations/current/command.d.ts +12 -0
- package/dist/cli/commands/migrations/current/command.js +25 -0
- package/dist/cli/commands/migrations/current/index.d.ts +1 -0
- package/dist/cli/commands/migrations/current/index.js +1 -0
- package/dist/cli/commands/migrations/list/command.d.ts +11 -0
- package/dist/cli/commands/migrations/list/command.js +27 -0
- package/dist/cli/commands/migrations/list/index.d.ts +1 -0
- package/dist/cli/commands/migrations/list/index.js +1 -0
- package/dist/cli/commands/migrations/list/lib/render.d.ts +3 -0
- package/dist/cli/commands/migrations/list/lib/render.js +77 -0
- package/dist/cli/commands/migrations/run/command.d.ts +25 -0
- package/dist/cli/commands/migrations/run/command.js +141 -0
- package/dist/cli/commands/migrations/run/index.d.ts +1 -0
- package/dist/cli/commands/migrations/run/index.js +1 -0
- package/dist/cli/commands/migrations/run/lib/action-view.d.ts +7 -0
- package/dist/cli/commands/migrations/run/lib/action-view.js +2 -0
- package/dist/cli/commands/migrations/run/lib/actions.d.ts +4 -0
- package/dist/cli/commands/migrations/run/lib/actions.js +20 -0
- package/dist/cli/commands/migrations/run/lib/https.d.ts +1 -0
- package/dist/cli/commands/migrations/run/lib/https.js +8 -0
- package/dist/cli/commands/migrations/run/lib/input.d.ts +10 -0
- package/dist/cli/commands/migrations/run/lib/input.js +51 -0
- package/dist/cli/commands/migrations/run/lib/open-link.d.ts +7 -0
- package/dist/cli/commands/migrations/run/lib/open-link.js +11 -0
- package/dist/cli/commands/migrations/show/command.d.ts +17 -0
- package/dist/cli/commands/migrations/show/command.js +47 -0
- package/dist/cli/commands/migrations/show/index.d.ts +1 -0
- package/dist/cli/commands/migrations/show/index.js +1 -0
- package/dist/cli/commands/migrations/show/lib/render.d.ts +4 -0
- package/dist/cli/commands/migrations/show/lib/render.js +27 -0
- package/dist/cli/commands/migrations/status/command.d.ts +18 -0
- package/dist/cli/commands/migrations/status/command.js +54 -0
- package/dist/cli/commands/migrations/status/index.d.ts +1 -0
- package/dist/cli/commands/migrations/status/index.js +1 -0
- package/dist/cli/commands/migrations/status/lib/flags.d.ts +8 -0
- package/dist/cli/commands/migrations/status/lib/flags.js +29 -0
- package/dist/cli/commands/migrations/status/lib/notice.d.ts +3 -0
- package/dist/cli/commands/migrations/status/lib/notice.js +10 -0
- package/dist/cli/commands/migrations/steps/command.d.ts +14 -0
- package/dist/cli/commands/migrations/steps/command.js +34 -0
- package/dist/cli/commands/migrations/steps/index.d.ts +1 -0
- package/dist/cli/commands/migrations/steps/index.js +1 -0
- package/dist/cli/commands/migrations/steps/lib/render.d.ts +3 -0
- package/dist/cli/commands/migrations/steps/lib/render.js +28 -0
- package/dist/cli/commands/migrations/unuse/command.d.ts +11 -0
- package/dist/cli/commands/migrations/unuse/command.js +23 -0
- package/dist/cli/commands/migrations/unuse/index.d.ts +1 -0
- package/dist/cli/commands/migrations/unuse/index.js +1 -0
- package/dist/cli/commands/migrations/use/command.d.ts +14 -0
- package/dist/cli/commands/migrations/use/command.js +24 -0
- package/dist/cli/commands/migrations/use/index.d.ts +1 -0
- package/dist/cli/commands/migrations/use/index.js +1 -0
- package/dist/cli/context/migration/current.d.ts +32 -0
- package/dist/cli/context/migration/current.js +30 -0
- package/dist/cli/context/migration/index.d.ts +4 -0
- package/dist/cli/context/migration/index.js +2 -0
- package/dist/cli/context/migration/model.d.ts +14 -0
- package/dist/cli/context/migration/model.js +30 -0
- package/dist/cli/context/migration/resolve.d.ts +16 -0
- package/dist/cli/context/migration/resolve.js +26 -0
- package/dist/cli/context/migration/store.d.ts +9 -0
- package/dist/cli/context/migration/store.js +99 -0
- package/dist/cli/errors/wizard.d.ts +5 -0
- package/dist/cli/errors/wizard.js +54 -0
- package/dist/cli/errors.d.ts +37 -4
- package/dist/cli/errors.js +58 -4
- package/dist/cli/input/app.d.ts +8 -0
- package/dist/cli/{flags.js → input/app.js} +2 -9
- package/dist/cli/input/migration.d.ts +7 -0
- package/dist/cli/input/migration.js +17 -0
- package/dist/cli/input/pagination.d.ts +11 -0
- package/dist/cli/input/pagination.js +8 -0
- package/dist/cli/views/migrations/envelope/action-block.d.ts +2 -0
- package/dist/cli/views/migrations/envelope/action-block.js +32 -0
- package/dist/cli/views/migrations/envelope/envelope.d.ts +3 -0
- package/dist/cli/views/migrations/envelope/envelope.js +40 -0
- package/dist/cli/views/migrations/envelope/issue-block.d.ts +2 -0
- package/dist/cli/views/migrations/envelope/issue-block.js +13 -0
- package/dist/cli/views/migrations/notices.d.ts +7 -0
- package/dist/cli/views/migrations/notices.js +7 -0
- package/dist/commands/access-levels/create.js +1 -1
- package/dist/commands/access-levels/get.js +1 -1
- package/dist/commands/access-levels/list.js +1 -1
- package/dist/commands/access-levels/update.js +1 -1
- package/dist/commands/auth/logout.d.ts +1 -1
- package/dist/commands/auth/logout.js +1 -1
- package/dist/commands/auth/revoke.d.ts +1 -1
- package/dist/commands/auth/revoke.js +1 -1
- package/dist/commands/flows/config/get.js +1 -1
- package/dist/commands/flows/config/update.js +1 -1
- package/dist/commands/flows/config/validate.js +1 -1
- package/dist/commands/flows/create.js +1 -1
- package/dist/commands/flows/get.js +1 -1
- package/dist/commands/flows/list.js +1 -1
- package/dist/commands/flows/media/upload.js +1 -1
- package/dist/commands/flows/publish.js +1 -1
- package/dist/commands/flows/update.js +1 -1
- package/dist/commands/migrations/close.d.ts +1 -0
- package/dist/commands/migrations/close.js +2 -0
- package/dist/commands/migrations/create.d.ts +1 -0
- package/dist/commands/migrations/create.js +2 -0
- package/dist/commands/migrations/current.d.ts +1 -0
- package/dist/commands/migrations/current.js +1 -0
- package/dist/commands/migrations/list.d.ts +1 -0
- package/dist/commands/migrations/list.js +2 -0
- package/dist/commands/migrations/run.d.ts +1 -0
- package/dist/commands/migrations/run.js +2 -0
- package/dist/commands/migrations/show.d.ts +1 -0
- package/dist/commands/migrations/show.js +2 -0
- package/dist/commands/migrations/status.d.ts +1 -0
- package/dist/commands/migrations/status.js +2 -0
- package/dist/commands/migrations/steps.d.ts +1 -0
- package/dist/commands/migrations/steps.js +2 -0
- package/dist/commands/migrations/unuse.d.ts +1 -0
- package/dist/commands/migrations/unuse.js +1 -0
- package/dist/commands/migrations/use.d.ts +1 -0
- package/dist/commands/migrations/use.js +1 -0
- package/dist/commands/paywalls/create.js +1 -1
- package/dist/commands/paywalls/get.js +1 -1
- package/dist/commands/paywalls/list.js +1 -1
- package/dist/commands/paywalls/placements.js +1 -1
- package/dist/commands/paywalls/update.js +1 -1
- package/dist/commands/placements/create.js +1 -1
- package/dist/commands/placements/get.js +1 -1
- package/dist/commands/placements/list.js +1 -1
- package/dist/commands/placements/update.js +1 -1
- package/dist/commands/products/create.js +1 -1
- package/dist/commands/products/get.js +1 -1
- package/dist/commands/products/list.js +1 -1
- package/dist/commands/products/update.js +1 -1
- package/dist/commands/segments/get.js +1 -1
- package/dist/commands/segments/list.js +1 -1
- package/dist/lib/config.d.ts +1 -1
- package/dist/sdk/adapty/auth/model.d.ts +0 -1
- package/dist/sdk/adapty/errors.d.ts +4 -0
- package/dist/sdk/adapty/errors.js +25 -0
- package/dist/sdk/adapty/index.d.ts +5 -0
- package/dist/sdk/adapty/index.js +29 -3
- package/dist/sdk/adapty/migrations/action.d.ts +15 -0
- package/dist/sdk/adapty/migrations/action.js +17 -0
- package/dist/sdk/adapty/migrations/close.d.ts +16 -0
- package/dist/sdk/adapty/migrations/close.js +7 -0
- package/dist/sdk/adapty/migrations/create.d.ts +16 -0
- package/dist/sdk/adapty/migrations/create.js +43 -0
- package/dist/sdk/adapty/migrations/index.d.ts +9 -0
- package/dist/sdk/adapty/migrations/index.js +4 -0
- package/dist/sdk/adapty/migrations/model.d.ts +100 -0
- package/dist/sdk/adapty/migrations/model.js +1 -0
- package/dist/sdk/adapty/migrations/resource.d.ts +27 -0
- package/dist/sdk/adapty/migrations/resource.js +52 -0
- package/dist/sdk/adapty/migrations/wait.d.ts +6 -0
- package/dist/sdk/adapty/migrations/wait.js +9 -0
- package/dist/sdk/core/session.d.ts +1 -1
- package/dist/sdk/core/session.js +4 -3
- package/oclif.manifest.json +1002 -397
- package/package.json +4 -1
- package/dist/cli/commands/auth/logout.js +0 -28
- package/dist/cli/flags.d.ts +0 -20
package/README.md
CHANGED
|
@@ -33,8 +33,8 @@ Other auth commands:
|
|
|
33
33
|
```sh
|
|
34
34
|
adapty auth whoami # verify token, show user info
|
|
35
35
|
adapty auth status # show local auth state
|
|
36
|
-
adapty auth logout # clear stored
|
|
37
|
-
adapty auth revoke # revoke active token and clear
|
|
36
|
+
adapty auth logout # clear stored credentials and migration selection (local only)
|
|
37
|
+
adapty auth revoke # revoke active token and clear matching credentials and selection
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
`auth revoke` uses `ADAPTY_TOKEN` when set, otherwise the stored token. A different token in the
|
|
@@ -42,6 +42,14 @@ session file is preserved. After revoking an environment token, unset `ADAPTY_TO
|
|
|
42
42
|
stored session will then become active again. With no token, revoke succeeds without a request
|
|
43
43
|
and returns `{"status":"not_authenticated"}` under `--json`.
|
|
44
44
|
|
|
45
|
+
`auth logout` removes the saved migration selection even without stored credentials or with a
|
|
46
|
+
malformed context. `auth revoke` removes selection only after server success and only when it
|
|
47
|
+
belongs to the revoked token. Failed revocation preserves both files. Both cleanup operations
|
|
48
|
+
are attempted independently; incomplete cleanup returns exit 1 (`auth_cleanup_failed`). If the
|
|
49
|
+
server already revoked the token, the error says so: fix local files without repeating revocation.
|
|
50
|
+
Environment variables remain in the parent shell; unset `ADAPTY_TOKEN` and `ADAPTY_MIGRATION`
|
|
51
|
+
there when needed.
|
|
52
|
+
|
|
45
53
|
## Commands
|
|
46
54
|
|
|
47
55
|
All resource commands require `--app APP_ID` (UUID). Use `adapty apps list` to find your app ID.
|
|
@@ -105,6 +113,142 @@ adapty access-levels create --app UUID [flags]
|
|
|
105
113
|
adapty access-levels update --app UUID ACCESS_LEVEL_ID [flags]
|
|
106
114
|
```
|
|
107
115
|
|
|
116
|
+
### Migrations
|
|
117
|
+
|
|
118
|
+
Manage migrations into Adapty: catalog, transactions and store events. The server provides the
|
|
119
|
+
steps, available actions and input schemas; use the current response to choose what to do next.
|
|
120
|
+
|
|
121
|
+
#### Start or find a migration
|
|
122
|
+
|
|
123
|
+
```sh
|
|
124
|
+
adapty migrations list
|
|
125
|
+
adapty migrations create --name "Acme Fitness" --json
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`--name` starts a catalog migration into a new Adapty app. For an existing app, choose a flow and
|
|
129
|
+
its app from `list` and pass both flags (replace `FLOW` and `APP_ID` with those values):
|
|
130
|
+
|
|
131
|
+
```sh
|
|
132
|
+
adapty migrations create --flow FLOW --app APP_ID --json
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
These are alternative creation modes: `--name` cannot be combined with `--flow` or `--app`.
|
|
136
|
+
Creation starts the flow and saves it as current; the returned JSON contains its ID in `migration.id`.
|
|
137
|
+
Use `--no-select` to create without changing the saved selection. If creation succeeds but saving
|
|
138
|
+
fails, the command still succeeds and prints a warning with the explicit continuation command.
|
|
139
|
+
|
|
140
|
+
Commands choose a migration in this order: `-m, --migration`, non-empty `ADAPTY_MIGRATION`,
|
|
141
|
+
then the saved selection for the current token. Explicit IDs do not change the saved selection.
|
|
142
|
+
Replace `mig_7x2` below with an ID from `create` or `list`; agents should pass `-m` explicitly.
|
|
143
|
+
|
|
144
|
+
#### Manage a saved selection
|
|
145
|
+
|
|
146
|
+
```sh
|
|
147
|
+
adapty migrations use mig_7x2
|
|
148
|
+
adapty migrations current --json
|
|
149
|
+
adapty migrations unuse
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`use` verifies access through the API, then saves the returned ID for the current token.
|
|
153
|
+
`current` reads the effective selection locally: `ADAPTY_MIGRATION` takes precedence over the
|
|
154
|
+
saved context. `unuse` removes the saved selection without authentication; an environment override
|
|
155
|
+
must be unset in your shell. Changing tokens makes the previous token's selection inapplicable.
|
|
156
|
+
|
|
157
|
+
The selection is shared across terminals and survives restarting the CLI. After `use` or `create`,
|
|
158
|
+
you can run `adapty migrations status`, `steps`, `show`, `run` or `close` without `-m`.
|
|
159
|
+
An operation keeps its initially selected ID even if another terminal changes the selection.
|
|
160
|
+
`run` and `close` print the target on stderr before a mutation that uses saved selection.
|
|
161
|
+
Scripts should pass explicit IDs and use `create --no-select` to preserve the shared default.
|
|
162
|
+
|
|
163
|
+
#### Inspect and run an action
|
|
164
|
+
|
|
165
|
+
```sh
|
|
166
|
+
adapty migrations status -m mig_7x2 --json
|
|
167
|
+
adapty migrations steps -m mig_7x2
|
|
168
|
+
adapty migrations show -m mig_7x2
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`steps` shows the checklist. `show` lists readable resource names; pass one as `RESOURCE` to read
|
|
172
|
+
its data. Choose `ACTION_ID` from `next_actions` or `available_actions` in the status response.
|
|
173
|
+
For an input action, read its `reads` resources and prepare a JSON object matching `input_schema`:
|
|
174
|
+
|
|
175
|
+
```sh
|
|
176
|
+
adapty migrations show RESOURCE -m mig_7x2
|
|
177
|
+
adapty migrations run ACTION_ID -m mig_7x2 --input-file ./decisions.json --json
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Alternatively, use `--input-file -` with stdin (`< ./decisions.json`), or `--input '{}'` if the
|
|
181
|
+
schema allows an empty object. Omitting input also sends `{}`. Use only one input option.
|
|
182
|
+
|
|
183
|
+
Read the action's full `confirm` text in `status` or `status --json` before adding `--yes`. Input actions
|
|
184
|
+
requiring confirmation exit **6** without it; there is no interactive prompt. The CLI reads
|
|
185
|
+
status before refusing, but does not send the action request. After an action, check status again.
|
|
186
|
+
After `revision_conflict`, read status and review the current action, input and confirmation before retrying.
|
|
187
|
+
|
|
188
|
+
For an `external` action, complete the step in the browser, then check status:
|
|
189
|
+
|
|
190
|
+
```sh
|
|
191
|
+
adapty migrations run ACTION_ID -m mig_7x2 --no-browser
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
`--no-browser` prints the action details and link. By default, the browser opens in an interactive
|
|
195
|
+
terminal; pipes and `--json` require `--open`. `BROWSER=none` disables opening, and only HTTPS
|
|
196
|
+
links are supported. External actions do not send an action request; their JSON response reflects
|
|
197
|
+
the migration before the browser step. Passing `--input` or `--input-file` to an external action
|
|
198
|
+
returns a usage error (exit 2). File uploads are marked unsupported in `status`: use the dashboard or an
|
|
199
|
+
offered Cloud Export action. `list --all` is also unsupported.
|
|
200
|
+
|
|
201
|
+
#### Wait for a change or close a migration
|
|
202
|
+
|
|
203
|
+
```sh
|
|
204
|
+
adapty migrations status -m mig_7x2 --wait --timeout 5m --json
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
`--wait` returns when the revision changes, the state is no longer `running`, or the polling budget
|
|
208
|
+
cannot accommodate another pause. `--timeout` requires `--wait`: default 120s, range 1–600s,
|
|
209
|
+
with formats such as `300`, `300s` or `5m`. In-flight requests and retries may take longer.
|
|
210
|
+
Exit **0** means the request succeeded, even when the migration is still running or has failed;
|
|
211
|
+
inspect `migration.state`. Progress goes to stderr; Ctrl+C exits **130**.
|
|
212
|
+
|
|
213
|
+
To permanently mark a migration as completed:
|
|
214
|
+
|
|
215
|
+
```sh
|
|
216
|
+
adapty migrations close -m mig_7x2 --outcome finish --yes
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
To abandon it instead:
|
|
220
|
+
|
|
221
|
+
```sh
|
|
222
|
+
adapty migrations close -m mig_7x2 --outcome cancel --yes
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Both outcomes require `--yes`; there is no confirmation prompt.
|
|
226
|
+
|
|
227
|
+
#### JSON output
|
|
228
|
+
|
|
229
|
+
With `--json`, `list` returns `{items, available}`. All other migration commands return the full
|
|
230
|
+
migration response (the envelope):
|
|
231
|
+
|
|
232
|
+
| Data | JSON field |
|
|
233
|
+
| --- | --- |
|
|
234
|
+
| Migration ID, state and revision | `migration` |
|
|
235
|
+
| Next and optional actions | `next_actions`, `available_actions` |
|
|
236
|
+
| Checklist | `steps` |
|
|
237
|
+
| Readable resource names | `resources` |
|
|
238
|
+
| Data from `show RESOURCE` | `result` (may be `null`) |
|
|
239
|
+
|
|
240
|
+
Without `--json`, `show RESOURCE` prints just the resource data as JSON, or a message if empty.
|
|
241
|
+
Wizard Service errors preserve `detail`, `fields`, `next_step`, `request_id`, `retryable` and
|
|
242
|
+
`retry_after_seconds` under `error` in JSON when supplied. Human output includes details, field
|
|
243
|
+
errors, the next step and request ID. Retry metadata in the response does not change automatic retry behavior.
|
|
244
|
+
|
|
245
|
+
To extract specific fields from the full response, these examples require the separate `jq` utility:
|
|
246
|
+
|
|
247
|
+
```sh
|
|
248
|
+
adapty migrations steps -m mig_7x2 --json | jq '.steps'
|
|
249
|
+
adapty migrations show RESOURCE -m mig_7x2 --json | jq '.result'
|
|
250
|
+
```
|
|
251
|
+
|
|
108
252
|
### Apple Search Ads
|
|
109
253
|
|
|
110
254
|
Apple Search Ads commands live under `adapty asa` and talk to the ASA service rather than the Developer
|
|
@@ -457,6 +601,7 @@ the flags and the size ceiling.
|
|
|
457
601
|
| `ADAPTY_TOKEN` | Override stored auth token |
|
|
458
602
|
| `ADAPTY_API_URL` | Override Developer API base URL (default: `https://api-admin.adapty.io/api/v1/developer`) |
|
|
459
603
|
| `ADAPTY_ASA_API_URL` | Override Apple Search Ads base URL (default: `https://api-asa-admin.adapty.io/api/v1/cli`) |
|
|
604
|
+
| `ADAPTY_MIGRATION` | Migration id used by `adapty migrations` when `-m` is not given |
|
|
460
605
|
| `ADAPTY_ATTRIBUTION_API_URL` | Override attribution base URL (default: `https://api-ua.adapty.io/api/v1/cli`) |
|
|
461
606
|
| `ADAPTY_APP_URL` | Override dashboard base URL (default: `https://app.adapty.io`). Used by `flows config preview` for the fixed `/flow-preview` route, and by `auth login` to keep the verification link on that host |
|
|
462
607
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BaseCommand } from '../base-command.js';
|
|
2
2
|
import type { Adapty } from '../../../sdk/adapty/index.js';
|
|
3
|
-
import type { AuthenticatedSession } from '../session.js';
|
|
3
|
+
import type { AuthenticatedSession, ResolvedSession } from '../session.js';
|
|
4
4
|
/**
|
|
5
5
|
* Commands that require a token; auth commands other than `whoami` also work without one.
|
|
6
6
|
*
|
|
@@ -10,8 +10,9 @@ import type { AuthenticatedSession } from '../session.js';
|
|
|
10
10
|
*/
|
|
11
11
|
export declare abstract class AdaptyCommand extends BaseCommand {
|
|
12
12
|
#private;
|
|
13
|
-
static enableJsonFlag: boolean;
|
|
14
13
|
protected get adapty(): Adapty;
|
|
14
|
+
/** Effective session without enforcing auth, for local input resolution before SDK access. */
|
|
15
|
+
protected get resolvedSession(): ResolvedSession;
|
|
15
16
|
/** Narrowed once here, so nothing downstream re-checks the token. */
|
|
16
17
|
protected get session(): AuthenticatedSession;
|
|
17
18
|
protected init(): Promise<void>;
|
|
@@ -10,27 +10,30 @@ import { openSession } from './openSession.js';
|
|
|
10
10
|
* branch into all 75 commands, and one of them would forget it.
|
|
11
11
|
*/
|
|
12
12
|
export class AdaptyCommand extends BaseCommand {
|
|
13
|
-
// Keep one own static so oclif's manifest cache walks through this intermediate class and
|
|
14
|
-
// includes statics inherited from BaseCommand.
|
|
15
|
-
static enableJsonFlag = true;
|
|
16
13
|
#adapty;
|
|
17
14
|
#resolved;
|
|
18
15
|
get adapty() {
|
|
19
16
|
this.#adapty ??= build(this.session, {
|
|
20
17
|
config: this.config,
|
|
18
|
+
interactive: this.interactive,
|
|
21
19
|
signal: this.signal,
|
|
22
20
|
warn: message => this.warn(message),
|
|
23
21
|
});
|
|
24
22
|
return this.#adapty;
|
|
25
23
|
}
|
|
26
|
-
/**
|
|
27
|
-
get
|
|
24
|
+
/** Effective session without enforcing auth, for local input resolution before SDK access. */
|
|
25
|
+
get resolvedSession() {
|
|
28
26
|
const resolved = this.#resolved;
|
|
29
27
|
if (resolved === undefined) {
|
|
30
28
|
// A bug, not a user error, and deliberately not an SdkError: it lands as exit 1 with a
|
|
31
29
|
// stack instead of passing for a normal scenario.
|
|
32
30
|
throw new Error('session is available only after init()');
|
|
33
31
|
}
|
|
32
|
+
return resolved;
|
|
33
|
+
}
|
|
34
|
+
/** Narrowed once here, so nothing downstream re-checks the token. */
|
|
35
|
+
get session() {
|
|
36
|
+
const resolved = this.resolvedSession;
|
|
34
37
|
if (resolved.token === undefined) {
|
|
35
38
|
throw new AuthRequiredError('missing');
|
|
36
39
|
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { Adapty } from '../../../sdk/adapty/index.js';
|
|
2
2
|
import type { CommandContext } from '../base-command.js';
|
|
3
3
|
import type { ResolvedSession } from '../session.js';
|
|
4
|
+
/** The Adapty sdk also tells the server whether a person is watching (`X-Adapty-Interactive`). */
|
|
5
|
+
type AdaptyCommandContext = CommandContext & {
|
|
6
|
+
interactive: boolean;
|
|
7
|
+
};
|
|
4
8
|
/** Shared by authenticated commands and login/revoke, which can run without a token. */
|
|
5
|
-
export declare const build: (session: ResolvedSession, context:
|
|
9
|
+
export declare const build: (session: ResolvedSession, context: AdaptyCommandContext) => Adapty;
|
|
10
|
+
export {};
|
|
@@ -3,6 +3,7 @@ import { createAdapty } from '../../../sdk/adapty/index.js';
|
|
|
3
3
|
/** Shared by authenticated commands and login/revoke, which can run without a token. */
|
|
4
4
|
export const build = (session, context) => createAdapty({
|
|
5
5
|
baseUrl: session.apiUrl,
|
|
6
|
+
interactive: context.interactive,
|
|
6
7
|
onRetry: ({ attempt, delayMs }) => {
|
|
7
8
|
context.warn(`Request failed, retrying in ${delayMs / 1000}s (attempt ${attempt + 1})`);
|
|
8
9
|
},
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { AdaptyCommand } from './adapty-command.js';
|
|
2
2
|
export { build } from './build.js';
|
|
3
|
+
export { MigrationCommand } from './migration-command.js';
|
|
3
4
|
export { openSession, resolveSession } from './openSession.js';
|
|
4
5
|
export type { AuthenticatedSession, ResolvedSession } from '../session.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { AdaptyCommand } from './adapty-command.js';
|
|
2
|
+
import type { CurrentMigration } from '../../context/migration/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* `AdaptyCommand` plus the saved migration selection. One topic out of ten stays a base of its own
|
|
5
|
+
* rather than a getter on `AdaptyCommand`, where all 75 commands would pay for it.
|
|
6
|
+
*/
|
|
7
|
+
export declare abstract class MigrationCommand extends AdaptyCommand {
|
|
8
|
+
#private;
|
|
9
|
+
/** A getter, not a field: field initializers run in the constructor, before init() has a session. */
|
|
10
|
+
protected get currentMigration(): CurrentMigration;
|
|
11
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { openCurrentMigration } from '../../context/migration/index.js';
|
|
2
|
+
import { AdaptyCommand } from './adapty-command.js';
|
|
3
|
+
/**
|
|
4
|
+
* `AdaptyCommand` plus the saved migration selection. One topic out of ten stays a base of its own
|
|
5
|
+
* rather than a getter on `AdaptyCommand`, where all 75 commands would pay for it.
|
|
6
|
+
*/
|
|
7
|
+
export class MigrationCommand extends AdaptyCommand {
|
|
8
|
+
#current;
|
|
9
|
+
/** A getter, not a field: field initializers run in the constructor, before init() has a session. */
|
|
10
|
+
get currentMigration() {
|
|
11
|
+
this.#current ??= openCurrentMigration({
|
|
12
|
+
configDir: this.config.configDir,
|
|
13
|
+
session: this.resolvedSession,
|
|
14
|
+
});
|
|
15
|
+
return this.#current;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -18,6 +18,12 @@ export declare abstract class BaseCommand extends Command {
|
|
|
18
18
|
#private;
|
|
19
19
|
/** --json is a property of the CLI, not a per-command opt-in; `auth login` opts back out. */
|
|
20
20
|
static enableJsonFlag: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Whether a person is watching this run. --json means a program is reading, even from a
|
|
23
|
+
* terminal, and a pipe means one is reading whatever the flags say. The sdk sends the answer
|
|
24
|
+
* as `X-Adapty-Interactive`; `migrations run` asks it before opening a browser.
|
|
25
|
+
*/
|
|
26
|
+
protected get interactive(): boolean;
|
|
21
27
|
protected get signal(): AbortSignal;
|
|
22
28
|
protected catch(error: Error): Promise<unknown>;
|
|
23
29
|
protected toErrorJson(error: unknown): {
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
2
|
import { CliError, toCliError } from '../errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* On a pipe Node leaves `isTTY` absent, not false, while @types/node promises a boolean. Taken at
|
|
5
|
+
* its word, a piped run answers `undefined` — a third answer to a two-answer question. The
|
|
6
|
+
* parameter type is the one place to say so.
|
|
7
|
+
*/
|
|
8
|
+
const attachedToTerminal = (stream) => stream.isTTY === true;
|
|
3
9
|
/**
|
|
4
10
|
* What every command gets and nothing more: the output channel, cancellation, the single place
|
|
5
11
|
* where sdk errors become CLI errors. Product SDKs and sessions belong to their adapters.
|
|
@@ -16,6 +22,14 @@ export class BaseCommand extends Command {
|
|
|
16
22
|
#onSigint = () => {
|
|
17
23
|
this.#abort.abort();
|
|
18
24
|
};
|
|
25
|
+
/**
|
|
26
|
+
* Whether a person is watching this run. --json means a program is reading, even from a
|
|
27
|
+
* terminal, and a pipe means one is reading whatever the flags say. The sdk sends the answer
|
|
28
|
+
* as `X-Adapty-Interactive`; `migrations run` asks it before opening a browser.
|
|
29
|
+
*/
|
|
30
|
+
get interactive() {
|
|
31
|
+
return attachedToTerminal(process.stdout) && !this.jsonEnabled();
|
|
32
|
+
}
|
|
19
33
|
get signal() {
|
|
20
34
|
return this.#abort.signal;
|
|
21
35
|
}
|
|
@@ -5,7 +5,7 @@ export default class AppsCreate extends AdaptyCommand {
|
|
|
5
5
|
static examples: string[];
|
|
6
6
|
static flags: {
|
|
7
7
|
title: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
-
platform: import("@oclif/core/interfaces").OptionFlag<("
|
|
8
|
+
platform: import("@oclif/core/interfaces").OptionFlag<("android" | "ios")[], import("@oclif/core/interfaces").CustomOptions>;
|
|
9
9
|
'apple-bundle-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
10
|
'google-bundle-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
11
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AdaptyCommand } from '../../base/adapty/index.js';
|
|
2
|
-
import { appIdArg } from '../../
|
|
2
|
+
import { appIdArg } from '../../input/app.js';
|
|
3
3
|
import { renderRecord } from '../../views/record.js';
|
|
4
4
|
export default class AppsGet extends AdaptyCommand {
|
|
5
5
|
static args = { ...appIdArg };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AdaptyCommand } from '../../base/adapty/index.js';
|
|
2
|
-
import { pageParams, paginationFlags } from '../../
|
|
2
|
+
import { pageParams, paginationFlags } from '../../input/pagination.js';
|
|
3
3
|
import { renderPage } from '../../views/list.js';
|
|
4
4
|
export default class AppsList extends AdaptyCommand {
|
|
5
5
|
static description = 'List Adapty apps';
|
|
@@ -2,7 +2,7 @@ import { Flags } from '@oclif/core';
|
|
|
2
2
|
import { validateUpdateApp } from '../../../sdk/adapty/apps/index.js';
|
|
3
3
|
import { assertValid } from '../../../sdk/core/validation.js';
|
|
4
4
|
import { AdaptyCommand } from '../../base/adapty/index.js';
|
|
5
|
-
import { appIdArg } from '../../
|
|
5
|
+
import { appIdArg } from '../../input/app.js';
|
|
6
6
|
import { renderRecord } from '../../views/record.js';
|
|
7
7
|
export default class AppsUpdate extends AdaptyCommand {
|
|
8
8
|
static args = { ...appIdArg };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { AttributionCommand } from '../../../base/attribution/index.js';
|
|
2
|
+
import type { ReportFilter, ReportResponse, ReportSort } from '../../../../sdk/attribution/index.js';
|
|
3
|
+
export default class AttributionReport extends AttributionCommand {
|
|
4
|
+
static description: string;
|
|
5
|
+
static examples: string[];
|
|
6
|
+
static flags: {
|
|
7
|
+
sort: import("@oclif/core/interfaces").OptionFlag<ReportSort | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
'revenue-basis': import("@oclif/core/interfaces").OptionFlag<"gross" | "proceeds" | "net" | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
metrics: import("@oclif/core/interfaces").OptionFlag<string[], import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
'group-by': import("@oclif/core/interfaces").OptionFlag<("store" | "date" | "campaign" | "adset" | "ad" | "keyword" | "channel" | "country")[], import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
granularity: import("@oclif/core/interfaces").OptionFlag<"day" | "week" | "month" | "quarter" | "year" | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
filter: import("@oclif/core/interfaces").OptionFlag<ReportFilter[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
'date-from': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
'date-to': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
};
|
|
17
|
+
run(): Promise<ReportResponse>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { granularities, reportGroupBy, sortDirections, validateReport } from '../../../../sdk/attribution/index.js';
|
|
3
|
+
import { assertValid } from '../../../../sdk/core/validation.js';
|
|
4
|
+
import { AttributionCommand, periodFlags, periodParams, revenueBasisFlag } from '../../../base/attribution/index.js';
|
|
5
|
+
import { usageError } from '../../../errors.js';
|
|
6
|
+
import { appIdFlag } from '../../../input/app.js';
|
|
7
|
+
import { renderReport } from './lib/render.js';
|
|
8
|
+
/** A comma not preceded by a backslash: `\,` keeps a comma inside a value, as oclif's own delimiter does. */
|
|
9
|
+
const VALUE_SEPARATOR = /(?<!\\),/;
|
|
10
|
+
/** `dimension=value[,value]`: one value filters by equality, several by any of them. */
|
|
11
|
+
const parseFilter = (input) => {
|
|
12
|
+
const separator = input.indexOf('=');
|
|
13
|
+
const dimension = separator === -1 ? '' : input.slice(0, separator).trim();
|
|
14
|
+
const values = separator === -1
|
|
15
|
+
? []
|
|
16
|
+
: input.slice(separator + 1).split(VALUE_SEPARATOR).map(value => value.replaceAll('\\,', ',').trim())
|
|
17
|
+
.filter(value => value !== '');
|
|
18
|
+
return dimension === '' || values.length === 0
|
|
19
|
+
? Promise.reject(usageError(`Expected dimension=value[,value], got "${input}".`))
|
|
20
|
+
: Promise.resolve({ dimension, values });
|
|
21
|
+
};
|
|
22
|
+
const isDirection = (value) => sortDirections.includes(value);
|
|
23
|
+
/** `field[:asc|desc]`, ascending when the direction is left out. */
|
|
24
|
+
const parseSort = (input) => {
|
|
25
|
+
const separator = input.lastIndexOf(':');
|
|
26
|
+
const field = (separator === -1 ? input : input.slice(0, separator)).trim();
|
|
27
|
+
const direction = separator === -1 ? 'asc' : input.slice(separator + 1).trim();
|
|
28
|
+
if (field === '') {
|
|
29
|
+
return Promise.reject(usageError(`Expected field[:asc|desc], got "${input}".`));
|
|
30
|
+
}
|
|
31
|
+
return isDirection(direction)
|
|
32
|
+
? Promise.resolve({ direction, field })
|
|
33
|
+
: Promise.reject(usageError(`The sort direction must be asc or desc, got "${direction}".`));
|
|
34
|
+
};
|
|
35
|
+
export default class AttributionReport extends AttributionCommand {
|
|
36
|
+
static description = 'Run an Attribution report for an app: metrics over a period, grouped by dimensions';
|
|
37
|
+
static examples = [
|
|
38
|
+
'<%= config.bin %> attribution report --app APP_UUID --date-from 2026-08-01 --date-to 2026-08-31 --metrics spend,installs,d7_roas --group-by campaign',
|
|
39
|
+
'<%= config.bin %> attribution report --app APP_UUID --date-from 2026-08-01 --date-to 2026-08-31 --metrics spend --group-by date --granularity week --filter country=US,GB --sort spend:desc',
|
|
40
|
+
'<%= config.bin %> attribution report --app APP_UUID --date-from 2026-08-01 --date-to 2026-08-31 --metrics spend,installs,d7_roas --group-by campaign --json',
|
|
41
|
+
];
|
|
42
|
+
// Input shape here; the rules (dates in order, a granularity exactly when grouping by date) live
|
|
43
|
+
// in sdk/attribution/report.ts, and which names exist is the backend catalog's knowledge.
|
|
44
|
+
static flags = {
|
|
45
|
+
...appIdFlag,
|
|
46
|
+
...periodFlags,
|
|
47
|
+
'metrics': Flags.string({
|
|
48
|
+
delimiter: ',',
|
|
49
|
+
description: 'Metric names, as `adapty attribution metrics` lists them (repeatable or comma-separated)',
|
|
50
|
+
multiple: true,
|
|
51
|
+
required: true,
|
|
52
|
+
}),
|
|
53
|
+
'group-by': Flags.option({
|
|
54
|
+
delimiter: ',',
|
|
55
|
+
description: 'Dimensions to group rows by (repeatable or comma-separated)',
|
|
56
|
+
multiple: true,
|
|
57
|
+
options: reportGroupBy,
|
|
58
|
+
required: true,
|
|
59
|
+
})(),
|
|
60
|
+
'granularity': Flags.option({
|
|
61
|
+
description: 'Date bucket; required with --group-by date, and allowed only with it',
|
|
62
|
+
options: granularities,
|
|
63
|
+
})(),
|
|
64
|
+
'filter': Flags.custom({
|
|
65
|
+
description: 'Keep rows whose dimension has one of the values: dimension=value[,value] (repeatable; `\\,` for a comma inside a value)',
|
|
66
|
+
multiple: true,
|
|
67
|
+
parse: parseFilter,
|
|
68
|
+
})(),
|
|
69
|
+
...revenueBasisFlag,
|
|
70
|
+
'sort': Flags.custom({
|
|
71
|
+
description: 'Sort rows by a requested metric or dimension: field[:asc|desc] (ascending by default)',
|
|
72
|
+
parse: parseSort,
|
|
73
|
+
})(),
|
|
74
|
+
};
|
|
75
|
+
async run() {
|
|
76
|
+
const { flags } = await this.parse(AttributionReport);
|
|
77
|
+
const input = {
|
|
78
|
+
appId: flags.app,
|
|
79
|
+
...periodParams(flags),
|
|
80
|
+
filters: flags.filter,
|
|
81
|
+
granularity: flags.granularity,
|
|
82
|
+
groupBy: flags['group-by'],
|
|
83
|
+
metrics: flags.metrics.filter(metric => metric !== ''),
|
|
84
|
+
revenueBasis: flags['revenue-basis'],
|
|
85
|
+
sort: flags.sort,
|
|
86
|
+
};
|
|
87
|
+
// Before this.attribution: bad input is exit 2 even without a token
|
|
88
|
+
assertValid(validateReport(input));
|
|
89
|
+
const report = await this.attribution.report(input);
|
|
90
|
+
this.render(report, renderReport);
|
|
91
|
+
return report;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -1,18 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import type { ReportFilter, ReportResponse, ReportSort } from '../../../../sdk/attribution/index.js';
|
|
3
|
-
export default class AttributionReport extends AttributionCommand {
|
|
4
|
-
static description: string;
|
|
5
|
-
static examples: string[];
|
|
6
|
-
static flags: {
|
|
7
|
-
sort: import("@oclif/core/interfaces").OptionFlag<ReportSort | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
-
'revenue-basis': import("@oclif/core/interfaces").OptionFlag<"gross" | "proceeds" | "net" | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
-
metrics: import("@oclif/core/interfaces").OptionFlag<string[], import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
-
'group-by': import("@oclif/core/interfaces").OptionFlag<("store" | "date" | "campaign" | "adset" | "ad" | "keyword" | "channel" | "country")[], import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
-
granularity: import("@oclif/core/interfaces").OptionFlag<"day" | "week" | "month" | "quarter" | "year" | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
-
filter: import("@oclif/core/interfaces").OptionFlag<ReportFilter[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
-
'date-from': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
-
'date-to': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
-
app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
-
};
|
|
17
|
-
run(): Promise<ReportResponse>;
|
|
18
|
-
}
|
|
1
|
+
export { default } from './command.js';
|
|
@@ -1,93 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { granularities, reportGroupBy, sortDirections, validateReport } from '../../../../sdk/attribution/index.js';
|
|
3
|
-
import { assertValid } from '../../../../sdk/core/validation.js';
|
|
4
|
-
import { AttributionCommand, periodFlags, periodParams, revenueBasisFlag } from '../../../base/attribution/index.js';
|
|
5
|
-
import { usageError } from '../../../errors.js';
|
|
6
|
-
import { appIdFlag } from '../../../flags.js';
|
|
7
|
-
import { renderReport } from './lib/render.js';
|
|
8
|
-
/** A comma not preceded by a backslash: `\,` keeps a comma inside a value, as oclif's own delimiter does. */
|
|
9
|
-
const VALUE_SEPARATOR = /(?<!\\),/;
|
|
10
|
-
/** `dimension=value[,value]`: one value filters by equality, several by any of them. */
|
|
11
|
-
const parseFilter = (input) => {
|
|
12
|
-
const separator = input.indexOf('=');
|
|
13
|
-
const dimension = separator === -1 ? '' : input.slice(0, separator).trim();
|
|
14
|
-
const values = separator === -1
|
|
15
|
-
? []
|
|
16
|
-
: input.slice(separator + 1).split(VALUE_SEPARATOR).map(value => value.replaceAll('\\,', ',').trim())
|
|
17
|
-
.filter(value => value !== '');
|
|
18
|
-
return dimension === '' || values.length === 0
|
|
19
|
-
? Promise.reject(usageError(`Expected dimension=value[,value], got "${input}".`))
|
|
20
|
-
: Promise.resolve({ dimension, values });
|
|
21
|
-
};
|
|
22
|
-
const isDirection = (value) => sortDirections.includes(value);
|
|
23
|
-
/** `field[:asc|desc]`, ascending when the direction is left out. */
|
|
24
|
-
const parseSort = (input) => {
|
|
25
|
-
const separator = input.lastIndexOf(':');
|
|
26
|
-
const field = (separator === -1 ? input : input.slice(0, separator)).trim();
|
|
27
|
-
const direction = separator === -1 ? 'asc' : input.slice(separator + 1).trim();
|
|
28
|
-
if (field === '') {
|
|
29
|
-
return Promise.reject(usageError(`Expected field[:asc|desc], got "${input}".`));
|
|
30
|
-
}
|
|
31
|
-
return isDirection(direction)
|
|
32
|
-
? Promise.resolve({ direction, field })
|
|
33
|
-
: Promise.reject(usageError(`The sort direction must be asc or desc, got "${direction}".`));
|
|
34
|
-
};
|
|
35
|
-
export default class AttributionReport extends AttributionCommand {
|
|
36
|
-
static description = 'Run an Attribution report for an app: metrics over a period, grouped by dimensions';
|
|
37
|
-
static examples = [
|
|
38
|
-
'<%= config.bin %> attribution report --app APP_UUID --date-from 2026-08-01 --date-to 2026-08-31 --metrics spend,installs,d7_roas --group-by campaign',
|
|
39
|
-
'<%= config.bin %> attribution report --app APP_UUID --date-from 2026-08-01 --date-to 2026-08-31 --metrics spend --group-by date --granularity week --filter country=US,GB --sort spend:desc',
|
|
40
|
-
'<%= config.bin %> attribution report --app APP_UUID --date-from 2026-08-01 --date-to 2026-08-31 --metrics spend,installs,d7_roas --group-by campaign --json',
|
|
41
|
-
];
|
|
42
|
-
// Input shape here; the rules (dates in order, a granularity exactly when grouping by date) live
|
|
43
|
-
// in sdk/attribution/report.ts, and which names exist is the backend catalog's knowledge.
|
|
44
|
-
static flags = {
|
|
45
|
-
...appIdFlag,
|
|
46
|
-
...periodFlags,
|
|
47
|
-
'metrics': Flags.string({
|
|
48
|
-
delimiter: ',',
|
|
49
|
-
description: 'Metric names, as `adapty attribution metrics` lists them (repeatable or comma-separated)',
|
|
50
|
-
multiple: true,
|
|
51
|
-
required: true,
|
|
52
|
-
}),
|
|
53
|
-
'group-by': Flags.option({
|
|
54
|
-
delimiter: ',',
|
|
55
|
-
description: 'Dimensions to group rows by (repeatable or comma-separated)',
|
|
56
|
-
multiple: true,
|
|
57
|
-
options: reportGroupBy,
|
|
58
|
-
required: true,
|
|
59
|
-
})(),
|
|
60
|
-
'granularity': Flags.option({
|
|
61
|
-
description: 'Date bucket; required with --group-by date, and allowed only with it',
|
|
62
|
-
options: granularities,
|
|
63
|
-
})(),
|
|
64
|
-
'filter': Flags.custom({
|
|
65
|
-
description: 'Keep rows whose dimension has one of the values: dimension=value[,value] (repeatable; `\\,` for a comma inside a value)',
|
|
66
|
-
multiple: true,
|
|
67
|
-
parse: parseFilter,
|
|
68
|
-
})(),
|
|
69
|
-
...revenueBasisFlag,
|
|
70
|
-
'sort': Flags.custom({
|
|
71
|
-
description: 'Sort rows by a requested metric or dimension: field[:asc|desc] (ascending by default)',
|
|
72
|
-
parse: parseSort,
|
|
73
|
-
})(),
|
|
74
|
-
};
|
|
75
|
-
async run() {
|
|
76
|
-
const { flags } = await this.parse(AttributionReport);
|
|
77
|
-
const input = {
|
|
78
|
-
appId: flags.app,
|
|
79
|
-
...periodParams(flags),
|
|
80
|
-
filters: flags.filter,
|
|
81
|
-
granularity: flags.granularity,
|
|
82
|
-
groupBy: flags['group-by'],
|
|
83
|
-
metrics: flags.metrics.filter(metric => metric !== ''),
|
|
84
|
-
revenueBasis: flags['revenue-basis'],
|
|
85
|
-
sort: flags.sort,
|
|
86
|
-
};
|
|
87
|
-
// Before this.attribution: bad input is exit 2 even without a token
|
|
88
|
-
assertValid(validateReport(input));
|
|
89
|
-
const report = await this.attribution.report(input);
|
|
90
|
-
this.render(report, renderReport);
|
|
91
|
-
return report;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
1
|
+
export { default } from './command.js';
|
|
@@ -2,7 +2,7 @@ import { Flags } from '@oclif/core';
|
|
|
2
2
|
import { validateValues } from '../../../sdk/attribution/index.js';
|
|
3
3
|
import { assertValid } from '../../../sdk/core/validation.js';
|
|
4
4
|
import { AttributionCommand, periodFlags, periodParams, revenueBasisFlag } from '../../base/attribution/index.js';
|
|
5
|
-
import { appIdFlag } from '../../
|
|
5
|
+
import { appIdFlag } from '../../input/app.js';
|
|
6
6
|
const describeItem = (item) => {
|
|
7
7
|
if ('value' in item) {
|
|
8
8
|
return item.value ?? '—';
|
|
@@ -14,11 +14,12 @@ export default class AuthLogin extends BaseCommand {
|
|
|
14
14
|
await this.parse(AuthLogin);
|
|
15
15
|
const session = await openSession(this.config);
|
|
16
16
|
if (session.user !== undefined) {
|
|
17
|
-
this.log(`Already authenticated as ${session.user.
|
|
17
|
+
this.log(`Already authenticated as ${session.user.name}. Re-authenticating...`);
|
|
18
18
|
}
|
|
19
19
|
// No token yet: the same factory without one, which leaves only auth reachable
|
|
20
20
|
const adapty = build({ ...session, token: undefined }, {
|
|
21
21
|
config: this.config,
|
|
22
|
+
interactive: this.interactive,
|
|
22
23
|
signal: this.signal,
|
|
23
24
|
warn: message => this.warn(message),
|
|
24
25
|
});
|
|
@@ -44,7 +45,7 @@ export default class AuthLogin extends BaseCommand {
|
|
|
44
45
|
signal: this.signal,
|
|
45
46
|
});
|
|
46
47
|
await session.store.save({ token: issued.accessToken, user: issued.user });
|
|
47
|
-
this.log(`\nAuthenticated as ${issued.user.
|
|
48
|
+
this.log(`\nAuthenticated as ${issued.user.name}`);
|
|
48
49
|
this.log(`Session saved to ${session.store.path}`);
|
|
49
50
|
}
|
|
50
51
|
/** ADAPTY_APP_URL is the CLI's business: the sdk knows nothing about browsers or dashboards. */
|