Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ src/
# capture is the caller's job, the CLI only builds the URL;
# validate — advisory publishability check, always 200, exits non-zero when invalid);
# media/ (upload — multipart image upload, returns CDN url to reference in a config)
migrations/ # create, list, status (--wait), steps, show, run, close — a thin client of the
# Wizard Service: the flow lives on the server, every answer is one envelope.
# Implementation in src/cli/commands/migrations, contract in docs/plans
segments/ # list, get
access-levels/ # list, get, create, update
asa/ # Apple Search Ads: whoami, connect, orgs, apps, campaigns, ad-groups, keywords,
Expand Down
116 changes: 116 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,121 @@ adapty access-levels create --app UUID [flags]
adapty access-levels update --app UUID ACCESS_LEVEL_ID [flags]
```

### Migrations

Manage migrations into Adapty: catalog, transactions and store events. The server provides the
steps, available actions and input schemas; use the current response to choose what to do next.

#### Start or find a migration

```sh
adapty migrations list
adapty migrations create --name "Acme Fitness" --json
```

`--name` starts a catalog migration into a new Adapty app. For an existing app, choose a flow and
its app from `list` and pass both flags (replace `FLOW` and `APP_ID` with those values):

```sh
adapty migrations create --flow FLOW --app APP_ID --json
```

These are alternative creation modes: `--name` cannot be combined with `--flow` or `--app`.
Creation starts the flow; the returned JSON contains its ID in `migration.id`.

Commands operating on a migration require `-m, --migration` or `ADAPTY_MIGRATION`. An explicit
flag overrides the environment variable. The CLI does not select a migration automatically.
Replace `mig_7x2` below with an ID from `create` or `list`; agents should pass `-m` explicitly.

#### Inspect and run an action

```sh
adapty migrations status -m mig_7x2 --json
adapty migrations steps -m mig_7x2
adapty migrations show -m mig_7x2
```

`steps` shows the checklist. `show` lists readable resource names; pass one as `RESOURCE` to read
its data. Choose `ACTION_ID` from `next_actions` or `available_actions` in the status response.
For an input action, read its `reads` resources and prepare a JSON object matching `input_schema`:

```sh
adapty migrations show RESOURCE -m mig_7x2
adapty migrations run ACTION_ID -m mig_7x2 --input-file ./decisions.json --json
```

Alternatively, use `--input-file -` with stdin (`< ./decisions.json`), or `--input '{}'` if the
schema allows an empty object. Omitting input also sends `{}`. Use only one input option.

Read the action's full `confirm` text in `status` or `status --json` before adding `--yes`. Input actions
requiring confirmation exit **6** without it; there is no interactive prompt. The CLI reads
status before refusing, but does not send the action request. After an action, check status again.
After `revision_conflict`, read status and review the current action, input and confirmation before retrying.

For an `external` action, complete the step in the browser, then check status:

```sh
adapty migrations run ACTION_ID -m mig_7x2 --no-browser
```

`--no-browser` prints the action details and link. By default, the browser opens in an interactive
terminal; pipes and `--json` require `--open`. `BROWSER=none` disables opening, and only HTTPS
links are supported. External actions do not send an action request; their JSON response reflects
the migration before the browser step. Passing `--input` or `--input-file` to an external action
returns a usage error (exit 2). File uploads are marked unsupported in `status`: use the dashboard or an
offered Cloud Export action. `list --all` is also unsupported.

#### Wait for a change or close a migration

```sh
adapty migrations status -m mig_7x2 --wait --timeout 5m --json
```

`--wait` returns when the revision changes, the state is no longer `running`, or the polling budget
cannot accommodate another pause. `--timeout` requires `--wait`: default 120s, range 1–600s,
with formats such as `300`, `300s` or `5m`. In-flight requests and retries may take longer.
Exit **0** means the request succeeded, even when the migration is still running or has failed;
inspect `migration.state`. Progress goes to stderr; Ctrl+C exits **130**.

To permanently mark a migration as completed:

```sh
adapty migrations close -m mig_7x2 --outcome finish --yes
```

To abandon it instead:

```sh
adapty migrations close -m mig_7x2 --outcome cancel --yes
```

Both outcomes require `--yes`; there is no confirmation prompt.

#### JSON output

With `--json`, `list` returns `{items, available}`. All other migration commands return the full
migration response (the envelope):

| Data | JSON field |
| --- | --- |
| Migration ID, state and revision | `migration` |
| Next and optional actions | `next_actions`, `available_actions` |
| Checklist | `steps` |
| Readable resource names | `resources` |
| Data from `show RESOURCE` | `result` (may be `null`) |

Without `--json`, `show RESOURCE` prints just the resource data as JSON, or a message if empty.
Wizard Service errors preserve `detail`, `fields`, `next_step`, `request_id`, `retryable` and
`retry_after_seconds` under `error` in JSON when supplied. Human output includes details, field
errors, the next step and request ID. Retry metadata in the response does not change automatic retry behavior.

To extract specific fields from the full response, these examples require the separate `jq` utility:

```sh
adapty migrations steps -m mig_7x2 --json | jq '.steps'
adapty migrations show RESOURCE -m mig_7x2 --json | jq '.result'
```

### Apple Search Ads

Apple Search Ads commands live under `adapty asa` and talk to the ASA service rather than the Developer
Expand Down Expand Up @@ -429,6 +544,7 @@ the flags and the size ceiling.
| `ADAPTY_TOKEN` | Override stored auth token |
| `ADAPTY_API_URL` | Override Developer API base URL (default: `https://api-admin.adapty.io/api/v1/developer`) |
| `ADAPTY_ASA_API_URL` | Override Apple Search Ads base URL (default: `https://api-asa-admin.adapty.io/api/v1/cli`) |
| `ADAPTY_MIGRATION` | Migration id used by `adapty migrations` when `-m` is not given |
| `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 |

The two API URLs are independent: pointing `ADAPTY_API_URL` at a staging host leaves `adapty asa` on the ASA
Expand Down
32 changes: 29 additions & 3 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,32 @@ text or JSON.
authorization" is expressed in what a command extends, not re-checked inside `run()` bodies.
- `errors.ts` — the single `SdkError` → CLI error mapping. The switch has no default, so a new
error kind fails to compile until it is given a message and an exit code.
- `flags.ts` — shared flags and args (app id UUID, pagination) and the one place flag names meet
sdk field names.
- `input/` — shared flags and args (app id UUID, pagination, migration id), one module per concern,
and the one place flag names meet sdk field names.
- `views/` — plain functions, value in, string out.
- `commands/` — one class per command.

### The shape of a command

A command is one file for as long as it fits in one: `commands/apps/get.ts`. When it outgrows that,
it becomes a directory of three parts, and `test/cli/command-layout.test.ts` holds them apart:

```text
commands/migrations/status/
├── command.ts # the class oclif runs
├── index.ts # the door: `export { default } from './command.js';` and nothing else
└── lib/ # this command's own helpers, private to it
```

The door exists because the re-export under `src/commands` names a directory, and Node.js ESM
resolves a directory only through its `index.js`. Keeping the class out of it means one spelling for
"where does this command begin": `command.ts`, whether or not the directory has grown a `lib/`.

A `lib/` belongs to the command beside it, never to a topic: a helper two commands need is not a
helper any more, and moves to `cli/input` or `cli/views` (adapter) or to `sdk/adapty` (product).
Eslint blocks the import of a stranger's `lib/`; the layout test covers what a specifier pattern
cannot see.

### Command bases and imports

```text
Expand Down Expand Up @@ -164,10 +185,15 @@ quietly changing what users parse.
| New endpoint | a resource module in `sdk/adapty` |
| New rule ("X is required when Y") | next to the operation it constrains, in `sdk/adapty` |
| New command | `cli/commands/...` + a re-export in `src/commands/...` |
| New flag | the command, or `cli/flags.ts` if shared |
| New flag or argument | the command (or its `lib/flags.ts` for complex parsing); shared input belongs in `cli/input/<concern>.ts` |
| New error kind | `sdk/core/errors.ts` + `cli/errors.ts` (the compiler insists) |
| Adapty session environment variables | `cli/base/adapty/openSession.ts` |

Shared input modules group declarations, private parsers and SDK parameter mapping by concern
(for example, `cli/input/pagination.ts`). Import each module directly; there is no barrel index.
Keep command-specific input local until another command needs it, and export only what consumers
use. Global flags belong to the base command; shared subsets stay composable objects.

## Migration state

The pre-sdk stack (`src/lib` + the commands written against it) is still there and still serves
Expand Down
87 changes: 0 additions & 87 deletions docs/plans/2026-09-11-feat-migration-sdk-resource-plan.md

This file was deleted.

Loading
Loading