naro-cli 0.9.1 → 0.11.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 +248 -52
- package/dist/artifact-types.d.ts +5 -0
- package/dist/{chunk-Z757IOF3.js → chunk-L7JOGWP3.js} +2455 -133
- package/dist/deploy.js +1 -1
- package/dist/git-info.d.ts +14 -1
- package/dist/index.js +2342 -1833
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -124,7 +124,16 @@ naro backend keys --reveal Print the service key (a secret; needs databa
|
|
|
124
124
|
naro backend keys rotate --service Replace a key (--anon or --service); the old one stops
|
|
125
125
|
working at once (asks first)
|
|
126
126
|
naro backend auth Sign-in settings: email, Google, GitHub, redirects,
|
|
127
|
-
|
|
127
|
+
the callback URL to register for each production host,
|
|
128
|
+
sign-ups and the minimum password length
|
|
129
|
+
naro backend auth signups off Nobody signs up on their own (on opens them again)
|
|
130
|
+
naro backend auth password --min-length 10
|
|
131
|
+
The fewest characters a new password takes (6–128)
|
|
132
|
+
naro backend users The project's users: id, username, email, verified, created
|
|
133
|
+
naro backend users create --username admin --password-stdin < password.txt
|
|
134
|
+
A confirmed account (--email too, or instead); works
|
|
135
|
+
while sign-ups are closed
|
|
136
|
+
naro backend users delete admin By id, username or email; ends its sessions (asks first)
|
|
128
137
|
naro backend auth email set --provider resend --from "App <no-reply@example.com>" --api-key-stdin < resend-key.txt
|
|
129
138
|
Confirmation and password reset emails from your Resend account
|
|
130
139
|
naro backend auth oauth set github --client-id Iv1.abc --client-secret-stdin < github-secret.txt
|
|
@@ -135,12 +144,19 @@ naro backend auth redirects remove https://app.example.com/auth/
|
|
|
135
144
|
Exits 1, changing nothing, when it is not in the list
|
|
136
145
|
naro backend auth require-verification on
|
|
137
146
|
Refuse sign-in until the email is confirmed (needs email)
|
|
138
|
-
naro policy list
|
|
147
|
+
naro policy list [--table todos] Row-level security policies, as CREATE POLICY
|
|
148
|
+
naro policy create 'CREATE POLICY "own todos" ON todos TO authenticated USING (user_id = auth.uid()) WITH CHECK (user_id = auth.uid())'
|
|
149
|
+
naro policy create --file policies.sql
|
|
150
|
+
CREATE POLICY and DROP POLICY statements, run in order;
|
|
151
|
+
--allow-unindexed accepts a subquery on the outer row
|
|
152
|
+
that no index serves (refused otherwise)
|
|
153
|
+
naro policy drop "own todos" --table todos
|
|
154
|
+
DROP POLICY (asks first)
|
|
139
155
|
naro policy set todos --select owner --insert owner --update owner --delete owner --owner user_id
|
|
140
156
|
naro policy set posts --select-when 'or(is_public.eq.true,user_id.eq.auth.uid())' --insert-when 'user_id.eq.auth.uid()'
|
|
141
157
|
naro policy set posts --from-file policy.json
|
|
142
|
-
|
|
143
|
-
naro policy remove todos
|
|
158
|
+
Shorthand: writes the policies naro_select … naro_delete
|
|
159
|
+
naro policy remove todos Drop every policy on the table: back to deny (asks first)
|
|
144
160
|
|
|
145
161
|
naro promote <deployment-id> Publish a build to production
|
|
146
162
|
naro rollback [deployment-id] Back to the previous production build
|
|
@@ -216,11 +232,28 @@ A column is `name:type[:modifier…]`:
|
|
|
216
232
|
Every primary key but a single `integer` one is `NOT NULL`; a foreign key must
|
|
217
233
|
point at its table's primary key or a unique column. `--owner user_id` adds
|
|
218
234
|
`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
|
|
220
|
-
`naro policy
|
|
221
|
-
drop removes the table's
|
|
222
|
-
|
|
223
|
-
|
|
235
|
+
four actions; any other new table is closed to the Data API until you give it
|
|
236
|
+
a policy (`naro policy create` or `naro policy set`; the command says so).
|
|
237
|
+
Policies are kept by table name: a drop removes the table's policies whenever
|
|
238
|
+
the project has a backend, on or off (the command says so). A rename leaves
|
|
239
|
+
them under the old name, where they apply to no table — the command prints
|
|
240
|
+
what to run to move them. Move them then: the next time `naro db table`
|
|
241
|
+
creates a table as that name or renames one to it, renaming the table back
|
|
242
|
+
included, the policies stored under the name are removed first, so a new
|
|
243
|
+
table never inherits an earlier one's (a table made by `naro db push` or
|
|
244
|
+
`naro db query` gets them as they are). Whatever a command removes it prints
|
|
245
|
+
so it can be made again: as `CREATE POLICY` statements, which
|
|
246
|
+
`naro policy create` takes back, and — for owner rules, whose owner column
|
|
247
|
+
`CREATE POLICY` cannot say — as the `naro policy set <table> … --owner <column>`
|
|
248
|
+
command that sets them again. A statement that cannot be shown as it is (a
|
|
249
|
+
control character, or a line break inside a string) prints as a `--` comment
|
|
250
|
+
in its place; `--json` has the exact text.
|
|
251
|
+
|
|
252
|
+
A policy reads columns and, in a subquery, other tables. When a rename or a
|
|
253
|
+
drop takes one of them away, the Data API answers `500 POLICY_INVALID` to the
|
|
254
|
+
requests that policy applies to — on another table, when the policy of `docs`
|
|
255
|
+
reads `members`. The command names each policy the change breaks and why;
|
|
256
|
+
`--dry-run` names them before anything is applied.
|
|
224
257
|
|
|
225
258
|
SQLite cannot add a PRIMARY KEY, UNIQUE or NOT-NULL-without-default column, or
|
|
226
259
|
drop a primary key, a unique or indexed column, or a table another table
|
|
@@ -316,10 +349,12 @@ message that the worker may still expect the new key — run
|
|
|
316
349
|
`naro backend enable`; the CLI prints that hint (JSON `hint`) and exits 1.
|
|
317
350
|
For a few seconds during a rotation the worker can already refuse the old key.
|
|
318
351
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
352
|
+
Sign-in is naro Auth, the project's own (like Supabase Auth). Email and
|
|
353
|
+
password sign-up and sign-in work as soon as the backend is on, with nothing
|
|
354
|
+
to set up. `naro backend auth` adds the rest with the project's own
|
|
355
|
+
accounts — Naro runs no shared sender. Sending email (confirmation and
|
|
356
|
+
password reset links) needs a Resend API key and a `from` address; Google
|
|
357
|
+
and GitHub need the project's own OAuth app. `naro backend auth` prints a callback URL for
|
|
323
358
|
each production host — `https://<project>.naro.sh/_naro/v1/auth/callback/<provider>`
|
|
324
359
|
and the same path on each custom domain. Register, as the app's redirect URI,
|
|
325
360
|
the one for the origin your app passes to `createClient({ url })`: the
|
|
@@ -347,25 +382,192 @@ code or reset token rides in the URL fragment, which any script on the page
|
|
|
347
382
|
— or an open redirect like `/logout?next=…` — can pass on: allow a path of
|
|
348
383
|
sign-in pages rather than a whole origin. Each change uploads
|
|
349
384
|
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
|
|
351
|
-
`naro backend enable` to finish.
|
|
385
|
+
same and the command exits 1 with `BACKEND_PROVISION_FAILED`, saying
|
|
386
|
+
whether the worker runs it yet — run `naro backend enable` to finish. Until
|
|
387
|
+
then `naro backend auth` shows the saved settings with a warning that the
|
|
388
|
+
worker may still run older ones (`applied: false` with `--json`), so a
|
|
389
|
+
`signups off` that did not upload never reads back as simply closed.
|
|
390
|
+
`require-verification on` needs email set
|
|
352
391
|
up (`VERIFICATION_NEEDS_EMAIL`), and email cannot be removed while it is on.
|
|
353
392
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
393
|
+
For an app only its admins sign in to, close sign-ups and create the
|
|
394
|
+
accounts yourself — with sign-ups closed, `authenticated` in a policy means
|
|
395
|
+
exactly the users you made:
|
|
396
|
+
|
|
397
|
+
```bash
|
|
398
|
+
naro backend auth signups off
|
|
399
|
+
naro backend users create --username admin --password-stdin < password.txt
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
`signups off` makes every sign-up answer `403 SIGNUPS_DISABLED` ("Sign-ups
|
|
403
|
+
are closed for this project."), and a first Google or GitHub sign-in too;
|
|
404
|
+
users that exist keep signing in. `naro backend users create` takes
|
|
405
|
+
`--username`, `--email` or both, and the password from stdin
|
|
406
|
+
(`--password-stdin`: a pipe, or a hidden prompt in a terminal). `--password
|
|
407
|
+
<p>` works too, with a warning on stderr: it lands in shell history. The
|
|
408
|
+
account is confirmed and signs in at once — by username with
|
|
409
|
+
`naro.auth.signIn({ username, password })` — also while sign-ups are closed
|
|
410
|
+
and while sign-in waits for a confirmed email; a username-only account has
|
|
411
|
+
`email: null`. The output never carries the password. `naro backend users`
|
|
412
|
+
lists every user (id, username, email, verified, created; `--json` for all
|
|
413
|
+
fields), and `naro backend users delete <id|username|email>` removes one and
|
|
414
|
+
ends its sessions — it asks first, and without a terminal needs `--yes`
|
|
415
|
+
(`CONFIRMATION_REQUIRED` otherwise, exit 1, nothing deleted). A username is
|
|
416
|
+
3–30 letters, digits, `_` and `.`, in any case. The backend's refusals pass
|
|
417
|
+
through: `409 USERNAME_TAKEN` "That username is already taken.",
|
|
418
|
+
`422 USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL`, `400 PASSWORD_TOO_SHORT`
|
|
419
|
+
"Use at least {n} characters.", `429 WRITE_QUOTA_EXCEEDED`.
|
|
420
|
+
|
|
421
|
+
While sign-ups are open, a sign-up with a username someone has answers
|
|
422
|
+
`409 USERNAME_TAKEN`, so anyone can learn which usernames exist, as fast as
|
|
423
|
+
sign-up's per-address rate limit allows. With sign-ups closed, every
|
|
424
|
+
sign-up answers `403 SIGNUPS_DISABLED` first.
|
|
425
|
+
|
|
426
|
+
`naro backend auth password --min-length <n>` sets the fewest characters a
|
|
427
|
+
new password takes, 6 to 128 (6 by default). Sign-up, password reset and
|
|
428
|
+
`users create` refuse a shorter one with "Use at least {n} characters.";
|
|
429
|
+
sign-in never checks it, so passwords set before keep working. Both
|
|
430
|
+
settings re-upload the Naro worker, like the other `naro backend auth`
|
|
431
|
+
changes, and `naro backend auth` shows them (`Sign-ups:`, `Minimum password
|
|
432
|
+
length:`).
|
|
433
|
+
|
|
434
|
+
A backend turned on before user management answers the `users` commands
|
|
435
|
+
`409 BACKEND_OUTDATED`: run `naro backend enable` once to update this
|
|
436
|
+
project's backend, then run the command again.
|
|
437
|
+
|
|
438
|
+
### Row-level security
|
|
439
|
+
|
|
440
|
+
Policies are Postgres's row-level security, as in Supabase. A table without a
|
|
441
|
+
policy refuses every anon and signed-in request; the service key skips
|
|
442
|
+
policies. Write them as `CREATE POLICY`, a change is live within about 10
|
|
443
|
+
seconds, and `naro policy list` prints them back as SQL:
|
|
444
|
+
|
|
445
|
+
```sql
|
|
446
|
+
-- Each signed-in user reads and changes their own todos.
|
|
447
|
+
CREATE POLICY "own todos" ON todos TO authenticated
|
|
448
|
+
USING (user_id = auth.uid()) WITH CHECK (user_id = auth.uid());
|
|
449
|
+
|
|
450
|
+
-- Anyone reads published posts.
|
|
451
|
+
CREATE POLICY "published posts" ON posts FOR SELECT USING (published);
|
|
452
|
+
|
|
453
|
+
-- Members read their team's docs. The subquery reads members through
|
|
454
|
+
-- members' own select policies, so members needs one.
|
|
455
|
+
CREATE POLICY "own memberships" ON members FOR SELECT TO authenticated
|
|
456
|
+
USING (user_id = auth.uid());
|
|
457
|
+
CREATE POLICY "team docs" ON docs FOR SELECT TO authenticated
|
|
458
|
+
USING (EXISTS (SELECT 1 FROM members m
|
|
459
|
+
WHERE m.team_id = docs.team_id AND m.user_id = auth.uid()));
|
|
460
|
+
|
|
461
|
+
-- Deleted docs stay hidden, whatever else lets them through.
|
|
462
|
+
CREATE POLICY "hide deleted" ON docs AS RESTRICTIVE FOR SELECT
|
|
463
|
+
USING (deleted_at IS NULL);
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
A request passes when at least one permissive policy for its command and role
|
|
467
|
+
passes and every restrictive one does. `FOR ALL` (the default) is every
|
|
468
|
+
command; `TO public` (the default) is anon and signed-in users. As in
|
|
469
|
+
Postgres:
|
|
470
|
+
|
|
471
|
+
- select reaches the rows the select policies pass;
|
|
472
|
+
- insert writes a row only when it passes `WITH CHECK`, and with `.select()`
|
|
473
|
+
the select policies too;
|
|
474
|
+
- update reaches the rows its `USING` and the select policies pass, and each
|
|
475
|
+
changed row must pass `WITH CHECK` (`USING` when there is none) and the
|
|
476
|
+
select policies — so an update that moves a row out of what select shows,
|
|
477
|
+
like a soft delete, is refused;
|
|
478
|
+
- delete reaches the rows its `USING` and the select policies pass.
|
|
479
|
+
|
|
480
|
+
A row outside `USING` is skipped without a word. A written row that misses a
|
|
481
|
+
check writes nothing and answers `403 PERMISSION_DENIED` "New row violates
|
|
482
|
+
row-level security policy for table todos." (with the name of a restrictive
|
|
483
|
+
policy it missed). With no permissive policy for the command, the request is
|
|
484
|
+
refused — `403 PERMISSION_DENIED` "This request is not allowed by the update
|
|
485
|
+
policy of todos." — where Postgres would answer no rows.
|
|
486
|
+
|
|
487
|
+
Expressions: columns, literals, comparisons, `AND`/`OR`/`NOT`, `IS [NOT] NULL`,
|
|
488
|
+
`IS [NOT] DISTINCT FROM`, `[NOT] IN (…)`, `[NOT] LIKE`/`ILIKE`, `BETWEEN`,
|
|
489
|
+
`EXISTS (SELECT …)` and `IN (SELECT …)`, `auth.uid()`, `auth.role()`,
|
|
490
|
+
`auth.jwt() ->> 'sub' | 'role' | 'email' | 'email_verified'`, and `lower`,
|
|
491
|
+
`upper`, `coalesce`, `length`, `now()`. Supabase's `(select auth.uid())` reads
|
|
492
|
+
as `auth.uid()`. A subquery's table is read through its own select policies;
|
|
493
|
+
policies that would expand themselves are refused ("Infinite recursion
|
|
494
|
+
detected in policy for relation members."). A statement's policies bind at
|
|
495
|
+
most 50 values and write at most 50,000 bytes of SQL, leaving the rest of
|
|
496
|
+
D1's 100 values and 100,000 bytes to the request. A `LIKE` or `ILIKE` pattern
|
|
497
|
+
is at most 50 bytes of UTF-8, which is D1's limit; `LIKE` counts each `*`,
|
|
498
|
+
`?` and `[` as 3.
|
|
499
|
+
|
|
500
|
+
A subquery that reads the outer row (`m.team_id = docs.team_id`) runs once for
|
|
501
|
+
every row the statement looks at, so it needs an index to find its rows: the
|
|
502
|
+
control plane refuses such a policy until a column its WHERE compares with `=`
|
|
503
|
+
(or `IN`) to a value from outside it starts an index, and names the index to
|
|
504
|
+
create — `naro db index create members team_id`. `--allow-unindexed` accepts
|
|
505
|
+
it anyway; every run then reads the whole table.
|
|
506
|
+
|
|
507
|
+
For anon, `auth.uid()` is NULL and `auth.jwt()` holds only `role` `'anon'`, as
|
|
508
|
+
in Supabase, and NULL logic is Postgres's: `auth.uid() IS NULL` is true for
|
|
509
|
+
anon, `user_id = auth.uid()` and its `NOT` match no row, and a `NOT EXISTS`
|
|
510
|
+
over another table's policy reads that policy the same way. Target anon with
|
|
511
|
+
`TO anon` or `auth.role() = 'anon'`. `auth.jwt() ->> 'email'` is the user's
|
|
512
|
+
address only once it is verified, and NULL before — sign-up signs a user in
|
|
513
|
+
before the address is confirmed, so an unverified one is only what was typed;
|
|
514
|
+
`email_verified` says which. It is the text `'true'` or `'false'`: write
|
|
515
|
+
`(auth.jwt() ->> 'email_verified') = 'true'`. `IS TRUE`, `IS FALSE`, `NOT x`
|
|
516
|
+
and a value alone read a number or boolean column; on a claim, `auth.uid()`,
|
|
517
|
+
a string, a function or a TEXT column they are refused, with the comparison
|
|
518
|
+
to write — as text, `'true'` would read as false. So is comparing text with
|
|
519
|
+
`TRUE` or `FALSE` — `<> false`, `= true`, `IN (true)`, `BETWEEN`,
|
|
520
|
+
`IS DISTINCT FROM`, also through `coalesce` — since the text `'true'` is not
|
|
521
|
+
`TRUE`: `<> false` would hold for everyone.
|
|
522
|
+
|
|
523
|
+
Where naro differs from Postgres: no policy for a command is a 403, not zero
|
|
524
|
+
rows; row-level security is always on; roles are only `anon` and
|
|
525
|
+
`authenticated`, with no `security definer` functions; comparisons follow
|
|
526
|
+
SQLite (column affinity and collation), and `lower`, `upper` and `ILIKE` fold
|
|
527
|
+
ASCII only; `now()` is UTC text as `CURRENT_TIMESTAMP` stores it.
|
|
528
|
+
|
|
529
|
+
`naro policy create --file` parses every statement first, so a syntax error
|
|
530
|
+
sends none; then it runs them in order and stops at the first one refused,
|
|
531
|
+
keeping those before it. `naro policy list` prints what `naro policy create`
|
|
532
|
+
takes back, except the owner comment below. A policy whose name, table or
|
|
533
|
+
string holds a control character (a line break, an escape) is not printed as
|
|
534
|
+
a statement — with the character replaced it would create a different policy
|
|
535
|
+
— but as a `--` comment that says so; `naro policy list --json` has its exact
|
|
536
|
+
text.
|
|
537
|
+
|
|
538
|
+
`naro policy drop <name>` takes the name as you type it: the policy of exactly
|
|
539
|
+
that name, or else the one of its ASCII lower case. `CREATE POLICY MyPolicy`
|
|
540
|
+
stores `mypolicy`, as in Postgres, and `naro policy drop MyPolicy --table
|
|
541
|
+
todos` drops it; a policy created as `"MyPolicy"` is the one dropped when both
|
|
542
|
+
exist.
|
|
543
|
+
|
|
544
|
+
`naro policy set` is shorthand: it writes the policies `naro_select`,
|
|
545
|
+
`naro_insert`, `naro_update` and `naro_delete` of a table and leaves its other
|
|
546
|
+
policies alone. Each of `--select`, `--insert`, `--update` and `--delete`
|
|
547
|
+
takes `none` (drops that policy), `public`, `authenticated` or `owner`;
|
|
548
|
+
`owner` lets a signed-in user reach only rows whose `--owner` column holds
|
|
549
|
+
their user id, and fills that column in on insert — a Naro extension SQL
|
|
550
|
+
cannot say, so `naro policy list` prints it as a `-- naro: owner` comment.
|
|
551
|
+
What a `policy set` leaves out keeps its stored value. `naro gen types` reads
|
|
552
|
+
the policies too: with `--insert owner --owner user_id`, `user_id` is optional
|
|
553
|
+
in the generated `Insert` even when it is `NOT NULL` with no default — run it
|
|
362
554
|
again after changing a policy.
|
|
363
555
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
556
|
+
A backend turned on before named policies answers the commands that write a
|
|
557
|
+
policy — `naro policy create`, `naro policy drop` and `naro policy set` — with
|
|
558
|
+
`409 BACKEND_OUTDATED`:
|
|
559
|
+
``The project backend is older than named policies. Run `naro backend enable` to update it, then try again.``
|
|
560
|
+
`naro backend enable` uploads the current backend and turns the stored rules
|
|
561
|
+
into named policies; it also turns a backend that is off back on, so run
|
|
562
|
+
`naro backend disable` again afterwards if it should stay off. `naro policy
|
|
563
|
+
list` and `naro policy remove` work in the meantime.
|
|
564
|
+
|
|
565
|
+
`naro policy set` refuses with `409 POLICY_EXISTS` when the table's `naro_<action>` policy is not one it wrote (for example one made with `naro policy create`): `Policy <name> on <table> is not one naro policy set wrote. Change it with naro policy create, or drop it first: naro policy drop <name> --table <table>.` A `naro_<action>` policy that cannot be read as stored (the table answers `500 POLICY_INVALID`) is not refused: setting a rule replaces it, and `none` drops it.
|
|
566
|
+
|
|
567
|
+
Instead of a rule, an action can hold a condition on the row, written in the
|
|
568
|
+
filter syntax of `naro-js`'s `.or()` (commas are AND) with `auth.uid()` for
|
|
569
|
+
the signed-in user's id — unquoted; `"auth.uid()"` is the text, and
|
|
570
|
+
`auth.uid` without `()` is refused rather than read as text:
|
|
369
571
|
|
|
370
572
|
- `--select-when` and `--delete-when`: the rows reads and deletes reach.
|
|
371
573
|
- `--insert-when`: what every new row must meet.
|
|
@@ -377,10 +579,9 @@ rather than read as text:
|
|
|
377
579
|
so `not(and(is_public.eq.1,user_id.eq.auth.uid()))` matches every row that is
|
|
378
580
|
not public, anon included — write what a condition lets in.
|
|
379
581
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
and compares it with the column's collation (`COLLATE NOCASE`, `RTRIM`). So:
|
|
582
|
+
A check — `WITH CHECK`, and the select policies an update's new row must
|
|
583
|
+
pass — reads a row as it will be stored and compares it with the column's
|
|
584
|
+
collation (`COLLATE NOCASE`, `RTRIM`). So:
|
|
384
585
|
|
|
385
586
|
- a number column it reads takes numbers, booleans and null, not strings, and
|
|
386
587
|
cannot be compared with `like` or `ilike`;
|
|
@@ -391,28 +592,21 @@ and compares it with the column's collation (`COLLATE NOCASE`, `RTRIM`). So:
|
|
|
391
592
|
another expression must be sent;
|
|
392
593
|
- it cannot read a generated column or a virtual table's column.
|
|
393
594
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
takes the PUT body, or a policy object from `--json`
|
|
595
|
+
The 50 values count an update's check once more for each column it reads, and
|
|
596
|
+
a write with a check binds the table's schema too (48 left), to make sure it
|
|
597
|
+
checks against the table as it is now. `--from-file` takes the PUT body, or a
|
|
598
|
+
policy object from `--json`
|
|
399
599
|
(`naro policy set posts --json | jq .policy > policy.json`), where
|
|
400
600
|
`auth.uid()` is `{"auth":"uid"}`; its `table`, if it has one, must name the
|
|
401
601
|
table being set. It cannot be combined with the other policy options.
|
|
402
|
-
`naro policy
|
|
403
|
-
the same condition, or as JSON where filter syntax cannot
|
|
404
|
-
(`deleted_at.eq.null` would be the text `"null"`, and `= NULL`
|
|
405
|
-
`IS NULL`)
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
`.
|
|
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
|
|
602
|
+
`naro policy set` prints the table's rules — each condition in filter syntax
|
|
603
|
+
that reads back as the same condition, or as JSON where filter syntax cannot
|
|
604
|
+
say it exactly (`deleted_at.eq.null` would be the text `"null"`, and `= NULL`
|
|
605
|
+
is not `IS NULL`) — and then its policies as `CREATE POLICY`.
|
|
606
|
+
|
|
607
|
+
An `--update-check` you give replaces the default: without
|
|
608
|
+
`user_id.eq.auth.uid()` in it, a user can give a row away by changing its
|
|
609
|
+
`user_id`. Write
|
|
416
610
|
`--update-when 'user_id.eq.auth.uid()' --update-check 'user_id.eq.auth.uid(),status.eq.draft'`.
|
|
417
611
|
|
|
418
612
|
## Machine-readable output (CI / AI agents)
|
|
@@ -437,6 +631,7 @@ Failures carry stable codes (`NOT_LOGGED_IN`, `PROJECT_NOT_LINKED`,
|
|
|
437
631
|
`ENV_NOT_FOUND`, `DOMAIN_NOT_FOUND`, `DOMAIN_NOT_READY`,
|
|
438
632
|
`BACKEND_DISABLED`, `BACKEND_NOT_READY`, `BACKEND_BUSY`,
|
|
439
633
|
`BACKEND_ROTATION_FAILED`, `BACKEND_PROVISION_FAILED`, `POLICY_NOT_FOUND`,
|
|
634
|
+
`POLICY_EXISTS`,
|
|
440
635
|
`VALIDATION_FAILED`, `CONFIRMATION_REQUIRED`, `CANCELED`, …). A destructive
|
|
441
636
|
command run non-interactively without `--yes` fails with
|
|
442
637
|
`CONFIRMATION_REQUIRED` instead of silently doing nothing:
|
|
@@ -452,7 +647,8 @@ command run non-interactively without `--yes` fails with
|
|
|
452
647
|
|
|
453
648
|
`--yes` (or `-y`) suppresses every prompt — destructive actions
|
|
454
649
|
(production rollback/promote, env/domain removal, database reset, backend key
|
|
455
|
-
rotation, policy removal) run unconfirmed, so use it deliberately in
|
|
650
|
+
rotation, policy removal and drop) run unconfirmed, so use it deliberately in
|
|
651
|
+
automation.
|
|
456
652
|
|
|
457
653
|
## CI authentication
|
|
458
654
|
|
package/dist/artifact-types.d.ts
CHANGED
|
@@ -21,6 +21,11 @@ export interface DeploymentArtifact {
|
|
|
21
21
|
assetsConfig?: {
|
|
22
22
|
notFoundHandling: "single-page-application" | "404-page";
|
|
23
23
|
};
|
|
24
|
+
/**
|
|
25
|
+
* Next.js routes ("/posts/[id]") for Speed Insights, read from the app's
|
|
26
|
+
* folders. Absent for other frameworks.
|
|
27
|
+
*/
|
|
28
|
+
routes?: string[];
|
|
24
29
|
}
|
|
25
30
|
/** Mirrors the build log shape from the internal build package. */
|
|
26
31
|
export interface BuildLog {
|