@the-meridian/cli 0.1.0

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 ADDED
@@ -0,0 +1,248 @@
1
+ # @the-meridian/cli
2
+
3
+ A managed remote MySQL development database for every developer on your team, with no local database setup.
4
+
5
+ `meridian dev` provisions a private MySQL 8.4 database on Meridian's Cloud SQL instance, starts the Cloud SQL Auth Proxy on a local port, writes `DATABASE_URL` into your `.env`, and runs your app's dev server. Prisma, `mysql`, and anything else that reads `DATABASE_URL` then talk to `127.0.0.1` as if the database were local.
6
+
7
+ ```bash
8
+ npm install -g @the-meridian/cli
9
+
10
+ meridian login # once per machine
11
+ meridian link # once per project
12
+ meridian dev # every day
13
+ ```
14
+
15
+ ## Requirements
16
+
17
+ - Node.js 20 or newer
18
+ - A Meridian account with access to at least one organization and app
19
+
20
+ You do **not** need Docker, a local MySQL server, `gcloud`, or the Cloud SQL Auth Proxy — the CLI downloads and manages the proxy for you.
21
+
22
+ ## How it works
23
+
24
+ ```
25
+ meridian dev
26
+ │
27
+ ├─ POST /app/cli/apps/{app}/dev-database ensure a database exists (polls until ready)
28
+ ├─ POST …/dev-database/credentials short-lived MySQL user + password
29
+ ├─ POST …/dev-database/proxy-token short-lived GCP access token
30
+ ├─ download cloud-sql-proxy v2 → ~/.meridian/bin cached, SHA-256 verified
31
+ ├─ spawn proxy on 127.0.0.1:<free port> token passed in the environment, refreshed before it expires
32
+ ├─ patch DATABASE_URL into .env every other line preserved
33
+ └─ run your dev command npm run dev by default
34
+ ```
35
+
36
+ Both child processes are shut down together: quitting your dev server stops the proxy, and Ctrl-C stops both.
37
+
38
+ ## Commands
39
+
40
+ Run `meridian <command> --help` for the full flag list. Every command also accepts the [global flags](#global-flags).
41
+
42
+ ### `meridian login`
43
+
44
+ Device-code login: prints a short user code, opens your browser to the verification URL, and polls until you approve. The resulting token is written to `~/.meridian/config.json` with `0600` permissions.
45
+
46
+ | Flag | Behavior |
47
+ | --- | --- |
48
+ | `--force` | Re-authenticate even when a valid token is stored |
49
+ | `--no-browser` | Print the URL instead of opening a browser (also implied by `CI` or `MERIDIAN_NO_BROWSER`) |
50
+ | `--hostname <name>` | Hostname reported to Meridian for this device |
51
+
52
+ ### `meridian logout`
53
+
54
+ Deletes the stored token. Leaves `.meridian/project.json` alone, and exits `0` when you were not logged in.
55
+
56
+ ### `meridian whoami`
57
+
58
+ Shows the logged-in user, org memberships, and the linked project. `--offline` skips the API call and reports only cached state. Supports `--json`.
59
+
60
+ ### `meridian link` / `meridian unlink`
61
+
62
+ `link` prompts for an organization and app, then writes `.meridian/project.json` in the project root. Pass `--org` and `--app` (id, name, or slug) to skip the prompts, `--dev-command` to override what `meridian dev` runs, and `--force` to replace an existing link. `unlink` removes the file and leaves snapshots in place.
63
+
64
+ ### `meridian dev`
65
+
66
+ Starts the proxy and your dev server together (see [How it works](#how-it-works)).
67
+
68
+ | Flag | Behavior |
69
+ | --- | --- |
70
+ | `--port <port>` | Preferred local proxy port; a free port is picked if it's taken |
71
+ | `--env-file <file>` | Patch a file other than `.env` |
72
+ | `--no-env` | Don't touch any file; print the `DATABASE_URL` for manual use |
73
+
74
+ The command to run is resolved in this order: `meridian dev -- <cmd>` → `dev_command` in `project.json` → `npm run dev`. If your `npm run dev` script itself calls `meridian dev`, the CLI detects the recursion and stops instead of forking forever.
75
+
76
+ ### `meridian migrate`
77
+
78
+ Snapshots the database to `.meridian/snapshots/<timestamp>.sql`, runs `npx prisma migrate dev`, and restores the snapshot if the migration fails. Either way the outcome is reported to the API. Extra arguments go to Prisma verbatim: `meridian migrate -- --name add_orders`.
79
+
80
+ Rollback is automatic and unprompted: a failed migration resets the schema through the API and replays the snapshot, on the assumption that a half-applied migration is worse than the state you started in. Use `--no-rollback` to keep the snapshot and decide yourself, or `--skip-snapshot` when you don't want the dump at all. Pruning to `--keep-snapshots` only happens after a successful run, so a failure never removes the file you might need.
81
+
82
+ | Flag | Behavior |
83
+ | --- | --- |
84
+ | `--skip-snapshot` | Faster, but no rollback is possible |
85
+ | `--no-rollback` | Keep the snapshot but don't restore automatically |
86
+ | `--keep-snapshots <n>` | Snapshots to retain after success (default `5`) |
87
+ | `--port`, `--env-file` | As for `meridian dev` |
88
+
89
+ ### `meridian db reset`
90
+
91
+ Drops and recreates every table (the MySQL user is kept). Asks you to type the database name to confirm, then offers to run `npx prisma migrate deploy` and your seed script. Use `--migrate` and `--seed` to skip the follow-up prompts, or `--yes` to skip confirmation in scripts.
92
+
93
+ ### `meridian db seed`
94
+
95
+ Runs `npx prisma db seed` with the proxy up and `DATABASE_URL` set. Extra args: `meridian db seed -- --arg`.
96
+
97
+ ### `meridian db shell`
98
+
99
+ Opens a SQL shell against the dev database: the real `mysql` client when it's on `PATH` (the password is passed via `MYSQL_PWD`, so it never appears in `ps`), otherwise a small built-in REPL over `mysql2`. `--repl` forces the built-in one, and `-e "SELECT 1"` runs a single statement and exits (`--json` formats the rows as JSON).
100
+
101
+ ### Global flags
102
+
103
+ Accepted before or after the command name, so both `meridian --json whoami` and `meridian whoami --json` work.
104
+
105
+ | Flag | Behavior |
106
+ | --- | --- |
107
+ | `--api-url <url>` | Override the API base URL for this invocation |
108
+ | `--json` | Machine-readable JSON on stdout (human logs stay on stderr) |
109
+ | `-q, --quiet` | Warnings and errors only |
110
+ | `--verbose` | Debug output, including stack traces and proxy logs |
111
+ | `--no-color` | Disable ANSI colors (also honors `NO_COLOR`) |
112
+ | `-y, --yes` | Pre-approve confirmation prompts |
113
+ | `--no-input` | Never prompt; fail with an explanation instead |
114
+ | `--cwd <dir>` | Run as if started in `<dir>` |
115
+
116
+ ## Configuration files
117
+
118
+ | Path | Contents |
119
+ | --- | --- |
120
+ | `~/.meridian/config.json` | `token`, `token_expires_at`, `user`, `api_base_url`. Mode `0600` |
121
+ | `~/.meridian/bin/` | Cached, checksum-verified `cloud-sql-proxy` binaries |
122
+ | `<project>/.meridian/project.json` | `org_id`, `app_id`, `app_name`, optional `dev_command` and `skip_datasource_check` |
123
+ | `<project>/.meridian/snapshots/` | Pre-migration dumps, pruned to the newest few |
124
+ | `<project>/.meridian/.gitignore` | Written for you, so snapshots and logs stay out of git |
125
+
126
+ Commit `.meridian/project.json` if your whole team uses Meridian; each developer still gets their own isolated database, because databases are provisioned per user and app.
127
+
128
+ ### API base URL
129
+
130
+ Resolved in this order, first match wins:
131
+
132
+ 1. `--api-url <url>`
133
+ 2. `MERIDIAN_API_URL`
134
+ 3. `api_base_url` in `~/.meridian/config.json` (stored automatically when you log in against a non-default URL)
135
+ 4. `https://api.the-meridian.ai`
136
+
137
+ ## How `DATABASE_URL` is handled
138
+
139
+ Before starting your dev command, the CLI writes a line like this into `.env`:
140
+
141
+ ```
142
+ DATABASE_URL="mysql://devu_88_15:<password>@127.0.0.1:3307/devdb_88_u15"
143
+ SHADOW_DATABASE_URL="mysql://devu_88_15:<password>@127.0.0.1:3307/devdb_88_u15_shadow"
144
+ ```
145
+
146
+ - `SHADOW_DATABASE_URL` points at a second database provisioned for you, which `prisma migrate dev` needs (see below). It is reachable with the same credentials and is dropped when your dev database is.
147
+ - Every other line — comments, ordering, blank lines, quoting style — is preserved; only these two are replaced.
148
+ - The first time a file is modified, a one-time copy is saved as `.env.backup`, with the same permissions as the file it copies.
149
+ - An existing file keeps its permissions; a file the CLI creates is `0600`, because the line it writes contains a live password.
150
+ - The port changes between runs when the preferred port is busy, so re-running `meridian dev` rewrites the line.
151
+ - Credentials are short-lived and rotate. Treat the value as a cache, not a secret to share, and keep `.env` out of git.
152
+ - `--no-env` prints the URL instead of writing it, for cases where you manage `.env` yourself.
153
+
154
+ Your dev command also receives both variables directly in its environment, so it works even if you skip the file.
155
+
156
+ ### The Prisma datasource
157
+
158
+ Your schema has to read both variables, or the CLI stops before provisioning anything:
159
+
160
+ ```prisma
161
+ datasource db {
162
+ provider = "mysql"
163
+ url = env("DATABASE_URL")
164
+ shadowDatabaseUrl = env("SHADOW_DATABASE_URL")
165
+ }
166
+ ```
167
+
168
+ `meridian migrate` offers to add the `shadowDatabaseUrl` line for you the first time it finds it missing, and writes it in place. It asks rather than just doing it, because a schema is source you keep in git, unlike the generated `.env`. A datasource that already names some other variable is left alone.
169
+
170
+ `shadowDatabaseUrl` is required because `prisma migrate dev` does not apply migrations directly: it replays your migration history into a throwaway shadow database and diffs that against your schema to detect drift. Prisma creates that database itself when it can, and your dev user cannot — it holds privileges on its own two databases and nothing else, so `CREATE DATABASE` is denied and Prisma reports `P3014`. Meridian provisions the shadow database per developer instead of granting the privilege, because Prisma names shadow databases with a fixed `prisma_migrate_shadow_db_*` prefix: a grant broad enough to cover that would be shared by every developer on the instance.
171
+
172
+ Only `meridian migrate` needs it. `dev`, `db seed` and `db shell` run fine without it.
173
+
174
+ ## Non-interactive and CI use
175
+
176
+ Prompts are skipped automatically when stdin/stderr aren't TTYs, when `CI` is set, or when `MERIDIAN_NON_INTERACTIVE` is set — in that mode a command that would have prompted fails with a message naming the flag or variable to set instead.
177
+
178
+ | Variable | Purpose |
179
+ | --- | --- |
180
+ | `MERIDIAN_API_URL` | API base URL |
181
+ | `MERIDIAN_HOME` | Relocate `~/.meridian` (useful for sandboxes and tests) |
182
+ | `MERIDIAN_ORG`, `MERIDIAN_APP` | Answers for `meridian link`'s prompts |
183
+ | `MERIDIAN_YES` | Same as `--yes` |
184
+ | `MERIDIAN_NON_INTERACTIVE` | Same as `--no-input` |
185
+ | `MERIDIAN_NO_BROWSER` | Same as `login --no-browser` |
186
+ | `MERIDIAN_DEV_ACTIVE` | Set to `1` in every process `meridian dev` starts. Read it to detect that your script is already inside a session; setting it yourself makes `meridian dev` refuse to start |
187
+ | `MERIDIAN_DEV_DATABASE_ID` | Exported (alongside `DATABASE_URL`) into the children of `meridian dev`, `migrate` and `db …`, holding the id of the dev database that session provisioned. The Meridian SDK sends it as `devDatabaseId` on its identify call, so installs a locally-run app creates are billed to your dev database. Set by the CLI only — never set it yourself |
188
+ | `MERIDIAN_PROXY_PATH` | Use an existing proxy binary instead of downloading one |
189
+ | `MERIDIAN_PROXY_VERSION` | Pin a different proxy version (checksum is fetched from that release) |
190
+ | `MERIDIAN_PROXY_SKIP_CHECKSUM=1` | Allow install when no checksum is published. Use only if you trust the source |
191
+ | `NO_COLOR` / `FORCE_COLOR` | Color detection |
192
+
193
+ Read commands support `--json`. Errors are also JSON in that mode:
194
+
195
+ ```json
196
+ { "error": { "message": "Your Meridian CLI token is no longer valid (401).", "hint": "Run `meridian login` again to issue a fresh token." } }
197
+ ```
198
+
199
+ ## Troubleshooting
200
+
201
+ **`You are not logged in to Meridian.`** — Run `meridian login`.
202
+
203
+ **`This project is not linked to a Meridian app.`** — Run `meridian link` in the project root. The CLI looks for `.meridian/project.json` in the current directory and its parents.
204
+
205
+ **`Your Meridian CLI token is no longer valid (401).`** — The token was revoked or expired. Run `meridian login` again.
206
+
207
+ **`Provisioning the development database failed.`** — Provisioning ended in an error state on Meridian's side; the reason the API reports is appended to the message. Check the dashboard for the full history, then re-run `meridian dev` to retry.
208
+
209
+ **`The development database is not ready yet.`** — A credentials or reset call arrived before provisioning finished (HTTP 409). `meridian dev` polls for you, so this normally only appears when a command runs against a database that is still being created.
210
+
211
+ **`Could not reach 127.0.0.1:<port>/<db> (ECONNREFUSED).`** — Nothing is listening. `meridian dev` and `meridian migrate` start the proxy themselves; if you're running Prisma directly, keep `meridian dev` running in another terminal.
212
+
213
+ **`Timed out waiting for MySQL …`** — The proxy is up but can't reach Cloud SQL. Check whether a VPN, firewall, or corporate proxy blocks outbound connections, then retry with `--verbose` to see the proxy's own logs.
214
+
215
+ **`Another process is already listening on that port.`** — Pass `--port` to choose a different one.
216
+
217
+ **Proxy download fails** (air-gapped network, blocked storage.googleapis.com) — Install the [Cloud SQL Auth Proxy](https://github.com/GoogleCloudPlatform/cloud-sql-proxy) yourself and set `MERIDIAN_PROXY_PATH` to it.
218
+
219
+ **Checksum verification failed** — A cached binary that no longer matches its published SHA-256 is deleted and downloaded again automatically. If a fresh download also fails verification the CLI refuses to install it, which usually means something on the network is rewriting the response. Don't reach for `MERIDIAN_PROXY_SKIP_CHECKSUM` in that situation.
220
+
221
+ **A migration failed and left the schema half-applied** — `meridian migrate` restores the pre-migration snapshot automatically. To redo it by hand, use the newest file in `.meridian/snapshots/`, or run `meridian db reset` for a clean slate.
222
+
223
+ **The rollback itself failed** — Treat the database as unsafe: the reset succeeded but the replay didn't finish, so the schema is partial. Start the proxy with `meridian dev` in another terminal and replay the snapshot by hand (`mysql … < .meridian/snapshots/<timestamp>.sql`, or `meridian db shell` and paste it), or take the clean slate with `meridian db reset --migrate`. The failing statement's index is in the error message.
224
+
225
+ **`meridian dev` says it would call itself** — Your `npm run dev` script invokes `meridian dev`. Point `dev_command` in `.meridian/project.json` at the real dev server (for example `next dev`), or run `meridian dev -- next dev`.
226
+
227
+ **"…would not touch the managed development database"** — Your Prisma datasource does not read `DATABASE_URL`, so Prisma would ignore the database the CLI just connected you to and use whatever the schema hardcodes (typically `file:dev.sqlite`). Set `provider = "mysql"` and `url = env("DATABASE_URL")`, and regenerate any migrations created for the old database — their SQL is dialect-specific and MySQL will reject it. Projects that deliberately manage their datasource some other way can set `"skip_datasource_check": true` in `.meridian/project.json`.
228
+
229
+ **"written for a different database engine"** — `migrate` screens `prisma/migrations` before it provisions anything, because a history generated for another engine cannot apply to MySQL. SQLite and Postgres quote identifiers as `"Session"`, which MySQL reads as a string literal, and SQLite's `TEXT` primary keys are invalid as MySQL keys. Migration SQL is dialect-specific, so the history has to be regenerated: if it has only ever run locally, delete `prisma/migrations` and re-run. This is the normal state of a Shopify app template, which ships SQLite migrations. `--allow-foreign-migrations` waives the check.
230
+
231
+ If an earlier attempt already failed against the database, run `meridian db reset` before retrying. Prisma records the failure in `_prisma_migrations`, and once the history is deleted that row names a migration that no longer exists on disk, which Prisma reports as drift and refuses to work past.
232
+
233
+ **"declares no `shadowDatabaseUrl`"**, or Prisma's **`P3014`** — `prisma migrate dev` needs a shadow database and your dev user cannot create one. Meridian provisions it and exports `SHADOW_DATABASE_URL`; add `shadowDatabaseUrl = env("SHADOW_DATABASE_URL")` to your datasource. If you hit `P3014` despite having that line, your dev database predates shadow databases — `meridian db reset` re-provisions it.
234
+
235
+ ## Development
236
+
237
+ ```bash
238
+ npm install
239
+ npm run test:run # vitest, fully offline: no network, no real binaries
240
+ npm run typecheck # tsc --noEmit
241
+ npm run build # tsup → dist/index.js
242
+ ```
243
+
244
+ Tests mock the network, child processes, and MySQL, and the suite clears ambient `MERIDIAN_*`/`CI` variables so local runs match CI.
245
+
246
+ ## License
247
+
248
+ MIT