naro-cli 0.7.0 → 0.9.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 +312 -3
- package/dist/artifact-types.d.ts +1 -0
- package/dist/chunk-K3NQKI34.js +10 -0
- package/dist/{chunk-E2M6CQL7.js → chunk-Z757IOF3.js} +1615 -121
- package/dist/deploy.js +2 -1
- package/dist/index.js +2778 -296
- package/dist/stack-PDV3NPWC.js +117 -0
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -86,9 +86,61 @@ naro db enable Create the project's database (Cloudflare D1)
|
|
|
86
86
|
naro db info Status, size, table count, limits
|
|
87
87
|
naro db query "select 1" Run SQL; --file query.sql, or pipe stdin
|
|
88
88
|
naro db push [--dry-run] Apply pending ./migrations (asks first; --yes in CI)
|
|
89
|
+
naro db dump --output backup.sql SQL dump, streamed from Cloudflare (stdout without
|
|
90
|
+
--output); --schema-only | --data-only, --table a,b
|
|
91
|
+
naro db restore --bookmark <id> Time Travel, last 30 days (or --timestamp <time>);
|
|
92
|
+
asks first, prints the command that undoes it
|
|
93
|
+
naro db pull Existing database, no migrations yet: writes its schema
|
|
94
|
+
as migrations/0001_remote_schema.sql, recorded as applied
|
|
95
|
+
naro db reset [--dry-run] Drop every table, view and trigger, clear the ledger and
|
|
96
|
+
apply ./migrations again (asks first; --yes in CI)
|
|
97
|
+
naro db lint [--strict] Check the schema for what D1 actually trips on; exits 1
|
|
98
|
+
on an error, and on a warning too with --strict
|
|
99
|
+
naro db table create posts --column "id:integer:pk:autoincrement" --column "title:text:notnull" --column "created_at:datetime:notnull:default=current_timestamp" --owner user_id
|
|
100
|
+
A table without SQL (--if-not-exists too); --owner adds
|
|
101
|
+
user_id and an owner policy
|
|
102
|
+
naro db table add-column posts --column "published:boolean:notnull:default=false"
|
|
103
|
+
naro db table rename-column posts title headline | rename posts articles
|
|
104
|
+
naro db table drop-column posts published | drop posts (ask first; --yes in CI)
|
|
105
|
+
naro db index create posts user_id,created_at [--unique] [--name <n>] | drop <name>
|
|
106
|
+
Each is one migration in d1_migrations, written to
|
|
107
|
+
./migrations (--no-write skips it; --dry-run shows the SQL)
|
|
89
108
|
naro migration new create_users Creates migrations/0001_create_users.sql
|
|
90
109
|
naro migration list Local files vs the d1_migrations ledger
|
|
91
|
-
naro
|
|
110
|
+
naro migration repair 0001_init.sql --status applied
|
|
111
|
+
Fix the ledger alone — mark it applied, or --status
|
|
112
|
+
reverted to drop its row. Its SQL is never run
|
|
113
|
+
naro gen types --output src/database.types.ts
|
|
114
|
+
TypeScript types for the schema, for
|
|
115
|
+
createClient<Database>() from @naro/sdk
|
|
116
|
+
|
|
117
|
+
naro backend status On or off, status, URL and anon key (service key hidden)
|
|
118
|
+
naro backend enable Turn the backend on: D1, sign-in and the Data API; safe
|
|
119
|
+
to re-run, and a failed run resumes at the step that
|
|
120
|
+
stopped it (provision is the same command)
|
|
121
|
+
naro backend disable Turn it off: /_naro/* answers 404; data, users and
|
|
122
|
+
keys are kept (asks first; --yes to skip)
|
|
123
|
+
naro backend keys --reveal Print the service key (a secret; needs database write)
|
|
124
|
+
naro backend keys rotate --service Replace a key (--anon or --service); the old one stops
|
|
125
|
+
working at once (asks first)
|
|
126
|
+
naro backend auth Sign-in settings: email, Google, GitHub, redirects,
|
|
127
|
+
and the callback URL to register for each production host
|
|
128
|
+
naro backend auth email set --provider resend --from "App <no-reply@example.com>" --api-key-stdin < resend-key.txt
|
|
129
|
+
Confirmation and password reset emails from your Resend account
|
|
130
|
+
naro backend auth oauth set github --client-id Iv1.abc --client-secret-stdin < github-secret.txt
|
|
131
|
+
naro backend auth redirects add https://app.example.com/auth/
|
|
132
|
+
Where sign-in may send the browser back to: an
|
|
133
|
+
origin, or only the pages under a path
|
|
134
|
+
naro backend auth redirects remove https://app.example.com/auth/
|
|
135
|
+
Exits 1, changing nothing, when it is not in the list
|
|
136
|
+
naro backend auth require-verification on
|
|
137
|
+
Refuse sign-in until the email is confirmed (needs email)
|
|
138
|
+
naro policy list Per-table rules and conditions of the Data API
|
|
139
|
+
naro policy set todos --select owner --insert owner --update owner --delete owner --owner user_id
|
|
140
|
+
naro policy set posts --select-when 'or(is_public.eq.true,user_id.eq.auth.uid())' --insert-when 'user_id.eq.auth.uid()'
|
|
141
|
+
naro policy set posts --from-file policy.json
|
|
142
|
+
The policy as JSON, conditions and all
|
|
143
|
+
naro policy remove todos Back to deny (asks first)
|
|
92
144
|
|
|
93
145
|
naro promote <deployment-id> Publish a build to production
|
|
94
146
|
naro rollback [deployment-id] Back to the previous production build
|
|
@@ -108,6 +160,261 @@ An apex (`example.com`) is added as a pair by default because most registrars
|
|
|
108
160
|
goes on `www.example.com` and the apex answers with a 308 redirect to it.
|
|
109
161
|
`--no-www` registers the apex alone.
|
|
110
162
|
|
|
163
|
+
`naro db dump` asks Cloudflare for the dump and streams it straight from
|
|
164
|
+
Cloudflare's storage to the file — the bytes never pass through naro. While the
|
|
165
|
+
export runs, Cloudflare holds every other query to the database, so dump a busy
|
|
166
|
+
production database when a short pause is acceptable. It ends with the bookmark
|
|
167
|
+
the dump reflects; `naro db restore --bookmark <that>` brings the database back
|
|
168
|
+
to it. A restore rolls back everything written after that point — the
|
|
169
|
+
`d1_migrations` ledger included — and prints the bookmark that undoes it.
|
|
170
|
+
|
|
171
|
+
`naro db reset` never deletes the database itself: it drops every table, view
|
|
172
|
+
and trigger the project owns, empties the `d1_migrations` ledger and applies
|
|
173
|
+
the local migration files again, so the schema is what those files say. It
|
|
174
|
+
reads the bookmark from just before the drops and prints it, so
|
|
175
|
+
`naro db restore --bookmark <that>` puts the data back. `--dry-run` lists what
|
|
176
|
+
it would drop and re-apply without touching anything.
|
|
177
|
+
|
|
178
|
+
`naro db lint` reads the schema and runs the checks D1 allows. Its rules are
|
|
179
|
+
the ones D1 actually trips on — orphaned rows, a failed `quick_check`, a
|
|
180
|
+
missing or nullable primary key, a foreign key no index starts with, a trigger
|
|
181
|
+
the REST API cannot recreate, a virtual table that makes `db dump` refuse the
|
|
182
|
+
whole database. There is no "unused index" guess: D1 keeps no execution
|
|
183
|
+
statistics to base one on. An error exits 1, a warning exits 0 — `--strict`
|
|
184
|
+
makes warnings fail too, so CI can gate on it:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
naro db lint --strict --json || exit 1
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
`db lint` changes nothing, but it needs **write** access to the database, not
|
|
191
|
+
read: its two checks (`foreign_key_check`, `quick_check`) are whole-database
|
|
192
|
+
scans, and naro classifies those as writes on purpose so a read-only role
|
|
193
|
+
cannot start them. That is also the cost warning — both walk every row that
|
|
194
|
+
has a foreign key, and `quick_check` walks the file, so on a large database
|
|
195
|
+
the lint is billed rows-read and can run into the 30-second query limit. Run
|
|
196
|
+
it in CI on a branch's own data, not on a busy production database.
|
|
197
|
+
|
|
198
|
+
`naro db table` and `naro db index` change the schema without SQL. naro
|
|
199
|
+
renders the DDL from checked input — every name quoted, types and defaults
|
|
200
|
+
from a fixed list — and applies it as one migration, recorded in
|
|
201
|
+
`d1_migrations` and written to `./migrations` as `NNNN_schema_<op>_<table>.sql`,
|
|
202
|
+
numbered after your local files and the ledger. `naro migration list`,
|
|
203
|
+
`db pull` and `db reset` see it like any other migration; with `--no-write`
|
|
204
|
+
there is no file, so `db reset` will not re-apply it. `--dry-run` prints the
|
|
205
|
+
SQL and changes nothing. While a local migration is not in the ledger yet, a
|
|
206
|
+
change is refused — `naro db push` it first, or the change would run before it.
|
|
207
|
+
|
|
208
|
+
A column is `name:type[:modifier…]`:
|
|
209
|
+
|
|
210
|
+
| Part | Values |
|
|
211
|
+
| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
212
|
+
| type | `integer`, `real`, `text`, `blob`, `numeric`, `boolean`, `datetime`, `json` (keep JSON in `text`: a `json` column turns `'123'` into a number) |
|
|
213
|
+
| modifier | `pk`, `autoincrement`, `notnull`, `unique`, `default=<value>`, `references=<table>.<column>`, `ondelete=cascade\|set-null\|set-default\|restrict\|no-action` |
|
|
214
|
+
| default | `'text'` (quotes keep `:` and `''` is a quote), `null`, `true`, `false`, `current_timestamp`, `current_date`, `current_time`, a number, or a bare word as text |
|
|
215
|
+
|
|
216
|
+
Every primary key but a single `integer` one is `NOT NULL`; a foreign key must
|
|
217
|
+
point at its table's primary key or a unique column. `--owner user_id` adds
|
|
218
|
+
`user_id TEXT NOT NULL` and, when the backend is on and ready, an owner policy for all
|
|
219
|
+
four actions; any other new table is closed to the Data API until you run
|
|
220
|
+
`naro policy set` (the command says so). Policies are kept by table name: a
|
|
221
|
+
drop removes the table's policy whenever the project has a backend, on or off
|
|
222
|
+
(the command says so), and a rename leaves the old one behind — the command
|
|
223
|
+
prints what to run.
|
|
224
|
+
|
|
225
|
+
SQLite cannot add a PRIMARY KEY, UNIQUE or NOT-NULL-without-default column, or
|
|
226
|
+
drop a primary key, a unique or indexed column, or a table another table
|
|
227
|
+
references; naro refuses those before it tries and says why. It also reads the
|
|
228
|
+
SQL of your triggers and views, and refuses to drop or rename a table or
|
|
229
|
+
column one of them uses (it warns when the SQL leaves that open) — drop or
|
|
230
|
+
recreate the trigger or view with a migration first. Changing a
|
|
231
|
+
column's type needs a new table — write that migration with
|
|
232
|
+
`naro migration new` (create the new table, `INSERT … SELECT`, drop the old
|
|
233
|
+
one, rename the new one) and `naro db push` it.
|
|
234
|
+
|
|
235
|
+
`naro migration repair <name> --status applied|reverted` edits the
|
|
236
|
+
`d1_migrations` ledger and nothing else — it never runs the migration's SQL.
|
|
237
|
+
It is for a ledger that stopped matching the database: a migration whose file
|
|
238
|
+
ran but whose ledger row was never written (a request that timed out between
|
|
239
|
+
the two), or a `db pull` baseline you want to take back. If the ledger already
|
|
240
|
+
says what you asked for, it says so and writes nothing.
|
|
241
|
+
|
|
242
|
+
`naro gen types` writes TypeScript for the schema: a `…Row` interface per
|
|
243
|
+
table and view, and a `Database` type that `createClient<Database>()` from
|
|
244
|
+
`@naro/sdk` reads, with `Tables<"name">`, `TablesInsert<"name">` and
|
|
245
|
+
`TablesUpdate<"name">` to pick one out. `--lang typescript` is the default and
|
|
246
|
+
the only language.
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
naro gen types --output src/database.types.ts
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
import { createClient } from "@naro/sdk";
|
|
254
|
+
import type { Database } from "./database.types";
|
|
255
|
+
|
|
256
|
+
const naro = createClient<Database>({ url, anonKey });
|
|
257
|
+
const { data } = await naro.from("posts").select("id, title"); // { id: number; title: string }[] | null
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
`Insert` leaves out generated columns and makes a column optional when it has
|
|
261
|
+
a default, allows NULL, is the `INTEGER PRIMARY KEY`, or is the `--owner`
|
|
262
|
+
column of a table whose insert rule is `owner` (the backend fills it; see
|
|
263
|
+
below); `Update` makes every column optional. A TEXT column also takes an array
|
|
264
|
+
or object (`Json`), which the Data API stores as its `JSON.stringify` text — it
|
|
265
|
+
reads back as that string, so `JSON.parse` it. A BLOB is `number[]` (what D1
|
|
266
|
+
returns) and cannot be written through the Data API; a column with no type, or
|
|
267
|
+
a STRICT table's `ANY`, is `number | string | number[]`. When the backend's
|
|
268
|
+
policies cannot be read (no backend yet, not ready, no permission), the types
|
|
269
|
+
are generated without them and one `⚠` line on stderr says why — `--json`
|
|
270
|
+
carries `policies: "unavailable"` and `policiesReason`.
|
|
271
|
+
Before 2026-09-24 `Database` was a flat map (`Database["users"]` was the row)
|
|
272
|
+
and BLOB was `ArrayBuffer`: use `Tables<"users">` now. The `…Row` interfaces
|
|
273
|
+
are unchanged.
|
|
274
|
+
|
|
275
|
+
`naro backend` is the project's Naro backend: its own D1, email and password
|
|
276
|
+
sign-in, and a Data API that browser code calls through `@naro/sdk` at the
|
|
277
|
+
project's URL. It is off until you turn it on, one project at a time:
|
|
278
|
+
`naro backend enable` (or the Database page of the dashboard). The anon key
|
|
279
|
+
is public — it identifies the project and grants nothing. The service key
|
|
280
|
+
skips every policy, so the CLI prints it only for
|
|
281
|
+
`naro backend keys --reveal` and for a rotation that made a new one; keep it
|
|
282
|
+
on a server. `naro backend enable` exits 1 with `BACKEND_PROVISION_FAILED`
|
|
283
|
+
when a step stopped the run (the server still answers 200), and running it
|
|
284
|
+
again picks up at that step. `naro backend status` calls a run that died
|
|
285
|
+
part-way (still `provisioning`, but nothing holds its lease) stalled; the
|
|
286
|
+
same `enable` picks it up. `provision`, the command's first name, still works.
|
|
287
|
+
|
|
288
|
+
`naro backend disable` turns the backend off: the app's `/_naro/*` requests
|
|
289
|
+
get 404 in up to about 90 seconds, as the routing cache ages out. The D1 with
|
|
290
|
+
its data and users, the keys, the sign-in settings and the policies are kept,
|
|
291
|
+
and `naro backend enable` brings it back with the same keys — the app needs
|
|
292
|
+
no new anon key. It asks first; without a terminal it needs `--yes`, and
|
|
293
|
+
exits 1 with `CONFIRMATION_REQUIRED` otherwise. `naro backend status` prints
|
|
294
|
+
`Backend: off since <time>` (JSON `backend.enabled`, `backend.disabledAt`).
|
|
295
|
+
`Backend: turning off` (JSON `backend.turningOff`) is a turn-off whose routes
|
|
296
|
+
may still answer. While a run holds its lease (`backend.leaseExpiresAt` still
|
|
297
|
+
ahead) it is in progress, and the advice says to check again in a minute;
|
|
298
|
+
otherwise it stopped half-way: run `naro backend disable` again to finish it
|
|
299
|
+
(the control plane retries it too). Another run holding the lease answers
|
|
300
|
+
`BACKEND_BUSY` "Another change to the project backend is in progress. Try
|
|
301
|
+
again in a minute." Both commands are safe to
|
|
302
|
+
re-run and repair what they can: disable removes the routes again, enable
|
|
303
|
+
writes them again. While it is off, key rotation and sign-in changes answer
|
|
304
|
+
`409 BACKEND_OFF`; reads and policy changes still work.
|
|
305
|
+
|
|
306
|
+
A rotation (`naro backend keys rotate --anon|--service`) needs a ready backend;
|
|
307
|
+
a 409 (`BACKEND_NOT_READY`, `BACKEND_BUSY`, `BACKEND_OFF`) means nothing was tried. It
|
|
308
|
+
installs the new key on the Naro worker first and swaps it in the database
|
|
309
|
+
second, and the database decides the outcome: whenever the new key ends up
|
|
310
|
+
live the answer carries it, and the CLI prints it — with a warning to run
|
|
311
|
+
`naro backend enable` if a step after the swap failed. A
|
|
312
|
+
`BACKEND_ROTATION_FAILED` prints no key and usually leaves the old key
|
|
313
|
+
working. If you are not sure — any 5xx, a timeout, a lost connection, or the
|
|
314
|
+
message that the worker may still expect the new key — run
|
|
315
|
+
`naro backend keys --reveal` to see which key is live, then
|
|
316
|
+
`naro backend enable`; the CLI prints that hint (JSON `hint`) and exits 1.
|
|
317
|
+
For a few seconds during a rotation the worker can already refuse the old key.
|
|
318
|
+
|
|
319
|
+
`naro backend auth` sets up email and social sign-in with the project's own
|
|
320
|
+
accounts — Naro runs no shared sender. Email (confirmation and password reset
|
|
321
|
+
links) needs a Resend API key and a `from` address; Google and GitHub need
|
|
322
|
+
the project's own OAuth app. `naro backend auth` prints a callback URL for
|
|
323
|
+
each production host — `https://<project>.naro.sh/_naro/v1/auth/callback/<provider>`
|
|
324
|
+
and the same path on each custom domain. Register, as the app's redirect URI,
|
|
325
|
+
the one for the origin your app passes to `createClient({ url })`: the
|
|
326
|
+
provider sends the browser back to the host the sign-in began on, where its
|
|
327
|
+
cookie is (otherwise `redirect_uri_mismatch`). A GitHub OAuth app takes one
|
|
328
|
+
callback URL, so it serves one of those hosts.
|
|
329
|
+
Secrets are read from stdin only — `--api-key-stdin` and
|
|
330
|
+
`--client-secret-stdin` take a pipe, or ask without echo in a terminal — and
|
|
331
|
+
`--api-key <value>` is refused, so a key never lands in shell history. The
|
|
332
|
+
answers show a secret as its first 7 characters (the email key) or `set`.
|
|
333
|
+
|
|
334
|
+
A sign-in may send the browser back only to the project's production hosts —
|
|
335
|
+
its naro.sh address and its custom domains — and the entries
|
|
336
|
+
`naro backend auth redirects add` allowed: an origin
|
|
337
|
+
(`https://host[:port]`, every page) or an origin and a path ending in `/`
|
|
338
|
+
(`https://app.example.com/auth/`, only the pages under it); `http://` only
|
|
339
|
+
for localhost. The preview host (`<project>-preview.naro.sh`) runs whatever
|
|
340
|
+
branch was pushed against the same accounts, so it is allowed only once it
|
|
341
|
+
is added, and confirmation and reset emails always link to a production
|
|
342
|
+
host. An entry for a production host narrows it too.
|
|
343
|
+
`naro backend auth redirects remove` exits 1 and changes nothing when the
|
|
344
|
+
entry is not in the list, naming the entries that still allow that origin;
|
|
345
|
+
after a removal it names them too. The
|
|
346
|
+
code or reset token rides in the URL fragment, which any script on the page
|
|
347
|
+
— or an open redirect like `/logout?next=…` — can pass on: allow a path of
|
|
348
|
+
sign-in pages rather than a whole origin. Each change uploads
|
|
349
|
+
the Naro worker again; when that upload stops, the change is saved all the
|
|
350
|
+
same and the command exits 1 with `BACKEND_PROVISION_FAILED` — run
|
|
351
|
+
`naro backend enable` to finish. `require-verification on` needs email set
|
|
352
|
+
up (`VERIFICATION_NEEDS_EMAIL`), and email cannot be removed while it is on.
|
|
353
|
+
|
|
354
|
+
Policies are default deny: a table without one refuses every anon and
|
|
355
|
+
signed-in request. Each of `--select`, `--insert`, `--update` and `--delete`
|
|
356
|
+
takes `none`, `public`, `authenticated` or `owner`; `owner` lets a signed-in
|
|
357
|
+
user reach only rows whose `--owner` column holds their user id, and fills
|
|
358
|
+
that column in on insert. What a `policy set` leaves out keeps its stored
|
|
359
|
+
value. A change is live within about 10 seconds. `naro gen types` reads the
|
|
360
|
+
policies too: with `--insert owner --owner user_id`, `user_id` is optional in
|
|
361
|
+
the generated `Insert` even when it is `NOT NULL` with no default — run it
|
|
362
|
+
again after changing a policy.
|
|
363
|
+
|
|
364
|
+
Instead of a rule, an action can hold a condition on the row, like a Supabase
|
|
365
|
+
row-level security policy, written in the filter syntax of `@naro/sdk`'s
|
|
366
|
+
`.or()` (commas are AND) with `auth.uid()` for the signed-in user's id —
|
|
367
|
+
unquoted; `"auth.uid()"` is the text, and `auth.uid` without `()` is refused
|
|
368
|
+
rather than read as text:
|
|
369
|
+
|
|
370
|
+
- `--select-when` and `--delete-when`: the rows reads and deletes reach.
|
|
371
|
+
- `--insert-when`: what every new row must meet.
|
|
372
|
+
- `--update-when`: the rows updates reach; `--update-check`: what every updated
|
|
373
|
+
row must still meet (by default the same as `--update-when`).
|
|
374
|
+
- `--signed-in`: the conditions of this command refuse anon. Without it,
|
|
375
|
+
`auth.uid()` has no value for anon: one comparison with it matches no row,
|
|
376
|
+
and neither does its `not`. Inside a group SQL's three-valued logic applies,
|
|
377
|
+
so `not(and(is_public.eq.1,user_id.eq.auth.uid()))` matches every row that is
|
|
378
|
+
not public, anon included — write what a condition lets in.
|
|
379
|
+
|
|
380
|
+
When any row of an insert or an update misses its condition, nothing is
|
|
381
|
+
written and the Data API answers `403 PERMISSION_DENIED` ("The new row does not
|
|
382
|
+
meet the insert policy of posts."). A check reads a row as it will be stored
|
|
383
|
+
and compares it with the column's collation (`COLLATE NOCASE`, `RTRIM`). So:
|
|
384
|
+
|
|
385
|
+
- a number column it reads takes numbers, booleans and null, not strings, and
|
|
386
|
+
cannot be compared with `like` or `ilike`;
|
|
387
|
+
- a primary key column it reads must be sent, and not as null;
|
|
388
|
+
- any other column it reads may be left out only when it has no default or a
|
|
389
|
+
plain one — `NULL`, `TRUE`, `FALSE`, a number, or a quoted string on a column
|
|
390
|
+
that is not a number column; a column defaulting to `CURRENT_TIMESTAMP` or
|
|
391
|
+
another expression must be sent;
|
|
392
|
+
- it cannot read a generated column or a virtual table's column.
|
|
393
|
+
|
|
394
|
+
One policy's conditions use at most 50 values together (an update's check
|
|
395
|
+
counts each column it reads once more), leaving 50 of D1's 100 per statement
|
|
396
|
+
for the request — 48 for a write with a check, which also binds the table's
|
|
397
|
+
schema to make sure it checks against the table as it is now. `--from-file`
|
|
398
|
+
takes the PUT body, or a policy object from `--json`
|
|
399
|
+
(`naro policy set posts --json | jq .policy > policy.json`), where
|
|
400
|
+
`auth.uid()` is `{"auth":"uid"}`; its `table`, if it has one, must name the
|
|
401
|
+
table being set. It cannot be combined with the other policy options.
|
|
402
|
+
`naro policy list` prints each condition in filter syntax that reads back as
|
|
403
|
+
the same condition, or as JSON where filter syntax cannot say it exactly
|
|
404
|
+
(`deleted_at.eq.null` would be the text `"null"`, and `= NULL` is not
|
|
405
|
+
`IS NULL`); `--json` gives the exact JSON.
|
|
406
|
+
|
|
407
|
+
Two things a condition does not do for you. An update or delete is held to its
|
|
408
|
+
own condition only, not to select's, unless it asks for rows back with
|
|
409
|
+
`.select()`. With an update condition broader than select's, a caller can tell
|
|
410
|
+
rows it cannot select apart without `.select()`: an update that sets a value
|
|
411
|
+
the check refuses answers 403 when a hidden row matches its filters and 200
|
|
412
|
+
when none does, so filter by filter it learns what those rows hold. Keep
|
|
413
|
+
update and delete conditions no broader than select's. And an `--update-check`
|
|
414
|
+
you give replaces the default: without `user_id.eq.auth.uid()` in it, a user
|
|
415
|
+
can give a row away by changing its `user_id`. Write
|
|
416
|
+
`--update-when 'user_id.eq.auth.uid()' --update-check 'user_id.eq.auth.uid(),status.eq.draft'`.
|
|
417
|
+
|
|
111
418
|
## Machine-readable output (CI / AI agents)
|
|
112
419
|
|
|
113
420
|
Every command supports `--json`: stdout carries exactly one JSON object,
|
|
@@ -128,6 +435,8 @@ $ naro deploy --prod --yes --json
|
|
|
128
435
|
Failures carry stable codes (`NOT_LOGGED_IN`, `PROJECT_NOT_LINKED`,
|
|
129
436
|
`FRAMEWORK_UNSUPPORTED`, `BUILD_FAILED`, `DEPLOYMENT_NOT_FOUND`,
|
|
130
437
|
`ENV_NOT_FOUND`, `DOMAIN_NOT_FOUND`, `DOMAIN_NOT_READY`,
|
|
438
|
+
`BACKEND_DISABLED`, `BACKEND_NOT_READY`, `BACKEND_BUSY`,
|
|
439
|
+
`BACKEND_ROTATION_FAILED`, `BACKEND_PROVISION_FAILED`, `POLICY_NOT_FOUND`,
|
|
131
440
|
`VALIDATION_FAILED`, `CONFIRMATION_REQUIRED`, `CANCELED`, …). A destructive
|
|
132
441
|
command run non-interactively without `--yes` fails with
|
|
133
442
|
`CONFIRMATION_REQUIRED` instead of silently doing nothing:
|
|
@@ -142,8 +451,8 @@ command run non-interactively without `--yes` fails with
|
|
|
142
451
|
```
|
|
143
452
|
|
|
144
453
|
`--yes` (or `-y`) suppresses every prompt — destructive actions
|
|
145
|
-
(production rollback/promote, env/domain removal
|
|
146
|
-
deliberately in automation.
|
|
454
|
+
(production rollback/promote, env/domain removal, database reset, backend key
|
|
455
|
+
rotation, policy removal) run unconfirmed, so use it deliberately in automation.
|
|
147
456
|
|
|
148
457
|
## CI authentication
|
|
149
458
|
|
package/dist/artifact-types.d.ts
CHANGED